//! Typed authority validator — the three variants and their validation logic.
//!
//! See parent module [`crate::authority`] for the doctrine narrative and
//! status notes. This file holds the mechanical truth.
//!
//! # Mechanical separation
//!
//! [`ObservedAuthority`], [`ProvenAuthority`], and [`ImposedAuthority`] are
//! three **distinct, non-convertible** structs. There are no `From`/`Into`
//! impls between them, no shared trait, no common base type. Merging is a
//! compile error.
//!
//! Each variant carries its own validated [`AuthorityDerivation`] (the ADG
//! sub-object). Construction goes through the type's `try_new` method which
//! invokes [`AuthorityDerivation::validate`] plus type-class compatibility
//! checks. Once constructed, fields are immutable (no public setters; ADG
//! and output are exposed read-only).

use std::collections::BTreeSet;

use serde::{Deserialize, Serialize};

// ---------------------------------------------------------------------------
// Enums shared across the three typed-authority variants
// ---------------------------------------------------------------------------

/// Capability-tier and confidence semantics from
/// [docs/authority-model.md §5b](../../../../docs/authority-model.md).
///
/// `STATISTICALLY_BOUNDED` is reserved per §5a but not yet emitted by any
/// rule-class; it does not appear in the canonical mapping.
///
/// `DECLARED` is the [ADR-0006] `DeclaredAuthority` variant — guest-agent
/// declarations forwarded over the host-bound vsock channel. It is the
/// **floor** of the precedence ladder: any co-occurring class outranks it.
///
/// [ADR-0006]: ../../../../docs/adr/0006-in-vm-observability-runner-evidence.md
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EpistemicStatus {
    /// Raw signal seen on the wire; no structural cross-check applied.
    DirectObservation,
    /// Two or more signals align in the way the protocol mandates.
    StructurallyBound,
    /// Verified via signature or chain-of-trust the workload cannot forge.
    CryptographicProof,
    /// A structural signal we expected to see was deliberately encrypted/withheld.
    Opaque,
    /// Authority asserted via in-line interception (Stack 3 / M2 quarantine only).
    Imposed,
    /// Guest-agent declaration forwarded over a host-bound vsock channel
    /// (ADR-0006). Strictly weaker than [`Self::DirectObservation`] — any
    /// co-occurring class outranks it in the canonical mapping.
    Declared,
}

/// Rule-class drives [`EpistemicStatus`]; this is the canonical enum from
/// [docs/authority-derivation-graph.md](../../../../docs/authority-derivation-graph.md).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RuleClass {
    /// Lone-signal sighting. Tier ceiling 2.
    RawObservation,
    /// Two protocol-mandated signals align. Tier ceiling 3.
    StructuralAlignment,
    /// Cryptographic verification (JWS, DANE-TLSA, SVID). Tier ceiling 4.
    CryptographicProof,
    /// Expected signal deliberately withheld (ECH, encrypted inner SNI).
    /// Tier ceiling 1.
    Opacity,
    /// In-line termination observed plaintext post-MITM. Tier ceiling 4
    /// (with named structural residuals; emission must carry
    /// `non_authoritative_enforcement: true`).
    ImposedInterception,
    /// Guest-agent declaration forwarded over the per-cell vsock channel
    /// (ADR-0006). Tier ceiling 1; floor of the precedence ladder. Backs
    /// [`DeclaredAuthority`] exclusively.
    GuestAgentDeclaration,
}

/// Named rules per
/// [docs/authority-derivation-graph.md](../../../../docs/authority-derivation-graph.md).
/// Adding a new rule requires updating that document AND
/// [`canonical_class_for_rule`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Rule {
    /// SNI parsed from ClientHello, no cross-check.
    RawSniObserved,
    /// Host header parsed, no cross-check.
    RawHostHeaderObserved,
    /// h2 `:authority` parsed.
    RawH2AuthorityObserved,
    /// SNI value byte-equals Host value (post-IDN, post-port-strip).
    SniHostMatch,
    /// h2 `:authority` matches HTTP/1.x Host on the same flow.
    H2AuthorityHostMatch,
    /// SNI matches a declared `cdnAuthority.providers[].hostnamePattern`.
    SniCdnProviderMatch,
    /// Workload-signed authority claim verified against cell-ephemeral key.
    JwsSignatureVerified,
    /// Upstream cert chain matches DNSSEC-validated TLSA record.
    DaneTlsaBound,
    /// SPIFFE SVID chain verified (future).
    SvidChainVerified,
    /// `encrypted_client_hello` extension present.
    EchDetected,
    /// Successor to ECH_DETECTED when more state available.
    EncryptedInnerSni,
    /// Stack 3 / M2 only.
    MitmHandshakeTerminated,
    /// In-VM agent declared a process spawn (ADR-0006).
    GuestProcSpawnObserved,
    /// In-VM agent declared a process exit (ADR-0006).
    GuestProcExitObserved,
    /// In-VM agent declared an inotify watch firing (ADR-0006).
    GuestFsInotifyFired,
    /// In-VM agent declared a capability denial (ADR-0006).
    GuestCapDenialObserved,
    /// In-VM agent declared a `connect()` attempt (ADR-0006).
    GuestNetConnectAttempted,
}

