1use chrono::{DateTime, Utc};
21use serde::{Deserialize, Serialize};
22
23pub const CORTEX_CONTEXT_TRUST_SCHEMA: &str = "cortex_context_trust";
25
26pub const AXIOM_EXECUTION_TRUST_SCHEMA: &str = "axiom_execution_trust";
28
29pub const AUTHORITY_FEEDBACK_LOOP_SCHEMA: &str = "authority_feedback_loop";
31
32pub const TRUST_EXCHANGE_SCHEMA_VERSION: u16 = 1;
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct CortexContextTrust {
48 pub schema: String,
50 pub version: u16,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
55 pub cortex_context_trust_ref: Option<String>,
56 pub context_id: String,
58 pub compatibility_trust_label: CompatibilityTrustLabel,
60 pub proof_state: ContextProofState,
62 pub truth_ceiling: TruthCeiling,
64 pub semantic_trust: ContextSemanticTrust,
66 #[serde(default)]
68 pub provenance_refs: Vec<String>,
69 #[serde(default = "ContradictionState::default_unknown")]
71 pub contradiction_state: ContradictionState,
72 #[serde(default = "PromotionState::default_candidate")]
74 pub promotion_state: PromotionState,
75 pub quarantine_state: ContextQuarantineState,
77 pub redaction_state: ContextRedactionState,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub confidence: Option<ContextConfidence>,
82 pub policy_result: ContextPolicyResult,
84 #[serde(default)]
86 pub allowed_claim_language: Vec<ContextAllowedClaimLanguage>,
87 #[serde(default)]
89 pub forbidden_uses: Vec<ContextForbiddenUse>,
90 #[serde(default)]
92 pub allowed_use: Vec<ContextAllowedUse>,
93 #[serde(default)]
95 pub evidence_refs: Vec<String>,
96 pub source_anchors: Vec<ContextSourceAnchor>,
98 #[serde(default)]
100 pub residual_risk: Vec<String>,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case")]
106pub enum CompatibilityTrustLabel {
107 Untrusted,
109 Advisory,
111 Observed,
113 Validated,
115 AuthorityClaimed,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(deny_unknown_fields)]
122pub struct ContextProofState {
123 pub state: ContextProofStateValue,
125 pub failing_edges: Vec<String>,
127 pub proof_refs: Vec<String>,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(rename_all = "snake_case")]
134pub enum ContextProofStateValue {
135 Missing,
137 Partial,
139 Closed,
141 Failed,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
147#[serde(rename_all = "kebab-case")]
148pub enum TruthCeiling {
149 None,
151 Advisory,
153 Observed,
155 Validated,
157 PromotedByCortex,
159}
160
161impl Serialize for TruthCeiling {
162 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163 serializer.serialize_str(self.wire_string())
164 }
165}
166
167impl TruthCeiling {
168 #[must_use]
170 pub const fn wire_string(self) -> &'static str {
171 match self {
172 Self::None => "none",
173 Self::Advisory => "advisory",
174 Self::Observed => "observed",
175 Self::Validated => "validated",
176 Self::PromotedByCortex => "promoted-by-cortex",
177 }
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
183#[serde(deny_unknown_fields)]
184pub struct ContextSemanticTrust {
185 pub provenance_class: ContextProvenanceClass,
187 pub trust_weight: f64,
189 pub weighting_basis: String,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
195#[serde(rename_all = "snake_case")]
196pub enum ContextProvenanceClass {
197 Unknown,
199 Claimed,
201 Observed,
203 Derived,
205 Curated,
207 Promoted,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214pub enum ContradictionState {
215 NoneKnown,
217 Unresolved,
219 Resolved,
221 Unknown,
223}
224
225impl ContradictionState {
226 fn default_unknown() -> Self {
227 Self::Unknown
228 }
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
233#[serde(rename_all = "snake_case")]
234pub enum PromotionState {
235 Candidate,
237 Observed,
239 Validated,
241 Promoted,
243 Stale,
245 Quarantined,
247}
248
249impl PromotionState {
250 fn default_candidate() -> Self {
251 Self::Candidate
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
257#[serde(rename_all = "snake_case")]
258pub enum ContextQuarantineState {
259 Clear,
261 Quarantined,
263 DerivedFromQuarantined,
265 Unknown,
267}
268
269impl ContextQuarantineState {
270 #[must_use]
272 pub const fn propagates_quarantine(self) -> bool {
273 matches!(
274 self,
275 Self::Quarantined | Self::DerivedFromQuarantined | Self::Unknown
276 )
277 }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(deny_unknown_fields)]
288pub struct ContextRedactionState {
289 pub status: ContextRedactionStatus,
291 pub redaction_refs: Vec<String>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub blocks_critical_premise: Option<bool>,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
300#[serde(rename_all = "snake_case")]
301pub enum ContextRedactionStatus {
302 None,
304 Redacted,
306 PartiallyRedacted,
308 Unknown,
310}
311
312#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
314#[serde(deny_unknown_fields)]
315pub struct ContextConfidence {
316 pub value: ContextConfidenceValue,
318 pub scale: ContextConfidenceScale,
320}
321
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
324#[serde(untagged)]
325pub enum ContextConfidenceValue {
326 Numeric(f64),
328 Discrete(String),
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
334#[serde(rename_all = "snake_case")]
335pub enum ContextConfidenceScale {
336 Discrete,
338 Numeric,
340}
341
342#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
344#[serde(deny_unknown_fields)]
345pub struct ContextPolicyResult {
346 pub decision_id: String,
348 pub result: ContextPolicyResultValue,
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355pub enum ContextPolicyResultValue {
356 Deny,
358 ReviewRequired,
360 Partial,
362 Allow,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
368#[serde(rename_all = "snake_case")]
369pub enum ContextAllowedClaimLanguage {
370 Candidate,
372 Observed,
374 Validated,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
380#[serde(rename_all = "snake_case")]
381pub enum ContextForbiddenUse {
382 ExecutionPermission,
384 DurableTruthPromotion,
386 ReleaseClaim,
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
392#[serde(rename_all = "snake_case")]
393pub enum ContextAllowedUse {
394 RenderOnly,
396 AdvisoryReasoning,
398 PlanningInput,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404#[serde(deny_unknown_fields)]
405pub struct ContextSourceAnchor {
406 pub source_id: String,
408 pub source_type: ContextSourceAnchorType,
410 pub r#ref: String,
412 pub hash: String,
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
418#[serde(rename_all = "snake_case")]
419pub enum ContextSourceAnchorType {
420 Event,
422 Memory,
424 Principle,
426 Doctrine,
428 Ledger,
430 ContextPack,
432 External,
434}
435
436#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
442#[serde(deny_unknown_fields)]
443pub struct AxiomExecutionTrust {
444 pub schema: String,
446 pub version: u16,
448 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub axiom_execution_trust_ref: Option<String>,
452 pub action_id: String,
454 pub execution_trust_level: ExecutionTrustLevel,
456 pub repo_trust: RepoTrust,
458 pub actor_attestation: ActorAttestation,
460 pub policy_decision: ExecutionPolicyDecision,
462 pub token_scope: TokenScope,
464 pub tool_provenance: ExecutionToolProvenance,
466 pub source_anchors: Vec<ExecutionSourceAnchor>,
468 pub runtime_mode: String,
470 #[serde(default)]
472 pub evidence_refs: Vec<String>,
473 #[serde(default)]
475 pub residual_risk: Vec<String>,
476}
477
478#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
480#[serde(rename_all = "snake_case")]
481pub enum ExecutionTrustLevel {
482 Dev,
484 LocalUnsigned,
486 SignedLocal,
488 ExternallyAnchored,
490 AuthorityGrade,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(deny_unknown_fields)]
497pub struct RepoTrust {
498 pub result: RepoTrustResult,
500 pub evaluation_ref: String,
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
506#[serde(rename_all = "snake_case")]
507pub enum RepoTrustResult {
508 Trusted,
510 Partial,
512 Untrusted,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
518#[serde(deny_unknown_fields)]
519pub struct ActorAttestation {
520 pub identity_ref: String,
522 pub attestation_ref: String,
524 pub operator_approval_ref: String,
526 pub operator_approval_hash: String,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
532#[serde(deny_unknown_fields)]
533pub struct ExecutionPolicyDecision {
534 pub decision_id: String,
536 pub result: ExecutionPolicyResult,
538}
539
540#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
542#[serde(rename_all = "snake_case")]
543pub enum ExecutionPolicyResult {
544 Deny,
546 ReviewRequired,
548 Partial,
550 Allow,
552}
553
554#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
556#[serde(deny_unknown_fields)]
557pub struct TokenScope {
558 pub token_id: String,
560 pub audience: String,
562 pub scope_hash: String,
564 pub operation_hash: String,
566 pub manifest_hash: String,
568 pub request_hash: String,
570 pub expires_at: DateTime<Utc>,
572 pub revocation_result: TokenRevocationResult,
574}
575
576#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
582#[serde(rename_all = "snake_case")]
583pub enum TokenRevocationResult {
584 Active,
586 Revoked,
588 Inactive,
590 Unknown,
592}
593
594impl TokenRevocationResult {
595 #[must_use]
597 pub const fn must_reject(self) -> bool {
598 matches!(self, Self::Revoked | Self::Inactive)
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
604#[serde(deny_unknown_fields)]
605pub struct ExecutionToolProvenance {
606 pub tool_id: String,
608 pub tool_version: String,
610 pub command_ref: String,
612 pub source_commit: String,
614 pub dependency_lock_ref: String,
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
620#[serde(deny_unknown_fields)]
621pub struct ExecutionSourceAnchor {
622 pub source_id: String,
624 pub source_type: ExecutionSourceAnchorType,
626 pub r#ref: String,
628 pub hash: String,
630}
631
632#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
634#[serde(rename_all = "snake_case")]
635pub enum ExecutionSourceAnchorType {
636 Command,
638 File,
640 Test,
642 Ledger,
644 Runtime,
646 Approval,
648}
649
650#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
656#[serde(deny_unknown_fields)]
657pub struct AuthorityFeedbackLoop {
658 pub schema: String,
660 pub version: u16,
662 #[serde(default, skip_serializing_if = "Option::is_none")]
664 pub authority_feedback_loop_ref: Option<String>,
665 pub loop_id: String,
667 pub started_at: DateTime<Utc>,
669 pub initiating_context: FeedbackInitiatingContext,
671 pub axiom_action: FeedbackAxiomAction,
673 pub returned_artifacts: Vec<FeedbackReturnedArtifact>,
675 pub amplification_risk: AmplificationRisk,
677 #[serde(default)]
679 pub independent_evidence_refs: Vec<String>,
680 #[serde(default)]
682 pub external_grounding_refs: Vec<String>,
683 pub contradiction_scan_ref: String,
685 pub quarantine_state: ContextQuarantineState,
687 pub confidence_ceiling: ConfidenceCeiling,
689 pub same_loop_promotion_allowed: bool,
691 pub authority_claims: FeedbackAuthorityClaims,
694 pub target_domain_validation: TargetDomainValidation,
696 #[serde(default)]
698 pub residual_risk: Vec<String>,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
703#[serde(deny_unknown_fields)]
704pub struct FeedbackInitiatingContext {
705 pub context_id: String,
707 pub cortex_context_trust_ref: String,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
713#[serde(deny_unknown_fields)]
714pub struct FeedbackAxiomAction {
715 pub action_id: String,
717 pub axiom_execution_trust_ref: String,
719}
720
721#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
723#[serde(deny_unknown_fields)]
724pub struct FeedbackReturnedArtifact {
725 pub artifact_id: String,
727 pub lineage_ref: String,
729 pub lifecycle_state: ArtifactLifecycleState,
731 pub reproducibility_level: ReproducibilityLevel,
733}
734
735#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
737#[serde(rename_all = "snake_case")]
738pub enum ArtifactLifecycleState {
739 Candidate,
741 Observed,
743 Validated,
745 Promoted,
747 Stale,
749 Quarantined,
751}
752
753#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
755#[serde(rename_all = "kebab-case")]
756pub enum ReproducibilityLevel {
757 Deterministic,
759 BoundedNondeterministic,
761 Observational,
763 NonReproducible,
765}
766
767impl Serialize for ReproducibilityLevel {
768 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
769 serializer.serialize_str(self.wire_string())
770 }
771}
772
773impl ReproducibilityLevel {
774 #[must_use]
776 pub const fn wire_string(self) -> &'static str {
777 match self {
778 Self::Deterministic => "deterministic",
779 Self::BoundedNondeterministic => "bounded-nondeterministic",
780 Self::Observational => "observational",
781 Self::NonReproducible => "non-reproducible",
782 }
783 }
784}
785
786#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
788#[serde(rename_all = "snake_case")]
789pub enum AmplificationRisk {
790 Low,
792 Medium,
794 High,
796 Critical,
798}
799
800#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
802#[serde(rename_all = "snake_case")]
803pub enum ConfidenceCeiling {
804 Untrusted,
806 Advisory,
808 Observed,
810 Validated,
812}
813
814#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
816#[serde(deny_unknown_fields)]
817pub struct FeedbackAuthorityClaims {
818 pub durable_truth_promotion: AuthorityClaimStatus,
820 pub full_execution_authority: AuthorityClaimStatus,
822 pub review_required: bool,
824}
825
826#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
828#[serde(rename_all = "snake_case")]
829pub enum AuthorityClaimStatus {
830 Denied,
832 ReviewRequired,
834 EligibleAfterIndependentValidation,
836}
837
838impl AuthorityClaimStatus {
839 #[must_use]
842 pub const fn claims_authority(self) -> bool {
843 matches!(self, Self::EligibleAfterIndependentValidation)
844 }
845}
846
847#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
849#[serde(deny_unknown_fields)]
850pub struct TargetDomainValidation {
851 pub required: bool,
853 pub independent_validation_ref: Option<String>,
855 pub result: TargetDomainValidationResult,
857}
858
859#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
861#[serde(rename_all = "snake_case")]
862pub enum TargetDomainValidationResult {
863 Pending,
865 Pass,
867 Fail,
869 Partial,
871}
872
873#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
879#[serde(deny_unknown_fields)]
880pub struct QuarantineOutput {
881 pub invariant: String,
883 pub reason: String,
885 pub source_ref: Option<String>,
887}
888
889impl QuarantineOutput {
890 #[must_use]
892 pub fn new(invariant: impl Into<String>, reason: impl Into<String>) -> Self {
893 Self {
894 invariant: invariant.into(),
895 reason: reason.into(),
896 source_ref: None,
897 }
898 }
899
900 #[must_use]
902 pub fn with_source_ref(mut self, source_ref: impl Into<String>) -> Self {
903 self.source_ref = Some(source_ref.into());
904 self
905 }
906}
907
908#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
910#[serde(deny_unknown_fields)]
911pub struct NamedQuarantineOutputs {
912 #[serde(default, skip_serializing_if = "Option::is_none")]
914 pub source_context: Option<QuarantineOutput>,
915 #[serde(default, skip_serializing_if = "Option::is_none")]
917 pub token_revocation: Option<QuarantineOutput>,
918 #[serde(default, skip_serializing_if = "Option::is_none")]
920 pub repo_trust: Option<QuarantineOutput>,
921 #[serde(default, skip_serializing_if = "Option::is_none")]
923 pub policy_denial: Option<QuarantineOutput>,
924 #[serde(default, skip_serializing_if = "Option::is_none")]
926 pub target_validation: Option<QuarantineOutput>,
927 #[serde(default, skip_serializing_if = "Option::is_none")]
929 pub derived_artifact: Option<QuarantineOutput>,
930 #[serde(default, skip_serializing_if = "Option::is_none")]
932 pub contradiction: Option<QuarantineOutput>,
933}
934
935impl NamedQuarantineOutputs {
936 #[must_use]
938 pub fn any_present(&self) -> bool {
939 self.source_context.is_some()
940 || self.token_revocation.is_some()
941 || self.repo_trust.is_some()
942 || self.policy_denial.is_some()
943 || self.target_validation.is_some()
944 || self.derived_artifact.is_some()
945 || self.contradiction.is_some()
946 }
947
948 pub fn invariants(&self) -> impl Iterator<Item = &str> {
950 [
951 self.source_context.as_ref(),
952 self.token_revocation.as_ref(),
953 self.repo_trust.as_ref(),
954 self.policy_denial.as_ref(),
955 self.target_validation.as_ref(),
956 self.derived_artifact.as_ref(),
957 self.contradiction.as_ref(),
958 ]
959 .into_iter()
960 .flatten()
961 .map(|q| q.invariant.as_str())
962 }
963}
964
965#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
975#[serde(deny_unknown_fields)]
976pub struct TrustExchangeFieldError {
977 pub invariant: String,
979 pub reason: String,
981}
982
983impl TrustExchangeFieldError {
984 #[must_use]
986 pub fn new(invariant: impl Into<String>, reason: impl Into<String>) -> Self {
987 Self {
988 invariant: invariant.into(),
989 reason: reason.into(),
990 }
991 }
992}
993
994pub type TrustExchangeValidation = Result<(), Vec<TrustExchangeFieldError>>;
996
997#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1004#[serde(deny_unknown_fields)]
1005pub struct CortexContextTrustEnvelope {
1006 pub cortex_context_trust: CortexContextTrust,
1008}
1009
1010#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1013#[serde(deny_unknown_fields)]
1014pub struct AxiomExecutionTrustEnvelope {
1015 pub axiom_execution_trust: AxiomExecutionTrust,
1017}
1018
1019pub fn parse_cortex_context_trust(
1027 input: &str,
1028) -> Result<CortexContextTrust, TrustExchangeFieldError> {
1029 let value: serde_json::Value = serde_json::from_str(input).map_err(|err| {
1030 TrustExchangeFieldError::new(
1031 "cortex_context_trust.envelope.invalid_json",
1032 format!("invalid JSON: {err}"),
1033 )
1034 })?;
1035 let inner = match value.get("cortex_context_trust") {
1036 Some(inner) => inner.clone(),
1037 None => value,
1038 };
1039 serde_json::from_value(inner).map_err(|err| {
1040 TrustExchangeFieldError::new(
1041 "cortex_context_trust.envelope.schema_drift",
1042 format!("envelope failed schema check: {err}"),
1043 )
1044 })
1045}
1046
1047pub fn parse_axiom_execution_trust(
1050 input: &str,
1051) -> Result<AxiomExecutionTrust, TrustExchangeFieldError> {
1052 let value: serde_json::Value = serde_json::from_str(input).map_err(|err| {
1053 TrustExchangeFieldError::new(
1054 "axiom_execution_trust.envelope.invalid_json",
1055 format!("invalid JSON: {err}"),
1056 )
1057 })?;
1058 let inner = match value.get("axiom_execution_trust") {
1059 Some(inner) => inner.clone(),
1060 None => value,
1061 };
1062 serde_json::from_value(inner).map_err(|err| {
1063 TrustExchangeFieldError::new(
1064 "axiom_execution_trust.envelope.schema_drift",
1065 format!("envelope failed schema check: {err}"),
1066 )
1067 })
1068}
1069
1070pub fn parse_authority_feedback_loop(
1073 input: &str,
1074) -> Result<AuthorityFeedbackLoop, TrustExchangeFieldError> {
1075 serde_json::from_str(input).map_err(|err| {
1076 TrustExchangeFieldError::new(
1077 "authority_feedback_loop.envelope.schema_drift",
1078 format!("envelope failed schema check: {err}"),
1079 )
1080 })
1081}
1082
1083pub const AXIOM_EXECUTION_TRUST_SOURCE_COMMIT_STALE_INVARIANT: &str =
1107 "axiom_execution_trust.tool_provenance.source_commit.stale";
1108
1109pub const CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV: &str = "CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS";
1120
1121pub const DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS: &[&str] = &[
1146 "44b9a25dfbe5a93e64120f53b65730828d1af91c",
1148 "062d0d42ac5ef300fa3e04ef5b49b14864babcdd",
1150 "9a15d281ddcc2bcf36956fbe6d6c5736d8ce706a",
1152 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1154];
1155
1156#[must_use]
1171pub fn accepted_axiom_source_commits() -> Vec<String> {
1172 if let Ok(raw) = std::env::var(CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV) {
1173 return raw
1174 .split(',')
1175 .map(|piece| piece.trim().to_ascii_lowercase())
1176 .filter(|piece| !piece.is_empty())
1177 .collect();
1178 }
1179 DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
1180 .iter()
1181 .map(|sha| (*sha).to_string())
1182 .collect()
1183}
1184
1185#[must_use]
1195pub fn is_axiom_source_commit_fresh(candidate: &str, accepted: &[String]) -> bool {
1196 let lowered = candidate.to_ascii_lowercase();
1197 accepted.iter().any(|sha| sha == &lowered)
1198}
1199
1200const SHA256_PATTERN_PREFIX: &str = "sha256:";
1205const SHA256_HEX_LEN: usize = 64;
1206
1207fn is_sha256_hash(value: &str) -> bool {
1208 if let Some(hex) = value.strip_prefix(SHA256_PATTERN_PREFIX) {
1209 hex.len() == SHA256_HEX_LEN
1210 && hex
1211 .chars()
1212 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
1213 } else {
1214 false
1215 }
1216}
1217
1218fn is_commit_sha(value: &str) -> bool {
1219 value.len() == 40
1220 && value
1221 .chars()
1222 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
1223}
1224
1225fn push(errors: &mut Vec<TrustExchangeFieldError>, invariant: &str, reason: impl Into<String>) {
1226 errors.push(TrustExchangeFieldError::new(invariant, reason));
1227}
1228
1229fn require_nonblank(
1230 errors: &mut Vec<TrustExchangeFieldError>,
1231 value: &str,
1232 invariant: &str,
1233 reason: &str,
1234) {
1235 if value.trim().is_empty() {
1236 push(errors, invariant, reason);
1237 }
1238}
1239
1240impl CortexContextTrust {
1241 pub fn validate(&self) -> TrustExchangeValidation {
1246 let mut errors: Vec<TrustExchangeFieldError> = Vec::new();
1247
1248 if self.schema != CORTEX_CONTEXT_TRUST_SCHEMA {
1249 push(
1250 &mut errors,
1251 "cortex_context_trust.schema.mismatch",
1252 format!(
1253 "schema must be `{CORTEX_CONTEXT_TRUST_SCHEMA}`, got `{}`",
1254 self.schema
1255 ),
1256 );
1257 }
1258 if self.version != TRUST_EXCHANGE_SCHEMA_VERSION {
1259 push(
1260 &mut errors,
1261 "cortex_context_trust.version.mismatch",
1262 format!(
1263 "version must be {TRUST_EXCHANGE_SCHEMA_VERSION}, got {}",
1264 self.version
1265 ),
1266 );
1267 }
1268
1269 require_nonblank(
1270 &mut errors,
1271 &self.context_id,
1272 "cortex_context_trust.context_id.missing",
1273 "context_id must be a non-empty string",
1274 );
1275
1276 if self
1278 .proof_state
1279 .failing_edges
1280 .iter()
1281 .any(|e| e.trim().is_empty())
1282 {
1283 push(
1284 &mut errors,
1285 "cortex_context_trust.proof_state.failing_edges.blank_entry",
1286 "proof_state.failing_edges entries must not be blank",
1287 );
1288 }
1289 if self
1290 .proof_state
1291 .proof_refs
1292 .iter()
1293 .any(|e| e.trim().is_empty())
1294 {
1295 push(
1296 &mut errors,
1297 "cortex_context_trust.proof_state.proof_refs.blank_entry",
1298 "proof_state.proof_refs entries must not be blank",
1299 );
1300 }
1301 if matches!(
1302 self.proof_state.state,
1303 ContextProofStateValue::Missing | ContextProofStateValue::Failed
1304 ) {
1305 push(
1306 &mut errors,
1307 "cortex_context_trust.proof_state.state.unusable",
1308 "proof_state.state must be `closed` or `partial` for Cortex consumption",
1309 );
1310 }
1311
1312 require_nonblank(
1314 &mut errors,
1315 &self.semantic_trust.weighting_basis,
1316 "cortex_context_trust.semantic_trust.weighting_basis.missing",
1317 "semantic_trust.weighting_basis must be a non-empty string",
1318 );
1319 if !(0.0..=1.0).contains(&self.semantic_trust.trust_weight) {
1320 push(
1321 &mut errors,
1322 "cortex_context_trust.semantic_trust.trust_weight.out_of_range",
1323 "semantic_trust.trust_weight must be in [0, 1]",
1324 );
1325 }
1326 if matches!(
1327 self.semantic_trust.provenance_class,
1328 ContextProvenanceClass::Unknown
1329 ) {
1330 push(
1331 &mut errors,
1332 "cortex_context_trust.semantic_trust.provenance_class.unknown",
1333 "semantic_trust.provenance_class must be a known class",
1334 );
1335 }
1336
1337 if matches!(self.redaction_state.status, ContextRedactionStatus::Unknown) {
1339 push(
1340 &mut errors,
1341 "cortex_context_trust.redaction_state.status.unknown",
1342 "redaction_state.status must be a known status",
1343 );
1344 }
1345 if self
1346 .redaction_state
1347 .redaction_refs
1348 .iter()
1349 .any(|e| e.trim().is_empty())
1350 {
1351 push(
1352 &mut errors,
1353 "cortex_context_trust.redaction_state.redaction_refs.blank_entry",
1354 "redaction_state.redaction_refs entries must not be blank",
1355 );
1356 }
1357
1358 require_nonblank(
1360 &mut errors,
1361 &self.policy_result.decision_id,
1362 "cortex_context_trust.policy_result.decision_id.missing",
1363 "policy_result.decision_id must be a non-empty string",
1364 );
1365
1366 if self.allowed_claim_language.len() < 3 {
1368 push(
1369 &mut errors,
1370 "cortex_context_trust.allowed_claim_language.insufficient_coverage",
1371 "allowed_claim_language must include candidate, observed, and validated",
1372 );
1373 }
1374 if self.forbidden_uses.len() < 3 {
1375 push(
1376 &mut errors,
1377 "cortex_context_trust.forbidden_uses.insufficient_coverage",
1378 "forbidden_uses must include execution_permission, durable_truth_promotion, release_claim",
1379 );
1380 }
1381
1382 if self.source_anchors.is_empty() {
1384 push(
1385 &mut errors,
1386 "cortex_context_trust.source_anchors.missing",
1387 "source_anchors must contain at least one anchor",
1388 );
1389 }
1390 for anchor in &self.source_anchors {
1391 require_nonblank(
1392 &mut errors,
1393 &anchor.source_id,
1394 "cortex_context_trust.source_anchors.source_id.missing",
1395 "source_anchors[].source_id must be a non-empty string",
1396 );
1397 require_nonblank(
1398 &mut errors,
1399 &anchor.r#ref,
1400 "cortex_context_trust.source_anchors.ref.missing",
1401 "source_anchors[].ref must be a non-empty string",
1402 );
1403 if !is_sha256_hash(&anchor.hash) {
1404 push(
1405 &mut errors,
1406 "cortex_context_trust.source_anchors.hash.invalid_format",
1407 "source_anchors[].hash must match sha256:<64 lowercase hex>",
1408 );
1409 }
1410 }
1411
1412 if errors.is_empty() {
1413 Ok(())
1414 } else {
1415 Err(errors)
1416 }
1417 }
1418}
1419
1420impl AxiomExecutionTrust {
1421 pub fn validate(&self) -> TrustExchangeValidation {
1424 let mut errors: Vec<TrustExchangeFieldError> = Vec::new();
1425
1426 if self.schema != AXIOM_EXECUTION_TRUST_SCHEMA {
1427 push(
1428 &mut errors,
1429 "axiom_execution_trust.schema.mismatch",
1430 format!(
1431 "schema must be `{AXIOM_EXECUTION_TRUST_SCHEMA}`, got `{}`",
1432 self.schema
1433 ),
1434 );
1435 }
1436 if self.version != TRUST_EXCHANGE_SCHEMA_VERSION {
1437 push(
1438 &mut errors,
1439 "axiom_execution_trust.version.mismatch",
1440 format!(
1441 "version must be {TRUST_EXCHANGE_SCHEMA_VERSION}, got {}",
1442 self.version
1443 ),
1444 );
1445 }
1446
1447 require_nonblank(
1448 &mut errors,
1449 &self.action_id,
1450 "axiom_execution_trust.action_id.missing",
1451 "action_id must be a non-empty string",
1452 );
1453
1454 require_nonblank(
1456 &mut errors,
1457 &self.repo_trust.evaluation_ref,
1458 "axiom_execution_trust.repo_trust.evaluation_ref.missing",
1459 "repo_trust.evaluation_ref must be a non-empty string",
1460 );
1461
1462 require_nonblank(
1464 &mut errors,
1465 &self.actor_attestation.identity_ref,
1466 "axiom_execution_trust.actor_attestation.identity_ref.missing",
1467 "actor_attestation.identity_ref must be a non-empty string",
1468 );
1469 require_nonblank(
1470 &mut errors,
1471 &self.actor_attestation.attestation_ref,
1472 "axiom_execution_trust.actor_attestation.attestation_ref.missing",
1473 "actor_attestation.attestation_ref must be a non-empty string",
1474 );
1475 require_nonblank(
1476 &mut errors,
1477 &self.actor_attestation.operator_approval_ref,
1478 "axiom_execution_trust.actor_attestation.operator_approval_ref.missing",
1479 "actor_attestation.operator_approval_ref must be a non-empty string",
1480 );
1481 if !is_sha256_hash(&self.actor_attestation.operator_approval_hash) {
1482 push(
1483 &mut errors,
1484 "axiom_execution_trust.actor_attestation.operator_approval_hash.invalid_format",
1485 "actor_attestation.operator_approval_hash must match sha256:<64 lowercase hex>",
1486 );
1487 }
1488
1489 require_nonblank(
1491 &mut errors,
1492 &self.policy_decision.decision_id,
1493 "axiom_execution_trust.policy_decision.decision_id.missing",
1494 "policy_decision.decision_id must be a non-empty string",
1495 );
1496
1497 require_nonblank(
1499 &mut errors,
1500 &self.token_scope.token_id,
1501 "axiom_execution_trust.token_scope.token_id.missing",
1502 "token_scope.token_id must be a non-empty string",
1503 );
1504 require_nonblank(
1505 &mut errors,
1506 &self.token_scope.audience,
1507 "axiom_execution_trust.token_scope.audience.missing",
1508 "token_scope.audience must be a non-empty string",
1509 );
1510 for (field, value, invariant) in [
1511 (
1512 "scope_hash",
1513 &self.token_scope.scope_hash,
1514 "axiom_execution_trust.token_scope.scope_hash.invalid_format",
1515 ),
1516 (
1517 "operation_hash",
1518 &self.token_scope.operation_hash,
1519 "axiom_execution_trust.token_scope.operation_hash.invalid_format",
1520 ),
1521 (
1522 "manifest_hash",
1523 &self.token_scope.manifest_hash,
1524 "axiom_execution_trust.token_scope.manifest_hash.invalid_format",
1525 ),
1526 (
1527 "request_hash",
1528 &self.token_scope.request_hash,
1529 "axiom_execution_trust.token_scope.request_hash.invalid_format",
1530 ),
1531 ] {
1532 if !is_sha256_hash(value) {
1533 push(
1534 &mut errors,
1535 invariant,
1536 format!("token_scope.{field} must match sha256:<64 lowercase hex>"),
1537 );
1538 }
1539 }
1540
1541 require_nonblank(
1543 &mut errors,
1544 &self.tool_provenance.tool_id,
1545 "axiom_execution_trust.tool_provenance.tool_id.missing",
1546 "tool_provenance.tool_id must be a non-empty string",
1547 );
1548 require_nonblank(
1549 &mut errors,
1550 &self.tool_provenance.tool_version,
1551 "axiom_execution_trust.tool_provenance.tool_version.missing",
1552 "tool_provenance.tool_version must be a non-empty string",
1553 );
1554 require_nonblank(
1555 &mut errors,
1556 &self.tool_provenance.command_ref,
1557 "axiom_execution_trust.tool_provenance.command_ref.missing",
1558 "tool_provenance.command_ref must be a non-empty string",
1559 );
1560 if !is_commit_sha(&self.tool_provenance.source_commit) {
1561 push(
1562 &mut errors,
1563 "axiom_execution_trust.tool_provenance.source_commit.invalid_format",
1564 "tool_provenance.source_commit must be a 40-character lowercase hex SHA",
1565 );
1566 }
1567 require_nonblank(
1568 &mut errors,
1569 &self.tool_provenance.dependency_lock_ref,
1570 "axiom_execution_trust.tool_provenance.dependency_lock_ref.missing",
1571 "tool_provenance.dependency_lock_ref must be a non-empty string",
1572 );
1573
1574 if self.source_anchors.is_empty() {
1576 push(
1577 &mut errors,
1578 "axiom_execution_trust.source_anchors.missing",
1579 "source_anchors must contain at least one anchor",
1580 );
1581 }
1582 for anchor in &self.source_anchors {
1583 require_nonblank(
1584 &mut errors,
1585 &anchor.source_id,
1586 "axiom_execution_trust.source_anchors.source_id.missing",
1587 "source_anchors[].source_id must be a non-empty string",
1588 );
1589 require_nonblank(
1590 &mut errors,
1591 &anchor.r#ref,
1592 "axiom_execution_trust.source_anchors.ref.missing",
1593 "source_anchors[].ref must be a non-empty string",
1594 );
1595 if !is_sha256_hash(&anchor.hash) {
1596 push(
1597 &mut errors,
1598 "axiom_execution_trust.source_anchors.hash.invalid_format",
1599 "source_anchors[].hash must match sha256:<64 lowercase hex>",
1600 );
1601 }
1602 }
1603
1604 require_nonblank(
1606 &mut errors,
1607 &self.runtime_mode,
1608 "axiom_execution_trust.runtime_mode.missing",
1609 "runtime_mode must be a non-empty string",
1610 );
1611
1612 if errors.is_empty() {
1613 Ok(())
1614 } else {
1615 Err(errors)
1616 }
1617 }
1618
1619 #[must_use]
1621 pub fn token_expired_at(&self, now: DateTime<Utc>) -> bool {
1622 self.token_scope.expires_at < now
1623 }
1624}
1625
1626impl AuthorityFeedbackLoop {
1627 pub fn validate(&self) -> TrustExchangeValidation {
1629 let mut errors: Vec<TrustExchangeFieldError> = Vec::new();
1630
1631 if self.schema != AUTHORITY_FEEDBACK_LOOP_SCHEMA {
1632 push(
1633 &mut errors,
1634 "authority_feedback_loop.schema.mismatch",
1635 format!(
1636 "schema must be `{AUTHORITY_FEEDBACK_LOOP_SCHEMA}`, got `{}`",
1637 self.schema
1638 ),
1639 );
1640 }
1641 if self.version != TRUST_EXCHANGE_SCHEMA_VERSION {
1642 push(
1643 &mut errors,
1644 "authority_feedback_loop.version.mismatch",
1645 format!(
1646 "version must be {TRUST_EXCHANGE_SCHEMA_VERSION}, got {}",
1647 self.version
1648 ),
1649 );
1650 }
1651
1652 require_nonblank(
1653 &mut errors,
1654 &self.loop_id,
1655 "authority_feedback_loop.loop_id.missing",
1656 "loop_id must be a non-empty string",
1657 );
1658
1659 require_nonblank(
1660 &mut errors,
1661 &self.initiating_context.context_id,
1662 "authority_feedback_loop.initiating_context.context_id.missing",
1663 "initiating_context.context_id must be a non-empty string",
1664 );
1665 require_nonblank(
1666 &mut errors,
1667 &self.initiating_context.cortex_context_trust_ref,
1668 "authority_feedback_loop.initiating_context.cortex_context_trust_ref.missing",
1669 "initiating_context.cortex_context_trust_ref must be a non-empty string",
1670 );
1671 require_nonblank(
1672 &mut errors,
1673 &self.axiom_action.action_id,
1674 "authority_feedback_loop.axiom_action.action_id.missing",
1675 "axiom_action.action_id must be a non-empty string",
1676 );
1677 require_nonblank(
1678 &mut errors,
1679 &self.axiom_action.axiom_execution_trust_ref,
1680 "authority_feedback_loop.axiom_action.axiom_execution_trust_ref.missing",
1681 "axiom_action.axiom_execution_trust_ref must be a non-empty string",
1682 );
1683
1684 if self.returned_artifacts.is_empty() {
1685 push(
1686 &mut errors,
1687 "authority_feedback_loop.returned_artifacts.missing",
1688 "returned_artifacts must contain at least one artifact",
1689 );
1690 }
1691 for artifact in &self.returned_artifacts {
1692 require_nonblank(
1693 &mut errors,
1694 &artifact.artifact_id,
1695 "authority_feedback_loop.returned_artifacts.artifact_id.missing",
1696 "returned_artifacts[].artifact_id must be a non-empty string",
1697 );
1698 require_nonblank(
1699 &mut errors,
1700 &artifact.lineage_ref,
1701 "authority_feedback_loop.returned_artifacts.lineage_ref.missing",
1702 "returned_artifacts[].lineage_ref must be a non-empty string",
1703 );
1704 }
1705
1706 require_nonblank(
1707 &mut errors,
1708 &self.contradiction_scan_ref,
1709 "authority_feedback_loop.contradiction_scan_ref.missing",
1710 "contradiction_scan_ref must be a non-empty string",
1711 );
1712
1713 if !self.target_domain_validation.required {
1714 push(
1715 &mut errors,
1716 "authority_feedback_loop.target_domain_validation.required.must_be_true",
1717 "target_domain_validation.required must be true",
1718 );
1719 }
1720
1721 if errors.is_empty() {
1722 Ok(())
1723 } else {
1724 Err(errors)
1725 }
1726 }
1727
1728 #[must_use]
1731 pub const fn violates_same_loop_invariant(&self) -> bool {
1732 self.same_loop_promotion_allowed
1733 }
1734
1735 #[must_use]
1738 pub const fn claims_durable_authority(&self) -> bool {
1739 self.authority_claims
1740 .durable_truth_promotion
1741 .claims_authority()
1742 || self
1743 .authority_claims
1744 .full_execution_authority
1745 .claims_authority()
1746 }
1747}
1748
1749#[cfg(test)]
1754mod tests {
1755 use super::*;
1756 use chrono::TimeZone;
1757
1758 const VALID_CTX: &str =
1759 include_str!("../tests/fixtures/pai-axiom/valid-cortex-context-trust.json");
1760 const VALID_EXEC: &str =
1761 include_str!("../tests/fixtures/pai-axiom/valid-axiom-execution-trust.json");
1762
1763 #[test]
1764 fn parse_valid_cortex_context_trust_round_trips() {
1765 let envelope = parse_cortex_context_trust(VALID_CTX).expect("valid fixture parses");
1766 assert_eq!(envelope.schema, CORTEX_CONTEXT_TRUST_SCHEMA);
1767 assert_eq!(envelope.version, TRUST_EXCHANGE_SCHEMA_VERSION);
1768 assert_eq!(envelope.context_id, "ctx_valid_behavior_change");
1769 assert_eq!(envelope.quarantine_state, ContextQuarantineState::Clear);
1770 assert!(matches!(
1771 envelope.proof_state.state,
1772 ContextProofStateValue::Closed
1773 ));
1774 envelope.validate().expect("valid fixture validates");
1775
1776 let reserialized = serde_json::to_value(&envelope).unwrap();
1777 assert_eq!(reserialized["schema"], "cortex_context_trust");
1778 assert_eq!(reserialized["truth_ceiling"], "validated");
1779 }
1780
1781 #[test]
1782 fn parse_valid_axiom_execution_trust_round_trips() {
1783 let envelope = parse_axiom_execution_trust(VALID_EXEC).expect("valid fixture parses");
1784 assert_eq!(envelope.schema, AXIOM_EXECUTION_TRUST_SCHEMA);
1785 assert_eq!(envelope.version, TRUST_EXCHANGE_SCHEMA_VERSION);
1786 assert_eq!(envelope.action_id, "action_valid_authority_grade");
1787 assert_eq!(envelope.token_scope.audience, "cortex-admission");
1788 assert_eq!(
1789 envelope.token_scope.revocation_result,
1790 TokenRevocationResult::Active
1791 );
1792 envelope.validate().expect("valid fixture validates");
1793
1794 let reserialized = serde_json::to_value(&envelope).unwrap();
1795 assert_eq!(reserialized["schema"], "axiom_execution_trust");
1796 assert_eq!(reserialized["execution_trust_level"], "authority_grade");
1797 }
1798
1799 #[test]
1800 fn missing_token_audience_emits_stable_invariant() {
1801 let value: serde_json::Value = serde_json::from_str(VALID_EXEC).unwrap();
1802 let mut inner = value["axiom_execution_trust"].clone();
1803 inner["token_scope"]["audience"] = serde_json::Value::String(String::new());
1804
1805 let envelope: AxiomExecutionTrust = serde_json::from_value(inner).unwrap();
1806 let errors = envelope.validate().expect_err("empty audience must fail");
1807 assert!(errors
1808 .iter()
1809 .any(|e| e.invariant == "axiom_execution_trust.token_scope.audience.missing"));
1810 }
1811
1812 #[test]
1813 fn missing_operator_approval_hash_emits_stable_invariant() {
1814 let value: serde_json::Value = serde_json::from_str(VALID_EXEC).unwrap();
1815 let mut inner = value["axiom_execution_trust"].clone();
1816 inner["actor_attestation"]["operator_approval_hash"] =
1817 serde_json::Value::String("not-a-hash".to_string());
1818
1819 let envelope: AxiomExecutionTrust = serde_json::from_value(inner).unwrap();
1820 let errors = envelope.validate().expect_err("bad hash must fail");
1821 assert!(errors.iter().any(|e| e.invariant
1822 == "axiom_execution_trust.actor_attestation.operator_approval_hash.invalid_format"));
1823 }
1824
1825 #[test]
1826 fn invalid_source_commit_emits_stable_invariant() {
1827 let value: serde_json::Value = serde_json::from_str(VALID_EXEC).unwrap();
1828 let mut inner = value["axiom_execution_trust"].clone();
1829 inner["tool_provenance"]["source_commit"] =
1830 serde_json::Value::String("not-a-commit".to_string());
1831
1832 let envelope: AxiomExecutionTrust = serde_json::from_value(inner).unwrap();
1833 let errors = envelope.validate().expect_err("bad commit must fail");
1834 assert!(errors
1835 .iter()
1836 .any(|e| e.invariant
1837 == "axiom_execution_trust.tool_provenance.source_commit.invalid_format"));
1838 }
1839
1840 #[test]
1841 fn proof_state_failed_emits_stable_invariant() {
1842 let value: serde_json::Value = serde_json::from_str(VALID_CTX).unwrap();
1843 let mut inner = value["cortex_context_trust"].clone();
1844 inner["proof_state"]["state"] = serde_json::Value::String("failed".to_string());
1845 inner["proof_state"]["failing_edges"] = serde_json::json!(["proof.audit.missing"]);
1846
1847 let envelope: CortexContextTrust = serde_json::from_value(inner).unwrap();
1848 let errors = envelope
1849 .validate()
1850 .expect_err("failed proof state must fail");
1851 assert!(errors
1852 .iter()
1853 .any(|e| e.invariant == "cortex_context_trust.proof_state.state.unusable"));
1854 }
1855
1856 #[test]
1857 fn extra_top_level_field_fails_closed() {
1858 let json = serde_json::json!({
1859 "schema": "cortex_context_trust",
1860 "version": 1,
1861 "context_id": "ctx",
1862 "compatibility_trust_label": "validated",
1863 "proof_state": {"state": "closed", "failing_edges": [], "proof_refs": ["p"]},
1864 "truth_ceiling": "validated",
1865 "semantic_trust": {"provenance_class": "observed", "trust_weight": 1, "weighting_basis": "x"},
1866 "quarantine_state": "clear",
1867 "redaction_state": {"status": "none", "redaction_refs": []},
1868 "policy_result": {"decision_id": "p", "result": "allow"},
1869 "source_anchors": [{
1870 "source_id": "s",
1871 "source_type": "event",
1872 "ref": "r",
1873 "hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
1874 }],
1875 "rogue_field": true
1876 });
1877 let err = serde_json::from_value::<CortexContextTrust>(json)
1878 .expect_err("deny_unknown_fields must reject rogue field");
1879 assert!(err.to_string().contains("rogue_field"));
1880 }
1881
1882 #[test]
1883 fn revoked_token_is_must_reject() {
1884 assert!(TokenRevocationResult::Revoked.must_reject());
1885 assert!(TokenRevocationResult::Inactive.must_reject());
1886 assert!(!TokenRevocationResult::Active.must_reject());
1887 }
1888
1889 #[test]
1890 fn token_expiry_check_uses_injected_now() {
1891 let mut envelope = parse_axiom_execution_trust(VALID_EXEC).unwrap();
1892 envelope.token_scope.expires_at = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
1893 let now = Utc.with_ymd_and_hms(2026, 1, 1, 0, 0, 0).unwrap();
1894 assert!(envelope.token_expired_at(now));
1895 }
1896
1897 #[test]
1898 fn same_loop_promotion_invariant_is_structural() {
1899 let loop_record = AuthorityFeedbackLoop {
1900 schema: AUTHORITY_FEEDBACK_LOOP_SCHEMA.to_string(),
1901 version: 1,
1902 authority_feedback_loop_ref: None,
1903 loop_id: "loop_x".to_string(),
1904 started_at: Utc::now(),
1905 initiating_context: FeedbackInitiatingContext {
1906 context_id: "ctx".to_string(),
1907 cortex_context_trust_ref: "ref".to_string(),
1908 },
1909 axiom_action: FeedbackAxiomAction {
1910 action_id: "act".to_string(),
1911 axiom_execution_trust_ref: "ref".to_string(),
1912 },
1913 returned_artifacts: vec![FeedbackReturnedArtifact {
1914 artifact_id: "art".to_string(),
1915 lineage_ref: "lin".to_string(),
1916 lifecycle_state: ArtifactLifecycleState::Candidate,
1917 reproducibility_level: ReproducibilityLevel::Observational,
1918 }],
1919 amplification_risk: AmplificationRisk::Low,
1920 independent_evidence_refs: vec![],
1921 external_grounding_refs: vec![],
1922 contradiction_scan_ref: "scan".to_string(),
1923 quarantine_state: ContextQuarantineState::Clear,
1924 confidence_ceiling: ConfidenceCeiling::Advisory,
1925 same_loop_promotion_allowed: true,
1926 authority_claims: FeedbackAuthorityClaims {
1927 durable_truth_promotion: AuthorityClaimStatus::Denied,
1928 full_execution_authority: AuthorityClaimStatus::Denied,
1929 review_required: true,
1930 },
1931 target_domain_validation: TargetDomainValidation {
1932 required: true,
1933 independent_validation_ref: None,
1934 result: TargetDomainValidationResult::Pending,
1935 },
1936 residual_risk: vec![],
1937 };
1938 assert!(loop_record.violates_same_loop_invariant());
1939 assert!(!loop_record.claims_durable_authority());
1940 }
1941
1942 #[test]
1943 fn named_quarantine_outputs_iterate_invariants() {
1944 let outputs = NamedQuarantineOutputs {
1945 source_context: Some(QuarantineOutput::new(
1946 "axiom.admission.quarantine.propagated",
1947 "source quarantine",
1948 )),
1949 token_revocation: Some(QuarantineOutput::new(
1950 "axiom_execution_trust.token_scope.revoked",
1951 "revoked",
1952 )),
1953 ..Default::default()
1954 };
1955 let names: Vec<&str> = outputs.invariants().collect();
1956 assert_eq!(names.len(), 2);
1957 assert!(names.contains(&"axiom.admission.quarantine.propagated"));
1958 assert!(names.contains(&"axiom_execution_trust.token_scope.revoked"));
1959 }
1960
1961 #[test]
1962 fn quarantine_state_propagation_predicate() {
1963 assert!(ContextQuarantineState::Quarantined.propagates_quarantine());
1964 assert!(ContextQuarantineState::DerivedFromQuarantined.propagates_quarantine());
1965 assert!(ContextQuarantineState::Unknown.propagates_quarantine());
1966 assert!(!ContextQuarantineState::Clear.propagates_quarantine());
1967 }
1968
1969 #[test]
1976 fn source_commit_stale_invariant_is_stable() {
1977 assert_eq!(
1978 AXIOM_EXECUTION_TRUST_SOURCE_COMMIT_STALE_INVARIANT,
1979 "axiom_execution_trust.tool_provenance.source_commit.stale"
1980 );
1981 }
1982
1983 #[test]
1986 fn accepted_source_commits_env_var_name_is_stable() {
1987 assert_eq!(
1988 CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV,
1989 "CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS"
1990 );
1991 }
1992
1993 #[test]
1994 fn default_accept_list_includes_adr_0042_pin() {
1995 assert!(DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
1996 .contains(&"44b9a25dfbe5a93e64120f53b65730828d1af91c"));
1997 }
1998
1999 #[test]
2000 fn default_accept_list_includes_known_good_v2_corpus() {
2001 assert!(DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
2002 .contains(&"9a15d281ddcc2bcf36956fbe6d6c5736d8ce706a"));
2003 assert!(DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
2004 .contains(&"062d0d42ac5ef300fa3e04ef5b49b14864babcdd"));
2005 }
2006
2007 #[test]
2008 fn default_accept_list_entries_are_well_formed_commits() {
2009 for sha in DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS {
2010 assert!(
2011 is_commit_sha(sha),
2012 "default accept-list entry `{sha}` must be a 40-char lowercase-hex commit SHA",
2013 );
2014 }
2015 }
2016
2017 #[test]
2018 fn freshness_predicate_admits_accepted_sha() {
2019 let accepted: Vec<String> = DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
2020 .iter()
2021 .map(|sha| (*sha).to_string())
2022 .collect();
2023 assert!(is_axiom_source_commit_fresh(
2024 "44b9a25dfbe5a93e64120f53b65730828d1af91c",
2025 &accepted
2026 ));
2027 }
2028
2029 #[test]
2030 fn freshness_predicate_refuses_unknown_sha() {
2031 let accepted: Vec<String> = DEFAULT_ACCEPTED_AXIOM_SOURCE_COMMITS
2032 .iter()
2033 .map(|sha| (*sha).to_string())
2034 .collect();
2035 assert!(!is_axiom_source_commit_fresh(
2036 "0000000000000000000000000000000000000000",
2037 &accepted
2038 ));
2039 assert!(!is_axiom_source_commit_fresh(
2040 "1234567890abcdef1234567890abcdef12345678",
2041 &accepted
2042 ));
2043 }
2044
2045 #[test]
2046 fn freshness_predicate_is_case_insensitive_on_candidate() {
2047 let accepted = vec!["44b9a25dfbe5a93e64120f53b65730828d1af91c".to_string()];
2048 assert!(is_axiom_source_commit_fresh(
2049 "44B9A25DFBE5A93E64120F53B65730828D1AF91C",
2050 &accepted
2051 ));
2052 assert!(is_axiom_source_commit_fresh(
2053 "44b9a25dfbe5a93e64120f53b65730828d1af91c",
2054 &accepted
2055 ));
2056 }
2057
2058 #[test]
2059 fn freshness_predicate_empty_accept_list_refuses_everything() {
2060 let accepted: Vec<String> = Vec::new();
2061 assert!(!is_axiom_source_commit_fresh(
2062 "44b9a25dfbe5a93e64120f53b65730828d1af91c",
2063 &accepted
2064 ));
2065 }
2066
2067 #[test]
2071 fn accepted_axiom_source_commits_env_var_replaces_default() {
2072 let prior = std::env::var(CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV).ok();
2078
2079 std::env::set_var(
2080 CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV,
2081 " AAAAaaaaAAAAaaaaAAAAaaaaAAAAaaaaAAAAaaaa , 1111111111111111111111111111111111111111 ,,",
2082 );
2083 let resolved = accepted_axiom_source_commits();
2084 assert_eq!(resolved.len(), 2);
2085 assert!(resolved.contains(&"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string()));
2086 assert!(resolved.contains(&"1111111111111111111111111111111111111111".to_string()));
2087 assert!(!resolved.contains(&"44b9a25dfbe5a93e64120f53b65730828d1af91c".to_string()));
2090
2091 match prior {
2093 Some(value) => std::env::set_var(CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV, value),
2094 None => std::env::remove_var(CORTEX_AXIOM_ACCEPTED_SOURCE_COMMITS_ENV),
2095 }
2096 }
2097}