1use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::claims::{ClaimCeiling, ClaimProofState};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
16#[serde(rename_all = "snake_case")]
17pub enum AxiomElementKind {
18 CompletionCriterion,
20 AntiCriterion,
22 Hypothesis,
24 Capability,
26 Evidence,
28 Decision,
30 ResidualRisk,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
36#[serde(rename_all = "snake_case")]
37pub enum CortexClaimRole {
38 CandidateClaim,
40 EvidenceReference,
42 CapabilityReference,
44 Constraint,
46 ResidualRisk,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
52#[serde(rename_all = "snake_case")]
53pub enum AxiomEvidenceKind {
54 Observed,
56 Inferred,
58 Claimed,
60 Simulated,
62 Unknown,
64}
65
66impl AxiomEvidenceKind {
67 #[must_use]
73 pub const fn claim_ceiling(self) -> ClaimCeiling {
74 match self {
75 Self::Observed | Self::Inferred => ClaimCeiling::LocalUnsigned,
76 Self::Claimed | Self::Simulated | Self::Unknown => ClaimCeiling::DevOnly,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
83#[serde(rename_all = "snake_case")]
84pub enum AxiomStatus {
85 Supported,
87 Partial,
89 Rejected,
91 Unknown,
93 Invoked,
95 NotInvoked,
97}
98
99impl AxiomStatus {
100 #[must_use]
102 pub const fn proof_state(self, has_evidence_refs: bool) -> ClaimProofState {
103 match self {
104 Self::Supported | Self::Invoked if has_evidence_refs => ClaimProofState::Partial,
105 Self::Supported | Self::Invoked | Self::Partial => ClaimProofState::Unknown,
106 Self::Rejected => ClaimProofState::Broken,
107 Self::Unknown | Self::NotInvoked => ClaimProofState::Unknown,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
114pub struct AxiomClaimInput {
115 pub axiom_label: String,
117 pub axiom_kind: AxiomElementKind,
119 pub statement: String,
121 pub status: AxiomStatus,
123 pub evidence_kind: AxiomEvidenceKind,
125 pub evidence_refs: Vec<String>,
127 pub capability_refs: Vec<String>,
129 pub residual_risks: Vec<String>,
131 pub requested_ceiling: ClaimCeiling,
133}
134
135impl AxiomClaimInput {
136 #[must_use]
138 pub fn new(
139 axiom_label: impl Into<String>,
140 axiom_kind: AxiomElementKind,
141 statement: impl Into<String>,
142 status: AxiomStatus,
143 evidence_kind: AxiomEvidenceKind,
144 ) -> Self {
145 Self {
146 axiom_label: axiom_label.into(),
147 axiom_kind,
148 statement: statement.into(),
149 status,
150 evidence_kind,
151 evidence_refs: Vec::new(),
152 capability_refs: Vec::new(),
153 residual_risks: Vec::new(),
154 requested_ceiling: ClaimCeiling::LocalUnsigned,
155 }
156 }
157
158 #[must_use]
160 pub fn with_evidence_refs<I, S>(mut self, refs: I) -> Self
161 where
162 I: IntoIterator<Item = S>,
163 S: Into<String>,
164 {
165 self.evidence_refs = refs.into_iter().map(Into::into).collect();
166 self
167 }
168
169 #[must_use]
171 pub fn with_capability_refs<I, S>(mut self, refs: I) -> Self
172 where
173 I: IntoIterator<Item = S>,
174 S: Into<String>,
175 {
176 self.capability_refs = refs.into_iter().map(Into::into).collect();
177 self
178 }
179
180 #[must_use]
182 pub fn with_residual_risks<I, S>(mut self, risks: I) -> Self
183 where
184 I: IntoIterator<Item = S>,
185 S: Into<String>,
186 {
187 self.residual_risks = risks.into_iter().map(Into::into).collect();
188 self
189 }
190
191 #[must_use]
193 pub const fn with_requested_ceiling(mut self, ceiling: ClaimCeiling) -> Self {
194 self.requested_ceiling = ceiling;
195 self
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
201#[serde(rename_all = "snake_case")]
202pub enum AxiomConstraintSeverity {
203 Advisory,
205 Limit,
207 Hard,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
213#[serde(rename_all = "snake_case")]
214pub enum AxiomConstraintKind {
215 CandidateOnly,
217 ForbidPromotionShapedOutput,
219 RequireSourceAnchor,
221 RequireToolProvenance,
223 TruthCeiling,
225 ProofStateLimit,
227 ConflictPresent,
229 RedactionBoundary,
231 LowTrust,
233 NoExecutionAuthority,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
239pub struct AxiomConstraint {
240 pub kind: AxiomConstraintKind,
242 pub severity: AxiomConstraintSeverity,
244 pub message: String,
246 pub refs: Vec<String>,
248}
249
250impl AxiomConstraint {
251 #[must_use]
253 pub fn new(
254 kind: AxiomConstraintKind,
255 severity: AxiomConstraintSeverity,
256 message: impl Into<String>,
257 ) -> Self {
258 Self {
259 kind,
260 severity,
261 message: message.into(),
262 refs: Vec::new(),
263 }
264 }
265
266 #[must_use]
268 pub fn with_refs<I, S>(mut self, refs: I) -> Self
269 where
270 I: IntoIterator<Item = S>,
271 S: Into<String>,
272 {
273 self.refs = refs.into_iter().map(Into::into).collect();
274 self
275 }
276}
277
278#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
280pub struct CortexClaimMapping {
281 pub axiom_label: String,
283 pub axiom_kind: AxiomElementKind,
285 pub cortex_role: CortexClaimRole,
287 pub statement: String,
289 pub evidence_kind: AxiomEvidenceKind,
291 pub status: AxiomStatus,
293 pub evidence_refs: Vec<String>,
295 pub capability_refs: Vec<String>,
297 pub residual_risks: Vec<String>,
299 pub proof_state: ClaimProofState,
301 pub claim_ceiling: ClaimCeiling,
303 pub constraints: Vec<AxiomConstraint>,
305}
306
307impl CortexClaimMapping {
308 #[must_use]
310 pub fn from_axiom(input: AxiomClaimInput) -> Self {
311 let cortex_role = cortex_role_for(input.axiom_kind);
312 let proof_state = input.status.proof_state(!input.evidence_refs.is_empty());
313 let claim_ceiling = ClaimCeiling::weakest([
314 input.requested_ceiling,
315 input.evidence_kind.claim_ceiling(),
316 proof_state.claim_ceiling(),
317 ])
318 .expect("fixed-size ceiling set is non-empty");
319
320 let mut constraints = Vec::new();
321 constraints.push(AxiomConstraint::new(
322 AxiomConstraintKind::CandidateOnly,
323 AxiomConstraintSeverity::Hard,
324 "AXIOM output is candidate evidence only; Cortex promotion gates still apply",
325 ));
326
327 if input.evidence_refs.is_empty()
328 && matches!(
329 input.axiom_kind,
330 AxiomElementKind::CompletionCriterion
331 | AxiomElementKind::Hypothesis
332 | AxiomElementKind::Evidence
333 | AxiomElementKind::Decision
334 )
335 {
336 constraints.push(AxiomConstraint::new(
337 AxiomConstraintKind::RequireSourceAnchor,
338 AxiomConstraintSeverity::Hard,
339 "substantive AXIOM claims require Cortex-readable source anchors",
340 ));
341 }
342
343 if input.axiom_kind == AxiomElementKind::Capability && input.capability_refs.is_empty() {
344 constraints.push(AxiomConstraint::new(
345 AxiomConstraintKind::RequireToolProvenance,
346 AxiomConstraintSeverity::Hard,
347 "AXIOM capability claims require invocation provenance",
348 ));
349 }
350
351 if proof_state != ClaimProofState::FullChainVerified {
352 constraints.push(AxiomConstraint::new(
353 AxiomConstraintKind::ProofStateLimit,
354 AxiomConstraintSeverity::Limit,
355 format!("proof state {proof_state:?} limits Cortex authority"),
356 ));
357 }
358
359 if claim_ceiling < input.requested_ceiling {
360 let requested_ceiling = input.requested_ceiling;
361 constraints.push(AxiomConstraint::new(
362 AxiomConstraintKind::TruthCeiling,
363 AxiomConstraintSeverity::Limit,
364 format!("requested ceiling {requested_ceiling:?} downgraded to {claim_ceiling:?}"),
365 ));
366 }
367
368 Self {
369 axiom_label: input.axiom_label,
370 axiom_kind: input.axiom_kind,
371 cortex_role,
372 statement: input.statement,
373 evidence_kind: input.evidence_kind,
374 status: input.status,
375 evidence_refs: input.evidence_refs,
376 capability_refs: input.capability_refs,
377 residual_risks: input.residual_risks,
378 proof_state,
379 claim_ceiling,
380 constraints,
381 }
382 }
383}
384
385#[must_use]
387pub fn map_axiom_claims<I>(inputs: I) -> Vec<CortexClaimMapping>
388where
389 I: IntoIterator<Item = AxiomClaimInput>,
390{
391 inputs
392 .into_iter()
393 .map(CortexClaimMapping::from_axiom)
394 .collect()
395}
396
397fn cortex_role_for(kind: AxiomElementKind) -> CortexClaimRole {
398 match kind {
399 AxiomElementKind::CompletionCriterion
400 | AxiomElementKind::AntiCriterion
401 | AxiomElementKind::Hypothesis
402 | AxiomElementKind::Decision => CortexClaimRole::CandidateClaim,
403 AxiomElementKind::Capability => CortexClaimRole::CapabilityReference,
404 AxiomElementKind::Evidence => CortexClaimRole::EvidenceReference,
405 AxiomElementKind::ResidualRisk => CortexClaimRole::ResidualRisk,
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn axiom_observed_claim_maps_to_candidate_not_authority() {
415 let mapping = CortexClaimMapping::from_axiom(
416 AxiomClaimInput::new(
417 "CHK[1]",
418 AxiomElementKind::CompletionCriterion,
419 "workspace tests passed",
420 AxiomStatus::Supported,
421 AxiomEvidenceKind::Observed,
422 )
423 .with_evidence_refs(["cmd:cargo test --workspace"])
424 .with_requested_ceiling(ClaimCeiling::AuthorityGrade),
425 );
426
427 assert_eq!(mapping.cortex_role, CortexClaimRole::CandidateClaim);
428 assert_eq!(mapping.proof_state, ClaimProofState::Partial);
429 assert_eq!(mapping.claim_ceiling, ClaimCeiling::LocalUnsigned);
430 assert!(mapping
431 .constraints
432 .iter()
433 .any(|constraint| constraint.kind == AxiomConstraintKind::CandidateOnly));
434 }
435
436 #[test]
437 fn claimed_or_simulated_axiom_evidence_fails_down_to_dev_only() {
438 let mapping = CortexClaimMapping::from_axiom(
439 AxiomClaimInput::new(
440 "HYP[2]",
441 AxiomElementKind::Hypothesis,
442 "the system is production ready",
443 AxiomStatus::Supported,
444 AxiomEvidenceKind::Claimed,
445 )
446 .with_evidence_refs(["agent:final-answer"])
447 .with_requested_ceiling(ClaimCeiling::AuthorityGrade),
448 );
449
450 assert_eq!(mapping.claim_ceiling, ClaimCeiling::DevOnly);
451 assert!(mapping
452 .constraints
453 .iter()
454 .any(|constraint| constraint.kind == AxiomConstraintKind::TruthCeiling));
455 }
456
457 #[test]
458 fn capability_claim_without_provenance_requires_tool_reference() {
459 let mapping = CortexClaimMapping::from_axiom(AxiomClaimInput::new(
460 "CAP[1]",
461 AxiomElementKind::Capability,
462 "tests invoked",
463 AxiomStatus::Invoked,
464 AxiomEvidenceKind::Observed,
465 ));
466
467 assert_eq!(mapping.cortex_role, CortexClaimRole::CapabilityReference);
468 assert!(mapping.constraints.iter().any(|constraint| {
469 constraint.kind == AxiomConstraintKind::RequireToolProvenance
470 && constraint.severity == AxiomConstraintSeverity::Hard
471 }));
472 }
473
474 #[test]
475 fn shared_claim_language_wire_shape_is_stable() {
476 let mapping = CortexClaimMapping::from_axiom(
477 AxiomClaimInput::new(
478 "EVIDENCE[1]",
479 AxiomElementKind::Evidence,
480 "read docs/LANES.md",
481 AxiomStatus::Supported,
482 AxiomEvidenceKind::Observed,
483 )
484 .with_evidence_refs(["file:docs/LANES.md"]),
485 );
486
487 let value = serde_json::to_value(&mapping).expect("serialize mapping");
488 assert_eq!(value["axiom_label"], serde_json::json!("EVIDENCE[1]"));
489 assert_eq!(value["axiom_kind"], serde_json::json!("evidence"));
490 assert_eq!(
491 value["cortex_role"],
492 serde_json::json!("evidence_reference")
493 );
494 assert_eq!(value["claim_ceiling"], serde_json::json!("local_unsigned"));
495 }
496}