/// Binding status per
/// [docs/authority-model.md §6](../../../../docs/authority-model.md).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BindingStatus {
    /// At least one structural-alignment or cryptographic-proof rule fired
    /// AND no opacity/imposed rule fired.
    Bound,
    /// Any opacity rule fired, OR no alignment/proof rule fired against a
    /// multi-input set.
    Unknown,
    /// Imposed-interception rule fired (Stack 3 only).
    ImposedBound,
    /// Single-input emission where binding is undefined.
    NotApplicable,
}

/// Canonical input types — a strict subset of those listed in the ADG doc.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthorityInputType {
    /// Bytes from ClientHello SNI extension.
    Sni,
    /// Bytes from HTTP/1.x `Host:` header.
    HostHeader,
    /// Bytes from h2 HEADERS `:authority` pseudo-header.
    H2Authority,
    /// Subject Alternative Name from a verified upstream cert.
    TlsCertSan,
    /// DNSSEC-validated TLSA record content.
    DaneTlsaRecord,
    /// Verified body of a workload-signed JWS authority claim.
    JwsPayload,
    /// DNSSEC-validated A/AAAA answer (links to upstream Phase 3h emission).
    DnssecValidatedARecord,
    /// Stack 3 / `IMPOSED` only; bytes seen post-termination.
    MitmTerminatedObservation,
}

// ---------------------------------------------------------------------------
// ADG sub-object types
// ---------------------------------------------------------------------------

/// One evidence atom consumed by a derivation. Mirrors the ADG `inputs[]`
/// schema; see [docs/authority-derivation-graph.md](../../../../docs/authority-derivation-graph.md).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuthorityInput {
    /// Canonical input type.
    #[serde(rename = "type")]
    pub input_type: AuthorityInputType,
    /// Observed value (redacted per `RedactingEventSink` policy if applicable).
    pub value: String,
    /// Coverage score for this input alone, ∈ \[0, 1\].
    pub confidence: f64,
    /// Optional UUID — when this input itself was the output of a prior
    /// emission, links the chain.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub source_event_id: Option<String>,
}

/// One rule that fired during derivation.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AppliedRule {
    /// Named rule.
    pub rule: Rule,
    /// Class — must match [`canonical_class_for_rule`] for `rule`.
    pub class: RuleClass,
}

/// The derived `(tier, confidence, epistemic_status, binding_status)` tuple.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct AuthorityOutput {
    /// Capability tier, ∈ \[0, 4\].
    pub tier: u8,
    /// Coverage-derived confidence, ∈ \[0, 1\].
    pub confidence: f64,
    /// Deterministically derived from the rule-class set.
    pub epistemic_status: EpistemicStatus,
    /// Per [`BindingStatus`] semantics.
    pub binding_status: BindingStatus,
}

/// The complete ADG sub-object. Serializable for downstream events.
///
/// Construction does not validate; callers must invoke [`Self::validate`] (or
/// go through one of the typed-authority `try_new` constructors which call
/// `validate` for them).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AuthorityDerivation {
    /// Inputs consumed by the derivation. Must be non-empty.
    pub inputs: Vec<AuthorityInput>,
    /// Rules applied to those inputs. Must be non-empty.
    pub rules_applied: Vec<AppliedRule>,
    /// The derivation's output tuple.
    pub output: AuthorityOutput,
}

// ---------------------------------------------------------------------------
// Validation errors
// ---------------------------------------------------------------------------

