use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::claims::{ClaimCeiling, ClaimProofState};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AxiomElementKind {
CompletionCriterion,
AntiCriterion,
Hypothesis,
Capability,
Evidence,
Decision,
ResidualRisk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CortexClaimRole {
CandidateClaim,
EvidenceReference,
CapabilityReference,
Constraint,
ResidualRisk,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AxiomEvidenceKind {
Observed,
Inferred,
Claimed,
Simulated,
Unknown,
}
impl AxiomEvidenceKind {
#[must_use]
pub const fn claim_ceiling(self) -> ClaimCeiling {
match self {
Self::Observed | Self::Inferred => ClaimCeiling::LocalUnsigned,
Self::Claimed | Self::Simulated | Self::Unknown => ClaimCeiling::DevOnly,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AxiomStatus {
Supported,
Partial,
Rejected,
Unknown,
Invoked,
NotInvoked,
}
impl AxiomStatus {
#[must_use]
pub const fn proof_state(self, has_evidence_refs: bool) -> ClaimProofState {
match self {
Self::Supported | Self::Invoked if has_evidence_refs => ClaimProofState::Partial,
Self::Supported | Self::Invoked | Self::Partial => ClaimProofState::Unknown,
Self::Rejected => ClaimProofState::Broken,
Self::Unknown | Self::NotInvoked => ClaimProofState::Unknown,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AxiomClaimInput {
pub axiom_label: String,
pub axiom_kind: AxiomElementKind,
pub statement: String,
pub status: AxiomStatus,
pub evidence_kind: AxiomEvidenceKind,
pub evidence_refs: Vec<String>,
pub capability_refs: Vec<String>,
pub residual_risks: Vec<String>,
pub requested_ceiling: ClaimCeiling,
}
impl AxiomClaimInput {
#[must_use]
pub fn new(
axiom_label: impl Into<String>,
axiom_kind: AxiomElementKind,
statement: impl Into<String>,
status: AxiomStatus,
evidence_kind: AxiomEvidenceKind,
) -> Self {
Self {
axiom_label: axiom_label.into(),
axiom_kind,
statement: statement.into(),
status,
evidence_kind,
evidence_refs: Vec::new(),
capability_refs: Vec::new(),
residual_risks: Vec::new(),
requested_ceiling: ClaimCeiling::LocalUnsigned,
}
}
#[must_use]
pub fn with_evidence_refs<I, S>(mut self, refs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.evidence_refs = refs.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_capability_refs<I, S>(mut self, refs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.capability_refs = refs.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_residual_risks<I, S>(mut self, risks: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.residual_risks = risks.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub const fn with_requested_ceiling(mut self, ceiling: ClaimCeiling) -> Self {
self.requested_ceiling = ceiling;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AxiomConstraintSeverity {
Advisory,
Limit,
Hard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AxiomConstraintKind {
CandidateOnly,
ForbidPromotionShapedOutput,
RequireSourceAnchor,
RequireToolProvenance,
TruthCeiling,
ProofStateLimit,
ConflictPresent,
RedactionBoundary,
LowTrust,
NoExecutionAuthority,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AxiomConstraint {
pub kind: AxiomConstraintKind,
pub severity: AxiomConstraintSeverity,
pub message: String,
pub refs: Vec<String>,
}
impl AxiomConstraint {
#[must_use]
pub fn new(
kind: AxiomConstraintKind,
severity: AxiomConstraintSeverity,
message: impl Into<String>,
) -> Self {
Self {
kind,
severity,
message: message.into(),
refs: Vec::new(),
}
}
#[must_use]
pub fn with_refs<I, S>(mut self, refs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.refs = refs.into_iter().map(Into::into).collect();
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct CortexClaimMapping {
pub axiom_label: String,
pub axiom_kind: AxiomElementKind,
pub cortex_role: CortexClaimRole,
pub statement: String,
pub evidence_kind: AxiomEvidenceKind,
pub status: AxiomStatus,
pub evidence_refs: Vec<String>,
pub capability_refs: Vec<String>,
pub residual_risks: Vec<String>,
pub proof_state: ClaimProofState,
pub claim_ceiling: ClaimCeiling,
pub constraints: Vec<AxiomConstraint>,
}
impl CortexClaimMapping {
#[must_use]
pub fn from_axiom(input: AxiomClaimInput) -> Self {
let cortex_role = cortex_role_for(input.axiom_kind);
let proof_state = input.status.proof_state(!input.evidence_refs.is_empty());
let claim_ceiling = ClaimCeiling::weakest([
input.requested_ceiling,
input.evidence_kind.claim_ceiling(),
proof_state.claim_ceiling(),
])
.expect("fixed-size ceiling set is non-empty");
let mut constraints = Vec::new();
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::CandidateOnly,
AxiomConstraintSeverity::Hard,
"AXIOM output is candidate evidence only; Cortex promotion gates still apply",
));
if input.evidence_refs.is_empty()
&& matches!(
input.axiom_kind,
AxiomElementKind::CompletionCriterion
| AxiomElementKind::Hypothesis
| AxiomElementKind::Evidence
| AxiomElementKind::Decision
)
{
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::RequireSourceAnchor,
AxiomConstraintSeverity::Hard,
"substantive AXIOM claims require Cortex-readable source anchors",
));
}
if input.axiom_kind == AxiomElementKind::Capability && input.capability_refs.is_empty() {
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::RequireToolProvenance,
AxiomConstraintSeverity::Hard,
"AXIOM capability claims require invocation provenance",
));
}
if proof_state != ClaimProofState::FullChainVerified {
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::ProofStateLimit,
AxiomConstraintSeverity::Limit,
format!("proof state {proof_state:?} limits Cortex authority"),
));
}
if claim_ceiling < input.requested_ceiling {
let requested_ceiling = input.requested_ceiling;
constraints.push(AxiomConstraint::new(
AxiomConstraintKind::TruthCeiling,
AxiomConstraintSeverity::Limit,
format!("requested ceiling {requested_ceiling:?} downgraded to {claim_ceiling:?}"),
));
}
Self {
axiom_label: input.axiom_label,
axiom_kind: input.axiom_kind,
cortex_role,
statement: input.statement,
evidence_kind: input.evidence_kind,
status: input.status,
evidence_refs: input.evidence_refs,
capability_refs: input.capability_refs,
residual_risks: input.residual_risks,
proof_state,
claim_ceiling,
constraints,
}
}
}
#[must_use]
pub fn map_axiom_claims<I>(inputs: I) -> Vec<CortexClaimMapping>
where
I: IntoIterator<Item = AxiomClaimInput>,
{
inputs
.into_iter()
.map(CortexClaimMapping::from_axiom)
.collect()
}
fn cortex_role_for(kind: AxiomElementKind) -> CortexClaimRole {
match kind {
AxiomElementKind::CompletionCriterion
| AxiomElementKind::AntiCriterion
| AxiomElementKind::Hypothesis
| AxiomElementKind::Decision => CortexClaimRole::CandidateClaim,
AxiomElementKind::Capability => CortexClaimRole::CapabilityReference,
AxiomElementKind::Evidence => CortexClaimRole::EvidenceReference,
AxiomElementKind::ResidualRisk => CortexClaimRole::ResidualRisk,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn axiom_observed_claim_maps_to_candidate_not_authority() {
let mapping = CortexClaimMapping::from_axiom(
AxiomClaimInput::new(
"CHK[1]",
AxiomElementKind::CompletionCriterion,
"workspace tests passed",
AxiomStatus::Supported,
AxiomEvidenceKind::Observed,
)
.with_evidence_refs(["cmd:cargo test --workspace"])
.with_requested_ceiling(ClaimCeiling::AuthorityGrade),
);
assert_eq!(mapping.cortex_role, CortexClaimRole::CandidateClaim);
assert_eq!(mapping.proof_state, ClaimProofState::Partial);
assert_eq!(mapping.claim_ceiling, ClaimCeiling::LocalUnsigned);
assert!(mapping
.constraints
.iter()
.any(|constraint| constraint.kind == AxiomConstraintKind::CandidateOnly));
}
#[test]
fn claimed_or_simulated_axiom_evidence_fails_down_to_dev_only() {
let mapping = CortexClaimMapping::from_axiom(
AxiomClaimInput::new(
"HYP[2]",
AxiomElementKind::Hypothesis,
"the system is production ready",
AxiomStatus::Supported,
AxiomEvidenceKind::Claimed,
)
.with_evidence_refs(["agent:final-answer"])
.with_requested_ceiling(ClaimCeiling::AuthorityGrade),
);
assert_eq!(mapping.claim_ceiling, ClaimCeiling::DevOnly);
assert!(mapping
.constraints
.iter()
.any(|constraint| constraint.kind == AxiomConstraintKind::TruthCeiling));
}
#[test]
fn capability_claim_without_provenance_requires_tool_reference() {
let mapping = CortexClaimMapping::from_axiom(AxiomClaimInput::new(
"CAP[1]",
AxiomElementKind::Capability,
"tests invoked",
AxiomStatus::Invoked,
AxiomEvidenceKind::Observed,
));
assert_eq!(mapping.cortex_role, CortexClaimRole::CapabilityReference);
assert!(mapping.constraints.iter().any(|constraint| {
constraint.kind == AxiomConstraintKind::RequireToolProvenance
&& constraint.severity == AxiomConstraintSeverity::Hard
}));
}
#[test]
fn shared_claim_language_wire_shape_is_stable() {
let mapping = CortexClaimMapping::from_axiom(
AxiomClaimInput::new(
"EVIDENCE[1]",
AxiomElementKind::Evidence,
"read docs/LANES.md",
AxiomStatus::Supported,
AxiomEvidenceKind::Observed,
)
.with_evidence_refs(["file:docs/LANES.md"]),
);
let value = serde_json::to_value(&mapping).expect("serialize mapping");
assert_eq!(value["axiom_label"], serde_json::json!("EVIDENCE[1]"));
assert_eq!(value["axiom_kind"], serde_json::json!("evidence"));
assert_eq!(
value["cortex_role"],
serde_json::json!("evidence_reference")
);
assert_eq!(value["claim_ceiling"], serde_json::json!("local_unsigned"));
}
}