axon/pcc/proof_term.rs
1//! §Fase 51.a — Proof-Carrying Code: the portable proof object.
2//!
3//! A [`ProofTerm`] is the serializable artifact a producer (the axon
4//! compiler) emits alongside compiled code, certifying that a declared
5//! property holds. A consumer runs the INDEPENDENT checker
6//! ([`crate::pcc::checker`]) to verify it — WITHOUT trusting the
7//! producer (D51.2). The term travels as JSON, the same delivery
8//! surface as the SBOM / in-toto statements in [`crate::esk::attestation`],
9//! but unlike those it is a *proof* the consumer re-checks, not an
10//! attestation the consumer trusts.
11//!
12//! ## D51.1 — representation
13//!
14//! - [`PropertyClass`] — closed enum of property kinds. §51.a ships
15//! exactly [`PropertyClass::ComplianceCoverage`]; §51.b-e extend it.
16//! - `artifact_digest` — SHA-256 hex of the canonical IR JSON the proof
17//! is ABOUT. Binds the proof to a specific artifact: a proof for
18//! program A cannot be replayed against program B (the checker
19//! recomputes the digest and rejects a mismatch).
20//! - [`Witness`] — the property-specific derivation the checker
21//! re-verifies against the artifact.
22//! - `axon_version` — producer version. Diagnostic only: the checker
23//! does NOT trust it (it re-derives the property regardless).
24
25use serde::{Deserialize, Serialize};
26
27/// The closed catalog of properties a [`ProofTerm`] can certify.
28///
29/// §Fase 51.a ships [`Self::ComplianceCoverage`]. The §51.b-e classes
30/// (`EffectRowSoundness`, `CapabilityIsolation`, `ResourceBounds`,
31/// `ShieldHaltGuarantee`) land as the proof-term language generalizes
32/// (D51.4 — "universal" is the architecture, shipped one class at a
33/// time). Adding a variant here requires a matching witness variant +
34/// checker arm — the §51.a drift gate pins this lockstep.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub enum PropertyClass {
37 /// Every regulatory class an apx/axonendpoint declares in
38 /// `compliance:` is (a) a known class in the closed
39 /// [`crate::esk::compliance`] registry and (b) backed by a present,
40 /// resolvable shield (`shield_ref` non-empty AND that shield exists
41 /// in the program IR). Catches phantom compliance classes (a
42 /// typo'd `HIPPA`) and compliance-claimed-without-enforcement
43 /// (declaring GDPR with no attached shield).
44 ComplianceCoverage,
45 /// §51.b — every entry in a tool's `effects: <...>` row is
46 /// well-formed: its base is in the closed effect catalog
47 /// ([`crate::pcc::effects::EFFECT_BASES`]); `stream` / `trust`
48 /// carry a qualifier (`stream`'s in the backpressure catalog); and
49 /// `pure` is exclusive (a tool cannot be both `pure` and
50 /// effectful). Catches phantom effects (typo'd `network`), bare
51 /// `stream` without a backpressure policy, and pure/impure
52 /// contradictions.
53 EffectRowSoundness,
54 /// §51.c — every capability gate an `axonstore` declares (its
55 /// Pillar IV `capability` slug, §Fase 35.j) is a well-formed §32.g
56 /// capability scope (matches the closed grammar
57 /// `^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$`, via the OSS
58 /// `axon_frontend::parser::is_valid_capability_slug`). A malformed
59 /// gate is a Pillar IV defect: it can never match a properly-formed
60 /// bearer capability, so the store is either locked out or — worse,
61 /// if a consumer treats "unparseable gate" as "no gate" — silently
62 /// bypassed.
63 ///
64 /// **Scope (honest):** this is the gate-integrity half. The
65 /// containment half — an apx's reachable store gates ⊆ its declared
66 /// `requires:` set — needs the endpoint `requires_capabilities`
67 /// (AST / enterprise deploy metadata, NOT lowered to the frontend
68 /// IR) + flow→store reachability, and is deferred to §51.x
69 /// (enterprise PCC consumption, where `requires` lives).
70 CapabilityIsolation,
71 /// §51.d — declared resource bounds are within sane limits:
72 /// an apx/axonendpoint's `retries` is in `[0, MAX_RETRIES]`
73 /// (negative is nonsensical; above the ceiling is a retry storm),
74 /// and a `socket` carrying a DECLARED `backpressure: credit(k)`
75 /// has `k >= 1` (a credit window of 0 deadlocks the §Fase 41.b
76 /// typed-resource gate). Unspecified socket credit is a legitimate
77 /// type state and is NOT refuted. `timeout` is out of scope by
78 /// design — it is a closed duration enum (`{5s,15s,30s,60s}`)
79 /// already bounded at parse time, not an unbounded-resource risk.
80 ResourceBounds,
81 /// §51.e — a shield's breach policy provides a real guarantee:
82 /// `on_breach` is a recognized policy (closed catalog — catches a
83 /// typo'd `hault`, which the parser does NOT reject since it reads
84 /// `on_breach` as a bare identifier), and a shield declaring
85 /// `on_breach: halt` actually scans something (a halt shield with
86 /// an empty `scan: []` can never detect a breach, so the halt never
87 /// fires — a vacuous guarantee / security theater).
88 ShieldHaltGuarantee,
89 /// §51.x — the CONTAINMENT half of capability isolation (the
90 /// deferral §51.c flagged): every capability gate on a store the
91 /// apx's `execute_flow` REACHES is covered by the endpoint's
92 /// declared `requires:` scopes. Otherwise the flow touches a store
93 /// gated by capability `G` the endpoint does not declare requiring
94 /// — a request satisfying the endpoint's declared requires could
95 /// still reach a store it is not authorized for (capability leak /
96 /// privilege escalation). Reachability is a SOUND
97 /// over-approximation: every statically-reachable store op (both
98 /// conditional branches + the loop body) is counted. An
99 /// unresolvable `execute_flow` is REFUTED (cannot certify
100 /// containment for a flow the artifact does not contain).
101 CapabilityContainment,
102 /// §58.i — every structured `use <Tool>(k = v, …)` call satisfies the
103 /// called tool's declared `parameters:` input schema: no argument
104 /// names a parameter the tool does not declare; no argument is
105 /// supplied twice; every required (non-optional) parameter is
106 /// supplied; and every UNAMBIGUOUS-LITERAL argument's type aligns
107 /// with the declared type. This makes the §58.d CT-2 caller-blame
108 /// check an INDEPENDENTLY-VERIFIABLE proof — the tool schema rides
109 /// the bundle and the verifier re-derives the call's soundness from
110 /// the artifact, never trusting the compiler that ran the type-check
111 /// (D1). Literal-type alignment is CONSERVATIVE (only Int/Float/Bool
112 /// literals; bare identifiers + `${…}` interpolations are
113 /// runtime-resolved and skipped — zero false positives), so the
114 /// structural facts (unknown / duplicate / missing) are always
115 /// certified and the literal-type facts are certified where decidable
116 /// statically. A schema-less tool (no `parameters:`) and the legacy
117 /// `use <Tool> on <arg>` form carry no contract → no proof (D5).
118 ToolCallSoundness,
119}
120
121/// §51.e — the closed breach-policy catalog. Mirror of
122/// `axon_frontend::type_checker::VALID_ON_BREACH_POLICIES` (private
123/// const). The checker's own statement of the spec (D51.2). Cross-crate
124/// drift gate deferred to §51.f alongside the effect catalog.
125pub const VALID_BREACH_POLICIES: &[&str] =
126 &["deflect", "escalate", "halt", "quarantine", "sanitize_and_retry"];
127
128/// §51.d — the retry-storm ceiling. A declared `retries` above this is
129/// almost certainly a defect (an unbounded-ish retry storm), not a
130/// legitimate config. Generous on purpose so legitimate high-retry
131/// configs are not false-positived; the negative-retries case is the
132/// unambiguous defect this bound primarily guards.
133pub const MAX_RETRIES: i64 = 100;
134
135impl PropertyClass {
136 /// Stable wire slug for the property class.
137 pub fn slug(&self) -> &'static str {
138 match self {
139 PropertyClass::ComplianceCoverage => "compliance_coverage",
140 PropertyClass::EffectRowSoundness => "effect_row_soundness",
141 PropertyClass::CapabilityIsolation => "capability_isolation",
142 PropertyClass::ResourceBounds => "resource_bounds",
143 PropertyClass::ShieldHaltGuarantee => "shield_halt_guarantee",
144 PropertyClass::CapabilityContainment => "capability_containment",
145 PropertyClass::ToolCallSoundness => "tool_call_soundness",
146 }
147 }
148}
149
150/// §51.a — witness for [`PropertyClass::ComplianceCoverage`].
151///
152/// The derivation the producer recorded. The checker RE-DERIVES every
153/// field from the artifact and rejects the proof if the witness
154/// disagrees (D51.2 — a forged witness is caught because the checker
155/// recomputes, it does not believe the claim).
156///
157/// The property certified: the shield attached to a compliance-bearing
158/// apx **actually covers** every regulatory class the apx declares —
159/// `covers(provided, required) == ∅` (the existing
160/// [`crate::esk::compliance::covers`] predicate), with no phantom
161/// classes and a present, resolvable shield.
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
163pub struct ComplianceCoverageWitness {
164 /// The apx / axonendpoint this proof is about.
165 pub endpoint_name: String,
166 /// The regulatory classes the endpoint declared, sorted + deduped
167 /// (canonical so the checker's re-derivation compares equal).
168 pub required_classes: Vec<String>,
169 /// The endpoint's `shield_ref` (empty string = no shield declared).
170 pub shield_ref: String,
171 /// Whether `shield_ref` is non-empty AND resolves to a shield
172 /// present in the program IR.
173 pub shield_present: bool,
174 /// The regulatory classes the resolved shield PROVIDES (its
175 /// `compliance:` set), sorted + deduped. Empty when no shield
176 /// resolves.
177 pub provided_classes: Vec<String>,
178 /// The subset of `required_classes` that are NOT in the closed
179 /// regulatory registry (phantom compliance claims). Empty for a
180 /// verifying proof.
181 pub unknown_classes: Vec<String>,
182 /// The subset of `required_classes` the shield does NOT provide
183 /// (`required \ provided` — the coverage gap), sorted. Empty for a
184 /// verifying proof.
185 pub uncovered_classes: Vec<String>,
186}
187
188/// §51.b — witness for [`PropertyClass::EffectRowSoundness`].
189///
190/// The derivation for one tool's declared effect row. The checker
191/// re-derives every field from the tool's IR and rejects a disagreement
192/// as forgery (D51.2).
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct EffectRowSoundnessWitness {
195 /// The tool this proof is about.
196 pub tool_name: String,
197 /// The tool's declared effect-row entries, sorted + deduped
198 /// (canonical so the checker's re-derivation compares equal). Each
199 /// entry is `base` or `base:qualifier`.
200 pub declared_effects: Vec<String>,
201 /// Entries whose base effect is NOT in the closed catalog (phantom
202 /// effects). Empty for a verifying proof.
203 pub unknown_bases: Vec<String>,
204 /// Qualifier-required bases (`stream` / `trust`) declared WITHOUT a
205 /// qualifier (bare `stream` / `trust`). Empty for a verifying proof.
206 pub missing_qualifier: Vec<String>,
207 /// `stream:<q>` entries whose qualifier is not a valid backpressure
208 /// policy. Empty for a verifying proof.
209 pub invalid_stream_qualifier: Vec<String>,
210 /// True when `pure` appears alongside any other effect (a tool
211 /// cannot be both pure and effectful). False for a verifying proof.
212 pub purity_violation: bool,
213}
214
215/// §51.c — witness for [`PropertyClass::CapabilityIsolation`].
216///
217/// The derivation for one `axonstore`'s capability gate. The checker
218/// re-reads the store's `capability` from the IR and re-runs the §32.g
219/// grammar validator; a forged witness is rejected because the
220/// recomputation disagrees (D51.2).
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct CapabilityIsolationWitness {
223 /// The `axonstore` this proof is about.
224 pub store_name: String,
225 /// The store's declared Pillar IV capability gate slug.
226 pub capability: String,
227 /// True when `capability` is non-empty AND does NOT match the
228 /// §32.g capability-scope grammar. False for a verifying proof.
229 pub malformed: bool,
230}
231
232/// §51.d — witness for [`PropertyClass::ResourceBounds`]. One subject
233/// per proof: an endpoint's retry bound OR a socket's credit window.
234/// Tagged by `subject` so the JSON is self-describing.
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(tag = "subject")]
237pub enum ResourceBoundsWitness {
238 /// An apx/axonendpoint's declared `retries`.
239 EndpointRetry {
240 endpoint_name: String,
241 retries: i64,
242 /// True when `retries` is in `[0, MAX_RETRIES]`.
243 in_bounds: bool,
244 },
245 /// A socket's DECLARED `backpressure: credit(k)` window. Generated
246 /// only when the socket declares a credit (unspecified is not
247 /// witnessed — it is a legitimate type state, not a bound to
248 /// certify).
249 SocketCredit {
250 socket_name: String,
251 credit: i64,
252 /// True when `credit >= 1` (a 0 window deadlocks per §41.b).
253 positive: bool,
254 },
255}
256
257/// §51.e — witness for [`PropertyClass::ShieldHaltGuarantee`].
258///
259/// The derivation for one shield's breach policy. The checker re-reads
260/// the shield's `on_breach` + `scan` from the IR and recomputes both
261/// facts; a forged witness is rejected because the recomputation
262/// disagrees (D51.2).
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub struct ShieldHaltGuaranteeWitness {
265 /// The shield this proof is about.
266 pub shield_name: String,
267 /// The shield's declared `on_breach` policy.
268 pub on_breach: String,
269 /// True when `on_breach` is in the closed breach-policy catalog.
270 pub known_policy: bool,
271 /// Count of declared `scan` categories (for the vacuous-halt check).
272 pub scan_count: usize,
273 /// True when `on_breach == "halt"` AND `scan` is empty — the halt
274 /// can never fire (no scan ⟹ no breach ⟹ no halt). False for a
275 /// verifying proof.
276 pub vacuous_halt: bool,
277}
278
279/// §51.x — witness for [`PropertyClass::CapabilityContainment`].
280///
281/// The derivation for one endpoint's reachable-store-gate containment.
282/// The checker re-resolves the `execute_flow`, re-walks its reachable
283/// store ops, re-resolves each store's capability gate, and recomputes
284/// the uncovered set; a forged witness is rejected because the
285/// recomputation disagrees (D51.2).
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287pub struct CapabilityContainmentWitness {
288 /// The apx / axonendpoint this proof is about.
289 pub endpoint_name: String,
290 /// The flow the endpoint executes.
291 pub execute_flow: String,
292 /// Whether `execute_flow` resolves to a flow present in the IR.
293 pub flow_resolved: bool,
294 /// The capability scopes the endpoint declares (`requires:`),
295 /// sorted + deduped.
296 pub declared_requires: Vec<String>,
297 /// The capability gates of the stores the flow REACHES (each
298 /// reached store's non-empty `capability`), sorted + deduped.
299 pub reached_gates: Vec<String>,
300 /// `reached_gates \ declared_requires` — gates the flow reaches but
301 /// the endpoint does not declare requiring. Empty for a verifying
302 /// proof.
303 pub uncovered_gates: Vec<String>,
304}
305
306/// §58.i — witness for [`PropertyClass::ToolCallSoundness`].
307///
308/// The derivation for ONE structured `use <Tool>(k = v, …)` call site.
309/// The checker re-walks the named flow, locates the call at `call_index`
310/// (deterministic walk order over the same digest-bound IR), re-reads
311/// the called tool's `parameters:` schema, and recomputes every fact; a
312/// forged witness (e.g. hiding an unknown argument) is rejected because
313/// the recomputation disagrees (D51.2).
314#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
315pub struct ToolCallSoundnessWitness {
316 /// The flow containing the call.
317 pub flow_name: String,
318 /// Ordinal of this call among ALL named-arg `use` calls in the flow,
319 /// in deterministic walk order (recursing into conditional branches +
320 /// for-in bodies). Locates the exact call site so the checker
321 /// re-derives the SAME one (two calls to the same tool in one flow
322 /// are distinguished).
323 pub call_index: usize,
324 /// The tool the call invokes.
325 pub tool_name: String,
326 /// The argument names supplied at the call, in SOURCE ORDER (so a
327 /// duplicate is visible). Re-derived from the IR call.
328 pub arg_names: Vec<String>,
329 /// The called tool's declared parameter names, sorted + deduped
330 /// (context + forgery surface). Empty if the tool is undeclared.
331 pub declared_params: Vec<String>,
332 /// Whether the called tool is declared with a NON-EMPTY `parameters:`
333 /// schema. A generated proof always has this `true` (a schema-less /
334 /// undeclared tool carries no contract → no proof).
335 pub schema_present: bool,
336 /// Args naming a parameter the tool does not declare, sorted +
337 /// deduped. Empty for a verifying proof.
338 pub unknown_args: Vec<String>,
339 /// Argument names supplied more than once, sorted + deduped. Empty
340 /// for a verifying proof.
341 pub duplicate_args: Vec<String>,
342 /// Required (non-optional) parameters not supplied, sorted. Empty for
343 /// a verifying proof.
344 pub missing_required: Vec<String>,
345 /// Unambiguous-literal args whose type does not align with the
346 /// declared type, each `name:expected:got`, sorted. Empty for a
347 /// verifying proof. (Bare identifiers / `${…}` interpolations are
348 /// runtime-resolved → not inferred → never listed.)
349 pub type_mismatches: Vec<String>,
350}
351
352/// The property-specific witness. Tagged so the JSON is self-describing
353/// + a future class adds a variant without ambiguity.
354#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
355#[serde(tag = "kind")]
356pub enum Witness {
357 ComplianceCoverage(ComplianceCoverageWitness),
358 EffectRowSoundness(EffectRowSoundnessWitness),
359 CapabilityIsolation(CapabilityIsolationWitness),
360 ResourceBounds(ResourceBoundsWitness),
361 ShieldHaltGuarantee(ShieldHaltGuaranteeWitness),
362 CapabilityContainment(CapabilityContainmentWitness),
363 ToolCallSoundness(ToolCallSoundnessWitness),
364}
365
366impl Witness {
367 /// §51.f — the subject (endpoint / tool / store / socket / shield)
368 /// the witness is about, for human-readable CLI output. Total.
369 pub fn subject_name(&self) -> &str {
370 match self {
371 Witness::ComplianceCoverage(w) => &w.endpoint_name,
372 Witness::EffectRowSoundness(w) => &w.tool_name,
373 Witness::CapabilityIsolation(w) => &w.store_name,
374 Witness::ResourceBounds(ResourceBoundsWitness::EndpointRetry {
375 endpoint_name,
376 ..
377 }) => endpoint_name,
378 Witness::ResourceBounds(ResourceBoundsWitness::SocketCredit {
379 socket_name,
380 ..
381 }) => socket_name,
382 Witness::ShieldHaltGuarantee(w) => &w.shield_name,
383 Witness::CapabilityContainment(w) => &w.endpoint_name,
384 Witness::ToolCallSoundness(w) => &w.tool_name,
385 }
386 }
387}
388
389/// The portable proof object (D51.1). Serializes to JSON; travels with
390/// the artifact; the independent checker verifies it.
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392pub struct ProofTerm {
393 /// The property class this term certifies.
394 pub property: PropertyClass,
395 /// SHA-256 hex of the canonical IR JSON the proof is about (binds
396 /// the proof to a specific artifact).
397 pub artifact_digest: String,
398 /// The derivation the checker re-verifies.
399 pub witness: Witness,
400 /// Producer version (diagnostic; NOT trusted by the checker).
401 pub axon_version: String,
402}
403
404/// §51.f — the portable proof bundle the `axon pcc prove` CLI emits +
405/// `axon pcc verify` consumes. Carries every proof generated for an
406/// artifact plus the artifact digest they all bind to (a quick
407/// sanity field — the per-proof `artifact_digest` is the authoritative
408/// binding the checker re-verifies).
409#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
410pub struct ProofBundle {
411 /// Producer version that generated the bundle.
412 pub axon_version: String,
413 /// SHA-256 hex digest of the artifact all proofs bind to.
414 pub artifact_digest: String,
415 /// Every generated proof (across all property classes).
416 pub proofs: Vec<ProofTerm>,
417}