/// Errors the validator can return. Each variant names the specific §9
/// invariant it violates so debug logs and tests can distinguish them.
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum ValidationError {
    /// `inputs` is empty (Property 4 — ADG presence).
    #[error("authority derivation has no inputs")]
    NoInputs,
    /// `rules_applied` is empty (Property 4 — ADG presence).
    #[error("authority derivation has no applied rules")]
    NoRulesApplied,
    /// An input's `confidence` is outside \[0, 1\].
    #[error("input confidence {0} out of range [0, 1]")]
    InputConfidenceOutOfRange(f64),
    /// An input's `confidence` is NaN.
    #[error("input confidence is NaN")]
    InputConfidenceNaN,
    /// `output.confidence` is outside \[0, 1\] or NaN.
    #[error("output confidence {0} out of range [0, 1] or NaN")]
    OutputConfidenceInvalid(f64),
    /// `output.tier` is outside \[0, 4\].
    #[error("output tier {0} out of range [0, 4]")]
    TierOutOfRange(u8),
    /// Property 1 — `output.confidence > max(inputs.confidence)`.
    #[error("confidence inflation: output {output} exceeds max input {max_input}")]
    ConfidenceInflation {
        /// Declared output confidence.
        output: f64,
        /// Maximum input confidence observed.
        max_input: f64,
    },
    /// Property 2 — `output.tier > min(class_tier_ceiling)`.
    #[error("tier inflation: output tier {output} exceeds ceiling {ceiling} (limited by {limiting_class:?})")]
    TierInflation {
        /// Declared output tier.
        output: u8,
        /// Maximum tier permitted by the rule-class set.
        ceiling: u8,
        /// The class whose ceiling was violated.
        limiting_class: RuleClass,
    },
    /// Property 3 — `output.epistemic_status` is not the deterministic image
    /// of the rule-class set.
    #[error("epistemic status mismatch: declared {declared:?}, expected {expected:?}")]
    EpistemicMismatch {
        /// Declared `epistemic_status`.
        declared: EpistemicStatus,
        /// Status the canonical mapping demands.
        expected: EpistemicStatus,
    },
    /// Property 5 — a rule's declared class does not match the canonical
    /// `rule -> class` mapping.
    #[error("rule {rule:?} declared class {declared:?}, canonical class is {canonical:?}")]
    RuleClassMismatch {
        /// Rule whose class was declared.
        rule: Rule,
        /// Class the caller declared.
        declared: RuleClass,
        /// Class the canonical mapping demands.
        canonical: RuleClass,
    },
    /// `binding_status` does not match the rules' implications.
    #[error("binding status mismatch: declared {declared:?}, expected {expected:?}")]
    BindingStatusMismatch {
        /// Declared `binding_status`.
        declared: BindingStatus,
        /// Status the canonical mapping demands.
        expected: BindingStatus,
    },
    /// Type-class compatibility violation — the typed authority's permitted
    /// rule-class envelope was exceeded.
    #[error(
        "type-class incompatible: {type_name} requires rule-classes within {permitted:?}, got {rejected:?}"
    )]
    TypeClassIncompatible {
        /// `"ObservedAuthority"` / `"ProvenAuthority"` / `"ImposedAuthority"`.
        type_name: &'static str,
        /// The permitted set for this type.
        permitted: &'static [RuleClass],
        /// The class that triggered the rejection.
        rejected: RuleClass,
    },
    /// `ProvenAuthority` was constructed without a verified-signature artifact.
    #[error("ProvenAuthority requires a verified-signature artifact")]
    ProvenAuthorityMissingArtifact,
    /// `ImposedAuthority` requires the IMPOSED_INTERCEPTION rule-class to
    /// have fired in its derivation.
    #[error("ImposedAuthority requires the IMPOSED_INTERCEPTION rule-class in its derivation")]
    ImposedAuthorityMissingInterception,
    /// The empty-class-set case from the ADG mapping table.
    #[error("rule-class set is empty after deduplication; refusing emission")]
    EmptyClassSet,
}

// ---------------------------------------------------------------------------
// Canonical mappings
// ---------------------------------------------------------------------------

