use cortex_core::{
AllowedClaimLanguage, AxiomConstraint, AxiomConstraintKind, AxiomConstraintSeverity,
BoundaryContradictionState, BoundaryQuarantineState, BoundaryRedactionState, ClaimCeiling,
ClaimProofState, ContextPackId, CortexAxiomConstraintEnvelopeV1, FailingEdge, PolicyOutcome,
ProofClosureReport, ProofEdgeFailure, ProofEdgeKind, ProvenanceClass,
};
use serde::{Deserialize, Serialize};
use crate::pack::{ContextPack, ContextRefId};
use crate::redaction::{ContentRedaction, PackMode, RawEventPayloadPolicy};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AxiomContextExport {
pub context_pack_id: ContextPackId,
pub task: String,
pub claim_ceiling: ClaimCeiling,
pub policy_outcome: PolicyOutcome,
pub unknown_refs: Vec<ContextRefId>,
pub limited_proof_refs: Vec<ContextRefId>,
pub conflict_refs: Vec<String>,
pub redaction_limits: Vec<String>,
pub constraints: Vec<AxiomConstraint>,
}
#[must_use]
pub fn axiom_export_for_pack(pack: &ContextPack) -> AxiomContextExport {
let unknown_refs = pack
.selected_refs
.iter()
.filter(|selected| selected.proof_state == ClaimProofState::Unknown)
.map(|selected| selected.ref_id.clone())
.collect::<Vec<_>>();
let limited_proof_refs = pack
.selected_refs
.iter()
.filter(|selected| selected.proof_state != ClaimProofState::FullChainVerified)
.map(|selected| selected.ref_id.clone())
.collect::<Vec<_>>();
let conflict_refs = pack
.conflicts
.iter()
.map(|conflict| conflict.contradiction_id.to_string())
.collect::<Vec<_>>();
let redaction_limits = redaction_limits(pack);
let claim_ceiling = pack_claim_ceiling(pack);
let policy = pack.policy_decision();
let mut constraints = Vec::new();
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::NoExecutionAuthority,
AxiomConstraintSeverity::Hard,
"context pack constraints do not grant execution authority or tool permission",
));
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::TruthCeiling,
AxiomConstraintSeverity::Limit,
format!("AXIOM output must not claim above {claim_ceiling:?} for this pack"),
));
if !unknown_refs.is_empty() {
constraints.push(
AxiomConstraint::new(
AxiomConstraintKind::ProofStateLimit,
AxiomConstraintSeverity::Limit,
"one or more selected refs have unknown proof state",
)
.with_refs(refs_to_strings(&unknown_refs)),
);
}
if !limited_proof_refs.is_empty() {
constraints.push(
AxiomConstraint::new(
AxiomConstraintKind::ForbidPromotionShapedOutput,
AxiomConstraintSeverity::Hard,
"limited proof state forbids promotion-shaped output",
)
.with_refs(refs_to_strings(&limited_proof_refs)),
);
}
if !conflict_refs.is_empty() {
constraints.push(
AxiomConstraint::new(
AxiomConstraintKind::ConflictPresent,
AxiomConstraintSeverity::Hard,
"context pack contains unresolved or surfaced conflicts",
)
.with_refs(conflict_refs.clone()),
);
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::ForbidPromotionShapedOutput,
AxiomConstraintSeverity::Hard,
"conflicting context forbids promotion-shaped output",
));
}
if claim_ceiling < ClaimCeiling::SignedLocalLedger {
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::LowTrust,
AxiomConstraintSeverity::Hard,
"low trust or unsigned context forbids durable authority claims",
));
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::ForbidPromotionShapedOutput,
AxiomConstraintSeverity::Hard,
"low-trust context forbids promotion-shaped output",
));
}
if !redaction_limits.is_empty() {
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::RedactionBoundary,
AxiomConstraintSeverity::Hard,
"redaction policy forbids inferring or reconstructing omitted raw context",
));
}
AxiomContextExport {
context_pack_id: pack.context_pack_id,
task: pack.task.clone(),
claim_ceiling,
policy_outcome: policy.final_outcome,
unknown_refs,
limited_proof_refs,
conflict_refs,
redaction_limits,
constraints,
}
}
#[must_use]
pub fn constraint_envelope_for_pack(pack: &ContextPack) -> CortexAxiomConstraintEnvelopeV1 {
let export = axiom_export_for_pack(pack);
let proof_report = proof_report_for_pack(pack);
let provenance_class = weakest_provenance_for_pack(pack);
let semantic_trust = weakest_semantic_trust_for_pack(pack);
let mut envelope = CortexAxiomConstraintEnvelopeV1::new(
pack.context_pack_id,
proof_report,
export.claim_ceiling,
semantic_trust,
provenance_class,
);
envelope.contradiction_state = contradiction_state_for_pack(pack);
envelope.quarantine_state = quarantine_state_for_pack(pack, export.policy_outcome);
envelope.redaction_state = redaction_state_for_pack(pack);
envelope.allowed_claim_language =
allowed_claim_language_for_quarantine(envelope.quarantine_state);
envelope.constraints = export.constraints;
envelope
}
fn pack_claim_ceiling(pack: &ContextPack) -> ClaimCeiling {
let selected_ceiling = ClaimCeiling::weakest(pack.selected_refs.iter().map(|selected| {
selected
.claim_ceiling
.min(selected.proof_state.claim_ceiling())
.min(selected.runtime_mode.claim_ceiling())
.min(selected.authority_class.claim_ceiling())
.min(selected.provenance_class.claim_ceiling())
.min(selected.semantic_trust.claim_ceiling())
}))
.unwrap_or(ClaimCeiling::DevOnly);
if pack.conflicts.is_empty() {
selected_ceiling
} else {
selected_ceiling.min(ClaimCeiling::DevOnly)
}
}
fn proof_report_for_pack(pack: &ContextPack) -> ProofClosureReport {
if pack
.selected_refs
.iter()
.all(|selected| selected.proof_state == ClaimProofState::FullChainVerified)
{
return ProofClosureReport::full_chain_verified(Vec::new());
}
let mut failures = Vec::new();
for selected in &pack.selected_refs {
match selected.proof_state {
ClaimProofState::FullChainVerified => {}
ClaimProofState::Broken => failures.push(FailingEdge::broken(
ProofEdgeKind::ContextPackLink,
ref_to_string(&selected.ref_id),
pack.context_pack_id.to_string(),
ProofEdgeFailure::Mismatch,
"selected context ref has broken proof state",
)),
ClaimProofState::Partial => failures.push(FailingEdge::unresolved(
ProofEdgeKind::ContextPackLink,
ref_to_string(&selected.ref_id),
"selected context ref has partial proof state",
)),
ClaimProofState::Unknown => failures.push(FailingEdge::missing(
ProofEdgeKind::ContextPackLink,
ref_to_string(&selected.ref_id),
"selected context ref has unknown proof state",
)),
}
}
ProofClosureReport::from_edges(Vec::new(), failures)
}
fn weakest_provenance_for_pack(pack: &ContextPack) -> ProvenanceClass {
pack.selected_refs
.iter()
.map(|selected| selected.provenance_class)
.min()
.unwrap_or(ProvenanceClass::UnknownProvenance)
}
fn weakest_semantic_trust_for_pack(pack: &ContextPack) -> cortex_core::SemanticTrustClass {
pack.selected_refs
.iter()
.map(|selected| selected.semantic_trust)
.min()
.unwrap_or(cortex_core::SemanticTrustClass::Unknown)
}
fn contradiction_state_for_pack(pack: &ContextPack) -> BoundaryContradictionState {
pack.contradiction_posture()
}
fn quarantine_state_for_pack(
pack: &ContextPack,
policy_outcome: PolicyOutcome,
) -> BoundaryQuarantineState {
match policy_outcome {
PolicyOutcome::Reject => BoundaryQuarantineState::Contaminated,
PolicyOutcome::Quarantine => BoundaryQuarantineState::Quarantined,
PolicyOutcome::Allow | PolicyOutcome::Warn | PolicyOutcome::BreakGlass => {
if pack.conflicts.is_empty() {
BoundaryQuarantineState::Clean
} else {
BoundaryQuarantineState::DiagnosticOnly
}
}
}
}
fn allowed_claim_language_for_quarantine(
quarantine_state: BoundaryQuarantineState,
) -> Vec<AllowedClaimLanguage> {
match quarantine_state {
BoundaryQuarantineState::Clean | BoundaryQuarantineState::DiagnosticOnly => {
cortex_core::default_allowed_claim_language()
}
BoundaryQuarantineState::Quarantined | BoundaryQuarantineState::Contaminated => vec![
AllowedClaimLanguage::Constraint,
AllowedClaimLanguage::ResidualRisk,
AllowedClaimLanguage::VerificationRequest,
AllowedClaimLanguage::Refusal,
],
}
}
fn redaction_state_for_pack(pack: &ContextPack) -> BoundaryRedactionState {
if pack.pack_mode == PackMode::Operator
&& pack.redaction_policy.raw_event_payloads == RawEventPayloadPolicy::OperatorOptIn
{
BoundaryRedactionState::RawOperatorOptIn
} else if pack.redaction_policy.content == ContentRedaction::Abstracted {
BoundaryRedactionState::Abstracted
} else if pack.redaction_policy.raw_event_payloads == RawEventPayloadPolicy::Excluded {
BoundaryRedactionState::Redacted
} else {
BoundaryRedactionState::ExportSafe
}
}
fn redaction_limits(pack: &ContextPack) -> Vec<String> {
let mut limits = Vec::new();
if pack.redaction_policy.content == ContentRedaction::Abstracted {
limits.push("content_abstracted".to_string());
}
if pack.redaction_policy.raw_event_payloads == RawEventPayloadPolicy::Excluded {
limits.push("raw_event_payloads_excluded".to_string());
}
if !pack.exclusions.is_empty() {
limits.push("explicit_exclusions_present".to_string());
}
limits
}
fn refs_to_strings(refs: &[ContextRefId]) -> Vec<String> {
refs.iter().map(ref_to_string).collect()
}
fn ref_to_string(ref_id: &ContextRefId) -> String {
match ref_id {
ContextRefId::Memory { memory_id } => format!("memory:{memory_id}"),
ContextRefId::Principle { principle_id } => format!("principle:{principle_id}"),
ContextRefId::Event { event_id } => format!("event:{event_id}"),
}
}
#[cfg(test)]
mod tests {
use cortex_core::{
AllowedClaimLanguage, AuthorityClass, BoundaryContradictionState, BoundaryQuarantineState,
BoundaryRedactionState, ClaimCeiling, ClaimProofState, ContradictionId, EventId,
ForbiddenBoundaryUse, ProvenanceClass, RuntimeMode, SemanticTrustClass,
CORTEX_TO_AXIOM_CONSTRAINT_ENVELOPE_V1,
};
use super::*;
use crate::{ContextPackBuilder, ContextRefCandidate, PackConflict, Sensitivity};
fn event_ref() -> ContextRefId {
ContextRefId::Event {
event_id: EventId::new(),
}
}
#[test]
fn axiom_export_includes_truth_ceiling_and_unknowns() {
let pack = ContextPackBuilder::new("prepare constrained AXIOM work", 512)
.select_ref(ContextRefCandidate::new(
event_ref(),
"unverified candidate context",
))
.build()
.expect("build pack");
let export = axiom_export_for_pack(&pack);
assert_eq!(export.claim_ceiling, ClaimCeiling::DevOnly);
assert_eq!(export.policy_outcome, PolicyOutcome::Allow);
assert_eq!(export.unknown_refs.len(), 1);
assert!(export
.constraints
.iter()
.any(|constraint| constraint.kind == AxiomConstraintKind::TruthCeiling));
assert!(export
.constraints
.iter()
.any(|constraint| constraint.kind == AxiomConstraintKind::ProofStateLimit));
}
#[test]
fn low_trust_conflicting_pack_forbids_promotion_shaped_output() {
let ref_id = event_ref();
let pack = ContextPackBuilder::new("prepare constrained AXIOM work", 512)
.select_ref(
ContextRefCandidate::new(ref_id.clone(), "conflicting candidate context")
.with_claim_metadata(
RuntimeMode::LocalUnsigned,
AuthorityClass::Derived,
ClaimProofState::Partial,
ClaimCeiling::AuthorityGrade,
),
)
.conflict(PackConflict {
contradiction_id: ContradictionId::new(),
posture: BoundaryContradictionState::Blocked,
refs: vec![ref_id],
summary: "candidate context conflicts with another memory".to_string(),
})
.build()
.expect("build pack");
let export = axiom_export_for_pack(&pack);
assert_eq!(export.claim_ceiling, ClaimCeiling::DevOnly);
assert_eq!(export.policy_outcome, PolicyOutcome::Quarantine);
assert!(!export.conflict_refs.is_empty());
assert!(export.constraints.iter().any(|constraint| {
constraint.kind == AxiomConstraintKind::ForbidPromotionShapedOutput
&& constraint.severity == AxiomConstraintSeverity::Hard
}));
}
#[test]
fn exported_constraints_do_not_grant_execution_authority() {
let pack = ContextPackBuilder::new("prepare constrained AXIOM work", 512)
.select_ref(
ContextRefCandidate::new(event_ref(), "verified context")
.with_claim_metadata(
RuntimeMode::AuthorityGrade,
AuthorityClass::Operator,
ClaimProofState::FullChainVerified,
ClaimCeiling::AuthorityGrade,
)
.with_semantic_metadata(
ProvenanceClass::OperatorAttested,
SemanticTrustClass::FalsificationTested,
),
)
.build()
.expect("build pack");
let export = axiom_export_for_pack(&pack);
assert!(export.constraints.iter().any(|constraint| {
constraint.kind == AxiomConstraintKind::NoExecutionAuthority
&& constraint.severity == AxiomConstraintSeverity::Hard
}));
assert_eq!(export.claim_ceiling, ClaimCeiling::AuthorityGrade);
}
#[test]
fn redaction_policy_exports_boundary_constraints() {
let pack = ContextPackBuilder::new("prepare constrained AXIOM work", 512)
.select_ref(
ContextRefCandidate::new(event_ref(), "private context")
.with_sensitivity(Sensitivity::Personal),
)
.build()
.expect("build pack");
let export = axiom_export_for_pack(&pack);
assert!(export
.redaction_limits
.contains(&"raw_event_payloads_excluded".to_string()));
assert!(export
.constraints
.iter()
.any(|constraint| constraint.kind == AxiomConstraintKind::RedactionBoundary));
}
#[test]
fn constraint_envelope_for_conflicted_pack_blocks_authority_uses() {
let ref_id = event_ref();
let pack = ContextPackBuilder::new("prepare constrained AXIOM work", 512)
.select_ref(
ContextRefCandidate::new(ref_id.clone(), "conflicted candidate context")
.with_claim_metadata(
RuntimeMode::LocalUnsigned,
AuthorityClass::Derived,
ClaimProofState::Partial,
ClaimCeiling::AuthorityGrade,
),
)
.conflict(PackConflict {
contradiction_id: ContradictionId::new(),
posture: BoundaryContradictionState::MultiHypothesis,
refs: vec![ref_id],
summary: "candidate context conflicts with another memory".to_string(),
})
.build()
.expect("build pack");
let envelope = constraint_envelope_for_pack(&pack);
assert_eq!(
envelope.envelope_type,
CORTEX_TO_AXIOM_CONSTRAINT_ENVELOPE_V1
);
assert_eq!(
envelope.contradiction_state,
BoundaryContradictionState::MultiHypothesis
);
assert_eq!(
envelope.quarantine_state,
BoundaryQuarantineState::Quarantined
);
assert_eq!(envelope.redaction_state, BoundaryRedactionState::Abstracted);
assert_eq!(envelope.semantic_trust, SemanticTrustClass::CandidateOnly);
assert_eq!(envelope.provenance_class, ProvenanceClass::RuntimeDerived);
assert_eq!(envelope.truth_ceiling, ClaimCeiling::DevOnly);
assert!(envelope
.forbidden_uses
.contains(&ForbiddenBoundaryUse::Promotion));
assert!(envelope
.forbidden_uses
.contains(&ForbiddenBoundaryUse::TrustedHistory));
assert!(envelope
.forbidden_uses
.contains(&ForbiddenBoundaryUse::Release));
assert!(!envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::CandidateClaim));
assert!(!envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::EvidenceReference));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::VerificationRequest));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::Refusal));
}
#[test]
fn constraint_envelope_carries_operator_raw_redaction_state() {
let pack = ContextPackBuilder::new("prepare operator-only AXIOM work", 512)
.pack_mode(crate::PackMode::Operator)
.include_raw_event_payloads_in_operator_mode()
.select_ref(
ContextRefCandidate::new(event_ref(), "operator raw context")
.with_sensitivity(Sensitivity::Internal)
.with_raw_event_payload(serde_json::json!({"payload": "operator local"}))
.with_claim_metadata(
RuntimeMode::AuthorityGrade,
AuthorityClass::Operator,
ClaimProofState::FullChainVerified,
ClaimCeiling::AuthorityGrade,
)
.with_semantic_metadata(
ProvenanceClass::OperatorAttested,
SemanticTrustClass::FalsificationTested,
),
)
.build()
.expect("build pack");
let envelope = constraint_envelope_for_pack(&pack);
assert_eq!(
envelope.redaction_state,
BoundaryRedactionState::RawOperatorOptIn
);
assert_eq!(envelope.provenance_class, ProvenanceClass::OperatorAttested);
assert_eq!(
envelope.semantic_trust,
SemanticTrustClass::FalsificationTested
);
}
#[test]
fn rejected_pack_allows_only_diagnostic_claim_language() {
let mut pack = ContextPackBuilder::new("prepare rejected AXIOM work", 512)
.select_ref(
ContextRefCandidate::new(event_ref(), "raw external context")
.with_raw_event_payload(serde_json::json!({"payload": "must not leak"})),
)
.build()
.expect("build pack");
pack.redaction_policy.raw_event_payloads = RawEventPayloadPolicy::OperatorOptIn;
let envelope = constraint_envelope_for_pack(&pack);
assert_eq!(
envelope.quarantine_state,
BoundaryQuarantineState::Contaminated
);
assert!(!envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::CandidateClaim));
assert!(!envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::EvidenceReference));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::Constraint));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::ResidualRisk));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::VerificationRequest));
assert!(envelope
.allowed_claim_language
.contains(&AllowedClaimLanguage::Refusal));
}
}