1use chrono::{DateTime, Utc};
32use cortex_core::{
33 compose_policy_outcomes, ArtifactLifecycleState, AuthorityFeedbackLoop, AxiomExecutionTrust,
34 ContextProofStateValue, ContextRedactionStatus, CortexContextTrust, ExecutionPolicyResult,
35 NamedQuarantineOutputs, PolicyContribution, PolicyDecision, PolicyOutcome, QuarantineOutput,
36 RepoTrustResult, TargetDomainValidationResult, TokenRevocationResult, TrustExchangeFieldError,
37};
38use serde::{Deserialize, Serialize};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum AdmissionLifecycle {
47 CandidateOnly,
49 Validated,
51 Promoted,
53 Stale,
55 Quarantined,
57 Unknown,
59}
60
61impl AdmissionLifecycle {
62 #[must_use]
64 pub const fn is_candidate_only(self) -> bool {
65 matches!(self, Self::CandidateOnly)
66 }
67}
68
69#[derive(Debug, Clone, PartialEq)]
72pub struct AxiomTrustExchangeAdmissionRequest {
73 pub cortex_context_trust: Option<CortexContextTrust>,
75 pub axiom_execution_trust: AxiomExecutionTrust,
77 pub authority_feedback_loop: Option<AuthorityFeedbackLoop>,
79 pub lifecycle: AdmissionLifecycle,
81 pub now: DateTime<Utc>,
83 pub derived_from_quarantined: bool,
86}
87
88impl AxiomTrustExchangeAdmissionRequest {
89 #[must_use]
91 pub fn new(axiom_execution_trust: AxiomExecutionTrust, lifecycle: AdmissionLifecycle) -> Self {
92 Self {
93 cortex_context_trust: None,
94 axiom_execution_trust,
95 authority_feedback_loop: None,
96 lifecycle,
97 now: Utc::now(),
98 derived_from_quarantined: false,
99 }
100 }
101
102 #[must_use]
104 pub fn with_cortex_context_trust(mut self, ctx: CortexContextTrust) -> Self {
105 self.cortex_context_trust = Some(ctx);
106 self
107 }
108
109 #[must_use]
111 pub fn with_authority_feedback_loop(mut self, loop_record: AuthorityFeedbackLoop) -> Self {
112 self.authority_feedback_loop = Some(loop_record);
113 self
114 }
115
116 #[must_use]
119 pub const fn with_now(mut self, now: DateTime<Utc>) -> Self {
120 self.now = now;
121 self
122 }
123
124 #[must_use]
126 pub const fn with_derived_from_quarantined(mut self, derived: bool) -> Self {
127 self.derived_from_quarantined = derived;
128 self
129 }
130
131 #[must_use]
133 pub fn decide(&self) -> TrustExchangeAdmission {
134 let mut rejects: Vec<TrustExchangeFieldError> = Vec::new();
135 let mut quarantines: Vec<TrustExchangeFieldError> = Vec::new();
136 let mut named_quarantine_outputs = NamedQuarantineOutputs::default();
137
138 if !self.lifecycle.is_candidate_only() {
140 rejects.push(TrustExchangeFieldError::new(
141 "axiom.admission.lifecycle.must_be_candidate_only",
142 "Cortex admits pai-axiom trust exchange only as candidate_only",
143 ));
144 }
145
146 if let Some(loop_record) = &self.authority_feedback_loop {
148 if loop_record.violates_same_loop_invariant() {
149 rejects.push(TrustExchangeFieldError::new(
150 "authority_feedback_loop.same_loop_promotion_must_be_false",
151 "same_loop_promotion_allowed must be false at the Cortex receiver",
152 ));
153 }
154 if loop_record.claims_durable_authority() {
155 rejects.push(TrustExchangeFieldError::new(
156 "authority_feedback_loop.authority_claims.over_authorized",
157 "Cortex refuses durable_truth_promotion or full_execution_authority claims",
158 ));
159 }
160 }
161
162 if let Err(errors) = self.axiom_execution_trust.validate() {
164 rejects.extend(errors);
165 }
166 if let Some(ctx) = &self.cortex_context_trust {
167 if let Err(errors) = ctx.validate() {
168 rejects.extend(errors);
169 }
170 }
171 if let Some(loop_record) = &self.authority_feedback_loop {
172 if let Err(errors) = loop_record.validate() {
173 rejects.extend(errors);
174 }
175 }
176
177 if let Some(ctx) = &self.cortex_context_trust {
179 if ctx.quarantine_state.propagates_quarantine() {
180 let invariant = "axiom.admission.quarantine.propagated".to_string();
181 let reason = format!(
182 "cortex_context_trust.quarantine_state == {:?}",
183 ctx.quarantine_state
184 );
185 quarantines.push(TrustExchangeFieldError::new(
186 invariant.clone(),
187 reason.clone(),
188 ));
189 named_quarantine_outputs.source_context = Some(
190 QuarantineOutput::new(invariant, reason)
191 .with_source_ref("cortex_context_trust"),
192 );
193 }
194 if matches!(ctx.redaction_state.status, ContextRedactionStatus::Redacted)
195 && ctx.redaction_state.blocks_critical_premise.unwrap_or(false)
196 {
197 quarantines.push(TrustExchangeFieldError::new(
198 "cortex_context_trust.redaction_state.blocks_critical_premise",
199 "redaction removed critical premise; treated as quarantine",
200 ));
201 }
202 }
203
204 if self
206 .axiom_execution_trust
207 .token_scope
208 .revocation_result
209 .must_reject()
210 {
211 let invariant = match self.axiom_execution_trust.token_scope.revocation_result {
212 TokenRevocationResult::Revoked => "axiom_execution_trust.token_scope.revoked",
213 TokenRevocationResult::Inactive => "axiom_execution_trust.token_scope.inactive",
214 _ => "axiom_execution_trust.token_scope.invalid_state",
215 };
216 rejects.push(TrustExchangeFieldError::new(
217 invariant,
218 "capability token must be active for Cortex admission",
219 ));
220 named_quarantine_outputs.token_revocation = Some(QuarantineOutput::new(
221 invariant,
222 "capability token must be active for Cortex admission",
223 ));
224 }
225 if self.axiom_execution_trust.token_expired_at(self.now) {
226 rejects.push(TrustExchangeFieldError::new(
227 "axiom_execution_trust.token_scope.expired",
228 "capability token expires_at is in the past for the supplied now",
229 ));
230 named_quarantine_outputs.token_revocation = Some(QuarantineOutput::new(
231 "axiom_execution_trust.token_scope.expired",
232 "capability token is expired",
233 ));
234 }
235
236 if matches!(
238 self.axiom_execution_trust.repo_trust.result,
239 RepoTrustResult::Untrusted
240 ) {
241 quarantines.push(TrustExchangeFieldError::new(
242 "axiom_execution_trust.repo_trust.untrusted",
243 "repo_trust.result == untrusted is propagated as quarantine",
244 ));
245 named_quarantine_outputs.repo_trust = Some(QuarantineOutput::new(
246 "axiom_execution_trust.repo_trust.untrusted",
247 "repo trust untrusted",
248 ));
249 }
250
251 if matches!(
253 self.axiom_execution_trust.policy_decision.result,
254 ExecutionPolicyResult::Deny
255 ) {
256 rejects.push(TrustExchangeFieldError::new(
257 "axiom_execution_trust.policy_decision.deny",
258 "policy_decision.result == deny is a hard receiver refusal",
259 ));
260 named_quarantine_outputs.policy_denial = Some(QuarantineOutput::new(
261 "axiom_execution_trust.policy_decision.deny",
262 "policy denied",
263 ));
264 }
265
266 if let Some(loop_record) = &self.authority_feedback_loop {
268 if loop_record.target_domain_validation.required
269 && !matches!(
270 loop_record.target_domain_validation.result,
271 TargetDomainValidationResult::Pass
272 )
273 {
274 quarantines.push(TrustExchangeFieldError::new(
275 "authority_feedback_loop.target_domain_validation.not_pass",
276 "target_domain_validation.result must be pass for clean admission",
277 ));
278 named_quarantine_outputs.target_validation = Some(QuarantineOutput::new(
279 "authority_feedback_loop.target_domain_validation.not_pass",
280 "target domain validation not pass",
281 ));
282 }
283
284 if loop_record
286 .returned_artifacts
287 .iter()
288 .any(|a| matches!(a.lifecycle_state, ArtifactLifecycleState::Quarantined))
289 {
290 quarantines.push(TrustExchangeFieldError::new(
291 "authority_feedback_loop.returned_artifacts.quarantined",
292 "at least one returned artifact lifecycle_state == quarantined",
293 ));
294 named_quarantine_outputs.derived_artifact = Some(QuarantineOutput::new(
295 "authority_feedback_loop.returned_artifacts.quarantined",
296 "derived artifact quarantined",
297 ));
298 }
299
300 if loop_record.quarantine_state.propagates_quarantine() {
301 quarantines.push(TrustExchangeFieldError::new(
302 "axiom.admission.quarantine.propagated",
303 "authority_feedback_loop.quarantine_state propagates",
304 ));
305 named_quarantine_outputs.contradiction = Some(QuarantineOutput::new(
306 "axiom.admission.quarantine.propagated",
307 "feedback loop quarantine state",
308 ));
309 }
310 }
311
312 if self.derived_from_quarantined {
314 quarantines.push(TrustExchangeFieldError::new(
315 "axiom.admission.quarantine.derived_from_quarantined",
316 "operator marked admission lineage as derived_from_quarantined",
317 ));
318 named_quarantine_outputs.source_context = Some(QuarantineOutput::new(
319 "axiom.admission.quarantine.derived_from_quarantined",
320 "lineage trace indicates quarantined ancestor",
321 ));
322 }
323
324 if let Some(ctx) = &self.cortex_context_trust {
326 if matches!(
327 ctx.proof_state.state,
328 ContextProofStateValue::Failed | ContextProofStateValue::Missing
329 ) {
330 rejects.push(TrustExchangeFieldError::new(
331 "cortex_context_trust.proof_state.state.unusable",
332 "proof_state.state failed or missing is a hard refusal",
333 ));
334 }
335 }
336
337 let forbidden_uses = forbidden_uses_for_candidate();
338 let policy_decision = compose_decision_outcomes(&rejects, &quarantines);
339
340 if !rejects.is_empty() {
341 TrustExchangeAdmission::Reject {
342 rejects,
343 quarantines,
344 named_quarantine_outputs,
345 policy_decision,
346 }
347 } else if !quarantines.is_empty() {
348 TrustExchangeAdmission::Quarantine {
349 quarantines,
350 named_quarantine_outputs,
351 policy_decision,
352 forbidden_uses,
353 }
354 } else {
355 TrustExchangeAdmission::AdmitCandidate {
356 forbidden_uses,
357 policy_decision,
358 }
359 }
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
365pub enum TrustExchangeAdmission {
366 AdmitCandidate {
368 forbidden_uses: Vec<ForbiddenUse>,
370 policy_decision: PolicyDecision,
372 },
373 Quarantine {
375 quarantines: Vec<TrustExchangeFieldError>,
377 named_quarantine_outputs: NamedQuarantineOutputs,
379 policy_decision: PolicyDecision,
381 forbidden_uses: Vec<ForbiddenUse>,
383 },
384 Reject {
386 rejects: Vec<TrustExchangeFieldError>,
388 quarantines: Vec<TrustExchangeFieldError>,
390 named_quarantine_outputs: NamedQuarantineOutputs,
392 policy_decision: PolicyDecision,
394 },
395}
396
397impl TrustExchangeAdmission {
398 #[must_use]
400 pub const fn decision_name(&self) -> &'static str {
401 match self {
402 Self::AdmitCandidate { .. } => "admit_candidate",
403 Self::Quarantine { .. } => "quarantine",
404 Self::Reject { .. } => "reject",
405 }
406 }
407
408 #[must_use]
410 pub fn named_quarantine_outputs(&self) -> Option<&NamedQuarantineOutputs> {
411 match self {
412 Self::AdmitCandidate { .. } => None,
413 Self::Quarantine {
414 named_quarantine_outputs,
415 ..
416 }
417 | Self::Reject {
418 named_quarantine_outputs,
419 ..
420 } => Some(named_quarantine_outputs),
421 }
422 }
423
424 #[must_use]
426 pub fn policy_decision(&self) -> &PolicyDecision {
427 match self {
428 Self::AdmitCandidate {
429 policy_decision, ..
430 }
431 | Self::Quarantine {
432 policy_decision, ..
433 }
434 | Self::Reject {
435 policy_decision, ..
436 } => policy_decision,
437 }
438 }
439
440 #[must_use]
442 pub fn invariants(&self) -> Vec<&str> {
443 match self {
444 Self::AdmitCandidate { .. } => Vec::new(),
445 Self::Quarantine { quarantines, .. } => {
446 quarantines.iter().map(|e| e.invariant.as_str()).collect()
447 }
448 Self::Reject {
449 rejects,
450 quarantines,
451 ..
452 } => rejects
453 .iter()
454 .chain(quarantines.iter())
455 .map(|e| e.invariant.as_str())
456 .collect(),
457 }
458 }
459
460 #[must_use]
464 pub fn forbidden_uses(&self) -> Option<&[ForbiddenUse]> {
465 match self {
466 Self::AdmitCandidate { forbidden_uses, .. }
467 | Self::Quarantine { forbidden_uses, .. } => Some(forbidden_uses),
468 Self::Reject { .. } => None,
469 }
470 }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
475#[serde(rename_all = "snake_case")]
476pub enum ForbiddenUse {
477 DurablePromotion,
479 ReleaseAcceptance,
481 RuntimeAuthority,
483 CortexTruth,
485 TrustedHistory,
487}
488
489#[must_use]
492pub fn forbidden_uses_for_candidate() -> Vec<ForbiddenUse> {
493 vec![
494 ForbiddenUse::DurablePromotion,
495 ForbiddenUse::ReleaseAcceptance,
496 ForbiddenUse::RuntimeAuthority,
497 ForbiddenUse::CortexTruth,
498 ForbiddenUse::TrustedHistory,
499 ]
500}
501
502fn compose_decision_outcomes(
503 rejects: &[TrustExchangeFieldError],
504 quarantines: &[TrustExchangeFieldError],
505) -> PolicyDecision {
506 let mut contributions: Vec<PolicyContribution> = Vec::new();
507 for err in rejects {
508 contributions.push(
509 PolicyContribution::new(
510 stable_policy_rule_id(&err.invariant),
511 PolicyOutcome::Reject,
512 err.reason.clone(),
513 )
514 .expect("stable invariant policy contribution is well-formed"),
515 );
516 }
517 for err in quarantines {
518 contributions.push(
519 PolicyContribution::new(
520 stable_policy_rule_id(&err.invariant),
521 PolicyOutcome::Quarantine,
522 err.reason.clone(),
523 )
524 .expect("stable invariant policy contribution is well-formed"),
525 );
526 }
527 if contributions.is_empty() {
528 contributions.push(
529 PolicyContribution::new(
530 "axiom.admission.trust_exchange.allow_candidate",
531 PolicyOutcome::Allow,
532 "pai-axiom trust exchange admitted as Cortex candidate only",
533 )
534 .expect("static policy contribution shape is valid"),
535 );
536 }
537 compose_policy_outcomes(contributions, None)
538}
539
540fn stable_policy_rule_id(invariant: &str) -> String {
541 format!("axiom.admission.trust_exchange.{invariant}")
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use chrono::TimeZone;
551 use cortex_core::{
552 parse_axiom_execution_trust, parse_cortex_context_trust, AmplificationRisk,
553 ArtifactLifecycleState, AuthorityClaimStatus, ConfidenceCeiling, ContextQuarantineState,
554 ExecutionPolicyResult, FeedbackAuthorityClaims, FeedbackAxiomAction,
555 FeedbackInitiatingContext, FeedbackReturnedArtifact, RepoTrustResult, ReproducibilityLevel,
556 TargetDomainValidation, TargetDomainValidationResult, AUTHORITY_FEEDBACK_LOOP_SCHEMA,
557 };
558
559 const VALID_CTX: &str =
560 include_str!("../../cortex-core/tests/fixtures/pai-axiom/valid-cortex-context-trust.json");
561 const VALID_EXEC: &str =
562 include_str!("../../cortex-core/tests/fixtures/pai-axiom/valid-axiom-execution-trust.json");
563
564 fn valid_loop_record() -> AuthorityFeedbackLoop {
565 AuthorityFeedbackLoop {
566 schema: AUTHORITY_FEEDBACK_LOOP_SCHEMA.to_string(),
567 version: 1,
568 authority_feedback_loop_ref: Some("loop_ref".to_string()),
569 loop_id: "loop_valid".to_string(),
570 started_at: Utc.with_ymd_and_hms(2026, 5, 4, 18, 0, 0).unwrap(),
571 initiating_context: FeedbackInitiatingContext {
572 context_id: "ctx_valid".to_string(),
573 cortex_context_trust_ref: "ref://ctx".to_string(),
574 },
575 axiom_action: FeedbackAxiomAction {
576 action_id: "action_valid".to_string(),
577 axiom_execution_trust_ref: "ref://exec".to_string(),
578 },
579 returned_artifacts: vec![FeedbackReturnedArtifact {
580 artifact_id: "art_valid".to_string(),
581 lineage_ref: "lin_valid".to_string(),
582 lifecycle_state: ArtifactLifecycleState::Candidate,
583 reproducibility_level: ReproducibilityLevel::Observational,
584 }],
585 amplification_risk: AmplificationRisk::Low,
586 independent_evidence_refs: vec!["evi://ind".to_string()],
587 external_grounding_refs: vec!["gnd://ext".to_string()],
588 contradiction_scan_ref: "scan_ref".to_string(),
589 quarantine_state: ContextQuarantineState::Clear,
590 confidence_ceiling: ConfidenceCeiling::Advisory,
591 same_loop_promotion_allowed: false,
592 authority_claims: FeedbackAuthorityClaims {
593 durable_truth_promotion: AuthorityClaimStatus::Denied,
594 full_execution_authority: AuthorityClaimStatus::Denied,
595 review_required: true,
596 },
597 target_domain_validation: TargetDomainValidation {
598 required: true,
599 independent_validation_ref: Some("validation_ref".to_string()),
600 result: TargetDomainValidationResult::Pass,
601 },
602 residual_risk: vec![],
603 }
604 }
605
606 fn fixed_now() -> DateTime<Utc> {
607 Utc.with_ymd_and_hms(2026, 5, 12, 0, 0, 0).unwrap()
608 }
609
610 fn valid_request() -> AxiomTrustExchangeAdmissionRequest {
611 let exec = parse_axiom_execution_trust(VALID_EXEC).unwrap();
612 let ctx = parse_cortex_context_trust(VALID_CTX).unwrap();
613 AxiomTrustExchangeAdmissionRequest::new(exec, AdmissionLifecycle::CandidateOnly)
614 .with_cortex_context_trust(ctx)
615 .with_authority_feedback_loop(valid_loop_record())
616 .with_now(fixed_now())
617 }
618
619 #[test]
620 fn valid_request_admits_candidate_with_forbidden_uses() {
621 let decision = valid_request().decide();
622 assert_eq!(decision.decision_name(), "admit_candidate");
623 let forbidden = decision
624 .forbidden_uses()
625 .expect("candidate has forbidden_uses");
626 assert!(forbidden.contains(&ForbiddenUse::DurablePromotion));
627 assert!(forbidden.contains(&ForbiddenUse::ReleaseAcceptance));
628 assert!(forbidden.contains(&ForbiddenUse::RuntimeAuthority));
629 assert!(forbidden.contains(&ForbiddenUse::CortexTruth));
630 assert!(forbidden.contains(&ForbiddenUse::TrustedHistory));
631 assert_eq!(
632 decision.policy_decision().final_outcome,
633 PolicyOutcome::Allow
634 );
635 }
636
637 #[test]
638 fn lifecycle_not_candidate_only_rejects() {
639 let exec = parse_axiom_execution_trust(VALID_EXEC).unwrap();
640 let req = AxiomTrustExchangeAdmissionRequest::new(exec, AdmissionLifecycle::Validated)
641 .with_now(fixed_now());
642 let decision = req.decide();
643 assert_eq!(decision.decision_name(), "reject");
644 assert!(decision
645 .invariants()
646 .contains(&"axiom.admission.lifecycle.must_be_candidate_only"));
647 }
648
649 #[test]
650 fn same_loop_promotion_true_rejects_structurally() {
651 let mut req = valid_request();
652 if let Some(loop_record) = req.authority_feedback_loop.as_mut() {
653 loop_record.same_loop_promotion_allowed = true;
654 }
655 let decision = req.decide();
656 assert_eq!(decision.decision_name(), "reject");
657 assert!(decision
658 .invariants()
659 .contains(&"authority_feedback_loop.same_loop_promotion_must_be_false"));
660 }
661
662 #[test]
663 fn over_authorized_durable_truth_rejects() {
664 let mut req = valid_request();
665 if let Some(loop_record) = req.authority_feedback_loop.as_mut() {
666 loop_record.authority_claims.durable_truth_promotion =
667 AuthorityClaimStatus::EligibleAfterIndependentValidation;
668 }
669 let decision = req.decide();
670 assert_eq!(decision.decision_name(), "reject");
671 assert!(decision
672 .invariants()
673 .contains(&"authority_feedback_loop.authority_claims.over_authorized"));
674 }
675
676 #[test]
677 fn over_authorized_full_execution_authority_rejects() {
678 let mut req = valid_request();
679 if let Some(loop_record) = req.authority_feedback_loop.as_mut() {
680 loop_record.authority_claims.full_execution_authority =
681 AuthorityClaimStatus::EligibleAfterIndependentValidation;
682 }
683 let decision = req.decide();
684 assert_eq!(decision.decision_name(), "reject");
685 assert!(decision
686 .invariants()
687 .contains(&"authority_feedback_loop.authority_claims.over_authorized"));
688 }
689
690 #[test]
691 fn quarantine_state_propagated_quarantines() {
692 let mut req = valid_request();
693 req.cortex_context_trust
694 .as_mut()
695 .expect("ctx present")
696 .quarantine_state = ContextQuarantineState::Quarantined;
697 let decision = req.decide();
698 assert_eq!(decision.decision_name(), "quarantine");
699 assert!(decision
700 .invariants()
701 .contains(&"axiom.admission.quarantine.propagated"));
702 let outputs = decision.named_quarantine_outputs().unwrap();
703 assert!(outputs.source_context.is_some());
704 assert!(decision
706 .forbidden_uses()
707 .unwrap()
708 .contains(&ForbiddenUse::DurablePromotion));
709 }
710
711 #[test]
712 fn derived_from_quarantined_quarantines() {
713 let req = valid_request().with_derived_from_quarantined(true);
714 let decision = req.decide();
715 assert_eq!(decision.decision_name(), "quarantine");
716 assert!(decision
717 .invariants()
718 .contains(&"axiom.admission.quarantine.derived_from_quarantined"));
719 }
720
721 #[test]
722 fn target_domain_validation_not_pass_quarantines() {
723 let mut req = valid_request();
724 if let Some(loop_record) = req.authority_feedback_loop.as_mut() {
725 loop_record.target_domain_validation.result = TargetDomainValidationResult::Fail;
726 }
727 let decision = req.decide();
728 assert_eq!(decision.decision_name(), "quarantine");
729 assert!(decision
730 .invariants()
731 .contains(&"authority_feedback_loop.target_domain_validation.not_pass"));
732 }
733
734 #[test]
735 fn expired_token_rejects() {
736 let mut req = valid_request();
737 req.axiom_execution_trust.token_scope.expires_at =
738 Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
739 let decision = req.decide();
740 assert_eq!(decision.decision_name(), "reject");
741 assert!(decision
742 .invariants()
743 .contains(&"axiom_execution_trust.token_scope.expired"));
744 }
745
746 #[test]
747 fn revoked_token_rejects() {
748 let mut req = valid_request();
749 req.axiom_execution_trust.token_scope.revocation_result = TokenRevocationResult::Revoked;
750 let decision = req.decide();
751 assert_eq!(decision.decision_name(), "reject");
752 assert!(decision
753 .invariants()
754 .contains(&"axiom_execution_trust.token_scope.revoked"));
755 }
756
757 #[test]
758 fn inactive_token_rejects() {
759 let mut req = valid_request();
760 req.axiom_execution_trust.token_scope.revocation_result = TokenRevocationResult::Inactive;
761 let decision = req.decide();
762 assert_eq!(decision.decision_name(), "reject");
763 assert!(decision
764 .invariants()
765 .contains(&"axiom_execution_trust.token_scope.inactive"));
766 }
767
768 #[test]
769 fn untrusted_repo_quarantines() {
770 let mut req = valid_request();
771 req.axiom_execution_trust.repo_trust.result = RepoTrustResult::Untrusted;
772 let decision = req.decide();
773 assert_eq!(decision.decision_name(), "quarantine");
774 assert!(decision
775 .invariants()
776 .contains(&"axiom_execution_trust.repo_trust.untrusted"));
777 }
778
779 #[test]
780 fn deny_policy_rejects() {
781 let mut req = valid_request();
782 req.axiom_execution_trust.policy_decision.result = ExecutionPolicyResult::Deny;
783 let decision = req.decide();
784 assert_eq!(decision.decision_name(), "reject");
785 assert!(decision
786 .invariants()
787 .contains(&"axiom_execution_trust.policy_decision.deny"));
788 }
789}