/// The canonical rule-name → rule-class mapping. Mirrors the table in
/// [docs/authority-derivation-graph.md](../../../../docs/authority-derivation-graph.md).
///
/// **This function is the source of truth for Property 5 (rule-class
/// consistency).** Adding a new rule requires:
///
/// 1. Adding the variant to [`Rule`].
/// 2. Adding the entry here.
/// 3. Updating the ADG doc.
/// 4. Updating any tests that enumerate `Rule` exhaustively.
///
/// The compiler's exhaustiveness check catches step 2 if step 1 lands first.
#[must_use]
pub const fn canonical_class_for_rule(rule: Rule) -> RuleClass {
    match rule {
        Rule::RawSniObserved | Rule::RawHostHeaderObserved | Rule::RawH2AuthorityObserved => {
            RuleClass::RawObservation
        }
        Rule::SniHostMatch | Rule::H2AuthorityHostMatch | Rule::SniCdnProviderMatch => {
            RuleClass::StructuralAlignment
        }
        Rule::JwsSignatureVerified | Rule::DaneTlsaBound | Rule::SvidChainVerified => {
            RuleClass::CryptographicProof
        }
        Rule::EchDetected | Rule::EncryptedInnerSni => RuleClass::Opacity,
        Rule::MitmHandshakeTerminated => RuleClass::ImposedInterception,
        Rule::GuestProcSpawnObserved
        | Rule::GuestProcExitObserved
        | Rule::GuestFsInotifyFired
        | Rule::GuestCapDenialObserved
        | Rule::GuestNetConnectAttempted => RuleClass::GuestAgentDeclaration,
    }
}

/// Tier ceiling per rule-class — the canonical mapping. The ADG doc and
/// [docs/authority-model.md §5b](../../../../docs/authority-model.md) pin
/// these numbers; do not change them without an ADR amendment.
#[must_use]
pub const fn max_tier_for_class(class: RuleClass) -> u8 {
    match class {
        RuleClass::RawObservation => 2,
        RuleClass::StructuralAlignment => 3,
        RuleClass::CryptographicProof => 4,
        RuleClass::Opacity => 1,
        RuleClass::ImposedInterception => 4,
        RuleClass::GuestAgentDeclaration => 1,
    }
}

/// The deterministic class-set → epistemic-status mapping per
/// [docs/authority-derivation-graph.md](../../../../docs/authority-derivation-graph.md):
///
/// ```text
/// {ImposedInterception, ...}                                   → Imposed
/// {Opacity, ...except Imposed}                                  → Opaque
/// {CryptographicProof, ...except above}                         → CryptographicProof
/// {StructuralAlignment, ...except above}                        → StructurallyBound
/// {RawObservation, ...except above}                             → DirectObservation
/// {GuestAgentDeclaration only}                                  → Declared (ADR-0006)
/// empty                                                         → returns None
/// ```
///
/// Per [ADR-0006] §2 the precedence ladder is
/// `Imposed > Opaque > CryptographicProof > StructurallyBound > DirectObservation > Declared`
/// — `Declared` is the floor, so any host-side class co-occurring with
/// `GuestAgentDeclaration` outranks it.
///
/// Returns `None` for the empty set; callers translate to
/// [`ValidationError::EmptyClassSet`].
///
/// [ADR-0006]: ../../../../docs/adr/0006-in-vm-observability-runner-evidence.md
#[must_use]
pub fn epistemic_for_class_set(classes: &BTreeSet<RuleClass>) -> Option<EpistemicStatus> {
    if classes.is_empty() {
        return None;
    }
    if classes.contains(&RuleClass::ImposedInterception) {
        return Some(EpistemicStatus::Imposed);
    }
    if classes.contains(&RuleClass::Opacity) {
        return Some(EpistemicStatus::Opaque);
    }
    if classes.contains(&RuleClass::CryptographicProof) {
        return Some(EpistemicStatus::CryptographicProof);
    }
    if classes.contains(&RuleClass::StructuralAlignment) {
        return Some(EpistemicStatus::StructurallyBound);
    }
    if classes.contains(&RuleClass::RawObservation) {
        return Some(EpistemicStatus::DirectObservation);
    }
    // Floor of the precedence ladder — only GuestAgentDeclaration left.
    if classes.contains(&RuleClass::GuestAgentDeclaration) {
        return Some(EpistemicStatus::Declared);
    }
    // Unreachable given enum exhaustiveness above, but kept defensive.
    None
}

/// Compute the expected [`BindingStatus`] given the inputs and rule-class
/// set, per [`BindingStatus`] doc-comments.
fn expected_binding_status(
    inputs: &[AuthorityInput],
    classes: &BTreeSet<RuleClass>,
) -> BindingStatus {
    if classes.contains(&RuleClass::ImposedInterception) {
        return BindingStatus::ImposedBound;
    }
    let opacity_or_imposed = classes.contains(&RuleClass::Opacity);
    let alignment_or_proof = classes.contains(&RuleClass::StructuralAlignment)
        || classes.contains(&RuleClass::CryptographicProof);
    if opacity_or_imposed {
        return BindingStatus::Unknown;
    }
    if alignment_or_proof {
        return BindingStatus::Bound;
    }
    if inputs.len() <= 1 {
        // single-input derivation; binding is undefined.
        BindingStatus::NotApplicable
    } else {
        // multi-input set with no alignment/proof — explicit UNKNOWN per ADG §4.
        BindingStatus::Unknown
    }
}

