Skip to main content

cortex_core/
semantic_trust.rs

1//! Semantic provenance and trust classification.
2//!
3//! Structural proof closure answers whether Cortex can verify a claim's
4//! lineage and authority edges. Semantic trust answers a different question:
5//! whether the claim is grounded enough to influence future reasoning. ADR
6//! 0039 keeps these axes separate so proof, utility, and provenance cannot
7//! silently upgrade one another.
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::{ClaimCeiling, PolicyContribution, PolicyError, PolicyOutcome};
13
14/// Provenance family for a memory, principle support edge, or boundary claim.
15#[derive(
16    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
17)]
18#[serde(rename_all = "snake_case")]
19pub enum ProvenanceClass {
20    /// Provenance is missing, malformed, or not yet classified.
21    UnknownProvenance,
22    /// Produced by simulation, model inference, planning, or counterfactuals.
23    SimulatedOrHypothetical,
24    /// Claimed by an external source but not independently verified by Cortex.
25    ExternalClaimed,
26    /// Derived from summarization or interpretation over prior records.
27    SummaryDerived,
28    /// Derived from an agent or runtime execution record, including AXIOM.
29    RuntimeDerived,
30    /// Tool-backed observation with durable invocation provenance.
31    ToolObserved,
32    /// Direct operator-origin evidence with valid current-use attestation.
33    OperatorAttested,
34}
35
36impl ProvenanceClass {
37    /// Whether this provenance family is intrinsically derived rather than
38    /// independently observed.
39    #[must_use]
40    pub const fn is_derived(self) -> bool {
41        matches!(
42            self,
43            Self::RuntimeDerived | Self::SummaryDerived | Self::SimulatedOrHypothetical
44        )
45    }
46
47    /// Whether this provenance family can contribute to independent semantic
48    /// corroboration.
49    #[must_use]
50    pub const fn can_corroborate(self) -> bool {
51        matches!(self, Self::ToolObserved | Self::OperatorAttested)
52    }
53
54    /// Maximum claim ceiling this provenance class can support by itself.
55    ///
56    /// Provenance is not proof or authority. Even strong provenance is capped
57    /// by the separate proof, policy, and runtime ceilings elsewhere.
58    #[must_use]
59    pub const fn claim_ceiling(self) -> ClaimCeiling {
60        match self {
61            Self::UnknownProvenance | Self::SimulatedOrHypothetical => ClaimCeiling::DevOnly,
62            Self::ExternalClaimed | Self::SummaryDerived | Self::RuntimeDerived => {
63                ClaimCeiling::LocalUnsigned
64            }
65            Self::ToolObserved | Self::OperatorAttested => ClaimCeiling::AuthorityGrade,
66        }
67    }
68}
69
70/// Semantic trust posture for a claim after provenance review.
71#[derive(
72    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
73)]
74#[serde(rename_all = "snake_case")]
75pub enum SemanticTrustClass {
76    /// Trust posture is missing or cannot be computed.
77    Unknown,
78    /// The claim is retained only as an unverified candidate.
79    CandidateOnly,
80    /// The claim has one classified source family but lacks corroboration.
81    SingleFamily,
82    /// The claim has independent corroboration across acceptable families.
83    Corroborated,
84    /// The claim has corroboration plus explicit falsification/counterexample
85    /// evidence appropriate to the requested use.
86    FalsificationTested,
87}
88
89impl SemanticTrustClass {
90    /// Maximum claim ceiling supported by semantic trust alone.
91    #[must_use]
92    pub const fn claim_ceiling(self) -> ClaimCeiling {
93        match self {
94            Self::Unknown => ClaimCeiling::DevOnly,
95            Self::CandidateOnly | Self::SingleFamily => ClaimCeiling::LocalUnsigned,
96            Self::Corroborated => ClaimCeiling::SignedLocalLedger,
97            Self::FalsificationTested => ClaimCeiling::AuthorityGrade,
98        }
99    }
100}
101
102/// Authority surface that wants to consume semantic trust.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "snake_case")]
105pub enum SemanticUse {
106    /// Candidate memory or diagnostic review.
107    CandidateMemory,
108    /// Default retrieval or ordinary context use.
109    DefaultContext,
110    /// Advisory doctrine or weak principle support.
111    AdvisoryDoctrine,
112    /// High-force doctrine that can condition future behavior or gates.
113    HighForceDoctrine,
114    /// Trusted export, release, compliance, or external reporting surface.
115    TrustedExternalUse,
116}
117
118/// Evidence available for semantic trust evaluation.
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
120pub struct SemanticTrustInput {
121    /// Intended authority surface.
122    pub intended_use: SemanticUse,
123    /// Provenance classes represented by the supporting evidence.
124    pub provenance_classes: Vec<ProvenanceClass>,
125    /// Count of independent source families after deduplication by caller.
126    pub independent_source_families: u16,
127    /// Whether explicit falsification/counterexample review is attached.
128    pub falsification_evidence: bool,
129    /// Whether unresolved semantic unknowns remain.
130    pub unresolved_unknowns: bool,
131}
132
133impl SemanticTrustInput {
134    /// Construct input for one intended use.
135    #[must_use]
136    pub fn new(intended_use: SemanticUse) -> Self {
137        Self {
138            intended_use,
139            provenance_classes: Vec::new(),
140            independent_source_families: 0,
141            falsification_evidence: false,
142            unresolved_unknowns: true,
143        }
144    }
145
146    /// Attach provenance classes.
147    #[must_use]
148    pub fn with_provenance<I>(mut self, provenance_classes: I) -> Self
149    where
150        I: IntoIterator<Item = ProvenanceClass>,
151    {
152        self.provenance_classes = provenance_classes.into_iter().collect();
153        self
154    }
155
156    /// Attach caller-computed independent source-family count.
157    #[must_use]
158    pub const fn with_independent_source_families(mut self, count: u16) -> Self {
159        self.independent_source_families = count;
160        self
161    }
162
163    /// Attach falsification evidence state.
164    #[must_use]
165    pub const fn with_falsification_evidence(mut self, present: bool) -> Self {
166        self.falsification_evidence = present;
167        self
168    }
169
170    /// Attach unresolved semantic unknown state.
171    #[must_use]
172    pub const fn with_unresolved_unknowns(mut self, present: bool) -> Self {
173        self.unresolved_unknowns = present;
174        self
175    }
176}
177
178/// Result of semantic trust evaluation.
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
180pub struct SemanticTrustReport {
181    /// Computed semantic trust class.
182    pub semantic_trust: SemanticTrustClass,
183    /// Weakest provenance family present in the support set.
184    pub weakest_provenance: ProvenanceClass,
185    /// Policy outcome for the requested use.
186    pub policy_outcome: PolicyOutcome,
187    /// Maximum claim ceiling supported by semantic trust/provenance.
188    pub claim_ceiling: ClaimCeiling,
189    /// Stable reasons explaining downgrades or blocks.
190    pub reasons: Vec<String>,
191}
192
193impl SemanticTrustReport {
194    /// Whether this report permits the intended use without warning.
195    #[must_use]
196    pub const fn is_allow(&self) -> bool {
197        matches!(self.policy_outcome, PolicyOutcome::Allow)
198    }
199
200    /// Convert this report to an ADR 0026 policy contribution.
201    pub fn policy_contribution(&self) -> Result<PolicyContribution, PolicyError> {
202        PolicyContribution::new(
203            "semantic_trust.provenance",
204            self.policy_outcome,
205            self.reasons
206                .first()
207                .cloned()
208                .unwrap_or_else(|| "semantic trust evaluated".to_string()),
209        )
210    }
211}
212
213/// Evaluate semantic trust for a requested use.
214#[must_use]
215pub fn evaluate_semantic_trust(input: &SemanticTrustInput) -> SemanticTrustReport {
216    let weakest_provenance = input
217        .provenance_classes
218        .iter()
219        .copied()
220        .min()
221        .unwrap_or(ProvenanceClass::UnknownProvenance);
222
223    let has_unknown = input.provenance_classes.is_empty()
224        || input.unresolved_unknowns
225        || input
226            .provenance_classes
227            .contains(&ProvenanceClass::UnknownProvenance);
228    let corroborating_families = input
229        .provenance_classes
230        .iter()
231        .filter(|class| class.can_corroborate())
232        .count() as u16;
233    let runtime_or_weaker_only = !input.provenance_classes.is_empty()
234        && input
235            .provenance_classes
236            .iter()
237            .all(|class| !class.can_corroborate());
238
239    let semantic_trust = if has_unknown {
240        SemanticTrustClass::Unknown
241    } else if input.falsification_evidence
242        && input.independent_source_families >= 2
243        && corroborating_families >= 1
244    {
245        SemanticTrustClass::FalsificationTested
246    } else if input.independent_source_families >= 2 && corroborating_families >= 1 {
247        SemanticTrustClass::Corroborated
248    } else if !input.provenance_classes.is_empty() {
249        SemanticTrustClass::SingleFamily
250    } else {
251        SemanticTrustClass::CandidateOnly
252    };
253
254    let mut reasons = Vec::new();
255    let policy_outcome = match input.intended_use {
256        SemanticUse::CandidateMemory => {
257            if has_unknown {
258                reasons.push("unknown provenance retained as candidate-only".to_string());
259                PolicyOutcome::Warn
260            } else {
261                reasons
262                    .push("candidate memory may retain classified semantic evidence".to_string());
263                PolicyOutcome::Allow
264            }
265        }
266        SemanticUse::DefaultContext | SemanticUse::AdvisoryDoctrine => {
267            if has_unknown {
268                reasons.push("unknown provenance cannot enter default authority use".to_string());
269                PolicyOutcome::Quarantine
270            } else if runtime_or_weaker_only {
271                reasons.push(
272                    "runtime-derived or weak-only provenance is advisory/candidate only"
273                        .to_string(),
274                );
275                PolicyOutcome::Warn
276            } else {
277                reasons.push("semantic provenance permits bounded advisory use".to_string());
278                PolicyOutcome::Allow
279            }
280        }
281        SemanticUse::HighForceDoctrine | SemanticUse::TrustedExternalUse => {
282            if has_unknown {
283                reasons.push("unknown provenance cannot satisfy high-authority use".to_string());
284                PolicyOutcome::Reject
285            } else if runtime_or_weaker_only {
286                reasons.push(
287                    "runtime-only or weak-only support cannot satisfy high-authority use"
288                        .to_string(),
289                );
290                PolicyOutcome::Reject
291            } else if semantic_trust < SemanticTrustClass::FalsificationTested {
292                reasons.push(
293                    "high-authority use requires corroboration plus falsification evidence"
294                        .to_string(),
295                );
296                PolicyOutcome::Reject
297            } else {
298                reasons.push(
299                    "semantic trust permits high-authority use subject to other gates".to_string(),
300                );
301                PolicyOutcome::Allow
302            }
303        }
304    };
305
306    let claim_ceiling = weakest_provenance
307        .claim_ceiling()
308        .min(semantic_trust.claim_ceiling())
309        .min(policy_outcome.claim_ceiling());
310
311    SemanticTrustReport {
312        semantic_trust,
313        weakest_provenance,
314        policy_outcome,
315        claim_ceiling,
316        reasons,
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use serde_json::json;
323
324    use super::*;
325
326    #[test]
327    fn provenance_wire_strings_are_stable() {
328        assert_eq!(
329            serde_json::to_value(ProvenanceClass::OperatorAttested).unwrap(),
330            json!("operator_attested")
331        );
332        assert_eq!(
333            serde_json::to_value(ProvenanceClass::ToolObserved).unwrap(),
334            json!("tool_observed")
335        );
336        assert_eq!(
337            serde_json::to_value(ProvenanceClass::RuntimeDerived).unwrap(),
338            json!("runtime_derived")
339        );
340        assert_eq!(
341            serde_json::to_value(ProvenanceClass::UnknownProvenance).unwrap(),
342            json!("unknown_provenance")
343        );
344    }
345
346    #[test]
347    fn unknown_provenance_fails_closed_for_high_force_doctrine() {
348        let report = evaluate_semantic_trust(
349            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
350                .with_provenance([ProvenanceClass::UnknownProvenance])
351                .with_independent_source_families(1)
352                .with_unresolved_unknowns(true),
353        );
354
355        assert_eq!(report.semantic_trust, SemanticTrustClass::Unknown);
356        assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
357        assert_eq!(report.claim_ceiling, ClaimCeiling::DevOnly);
358    }
359
360    #[test]
361    fn runtime_only_support_cannot_promote_high_force_doctrine() {
362        let report = evaluate_semantic_trust(
363            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
364                .with_provenance([
365                    ProvenanceClass::RuntimeDerived,
366                    ProvenanceClass::SummaryDerived,
367                ])
368                .with_independent_source_families(2)
369                .with_falsification_evidence(true)
370                .with_unresolved_unknowns(false),
371        );
372
373        assert_eq!(report.policy_outcome, PolicyOutcome::Reject);
374        assert_eq!(report.semantic_trust, SemanticTrustClass::SingleFamily);
375        assert!(report
376            .reasons
377            .iter()
378            .any(|reason| reason.contains("runtime-only")));
379    }
380
381    #[test]
382    fn corroborated_and_falsified_support_can_pass_semantic_gate() {
383        let report = evaluate_semantic_trust(
384            &SemanticTrustInput::new(SemanticUse::HighForceDoctrine)
385                .with_provenance([
386                    ProvenanceClass::ToolObserved,
387                    ProvenanceClass::OperatorAttested,
388                ])
389                .with_independent_source_families(2)
390                .with_falsification_evidence(true)
391                .with_unresolved_unknowns(false),
392        );
393
394        assert_eq!(
395            report.semantic_trust,
396            SemanticTrustClass::FalsificationTested
397        );
398        assert_eq!(report.policy_outcome, PolicyOutcome::Allow);
399        assert_eq!(report.claim_ceiling, ClaimCeiling::AuthorityGrade);
400    }
401
402    #[test]
403    fn policy_contribution_is_machine_readable() {
404        let report = evaluate_semantic_trust(
405            &SemanticTrustInput::new(SemanticUse::DefaultContext)
406                .with_provenance([ProvenanceClass::RuntimeDerived])
407                .with_independent_source_families(1)
408                .with_unresolved_unknowns(false),
409        );
410
411        let contribution = report.policy_contribution().unwrap();
412        assert_eq!(contribution.rule_id.as_str(), "semantic_trust.provenance");
413        assert_eq!(contribution.outcome, PolicyOutcome::Warn);
414    }
415}