impl AuthorityDerivation {
    /// Run all six §9 invariants over this derivation. Returns `Ok(())` on
    /// success or the first violation as a [`ValidationError`].
    ///
    /// This is a pure function — no I/O, no allocations beyond the
    /// rule-class set deduplication.
    pub fn validate(&self) -> Result<(), ValidationError> {
        // Property 4 — ADG presence
        if self.inputs.is_empty() {
            return Err(ValidationError::NoInputs);
        }
        if self.rules_applied.is_empty() {
            return Err(ValidationError::NoRulesApplied);
        }

        // Range checks on numeric outputs and inputs
        for input in &self.inputs {
            if input.confidence.is_nan() {
                return Err(ValidationError::InputConfidenceNaN);
            }
            if !(0.0..=1.0).contains(&input.confidence) {
                return Err(ValidationError::InputConfidenceOutOfRange(input.confidence));
            }
        }
        if self.output.confidence.is_nan() || !(0.0..=1.0).contains(&self.output.confidence) {
            return Err(ValidationError::OutputConfidenceInvalid(
                self.output.confidence,
            ));
        }
        if self.output.tier > 4 {
            return Err(ValidationError::TierOutOfRange(self.output.tier));
        }

        // Property 5 — rule-name → class consistency
        for ar in &self.rules_applied {
            let canonical = canonical_class_for_rule(ar.rule);
            if canonical != ar.class {
                return Err(ValidationError::RuleClassMismatch {
                    rule: ar.rule,
                    declared: ar.class,
                    canonical,
                });
            }
        }

        let class_set: BTreeSet<RuleClass> = self.rules_applied.iter().map(|ar| ar.class).collect();

        // Property 3 — epistemic determinism
        let expected_epistemic =
            epistemic_for_class_set(&class_set).ok_or(ValidationError::EmptyClassSet)?;
        if self.output.epistemic_status != expected_epistemic {
            return Err(ValidationError::EpistemicMismatch {
                declared: self.output.epistemic_status,
                expected: expected_epistemic,
            });
        }

        // Property 1 — confidence non-inflation
        let max_input = self
            .inputs
            .iter()
            .map(|i| i.confidence)
            .fold(f64::NEG_INFINITY, f64::max);
        if self.output.confidence > max_input {
            return Err(ValidationError::ConfidenceInflation {
                output: self.output.confidence,
                max_input,
            });
        }

        // Property 2 — tier ceiling
        let (ceiling, limiting_class) = class_set
            .iter()
            .map(|c| (max_tier_for_class(*c), *c))
            .min_by_key(|(t, _)| *t)
            .expect("class_set non-empty (checked above)");
        if self.output.tier > ceiling {
            return Err(ValidationError::TierInflation {
                output: self.output.tier,
                ceiling,
                limiting_class,
            });
        }

        // Binding status determinism
        let expected_binding = expected_binding_status(&self.inputs, &class_set);
        if self.output.binding_status != expected_binding {
            return Err(ValidationError::BindingStatusMismatch {
                declared: self.output.binding_status,
                expected: expected_binding,
            });
        }

        Ok(())
    }

    /// Returns the deduplicated rule-class set this derivation applied.
    /// Exposed for tests and downstream consumers (e.g. taudit) that want to
    /// reason about class composition without re-walking `rules_applied`.
    #[must_use]
    pub fn class_set(&self) -> BTreeSet<RuleClass> {
        self.rules_applied.iter().map(|ar| ar.class).collect()
    }
}

// ---------------------------------------------------------------------------
// Typed authority — the three variants
// ---------------------------------------------------------------------------

/// Authority derived from CellOS's own observation of bytes inside the
/// cell's network namespace.
///
/// **Permitted rule-classes:** `RawObservation`, `StructuralAlignment`,
/// `Opacity`. Tier ceiling: 2 (or up to 3 via `StructuralAlignment`, capped
/// at 2 by the type-class envelope per Authority Model §14.1).
///
/// **Forbidden rule-classes:** `CryptographicProof` (no signature was
/// verified), `ImposedInterception` (no termination occurred),
/// `GuestAgentDeclaration` (reserved for `DeclaredAuthority`).
///
/// **Type-system discipline:** No `From`/`Into` impls to or from any other
/// authority variant. The only public constructor is [`Self::try_new`].
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ObservedAuthority {
    derivation: AuthorityDerivation,
}

impl ObservedAuthority {
    /// Permitted rule-classes for this variant. Public so tests and
    /// downstream consumers can introspect the envelope.
    pub const PERMITTED_CLASSES: &'static [RuleClass] = &[
        RuleClass::RawObservation,
        RuleClass::StructuralAlignment,
        RuleClass::Opacity,
    ];

    /// Construct an `ObservedAuthority` from a validated derivation.
    ///
    /// Runs [`AuthorityDerivation::validate`] plus the type-class envelope
    /// check (no `CryptographicProof`, no `ImposedInterception`, no
    /// `GuestAgentDeclaration`).
    ///
    /// # Errors
    ///
    /// Returns the first [`ValidationError`] encountered.
    pub fn try_new(derivation: AuthorityDerivation) -> Result<Self, ValidationError> {
        derivation.validate()?;
        for class in derivation.class_set() {
            if !Self::PERMITTED_CLASSES.contains(&class) {
                return Err(ValidationError::TypeClassIncompatible {
                    type_name: "ObservedAuthority",
                    permitted: Self::PERMITTED_CLASSES,
                    rejected: class,
                });
            }
        }
        Ok(Self { derivation })
    }

    /// Read-only access to the validated derivation.
    #[must_use]
    pub fn derivation(&self) -> &AuthorityDerivation {
        &self.derivation
    }

    /// Convenience accessor for the output tuple.
    #[must_use]
    pub fn output(&self) -> AuthorityOutput {
        self.derivation.output
    }
}

/// Verified-signature artifact backing a [`ProvenAuthority`]. Storing the
/// artifact alongside the derivation prevents `ProvenAuthority` from being
/// constructed from a derivation that *claims* `CryptographicProof` without
/// actually carrying the proof material.
///
/// The verifier path that produced this artifact is upstream of
/// `cellos-core` (e.g., supervisor's JWS verifier, DNSSEC validator); this
/// crate holds no signing/verification keys per D11.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ProvenAuthorityArtifact {
    /// JWS authority claim verified against a workload-bound key.
    JwsVerified {
        /// `kid` of the key that verified the signature.
        kid: String,
    },
    /// Upstream cert chain matched a DNSSEC-validated TLSA record.
    DaneTlsaBound {
        /// TLSA record selector / matching-type the chain matched.
        tlsa_record_id: String,
    },
    /// SPIFFE SVID chain verified.
    SvidChainVerified {
        /// SPIFFE ID this chain bound to.
        spiffe_id: String,
    },
}

/// Authority derived from cryptographic verification of a claim CellOS did
/// not author.
///
/// **Permitted rule-classes:** `CryptographicProof` (REQUIRED — at least one
/// rule from this class must fire). `StructuralAlignment` and
/// `RawObservation` may co-occur (e.g. SNI parsed alongside a verified JWS),
/// but `Opacity` and `ImposedInterception` are forbidden.
///
/// **Forbidden:** `Opacity` (would invalidate the proof claim);
/// `ImposedInterception` (different stack); `GuestAgentDeclaration`.
///
/// **Constructor discipline:** [`Self::try_new`] requires a
/// [`ProvenAuthorityArtifact`]. Without the artifact, construction fails —
/// preventing a non-cryptographic emission from minting a `ProvenAuthority`
/// even if its derivation declares `CryptographicProof`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProvenAuthority {
    derivation: AuthorityDerivation,
    artifact: ProvenAuthorityArtifact,
}

impl ProvenAuthority {
    /// Permitted rule-classes for this variant.
    pub const PERMITTED_CLASSES: &'static [RuleClass] = &[
        RuleClass::CryptographicProof,
        RuleClass::StructuralAlignment,
        RuleClass::RawObservation,
    ];

    /// Construct a `ProvenAuthority`.
    ///
    /// Runs [`AuthorityDerivation::validate`] plus the type-class envelope
    /// check, and additionally requires:
    ///
    /// 1. At least one rule with class `CryptographicProof` (otherwise we
    ///    have nothing to prove).
    /// 2. A non-trivial [`ProvenAuthorityArtifact`].
    ///
    /// # Errors
    ///
    /// Returns the first [`ValidationError`] encountered.
    pub fn try_new(
        derivation: AuthorityDerivation,
        artifact: ProvenAuthorityArtifact,
    ) -> Result<Self, ValidationError> {
        derivation.validate()?;
        let class_set = derivation.class_set();
        if !class_set.contains(&RuleClass::CryptographicProof) {
            return Err(ValidationError::ProvenAuthorityMissingArtifact);
        }
        for class in &class_set {
            if !Self::PERMITTED_CLASSES.contains(class) {
                return Err(ValidationError::TypeClassIncompatible {
                    type_name: "ProvenAuthority",
                    permitted: Self::PERMITTED_CLASSES,
                    rejected: *class,
                });
            }
        }
        Ok(Self {
            derivation,
            artifact,
        })
    }

    /// Read-only access to the derivation.
    #[must_use]
    pub fn derivation(&self) -> &AuthorityDerivation {
        &self.derivation
    }

    /// Read-only access to the verification artifact.
    #[must_use]
    pub fn artifact(&self) -> &ProvenAuthorityArtifact {
        &self.artifact
    }

    /// Convenience accessor for the output tuple.
    #[must_use]
    pub fn output(&self) -> AuthorityOutput {
        self.derivation.output
    }
}

/// Authority asserted by CellOS having terminated TLS in-line and observed
/// plaintext that the workload would not have shown without termination.
///
/// **Sources:** Stack 3 / M2 only — lives architecturally in the quarantined
/// `cellos-tls-term` crate (post-1.0). The type lives in `cellos-core` so
/// downstream code can refer to it without depending on the quarantined
/// crate, but no `cellos-core` code path constructs one today.
///
/// **Permitted rule-classes:** `ImposedInterception` (REQUIRED).
/// Co-occurrence with other classes is permitted only insofar as the ADG
/// mapping demands `Imposed` epistemic status anyway.
///
/// **Doctrine:** `ImposedAuthority` MUST NOT be emitted on the
/// `cell.observability.v1.l7_authority_evidence` event type. It uses
/// `cell.observability.v1.l7_imposed_enforcement` exclusively, with the
/// `non_authoritative_enforcement: true` flag. This module enforces the
/// type-system half of that contract; the event-builder half lands in B3.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ImposedAuthority {
    derivation: AuthorityDerivation,
}

impl ImposedAuthority {
    /// Permitted rule-classes for this variant. `ImposedInterception` is
    /// REQUIRED to appear; co-occurrence with other classes is permitted but
    /// the deterministic mapping forces output epistemic status to `Imposed`.
    pub const PERMITTED_CLASSES: &'static [RuleClass] = &[
        RuleClass::ImposedInterception,
        RuleClass::RawObservation,
        RuleClass::StructuralAlignment,
    ];

    /// Construct an `ImposedAuthority`.
    ///
    /// Runs [`AuthorityDerivation::validate`] plus the type-class envelope
    /// check, and additionally requires the `ImposedInterception`
    /// rule-class to be present.
    ///
    /// # Errors
    ///
    /// Returns the first [`ValidationError`] encountered.
    pub fn try_new(derivation: AuthorityDerivation) -> Result<Self, ValidationError> {
        derivation.validate()?;
        let class_set = derivation.class_set();
        if !class_set.contains(&RuleClass::ImposedInterception) {
            return Err(ValidationError::ImposedAuthorityMissingInterception);
        }
        for class in &class_set {
            if !Self::PERMITTED_CLASSES.contains(class) {
                return Err(ValidationError::TypeClassIncompatible {
                    type_name: "ImposedAuthority",
                    permitted: Self::PERMITTED_CLASSES,
                    rejected: *class,
                });
            }
        }
        Ok(Self { derivation })
    }

    /// Read-only access to the derivation.
    #[must_use]
    pub fn derivation(&self) -> &AuthorityDerivation {
        &self.derivation
    }

    /// Convenience accessor for the output tuple.
    #[must_use]
    pub fn output(&self) -> AuthorityOutput {
        self.derivation.output
    }
}

/// Authority asserted by a guest-side declaration (in-VM `cellos-telemetry`
/// agent over the per-cell vsock channel) per [ADR-0006].
///
/// **Permitted rule-classes:** [`RuleClass::GuestAgentDeclaration`] only —
/// the envelope is mono-class per ADR-0006 §3. Any other class violates the
/// type-system gate that prevents guest declarations from being silently
/// upgraded to host-side epistemic statuses.
///
/// **Tier ceiling:** 1. **Epistemic status:** [`EpistemicStatus::Declared`].
/// **Binding status:** [`BindingStatus::NotApplicable`] for single-input
/// derivations; multi-input declarations remain [`BindingStatus::Unknown`]
/// because no host-side alignment/proof rule can fire alongside.
///
/// **Type-system discipline:** No `From`/`Into` impls to or from any other
/// authority variant. Construction goes only through [`Self::try_new`].
///
/// [ADR-0006]: ../../../../docs/adr/0006-in-vm-observability-runner-evidence.md
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeclaredAuthority {
    derivation: AuthorityDerivation,
}

impl DeclaredAuthority {
    /// Permitted rule-classes for this variant. Mono-class envelope per
    /// ADR-0006 §3 — `GuestAgentDeclaration` is the only legal class.
    pub const PERMITTED_CLASSES: &'static [RuleClass] = &[RuleClass::GuestAgentDeclaration];

    /// Construct a `DeclaredAuthority`.
    ///
    /// Runs [`AuthorityDerivation::validate`] plus the mono-class envelope
    /// check (any class other than `GuestAgentDeclaration` is rejected with
    /// [`ValidationError::TypeClassIncompatible`]).
    ///
    /// # Errors
    ///
    /// Returns the first [`ValidationError`] encountered.
    pub fn try_new(derivation: AuthorityDerivation) -> Result<Self, ValidationError> {
        derivation.validate()?;
        for class in derivation.class_set() {
            if !Self::PERMITTED_CLASSES.contains(&class) {
                return Err(ValidationError::TypeClassIncompatible {
                    type_name: "DeclaredAuthority",
                    permitted: Self::PERMITTED_CLASSES,
                    rejected: class,
                });
            }
        }
        Ok(Self { derivation })
    }

    /// Read-only access to the validated derivation.
    #[must_use]
    pub fn derivation(&self) -> &AuthorityDerivation {
        &self.derivation
    }

    /// Convenience accessor for the output tuple.
    #[must_use]
    pub fn output(&self) -> AuthorityOutput {
        self.derivation.output
    }
}

/// Compile-fail gate for guest event-builders (ADR-0006 / O2 doctrine
/// red-line). Implementors declare their canonical [`RuleClass`] as an
/// associated constant; a `const`-asserted check in [`Self::rule_class`]
/// makes any class other than [`RuleClass::GuestAgentDeclaration`] a
/// **compilation error** at the call site.
///
/// This is the type-system half of the F-tier admission gate: a guest
/// event-builder cannot accidentally route through `RawObservation`
/// (Tier 2 / `DirectObservation`) and silently launder a guest declaration
/// into a host-side epistemic status.
///
/// # Compile-fail example
///
/// Pointing `RULE_CLASS` at a non-guest class fails to compile when
/// `rule_class()` is invoked:
///
/// ```compile_fail
/// use cellos_core::authority::{GuestEventBuilder, RuleClass};
///
/// struct BadBuilder;
/// impl GuestEventBuilder for BadBuilder {
///     const RULE_CLASS: RuleClass = RuleClass::RawObservation;
/// }
///
/// let _ = BadBuilder.rule_class();
/// ```
pub trait GuestEventBuilder {
    /// The canonical [`RuleClass`] this builder emits. **MUST** be
    /// [`RuleClass::GuestAgentDeclaration`].
    const RULE_CLASS: RuleClass;

    /// Returns [`Self::RULE_CLASS`]. The default implementation is a
    /// `const`-asserted compile-time check that the class is
    /// `GuestAgentDeclaration`; overriding is allowed but the assertion
    /// still fires on any path that calls the trait method.
    fn rule_class(&self) -> RuleClass {
        const {
            assert!(
                matches!(Self::RULE_CLASS, RuleClass::GuestAgentDeclaration),
                "GuestEventBuilder::RULE_CLASS must be RuleClass::GuestAgentDeclaration",
            );
        }
        Self::RULE_CLASS
    }
}

// ---------------------------------------------------------------------------
// Doctrine red-line: D9 — no Into/From between types.
// ---------------------------------------------------------------------------
//
// The doctrine gate D9 (mechanical separation, Authority Model §14 +
// ADR-0006 §1) requires that none of the four typed-authority variants
// (`ObservedAuthority`, `ProvenAuthority`, `ImposedAuthority`,
// `DeclaredAuthority`) can be converted into another. Rust's coherence
// rules enforce this trivially: we simply do NOT implement
// `From<ObservedAuthority> for DeclaredAuthority` and so on.
//
// `tests::no_cross_type_conversion_compiles` (in the sibling `tests` module)
// does the soft confirmation: trait-object-style `is::<TypeA>()` checks on
// stored values plus an `assert_not_impl_all!`-style negative trait test.
// A genuine compile-fail test would require `trybuild`, which is not in our
// dev-dep set; the §14 contract is upheld by review discipline plus the
// run-time test that asserts the four types are distinct via `TypeId`.
