Skip to main content

axon_frontend/
ir_nodes.rs

1//! AXON IR node definitions — direct port of axon/compiler/ir_nodes.py.
2//!
3//! All nodes serialize to JSON matching the Python IR output format exactly.
4
5#![allow(dead_code)]
6
7use serde::Serialize;
8
9// ── Program root ─────────────────────────────────────────────────────────────
10
11/// v2.67.0 — `Clone` is additive and every member IR type already derives it.
12/// The Cognitive-I/O supervisor owns the compiled program it drives (it outlives
13/// the deploy call that built it), and a lifetime-bound supervisor would have to be
14/// threaded through `ServerState` for no benefit.
15#[derive(Debug, Clone, Serialize)]
16pub struct IRProgram {
17    pub node_type: &'static str,
18    pub source_line: u32,
19    pub source_column: u32,
20    pub personas: Vec<IRPersona>,
21    pub contexts: Vec<IRContext>,
22    pub anchors: Vec<IRAnchor>,
23    pub tools: Vec<IRToolSpec>,
24    pub memories: Vec<IRMemory>,
25    pub types: Vec<IRType>,
26    pub flows: Vec<IRFlow>,
27    pub runs: Vec<IRRun>,
28    pub imports: Vec<IRImport>,
29    pub agents: Vec<IRAgent>,
30    pub shields: Vec<IRShield>,
31    /// v4.5.0 — signed determinations, riding beside what was proved.
32    ///
33    /// They live at the top of the artifact rather than inside the shield or
34    /// the step, because the evidence package reads them as their own class of
35    /// fact: this is the part no gate produced. `skip_serializing_if` keeps
36    /// every program without an attestation byte-identical.
37    #[serde(default, skip_serializing_if = "Vec::is_empty")]
38    pub attestations: Vec<IRAttestation>,
39    /// v2.27.0 — temporal execution-window guards.
40    pub windows: Vec<IRWindow>,
41    /// v2.69.0 — top-level `budget` declarations. A daemon's anonymous budget
42    /// stays on the daemon; these govern EVERY flow that calls the tools they
43    /// name, including the HTTP endpoints adopters actually deploy.
44    #[serde(default, skip_serializing_if = "Vec::is_empty")]
45    pub budgets: Vec<IRBudget>,
46    pub daemons: Vec<IRDaemon>,
47    pub ots_specs: Vec<IROts>,
48    pub pix_specs: Vec<IRPix>,
49    /// v2.12.0 — audit-chain (`ledger`) declarations. Distinct from
50    /// `pix_specs` (the retrieval navigator); a ledger binds a hash-linked
51    /// recorder to an audited surface.
52    pub ledger_specs: Vec<IRLedger>,
53    pub corpus_specs: Vec<IRCorpus>,
54    pub psyche_specs: Vec<IRPsyche>,
55    pub mandate_specs: Vec<IRMandate>,
56    pub lambda_data_specs: Vec<IRLambdaData>,
57    pub compute_specs: Vec<IRCompute>,
58    pub axonstore_specs: Vec<IRAxonStore>,
59    pub endpoints: Vec<IRAxonEndpoint>,
60    /// v2.5.0 — closed-catalog extension declarations (compiled).
61    /// `#[serde(skip)]` so the field is NOT emitted into the IR JSON —
62    /// this keeps the static IR-JSON drift-gate fixtures green without
63    /// regenerating them (the pattern `dataspace_specs` also used until
64    /// v2.63.0 un-skipped it).
65    /// The in-memory field feeds the v2.5.0 type-checker + v2.5.0 PCC (both
66    /// read `&IRProgram`); soundness invariant #1 holds via SOURCE
67    /// re-derivation — both the prover and the verifier read the
68    /// source-derived IR, which carries the extensions. v2.5.0 hardening
69    /// (optional): un-skip + regenerate fixtures + bind extensions into
70    /// the PCC `artifact_digest` (today the digest omits them; the
71    /// witness still binds them by re-derivation). Deterministically
72    /// sorted by `name` at the end of IR generation (v2.5.0 founder
73    /// refinement B) so multi-file declaration order can never perturb
74    /// the proof-bundle hash.
75    #[serde(skip)]
76    pub extensions: Vec<IRExtension>,
77    /// v2.63.0 — the compiled dataspace schemas, SERIALIZED into the
78    /// IR JSON (un-skipped). History: this field was `#[serde(skip)]` for
79    /// byte-identical parity with the retired Python reference frontend
80    /// (section 8.2.h.1) — which meant the runtime literally could not see a
81    /// declared dataspace (the v2.63.0 ground-truth finding). The parity
82    /// constraint is gone; the deploy hook walks this field to
83    /// instantiate the deterministic columnar engine's stores. Additive
84    /// for consumers: no IR deserializer uses `deny_unknown_fields`
85    /// (verified 2026-07-12), and `IRProgram` is `Serialize`-only —
86    /// consumers re-derive from source.
87    pub dataspace_specs: Vec<IRDataspace>,
88    /// v1.1.0 — I/O cognitivo primitives (compiled).
89    pub resources: Vec<IRResource>,
90    pub fabrics: Vec<IRFabric>,
91    pub manifests: Vec<IRManifest>,
92    pub observations: Vec<IRObserve>,
93    /// v1.1.0 (Free Monad root) — populated when the program
94    /// declares manifests/observes. `None` ⇒ serialises as `null`
95    /// (matches Python when the field is `None`).
96    pub intention_tree: Option<IRIntentionTree>,
97    /// v1.1.0 — Control cognitivo primitives (compiled).
98    pub reconciles: Vec<IRReconcile>,
99    pub leases: Vec<IRLease>,
100    pub ensembles: Vec<IREnsemble>,
101    /// v1.1.0 — Topology + Session (compiled).
102    pub sessions: Vec<IRSession>,
103    pub topologies: Vec<IRTopology>,
104    /// v1.1.0 — Immune system (compiled).
105    pub immunes: Vec<IRImmune>,
106    pub reflexes: Vec<IRReflex>,
107    pub heals: Vec<IRHeal>,
108    /// v1.3.1 — UI cognitiva declarativa (compiled).
109    pub components: Vec<IRComponent>,
110    pub views: Vec<IRView>,
111    /// v1.6.0 — Mobile typed channels (compiled).
112    pub channels: Vec<IRChannel>,
113    /// v2.3.0 — typed WebSocket transports (compiled). Each carries its
114    /// referenced `session` protocol + the credit-window backpressure so
115    /// axon-rs can realise the typed endpoint over a `tokio` WebSocket.
116    pub sockets: Vec<IRSocket>,
117    /// v2.4.0 — Pauli-sum observable declarations (compiled). Each carries
118    /// its real-coefficient × Pauli-string terms so axon-rs can build the
119    /// Hermitian measurement operator `M = Σ cₖ Pₖ` a `quant` block measures
120    /// against. `#[serde(skip)]` (like `extensions` / `dataspace_specs`) so the
121    /// static IR-JSON drift fixtures stay green; the in-memory field feeds the
122    /// v2.4.0 checker + the v2.4.0 runtime. The checker resolves
123    /// `quant(observable: …)` against the AST symbol table, not this field.
124    #[serde(skip)]
125    pub observables: Vec<IRObservable>,
126    /// v2.23.0 — Advantage-Witness declarations. `skip_serializing_if = empty`
127    /// keeps a witness-less program's IR JSON byte-identical (zero IR-SHA drift,
128    /// the v2.4.0/v2.21.0 pattern); when present it rides the IR to the enterprise
129    /// deploy/runtime evaluator (v2.23.0+).
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub witnesses: Vec<IRWitness>,
132    /// v2.37.0 — outbound vendor connections (compiled). Each carries its
133    /// axon-facing session binding (`protocol`/`role`), the per-tenant config
134    /// keys (`resolve`/`secret`), the auth handshake, the total wire↔session
135    /// projection (`map`) and the reconnect/overflow policies, so axon-rs can
136    /// dial + transcode without vendor-specific code. `skip_serializing_if =
137    /// empty` keeps an upstream-less program's IR JSON byte-identical (zero
138    /// IR-SHA drift — the standing v2.33.0 discipline).
139    #[serde(default, skip_serializing_if = "Vec::is_empty")]
140    pub upstreams: Vec<IRUpstream>,
141    /// v2.38.0 — named, referenced browser-origin policies. `skip_serializing_if
142    /// = empty` keeps a cors-less program's IR JSON byte-identical (zero IR-SHA
143    /// drift — the standing v2.33.0 discipline).
144    #[serde(default, skip_serializing_if = "Vec::is_empty")]
145    pub cors_policies: Vec<IRCors>,
146    /// v2.40.0 — named, referenced result-memoization policies. Same
147    /// `skip_serializing_if = empty` IR-SHA discipline as `cors_policies`.
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub caches: Vec<IRCache>,
150    /// v2.46.0 — ephemeral-credential contracts (`credential { ttl:
151    /// grants: }`), minted at runtime by the `mint` flow verb under the
152    /// attenuation law (`authority_only_attenuates`). Same
153    /// `skip_serializing_if = empty` IR-SHA discipline.
154    #[serde(default, skip_serializing_if = "Vec::is_empty")]
155    pub credentials: Vec<IRCredential>,
156    /// v2.42.0 — long-horizon autonomous research primitives (compiled). Each
157    /// carries its domain, cognition params, memory binding, compute budget and
158    /// mandates so the enterprise engine (v2.42.0+) can drive the active-inference
159    /// loop. Same `skip_serializing_if = empty` IR-SHA discipline as `caches`
160    /// (a savant-less program's IR JSON stays byte-identical — zero drift).
161    #[serde(default, skip_serializing_if = "Vec::is_empty")]
162    pub savants: Vec<IRSavant>,
163    /// v2.53.0 — compiled document declarations. Same
164    /// `skip_serializing_if = empty` IR-SHA discipline (a document-less
165    /// program's IR JSON stays byte-identical). Consumed by the runtime
166    /// `DocumentRenderer` tool + the `DocumentProvenanceSoundness` PCC class.
167    #[serde(default, skip_serializing_if = "Vec::is_empty")]
168    pub documents: Vec<IRDocument>,
169    /// v2.60.0 — compiled CRM delivery declarations. Same
170    /// `skip_serializing_if = empty` IR-SHA discipline (a delivery-less program's
171    /// IR JSON stays byte-identical). Consumed by the runtime delivery dispatch
172    /// (`axon::delivery`) + the `DeliveryProvenanceSoundness` PCC class (T920).
173    #[serde(default, skip_serializing_if = "Vec::is_empty")]
174    pub deliveries: Vec<IRDeliver>,
175    /// v2.66.0 — governed human notifications (the third egress dual).
176    pub notifications: Vec<IRNotify>,
177    /// v2.42.0 — dynamic tool-synthesis policies (compiled). Same
178    /// `skip_serializing_if = empty` IR-SHA discipline as `savants`.
179    #[serde(default, skip_serializing_if = "Vec::is_empty")]
180    pub synths: Vec<IRSynth>,
181    /// v2.43.0 — authorization-scope policies (compiled). Same
182    /// `skip_serializing_if = empty` IR-SHA discipline as `synths`.
183    #[serde(default, skip_serializing_if = "Vec::is_empty")]
184    pub scopes: Vec<IRScope>,
185    /// v1.17.0 — algebraic effect declarations (compiled).
186    /// Each declared effect persists into IR so axon-rs can build the
187    /// per-effect operation table at startup. The CPS state graph for
188    /// perform/handle sites lives inline within IRFlow.steps (each
189    /// IRPerform / IRHandlerFrame carries its assigned state_id /
190    /// frame_id).
191    ///
192    /// **v2.87.0 — THIS FIELD IS NOW POPULATED, from `.axon` source.**
193    ///
194    /// It used to be a mirror of the retired Python frontend's
195    /// `IRProgram.effects` field, kept only so a byte-identical structural
196    /// parity gate stayed green against an empty `effects: []`. The Python
197    /// frontend is gone (the 0-`.py` north star) and the field outlived it
198    /// carrying nothing — `axon-frontend` never emitted a single entry, which
199    /// is why `EffectRuntime` was constructible only from its own tests.
200    ///
201    /// v2.87.0 gives it its declarations, from `effect E { … }` in adopter
202    /// source. It is the CLOSED catalog the dispatcher validates a perform
203    /// site's arity against, and the set the design decision resolves a bare
204    /// `perform Emit(x)` over.
205    ///
206    /// ⚠️ Reusing this field rather than adding one was a deliberate correction
207    /// mid-cycle: a parallel `effect_specs` would have made TWO compiled
208    /// catalogs of one concept in one artifact — the v2.83.0 defect (a third
209    /// blame vocabulary) committed knowingly. Ask what already reads a concept
210    /// BEFORE giving it a new home.
211    pub effects: Vec<IREffectDeclaration>,
212    /// v2.76.0 — per-module provenance of a LINKED program: for every
213    /// module the linker merged, its path, origin file, both EMS hashes and
214    /// the virtual-line window its declarations occupy (the driver renumbers
215    /// each module's lines by a base offset so diagnostics and IR
216    /// `source_line`s stay globally unambiguous — map back with
217    /// `line − line_base`). `skip_serializing_if = empty` keeps every
218    /// single-file program's IR JSON byte-identical (zero IR-SHA drift —
219    /// the standing v2.33.0 discipline).
220    #[serde(default, skip_serializing_if = "Vec::is_empty")]
221    pub modules: Vec<IRModuleProvenance>,
222}
223
224/// v2.76.0 — one linked module's provenance record: the audit chain
225/// from a deployed multi-module artifact back to its sources.
226#[derive(Debug, Serialize, Clone)]
227pub struct IRModuleProvenance {
228    /// Dotted module path (`axon.security`).
229    pub module: String,
230    /// Display origin (file path or bundle key).
231    pub origin: String,
232    /// SHA-256 of the module's source bytes.
233    pub content_hash: String,
234    /// SHA-256 of the module's `.axi` interface (comment-stable).
235    pub interface_hash: String,
236    /// First virtual line assigned to this module by the link renumbering.
237    pub line_base: u32,
238    /// Number of source lines the module occupies.
239    pub line_count: u32,
240    /// The module's exported declaration names (deterministic order).
241    pub declarations: Vec<String>,
242}
243
244impl IRProgram {
245    pub fn new() -> Self {
246        IRProgram {
247            node_type: "program",
248            source_line: 1,
249            source_column: 1,
250            personas: Vec::new(),
251            contexts: Vec::new(),
252            anchors: Vec::new(),
253            tools: Vec::new(),
254            memories: Vec::new(),
255            types: Vec::new(),
256            flows: Vec::new(),
257            runs: Vec::new(),
258            imports: Vec::new(),
259            agents: Vec::new(),
260            attestations: Vec::new(),
261            shields: Vec::new(),
262            windows: Vec::new(),
263            budgets: Vec::new(),
264            daemons: Vec::new(),
265            ots_specs: Vec::new(),
266            pix_specs: Vec::new(),
267            ledger_specs: Vec::new(),
268            corpus_specs: Vec::new(),
269            psyche_specs: Vec::new(),
270            mandate_specs: Vec::new(),
271            lambda_data_specs: Vec::new(),
272            compute_specs: Vec::new(),
273            axonstore_specs: Vec::new(),
274            endpoints: Vec::new(),
275            extensions: Vec::new(),
276            dataspace_specs: Vec::new(),
277            resources: Vec::new(),
278            fabrics: Vec::new(),
279            manifests: Vec::new(),
280            observations: Vec::new(),
281            intention_tree: None,
282            reconciles: Vec::new(),
283            leases: Vec::new(),
284            ensembles: Vec::new(),
285            sessions: Vec::new(),
286            topologies: Vec::new(),
287            immunes: Vec::new(),
288            reflexes: Vec::new(),
289            heals: Vec::new(),
290            components: Vec::new(),
291            views: Vec::new(),
292            channels: Vec::new(),
293            sockets: Vec::new(),
294            observables: Vec::new(),
295            witnesses: Vec::new(),
296            upstreams: Vec::new(),
297            cors_policies: Vec::new(),
298            caches: Vec::new(),
299            credentials: Vec::new(),
300            savants: Vec::new(),
301            documents: Vec::new(),
302            deliveries: Vec::new(),
303            notifications: Vec::new(),
304            synths: Vec::new(),
305            scopes: Vec::new(),
306            effects: Vec::new(),
307            modules: Vec::new(),
308        }
309    }
310}
311
312/// v2.4.0 — IR for the `yield <expr>` measurement point.
313#[derive(Debug, Clone, Serialize)]
314pub struct IRYield {
315    pub node_type: &'static str,
316    pub source_line: u32,
317    pub source_column: u32,
318    pub value_expr: String,
319    pub value_kind: String,
320}
321
322/// v2.4.0 — one term `cₖ · Pₖ` of a Pauli-sum observable (compiled).
323#[derive(Debug, Clone, Serialize)]
324pub struct IRPauliTerm {
325    pub coefficient: f64,
326    pub pauli: String,
327}
328
329/// v2.4.0 — IR for a Pauli-sum observable `M = Σ cₖ Pₖ`.
330#[derive(Debug, Clone, Serialize)]
331pub struct IRObservable {
332    pub node_type: &'static str,
333    pub source_line: u32,
334    pub source_column: u32,
335    pub name: String,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub qubits: Option<i64>,
338    pub terms: Vec<IRPauliTerm>,
339}
340
341/// v2.23.0 — IR for an Advantage Witness. The deploy/runtime evaluator reads
342/// `metric` + `threshold` + `baseline`, computes the metric over `data`, and
343/// emits the verdict; a `holds == false` verdict is the honest fail-closed
344/// signal (`axon-W007`/`W008`). `claim`/`data` are references resolved per domain.
345#[derive(Debug, Clone, Serialize)]
346pub struct IRWitness {
347    pub node_type: &'static str,
348    pub source_line: u32,
349    pub source_column: u32,
350    pub name: String,
351    pub claim: String,
352    pub baseline: String,
353    pub metric: String,
354    pub threshold: f64,
355    pub data: String,
356}
357
358// ── v1.17.0 / v2.87.0 — Algebraic effect declarations ────────────────────
359//
360// v1.17.0 declared these as a MIRROR of the Python frontend's dataclasses,
361// emitted by nobody, so a structural-parity gate stayed green against an empty
362// `effects: []`. v2.87.0 makes `axon-frontend` emit them from `.axon` source:
363// `Declaration::Effect` → `IREffectDeclaration`, one per `effect E { … }`.
364//
365// The shape is unchanged on purpose. `axon-rs/src/effects/ir.rs` deserialises
366// exactly these field names (`name` / `operations` / `parameter_names` /
367// `parameter_types` / `return_type`), and keeping the wire identical means the
368// declaration an adopter writes today lands in the same table
369// `EffectRuntime::register_effect` has always read.
370
371#[derive(Debug, Serialize, Default, Clone)]
372pub struct IREffectDeclaration {
373    pub node_type: &'static str,
374    pub source_line: u32,
375    pub source_column: u32,
376    pub name: String,
377    pub operations: Vec<IREffectOperation>,
378}
379
380impl IREffectDeclaration {
381    pub fn new() -> Self {
382        Self {
383            node_type: "effect_declaration",
384            source_line: 0,
385            source_column: 0,
386            name: String::new(),
387            operations: Vec::new(),
388        }
389    }
390}
391
392#[derive(Debug, Serialize, Default, Clone)]
393pub struct IREffectOperation {
394    pub node_type: &'static str,
395    pub source_line: u32,
396    pub source_column: u32,
397    pub name: String,
398    pub type_parameters: Vec<String>,
399    pub parameter_names: Vec<String>,
400    pub parameter_types: Vec<String>,
401    pub return_type: String,
402}
403
404impl IREffectOperation {
405    pub fn new() -> Self {
406        Self {
407            node_type: "effect_operation",
408            source_line: 0,
409            source_column: 0,
410            name: String::new(),
411            type_parameters: Vec::new(),
412            parameter_names: Vec::new(),
413            parameter_types: Vec::new(),
414            return_type: String::new(),
415        }
416    }
417}
418
419// ── v1.1.0 — IRResource ──────────────────────────────────────────────
420
421/// Compiled resource declaration — linear/affine infrastructure token.
422///
423/// Python counterpart: `axon.compiler.ir_nodes.IRResource`.
424#[derive(Debug, Clone, Serialize)]
425pub struct IRResource {
426    pub node_type: &'static str,
427    pub source_line: u32,
428    pub source_column: u32,
429    pub name: String,
430    pub kind: String,
431    pub endpoint: String,
432    /// v2.67.0 — the pool size. Until v2.67.0 this was **read by nothing**: every
433    /// `postgresql` axonstore in existence got a hardcoded
434    /// `MAX_POOL_CONNECTIONS = 10` (`store/postgres_backend.rs`), with no env
435    /// var and no source-level knob. `capacity:` is that missing knob, and
436    /// wiring it is what makes `resource` a WIRE rather than a LABEL.
437    pub capacity: Option<i64>,
438    /// v2.67.0 — **how many holders may name this resource** (Linear Logic).
439    ///
440    /// Not "how long the connection lives" — that is `idle_timeout`, an
441    /// operational knob. The Linear-Logic reading is about *sharing*:
442    ///
443    /// - `linear` — **exactly one** holder, and failing to name it is itself a
444    ///   breach (a linear resource must be consumed).
445    /// - `affine` — **at most one** holder. It may go unused; **sharing it is a
446    ///   breach**.
447    /// - `persistent` — the `!` exponential. Freely shared.
448    ///
449    /// Before v2.67.0, two stores shared a connection pool by **accidental DSN
450    /// collision** (the registry keys its pool cache on the resolved DSN).
451    /// Sharing is now *declared*, and `axon-T945` checks it.
452    pub lifetime: String,
453    pub certainty_floor: Option<f64>, // c ∈ [0.0, 1.0]
454    pub shield_ref: String,
455    /// v2.67.0 — the `fabric` this resource lives in (`within: Prod`).
456    ///
457    /// **One field, therefore Separation-Logic disjointness is UNREPRESENTABLE
458    /// rather than verified**: a resource cannot be in two fabrics because
459    /// there is no syntax for it. A checked invariant is what you settle for
460    /// when you could not make the bad state unwritable; here we could.
461    ///
462    /// Empty ⇒ no fabric declared. Skip-if-empty ⇒ every pre-v2.67.0 program
463    /// serializes byte-identically (IR-SHA stability, the v2.48.0 `class`
464    /// precedent).
465    #[serde(default, skip_serializing_if = "String::is_empty")]
466    pub within: String,
467}
468
469impl IRResource {
470    pub fn new(name: String, line: u32, column: u32) -> Self {
471        IRResource {
472            node_type: "resource",
473            source_line: line,
474            source_column: column,
475            name,
476            kind: String::new(),
477            endpoint: String::new(),
478            capacity: None,
479            lifetime: "affine".to_string(),
480            certainty_floor: None,
481            shield_ref: String::new(),
482            within: String::new(),
483        }
484    }
485}
486
487// ── v1.1.0 — IRFabric ────────────────────────────────────────────────
488
489/// Compiled fabric declaration — topological substrate for resources.
490#[derive(Debug, Clone, Serialize)]
491pub struct IRFabric {
492    pub node_type: &'static str,
493    pub source_line: u32,
494    pub source_column: u32,
495    pub name: String,
496    pub provider: String,
497    pub region: String,
498    pub zones: Option<i64>,
499    pub ephemeral: Option<bool>,
500    pub shield_ref: String,
501}
502
503// ── v1.1.0 — IRManifest ──────────────────────────────────────────────
504
505/// Compiled manifest declaration — declarative belief about desired shape.
506#[derive(Debug, Clone, Serialize)]
507pub struct IRManifest {
508    pub node_type: &'static str,
509    pub source_line: u32,
510    pub source_column: u32,
511    pub name: String,
512    pub resources: Vec<String>,
513    pub fabric_ref: String,
514    pub region: String,
515    pub zones: Option<i64>,
516    pub compliance: Vec<String>,
517    /// v4.5.0 — the census this deployment relied on to reduce a postal code
518    /// to three digits.
519    ///
520    /// It rides the artifact because the claim it supports is made OUTSIDE the
521    /// compiler: Safe Harbor (B) permits three digits only where the unit holds
522    /// twenty thousand people or more, and the compiler checks that a source
523    /// was NAMED, never that it is true. Whoever evaluates that has to be able
524    /// to find which census the deployment leaned on — so it travels with the
525    /// evidence, or the citation exists only in source nobody ships.
526    ///
527    /// `skip_serializing_if` keeps every manifest without one byte-identical.
528    #[serde(default, skip_serializing_if = "String::is_empty")]
529    pub census: String,
530}
531
532// ── v1.1.0 — IRObserve ───────────────────────────────────────────────
533
534// ── v1.1.0 — IRIntentionTree (Free Monad root) ──────────────────────
535
536/// A single operation node in the intention tree.
537///
538/// Operations are heterogeneous IR nodes (manifests, observes) that the
539/// Handler layer (v1.1.0) interprets via CPS. The enum is `#[serde(untagged)]`
540/// so JSON output is just the inner struct — matching Python's `asdict`
541/// behaviour on a polymorphic `tuple[IRNode, ...]`.
542#[derive(Debug, Clone, Serialize)]
543#[serde(untagged)]
544pub enum IRIntentionOperation {
545    Manifest(IRManifest),
546    Observe(IRObserve),
547}
548
549/// The Free Monad F_Σ(X) — a pure description of I/O intentions. Flat in
550/// v1.1.0; nested continuations arrive with handlers + reconcile loops.
551#[derive(Debug, Clone, Serialize)]
552pub struct IRIntentionTree {
553    pub node_type: &'static str,
554    pub source_line: u32,
555    pub source_column: u32,
556    pub operations: Vec<IRIntentionOperation>,
557}
558
559/// Compiled observe declaration — quorum-gated observation with lag τ.
560#[derive(Debug, Clone, Serialize)]
561pub struct IRObserve {
562    pub node_type: &'static str,
563    pub source_line: u32,
564    pub source_column: u32,
565    pub name: String,
566    pub target: String,
567    pub sources: Vec<String>,
568    pub quorum: Option<i64>,
569    pub timeout: String,
570    pub on_partition: String,
571    pub certainty_floor: Option<f64>,
572}
573
574// ── v1.1.0 — IRReconcile / IRLease / IREnsemble ──────────────────────
575
576/// Compiled reconcile declaration — free-energy minimizing control loop.
577#[derive(Debug, Clone, Serialize)]
578pub struct IRReconcile {
579    pub node_type: &'static str,
580    pub source_line: u32,
581    pub source_column: u32,
582    pub name: String,
583    pub observe_ref: String,
584    pub threshold: Option<f64>,
585    pub tolerance: Option<f64>,
586    pub on_drift: String,
587    pub shield_ref: String,
588    pub mandate_ref: String,
589    pub max_retries: i64,
590}
591
592/// Compiled lease declaration — τ-decaying affine resource token.
593#[derive(Debug, Clone, Serialize)]
594pub struct IRLease {
595    pub node_type: &'static str,
596    pub source_line: u32,
597    pub source_column: u32,
598    pub name: String,
599    pub resource_ref: String,
600    pub duration: String,
601    pub acquire: String,
602    pub on_expire: String,
603}
604
605/// Compiled ensemble declaration — Byzantine quorum aggregator.
606#[derive(Debug, Clone, Serialize)]
607pub struct IREnsemble {
608    pub node_type: &'static str,
609    pub source_line: u32,
610    pub source_column: u32,
611    pub name: String,
612    pub observations: Vec<String>,
613    pub quorum: Option<i64>,
614    pub aggregation: String,
615    pub certainty_mode: String,
616}
617
618// ── v1.1.0 — IRSession / IRTopology ──────────────────────────────────
619
620/// One operation in a compiled session protocol
621/// (send / receive / loop / end / select / branch — v2.3.0 adds the choices).
622#[derive(Debug, Clone, Serialize)]
623pub struct IRSessionStep {
624    pub node_type: &'static str,
625    pub source_line: u32,
626    pub source_column: u32,
627    pub op: String,
628    pub message_type: String,
629    /// v2.3.0 — labelled branches (only for `op == "select" | "branch"`;
630    /// v2.36.0 reuses them for `op == "interrupt"`: `body` + `handler` arms).
631    #[serde(skip_serializing_if = "Vec::is_empty", default)]
632    pub branches: Vec<IRSessionBranch>,
633    /// v2.36.0 — `op == "interrupt"` only: the handler's signal binder
634    /// (`... as <sig> ...`). Skip-if-empty ⇒ zero IR-SHA drift for every
635    /// non-interrupt step (the v2.33.0/v2.34.0 additive-only discipline).
636    #[serde(skip_serializing_if = "String::is_empty", default)]
637    pub binder: String,
638    /// v2.36.0 — `op == "interrupt"` only: the block declares a `resumable`
639    /// handler. Skip-if-false ⇒ byte-identical IR for every other op.
640    #[serde(skip_serializing_if = "std::ops::Not::not", default)]
641    pub resumable: bool,
642}
643
644/// v2.3.0 — one labelled arm of a compiled `select`/`branch` choice.
645#[derive(Debug, Clone, Serialize)]
646pub struct IRSessionBranch {
647    pub node_type: &'static str,
648    pub label: String,
649    pub steps: Vec<IRSessionStep>,
650}
651
652/// A role's name and its ordered protocol steps.
653#[derive(Debug, Clone, Serialize)]
654pub struct IRSessionRole {
655    pub node_type: &'static str,
656    pub source_line: u32,
657    pub source_column: u32,
658    pub name: String,
659    pub steps: Vec<IRSessionStep>,
660}
661
662/// Compiled binary session — exactly two dual roles (verified at type-check).
663#[derive(Debug, Clone, Serialize)]
664pub struct IRSession {
665    pub node_type: &'static str,
666    pub source_line: u32,
667    pub source_column: u32,
668    pub name: String,
669    pub roles: Vec<IRSessionRole>,
670}
671
672/// Directed, session-typed edge between two topology nodes.
673#[derive(Debug, Clone, Serialize)]
674pub struct IRTopologyEdge {
675    pub node_type: &'static str,
676    pub source_line: u32,
677    pub source_column: u32,
678    pub source: String,
679    pub target: String,
680    pub session_ref: String,
681}
682
683/// Compiled topology — typed graph over Axon entities.
684#[derive(Debug, Clone, Serialize)]
685pub struct IRTopology {
686    pub node_type: &'static str,
687    pub source_line: u32,
688    pub source_column: u32,
689    pub name: String,
690    pub nodes: Vec<String>,
691    pub edges: Vec<IRTopologyEdge>,
692}
693
694// ── v1.1.0 — IRImmune / IRReflex / IRHeal ────────────────────────────
695
696/// Compiled immune sensor — KL+FEP anomaly detector descriptor.
697#[derive(Debug, Clone, Serialize)]
698pub struct IRImmune {
699    pub node_type: &'static str,
700    pub source_line: u32,
701    pub source_column: u32,
702    pub name: String,
703    pub watch: Vec<String>,
704    pub sensitivity: Option<f64>,
705    pub baseline: String,
706    pub window: i64,
707    pub scope: String,
708    pub tau: String,
709    pub decay: String,
710}
711
712/// Compiled reflex — deterministic O(1) motor response descriptor.
713#[derive(Debug, Clone, Serialize)]
714pub struct IRReflex {
715    pub node_type: &'static str,
716    pub source_line: u32,
717    pub source_column: u32,
718    pub name: String,
719    pub trigger: String,
720    pub on_level: String,
721    pub action: String,
722    pub scope: String,
723    pub sla: String,
724}
725
726/// Compiled heal — Linear-Logic one-shot patch kernel descriptor.
727#[derive(Debug, Clone, Serialize)]
728pub struct IRHeal {
729    pub node_type: &'static str,
730    pub source_line: u32,
731    pub source_column: u32,
732    pub name: String,
733    pub source: String,
734    pub on_level: String,
735    pub mode: String,
736    pub scope: String,
737    pub review_sla: String,
738    pub shield_ref: String,
739    pub max_patches: i64,
740}
741
742// ── v1.3.1 — IRComponent / IRView ────────────────────────────────────
743
744/// Compiled UI component — reusable fragment over a typed data source.
745#[derive(Debug, Clone, Serialize)]
746pub struct IRComponent {
747    pub node_type: &'static str,
748    pub source_line: u32,
749    pub source_column: u32,
750    pub name: String,
751    pub renders: String,
752    pub via_shield: String,
753    pub on_interact: String,
754    pub render_hint: String,
755}
756
757/// Compiled UI view — top-level screen composing declared components.
758#[derive(Debug, Clone, Serialize)]
759pub struct IRView {
760    pub node_type: &'static str,
761    pub source_line: u32,
762    pub source_column: u32,
763    pub name: String,
764    pub title: String,
765    pub components: Vec<String>,
766    pub route: String,
767}
768
769// ── Import ───────────────────────────────────────────────────────────────────
770
771#[derive(Debug, Serialize, Clone)]
772pub struct IRImport {
773    pub node_type: &'static str,
774    pub source_line: u32,
775    pub source_column: u32,
776    pub module_path: Vec<String>,
777    pub names: Vec<String>,
778    /// v2.76.0 — `true` iff the EMS resolved this import against a
779    /// module in the compilation (the fields this paper-era struct always
780    /// promised). Skipped when `false` so every pre-v2.76.0 program's IR
781    /// JSON stays byte-identical (zero IR-SHA drift).
782    #[serde(default, skip_serializing_if = "ir_import_unresolved")]
783    pub resolved: bool,
784    /// v2.76.0 — the resolved module's `.axi` interface hash.
785    #[serde(default, skip_serializing_if = "Option::is_none")]
786    pub interface_hash: Option<String>,
787}
788
789/// Serde helper: skip `resolved` while it is `false`.
790fn ir_import_unresolved(resolved: &bool) -> bool {
791    !*resolved
792}
793
794// ── Persona ──────────────────────────────────────────────────────────────────
795
796#[derive(Debug, Clone, Serialize)]
797pub struct IRPersona {
798    pub node_type: &'static str,
799    pub source_line: u32,
800    pub source_column: u32,
801    pub name: String,
802    pub domain: Vec<String>,
803    pub tone: String,
804    pub confidence_threshold: Option<f64>,
805    pub cite_sources: Option<bool>,
806    pub refuse_if: Vec<String>,
807    pub language: String,
808    pub description: String,
809}
810
811// ── Context ──────────────────────────────────────────────────────────────────
812
813#[derive(Debug, Clone, Serialize)]
814pub struct IRContext {
815    pub node_type: &'static str,
816    pub source_line: u32,
817    pub source_column: u32,
818    pub name: String,
819    pub memory_scope: String,
820    pub language: String,
821    pub depth: String,
822    pub max_tokens: Option<i64>,
823    pub temperature: Option<f64>,
824    pub cite_sources: Option<bool>,
825    /// v2.46.0 — the frame's declared cognitive timezone (IANA name).
826    /// Elided when absent → pre-v2.46.0 context IR JSON stays byte-identical.
827    #[serde(default, skip_serializing_if = "Option::is_none")]
828    pub now_tz: Option<String>,
829}
830
831// ── Anchor ───────────────────────────────────────────────────────────────────
832
833#[derive(Debug, Clone, Serialize)]
834pub struct IRAnchor {
835    pub node_type: &'static str,
836    pub source_line: u32,
837    pub source_column: u32,
838    pub name: String,
839    pub description: String,
840    pub require: String,
841    pub reject: Vec<String>,
842    pub enforce: String,
843    pub confidence_floor: Option<f64>,
844    pub unknown_response: String,
845    pub on_violation: String,
846    pub on_violation_target: String,
847}
848
849// ── Tool ─────────────────────────────────────────────────────────────────────
850
851/// v2.8.0 — one typed parameter of a tool's input schema (the IR mirror of
852/// the AST `Parameter`). `type_name` is the flattened BASE type string
853/// (`String`, `List<String>`); optionality (`T?`) is carried in `optional`, so
854/// `required` is derivable with no parallel bool (v2.8.0 D1, single source of
855/// truth). Lossless round-trip is gated in v2.8.0.
856#[derive(Debug, Clone, Serialize, PartialEq)]
857pub struct IRToolParam {
858    pub name: String,
859    pub type_name: String,
860    pub optional: bool,
861}
862
863/// v2.8.0 — one bound keyword argument of a `use Tool(k = v, …)` call (the
864/// IR mirror of `UseArgs::Named`). `value` is an expression string (the
865/// frontend has no structured `Expr`). The runtime (v2.8.0) assembles these into
866/// the structured JSON request body.
867#[derive(Debug, Clone, Serialize, PartialEq)]
868pub struct IRNamedArg {
869    pub name: String,
870    pub value: String,
871    /// v2.10.0 — `"literal"` or `"reference"` (classified by `parse_let_atom`).
872    /// A `"reference"` value (a bare identifier or `Step.output`) is resolved at
873    /// runtime against the bindings (flow-param / `let` / step output), like a
874    /// `let` reference — instead of being passed as the literal name (the pre-60
875    /// bug). `"literal"` values keep `${…}` interpolation + typed coercion.
876    pub value_kind: String,
877}
878
879#[derive(Debug, Serialize, Clone)]
880pub struct IRToolSpec {
881    pub node_type: &'static str,
882    pub source_line: u32,
883    pub source_column: u32,
884    pub name: String,
885    pub provider: String,
886    pub max_results: Option<i64>,
887    pub filter_expr: String,
888    pub timeout: String,
889    pub runtime: String,
890    /// v2.69.0 — the `resource` this tool's channel runs on. Empty ⇒ legacy
891    /// form. Skip-if-empty ⇒ every pre-v2.69.0 tool serializes byte-identically.
892    #[serde(default, skip_serializing_if = "String::is_empty")]
893    pub resource_ref: String,
894    pub sandbox: Option<bool>,
895    pub input_schema: Vec<String>,
896    pub output_schema: String,
897    /// v2.8.0 — the tool's typed INPUT SCHEMA (D1). Distinct from the v1.23.0
898    /// `input_schema`/`output_schema` validation hints (those say HOW to
899    /// validate raw output: JSON/number/…); these are the caller↔tool TYPE
900    /// contract the type-checker enforces (v2.8.0) and the runtime binds
901    /// structured args against (v2.8.0). Empty for a schema-less tool (D5).
902    pub parameters: Vec<IRToolParam>,
903    /// v2.8.0 — the tool's declared OUTPUT type (D8), so `${Step.output}`
904    /// is typed. `None` when undeclared. Single source of truth (lives here,
905    /// not denormalised onto each call site).
906    pub output_type: Option<String>,
907    /// v2.77.0 — the authorization scopes this tool's operation
908    /// requires: flat capability atoms, the `credential.grants` (v2.46.0) /
909    /// `requires_capabilities` (v2.4.0) vocabulary. `axon-T956` enforces subset
910    /// coverage at compile; the PCC `ScopeCoverage` witness rides the linked
911    /// IR. Elided when empty — every pre-v2.77.0 tool serializes byte-identically
912    /// (IR-SHA stability), and stored FlowIr hydrates via `default` (v2.4.0
913    /// mirror discipline).
914    #[serde(default, skip_serializing_if = "Vec::is_empty")]
915    pub requires: Vec<String>,
916    /// v2.48.0 — the per-tenant secret KEY injected into every dispatch
917    /// under the reserved `axon_secret` request field (resolved against the
918    /// tenant's custody at `use` time; the flow never touches the value).
919    /// Elided when empty — every pre-v2.48.0 tool serializes byte-identically
920    /// (IR-SHA stability).
921    #[serde(default, skip_serializing_if = "String::is_empty")]
922    pub secret: String,
923    /// v2.49.0 — the `secret_partition:` parameter name whose runtime
924    /// value is appended as a single segment to `secret` at dispatch
925    /// (`selection_without_revelation`). Elided when empty, so every v2.48.0 and
926    /// pre-v2.48.0 tool serializes byte-identically (IR-SHA stability). The
927    /// class prefix lives in `secret`; this only names the dynamic segment
928    /// source — no value ever rides the IR.
929    #[serde(default, skip_serializing_if = "String::is_empty")]
930    pub secret_partition: String,
931    pub effect_row: Vec<String>,
932    /// v2.39.0 — Remote Hands. All three fields are `skip_serializing_if`
933    /// so a program using none of them serialises **byte-identically** to the
934    /// pre-v2.39.0 IR (the v2.33.0 IR-SHA / additive-only gate — no drift for the
935    /// entire existing corpus).
936    #[serde(skip_serializing_if = "Option::is_none")]
937    pub target: Option<String>,
938    #[serde(skip_serializing_if = "Option::is_none")]
939    pub risk: Option<String>,
940    #[serde(skip_serializing_if = "Vec::is_empty")]
941    pub argv: Vec<String>,
942    /// v2.40.0 — the cache-policy reference (a declared `cache` name, or the
943    /// `none` opt-out sentinel). Empty ⇒ module-default-governed. Elided when
944    /// empty (IR-SHA stable for cache-less programs).
945    #[serde(default, skip_serializing_if = "String::is_empty")]
946    pub cache: String,
947    /// v2.52.0 — the closed-catalog web-acquisition config. `None` for
948    /// every non-scrape tool, and `skip_serializing_if` so the entire
949    /// pre-v2.52.0 corpus serialises byte-identically (the v2.33.0 IR-SHA /
950    /// additive-only gate). Present ⇒ this tool acquires open-web content
951    /// (born Untrusted, the design decision) and its `effect_row` carries `web`.
952    #[serde(skip_serializing_if = "Option::is_none")]
953    pub scrape: Option<IRScrapeSpec>,
954}
955
956/// v2.52.0 — the IR mirror of `ast::ScrapeSpec`. Every field is
957/// `skip_serializing_if` on its empty/none form so a minimal `scrape: {}`
958/// and each partially-populated block serialise deterministically with no
959/// null noise, keeping the IR-SHA additive.
960#[derive(Debug, Serialize, Clone)]
961pub struct IRScrapeSpec {
962    pub node_type: &'static str,
963    #[serde(skip_serializing_if = "Option::is_none")]
964    pub engine: Option<String>,
965    #[serde(skip_serializing_if = "Option::is_none")]
966    pub impersonate: Option<String>,
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub render_wait: Option<String>,
969    #[serde(default, skip_serializing_if = "String::is_empty")]
970    pub proxy: String,
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub respect_robots: Option<bool>,
973    #[serde(default, skip_serializing_if = "Vec::is_empty")]
974    pub extract: Vec<String>,
975    #[serde(skip_serializing_if = "Option::is_none")]
976    pub adaptive: Option<bool>,
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub similarity_floor: Option<f64>,
979    #[serde(default, skip_serializing_if = "String::is_empty")]
980    pub follow: String,
981    #[serde(skip_serializing_if = "Option::is_none")]
982    pub max_depth: Option<i64>,
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub max_pages: Option<i64>,
985    #[serde(skip_serializing_if = "Option::is_none")]
986    pub concurrency: Option<i64>,
987    #[serde(default, skip_serializing_if = "String::is_empty")]
988    pub politeness: String,
989    #[serde(default, skip_serializing_if = "String::is_empty")]
990    pub checkpoint: String,
991}
992
993// ── Memory ───────────────────────────────────────────────────────────────────
994
995#[derive(Debug, Serialize, Clone)]
996pub struct IRMemory {
997    pub node_type: &'static str,
998    pub source_line: u32,
999    pub source_column: u32,
1000    pub name: String,
1001    pub store: String,
1002    pub backend: String,
1003    pub retrieval: String,
1004    pub decay: String,
1005}
1006
1007// ── Type ─────────────────────────────────────────────────────────────────────
1008
1009#[derive(Debug, Clone, Serialize)]
1010pub struct IRTypeField {
1011    pub node_type: &'static str,
1012    pub source_line: u32,
1013    pub source_column: u32,
1014    pub name: String,
1015    pub type_name: String,
1016    pub generic_param: String,
1017    pub optional: bool,
1018}
1019
1020#[derive(Debug, Serialize, Clone)]
1021pub struct IRType {
1022    pub node_type: &'static str,
1023    pub source_line: u32,
1024    pub source_column: u32,
1025    pub name: String,
1026    pub fields: Vec<IRTypeField>,
1027    pub range_min: Option<f64>,
1028    pub range_max: Option<f64>,
1029    pub where_expression: String,
1030    /// ESK — κ regulatory class.
1031    pub compliance: Vec<String>,
1032}
1033
1034// ── Flow ─────────────────────────────────────────────────────────────────────
1035
1036#[derive(Debug, Clone, Serialize)]
1037pub struct IRParameter {
1038    pub node_type: &'static str,
1039    pub source_line: u32,
1040    pub source_column: u32,
1041    pub name: String,
1042    pub type_name: String,
1043    pub generic_param: String,
1044    pub optional: bool,
1045}
1046
1047#[derive(Debug, Clone, Serialize)]
1048pub struct IRDataEdge {
1049    pub node_type: &'static str,
1050    pub source_line: u32,
1051    pub source_column: u32,
1052    pub source_step: String,
1053    pub target_step: String,
1054    pub type_name: String,
1055}
1056
1057#[derive(Debug, Clone, Serialize)]
1058pub struct IRStep {
1059    pub node_type: &'static str,
1060    pub source_line: u32,
1061    pub source_column: u32,
1062    pub name: String,
1063    pub persona_ref: String,
1064    pub given: String,
1065    pub ask: String,
1066    pub use_tool: Option<serde_json::Value>,
1067    pub probe: Option<serde_json::Value>,
1068    pub reason: Option<serde_json::Value>,
1069    pub weave: Option<serde_json::Value>,
1070    pub output_type: String,
1071    pub confidence_floor: Option<f64>,
1072    pub navigate_ref: String,
1073    pub apply_ref: String,
1074    /// v2.22.0 — the step's model-capability requirement (context window in
1075    /// tokens). `skip_serializing_if = Option::is_none` keeps every pre-v2.22.0 step's
1076    /// IR JSON byte-identical (no IR-SHA drift, the design decision); a legacy IR deserialises
1077    /// to `None` → the v2.22.0 resolver picks the backend default exactly as today.
1078    #[serde(default, skip_serializing_if = "Option::is_none")]
1079    pub requires_context: Option<u32>,
1080    /// v2.46.0 — the step's declared cognitive timezone (IANA name). The
1081    /// runtime renders the run's captured instant in this zone into the step's
1082    /// cognitive context. Elided when absent → every pre-v2.46.0 step's IR JSON is
1083    /// byte-identical (no IR-SHA drift); legacy IR → `None` → no injection.
1084    #[serde(default, skip_serializing_if = "Option::is_none")]
1085    pub now_tz: Option<String>,
1086    /// v2.83.0 — governance applications scoped to this step.
1087    /// Elided when empty so every pre-v2.83.0 program's IR JSON stays
1088    /// byte-identical (no IR-SHA drift — the v2.22.0/v2.46.0 discipline).
1089    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1090    pub guards: Vec<IRStepGuard>,
1091    /// v2.83.0 — the PIX verbs written as statements in this step's body
1092    /// (`navigate` / `drill` / `trail` / `validate`), in source order. They
1093    /// are ELEVATIONS: dispatch runs them before the step generates, so each
1094    /// `as:` binding is in scope for the step's `ask:`. Elided when empty so
1095    /// every pre-v2.83.0 program's IR JSON is byte-identical.
1096    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1097    pub pix_ops: Vec<IRFlowNode>,
1098    /// v2.83.0 — a `stream<T> { … }` written in this step's body. NOT a
1099    /// `pix_ops` entry: those are elevations that run BEFORE generation, while a
1100    /// stream handler runs DURING it and this step's output IS the stream.
1101    /// Elided when absent, so every pre-v2.83.0 program's IR JSON is
1102    /// byte-identical and a legacy IR still deserialises.
1103    #[serde(default, skip_serializing_if = "Option::is_none")]
1104    pub stream: Option<Box<IRStreamBlock>>,
1105    /// v2.87.0 — the `perform Op(args)` statements written in this step's
1106    /// body, in source order. Dispatch runs them AFTER the step generates, with
1107    /// the step's output in scope — NOT as `pix_ops` elevations, because
1108    /// `the design plan` section 3.1's `perform Emit(response.token)` performs the step's own
1109    /// result. Elided when empty, so every pre-v2.87.0 program's IR JSON stays
1110    /// byte-identical (the v2.22.0/v2.46.0/v2.83.0 discipline).
1111    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1112    pub performs: Vec<IREffectPerform>,
1113    pub body: Vec<serde_json::Value>,
1114}
1115
1116/// v2.83.0 — one step-scoped governance application. For a
1117/// `mandate` guard, dispatch must run the v2.83.0 control loop over THIS
1118/// step's generation, with the mandate's declared `(D, L)` obligations.
1119#[derive(Debug, Clone, Serialize)]
1120pub struct IRStepGuard {
1121    pub kind: String,
1122    pub name: String,
1123    pub target: String,
1124    pub binding: String,
1125}
1126
1127#[derive(Debug, Clone, Serialize)]
1128pub struct IRFlow {
1129    pub node_type: &'static str,
1130    pub source_line: u32,
1131    pub source_column: u32,
1132    pub name: String,
1133    pub parameters: Vec<IRParameter>,
1134    pub return_type_name: String,
1135    pub return_type_generic: String,
1136    pub return_type_optional: bool,
1137    pub steps: Vec<IRFlowNode>,
1138    pub edges: Vec<IRDataEdge>,
1139    pub execution_levels: Vec<Vec<String>>,
1140}
1141
1142// ── Run ──────────────────────────────────────────────────────────────────────
1143
1144#[derive(Debug, Clone, Serialize)]
1145pub struct IRRun {
1146    pub node_type: &'static str,
1147    pub source_line: u32,
1148    pub source_column: u32,
1149    pub flow_name: String,
1150    pub arguments: Vec<String>,
1151    pub persona_name: String,
1152    pub context_name: String,
1153    pub anchor_names: Vec<String>,
1154    pub on_failure: String,
1155    pub on_failure_params: Vec<Vec<String>>,
1156    pub output_to: String,
1157    pub effort: String,
1158    pub resolved_flow: Option<IRFlow>,
1159    pub resolved_persona: Option<IRPersona>,
1160    pub resolved_context: Option<IRContext>,
1161    pub resolved_anchors: Vec<IRAnchor>,
1162}
1163
1164// ── Lambda Data (ΛD) — Epistemic State Vectors ─────────────────────────────
1165
1166#[derive(Debug, Clone, Serialize)]
1167pub struct IRLambdaData {
1168    pub node_type: &'static str,
1169    pub source_line: u32,
1170    pub source_column: u32,
1171    pub name: String,
1172    pub ontology: String,             // T — ontological type
1173    pub certainty: f64,               // c ∈ [0,1]
1174    pub temporal_frame_start: String, // τ_start
1175    pub temporal_frame_end: String,   // τ_end
1176    pub provenance: String,           // ρ — EntityRef origin
1177    pub derivation: String,           // δ ∈ Δ
1178}
1179
1180#[derive(Debug, Clone, Serialize)]
1181pub struct IRLambdaDataApply {
1182    pub node_type: &'static str,
1183    pub source_line: u32,
1184    pub source_column: u32,
1185    pub lambda_data_name: String, // reference to declared ΛD
1186    pub target: String,           // expression being bound
1187    pub output_type: String,      // result type after binding
1188}
1189
1190// ── Flow step IR nodes ──────────────────────────────────────────────────────
1191
1192/// Polymorphic flow body node — serializes via #[serde(untagged)] so each
1193/// variant emits its inner struct's JSON (with its own `node_type` field).
1194#[derive(Debug, Clone, Serialize)]
1195#[serde(untagged)]
1196pub enum IRFlowNode {
1197    Step(IRStep),
1198    Probe(IRProbe),
1199    Reason(IRReasonStep),
1200    Validate(IRValidateStep),
1201    Refine(IRRefineStep),
1202    Weave(IRWeaveStep),
1203    UseTool(IRUseToolStep),
1204    Remember(IRRememberStep),
1205    Recall(IRRecallStep),
1206    Conditional(IRConditional),
1207    ForIn(IRForIn),
1208    Let(IRLetBinding),
1209    Return(IRReturnStep),
1210    /// v1.14.0 — exit the enclosing for-in body. Payload-free;
1211    /// the runner translates it into a sentinel that terminates the
1212    /// loop. Parser scope check guarantees this only appears inside
1213    /// a for-in body.
1214    Break(IRBreakStep),
1215    /// v1.14.0 — skip to the next iteration of the enclosing for-in
1216    /// body. Same shape as Break — payload-free, sentinel-driven at
1217    /// runtime.
1218    Continue(IRContinueStep),
1219    LambdaDataApply(IRLambdaDataApply),
1220    Par(IRParallelBlock),
1221    Hibernate(IRHibernateStep),
1222    Deliberate(IRDeliberateBlock),
1223    Consensus(IRConsensusBlock),
1224    Forge(IRForgeBlock),
1225    /// v2.65.0 — the proof-carrying derivative step.
1226    Grad(IRGradStep),
1227    Focus(IRFocusStep),
1228    Associate(IRAssociateStep),
1229    Aggregate(IRAggregateStep),
1230    Explore(IRExploreStep),
1231    Ingest(IRIngestStep),
1232    ShieldApply(IRShieldApplyStep),
1233    Declassify(IRDeclassifyStep),
1234    Stream(IRStreamBlock),
1235    /// v2.87.0 — `handle E { … } in { … }`. The frame that makes an outer
1236    /// handler able to intercept an inner computation's effects without the
1237    /// inner code knowing — the compositional property nothing in the language
1238    /// could express before.
1239    Handle(IREffectHandle),
1240    /// v2.87.0 — `perform E.Op(args)`.
1241    Perform(IREffectPerform),
1242    /// v2.87.0 — `resume(v)`, the one-shot continuation invocation (D2).
1243    Resume(IREffectResume),
1244    /// v2.87.0 — `abort(v)`.
1245    Abort(IREffectAbort),
1246    /// v2.87.0 — `forward E.Op(args)` (D12).
1247    Forward(IREffectForward),
1248    Navigate(IRNavigateStep),
1249    Drill(IRDrillStep),
1250    Trail(IRTrailStep),
1251    Corroborate(IRCorroborateStep),
1252    OtsApply(IROtsApplyStep),
1253    MandateApply(IRMandateApplyStep),
1254    ComputeApply(IRComputeApplyStep),
1255    /// v2.83.0 — `<Agent>(arg, …)`. The 46th variant, and the one that
1256    /// makes v2.83.0's bounded control loop reachable from source.
1257    AgentCall(IRAgentCall),
1258    Listen(IRListenStep),
1259    DaemonStep(IRDaemonStepNode),
1260    /// v1.6.0 — π-calc output prefix (Chan-Output / Chan-Mobility).
1261    Emit(IREmit),
1262    /// v2.46.0 — ephemeral-credential minting (attenuated, TTL-bounded).
1263    Mint(IRMintStep),
1264    /// v2.48.0 — mediated secret renewal (`rotation_without_revelation`).
1265    Rotate(IRRotateStep),
1266    /// v1.6.0 — capability extrusion (Publish-Ext).
1267    Publish(IRPublish),
1268    /// v1.6.0 — dual of publish (typed handle import).
1269    Discover(IRDiscover),
1270    Persist(IRPersistStep),
1271    Retrieve(IRRetrieveStep),
1272    Mutate(IRMutateStep),
1273    Purge(IRPurgeStep),
1274    Transact(IRTransactBlock),
1275    /// v2.43.0 — the `warden` adversarial security-analysis block.
1276    Warden(IRWarden),
1277    /// v2.4.0 — the `quant` cognitive block (Hilbert-space projection).
1278    Quant(IRQuant),
1279    /// v2.4.0 — the `yield` measurement point inside a `quant` block.
1280    Yield(IRYield),
1281    /// v2.4.0 — `run <Flow>(args)` flow-step: invoke a declared flow from a
1282    /// body (a daemon listen handler). Reuses [`IRRun`] (the top-level run IR).
1283    Run(IRRun),
1284}
1285
1286#[derive(Debug, Clone, Serialize)]
1287pub struct IRProbe {
1288    pub node_type: &'static str,
1289    pub source_line: u32,
1290    pub source_column: u32,
1291    pub target: String,
1292}
1293
1294#[derive(Debug, Clone, Serialize)]
1295pub struct IRReasonStep {
1296    pub node_type: &'static str,
1297    pub source_line: u32,
1298    pub source_column: u32,
1299    pub strategy: String,
1300    pub target: String,
1301    /// v2.83.0 — the evidence the deliberation reasons OVER, resolved
1302    /// against the flow bindings at dispatch. Elided when empty so every
1303    /// pre-v2.83.0 program's IR JSON is byte-identical (no IR-SHA drift).
1304    #[serde(default, skip_serializing_if = "String::is_empty")]
1305    pub given: String,
1306    /// v2.83.0 — the deliberation's question. The README writes it in
1307    /// every one of its sixteen `reason { … }` blocks and, before this cycle, it
1308    /// reached the model in none of them.
1309    #[serde(default, skip_serializing_if = "String::is_empty")]
1310    pub ask: String,
1311    /// v2.83.0 — the declared deliberation depth. Consumed by the
1312    /// dispatch FRAMING, like `strategy`; not a runtime iteration bound.
1313    #[serde(default, skip_serializing_if = "Option::is_none")]
1314    pub depth: Option<u32>,
1315}
1316
1317#[derive(Debug, Clone, Serialize)]
1318pub struct IRValidateStep {
1319    pub node_type: &'static str,
1320    pub source_line: u32,
1321    pub source_column: u32,
1322    pub target: String,
1323    pub rule: String,
1324    /// v2.88.0 — the `type` that `against: <Schema>` names, RESOLVED at
1325    /// lowering and stamped into the artifact.
1326    ///
1327    /// # Why resolved here rather than threaded to the dispatcher
1328    ///
1329    /// The alternative was an `Arc<Vec<IRType>>` on `DispatchCtx`, the shape
1330    /// `mandate_specs` / `compute_specs` / `agent_specs` use. v2.83.0 records
1331    /// what that costs: threading `agent_specs` meant touching EIGHT production
1332    /// sites, and missing one is a silent hole. `IRRun` already shows the other
1333    /// pattern — `resolved_flow`, `resolved_persona`, `resolved_anchors` — and
1334    /// it is the better one here: the artifact carries its own derivation, so
1335    /// dispatch cannot be reached with the resolution missing, and the schema a
1336    /// validation scored against is auditable after the fact.
1337    ///
1338    /// `None` when `against:` was absent. It is NOT `None` for an *unresolved*
1339    /// name — the type-checker refuses that program, so a `None` here never
1340    /// means "the author named a schema I could not find".
1341    ///
1342    /// Elided when absent, so every pre-v2.88.0 program's IR JSON is
1343    /// byte-identical (the v2.22.0/v2.46.0/v2.83.0/v2.87.0 discipline).
1344    #[serde(default, skip_serializing_if = "Option::is_none")]
1345    pub resolved_schema: Option<Box<IRType>>,
1346    /// v2.88.0 — `if confidence < θ -> refine(max_attempts: N)`, attached to
1347    /// THIS validation (see `ast::ValidateStep::guard` for why attachment, not
1348    /// siblinghood). Elided when absent.
1349    #[serde(default, skip_serializing_if = "Option::is_none")]
1350    pub guard: Option<IRConfidenceGuard>,
1351}
1352
1353/// v2.88.0 — the lowered confidence guard: a floor over the validation's CSR
1354/// plus a bounded recovery. Termination is by construction — attempts strictly
1355/// increase toward `max_attempts`, a `u32` from a literal.
1356#[derive(Debug, Clone, Serialize)]
1357pub struct IRConfidenceGuard {
1358    pub node_type: &'static str,
1359    pub source_line: u32,
1360    pub source_column: u32,
1361    pub threshold: f64,
1362    pub max_attempts: u32,
1363}
1364
1365#[derive(Debug, Clone, Serialize)]
1366pub struct IRRefineStep {
1367    pub node_type: &'static str,
1368    pub source_line: u32,
1369    pub source_column: u32,
1370    pub target: String,
1371    pub strategy: String,
1372}
1373
1374#[derive(Debug, Clone, Serialize)]
1375pub struct IRWeaveStep {
1376    pub node_type: &'static str,
1377    pub source_line: u32,
1378    pub source_column: u32,
1379    pub sources: Vec<String>,
1380    pub target: String,
1381    pub format_type: String,
1382    pub priority: Vec<String>,
1383    pub style: String,
1384    /// v2.83.0 — the parts the synthesis must contain
1385    /// (`include: [summary, risks, recommendations]`). Elided when empty so
1386    /// every pre-v2.83.0 program's IR JSON is byte-identical (no IR-SHA drift).
1387    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1388    pub include: Vec<String>,
1389}
1390
1391#[derive(Debug, Clone, Serialize)]
1392pub struct IRUseToolStep {
1393    pub node_type: &'static str,
1394    pub source_line: u32,
1395    pub source_column: u32,
1396    pub tool_name: String,
1397    pub argument: String,
1398    /// v2.8.0 — the bound keyword args of `use Tool(k = v, …)` (W1: the
1399    /// structured args survive to the IR, no longer collapsed to one opaque
1400    /// string). Empty for the legacy single-`on <arg>` form (`argument`
1401    /// carries that, D5).
1402    pub named_args: Vec<IRNamedArg>,
1403}
1404
1405#[derive(Debug, Clone, Serialize)]
1406pub struct IRRememberStep {
1407    pub node_type: &'static str,
1408    pub source_line: u32,
1409    pub source_column: u32,
1410    pub expression: String,
1411    pub memory_target: String,
1412}
1413
1414#[derive(Debug, Clone, Serialize)]
1415pub struct IRRecallStep {
1416    pub node_type: &'static str,
1417    pub source_line: u32,
1418    pub source_column: u32,
1419    pub query: String,
1420    pub memory_source: String,
1421}
1422
1423/// v2.26.0 — the lowered form of a pure expression (`Expr`). Carried in the
1424/// IR for conditions the legacy `(condition, op, value)` triple cannot express.
1425/// Operators are canonical lowercase strings so the JSON is stable + readable;
1426/// the runtime evaluator (v2.26.0) matches on them. Externally-tagged by `kind`.
1427#[derive(Debug, Clone, Serialize)]
1428#[serde(tag = "kind", rename_all = "snake_case")]
1429pub enum IRExpr {
1430    /// A typed literal.
1431    Lit { lit: IRExprLit },
1432    /// A reference to a binding / dotted path.
1433    Ref { path: String },
1434    /// Unary op — `op ∈ {neg, not}`.
1435    Unary { op: String, operand: Box<IRExpr> },
1436    /// Binary op — `op ∈ {add,sub,mul,div,mod,eq,ne,lt,le,gt,ge,and,or}`.
1437    Binary {
1438        op: String,
1439        lhs: Box<IRExpr>,
1440        rhs: Box<IRExpr>,
1441    },
1442    /// v2.26.0 — a closed-catalog builtin call. `args[0]` is the receiver.
1443    /// `builtin ∈ {length,count,is_empty,is_null,contains,starts_with,ends_with}`.
1444    Call {
1445        builtin: String,
1446        args: Vec<IRExpr>,
1447    },
1448    /// v2.26.0 — field access on a non-reference base (the JSONB seam).
1449    Field {
1450        base: Box<IRExpr>,
1451        field: String,
1452    },
1453    /// v2.26.0 — index access `base[index]`.
1454    Index {
1455        base: Box<IRExpr>,
1456        index: Box<IRExpr>,
1457    },
1458    /// v2.83.0 — `let <name> = <value>` scoped over `<body>`. The lowered
1459    /// form of a `logic { let … return … }` chain: one `Let` per binding,
1460    /// nested, so each bound term is evaluated exactly once and shadowing falls
1461    /// out of the nesting.
1462    Let {
1463        name: String,
1464        value: Box<IRExpr>,
1465        body: Box<IRExpr>,
1466    },
1467}
1468
1469/// v2.26.0 — a literal inside an [`IRExpr`].
1470#[derive(Debug, Clone, Serialize)]
1471#[serde(tag = "ty", rename_all = "snake_case")]
1472pub enum IRExprLit {
1473    Int { value: i64 },
1474    Float { value: f64 },
1475    Bool { value: bool },
1476    Str { value: String },
1477}
1478
1479#[derive(Debug, Clone, Serialize)]
1480pub struct IRConditional {
1481    pub node_type: &'static str,
1482    pub source_line: u32,
1483    pub source_column: u32,
1484    pub condition: String,
1485    pub comparison_op: String,
1486    pub comparison_value: String,
1487    pub then_body: Vec<IRFlowNode>,
1488    pub else_body: Vec<IRFlowNode>,
1489    pub conditions: Vec<(String, String, String)>,
1490    pub conjunctor: String,
1491    /// v2.26.0 — the lowered expression form, present only for conditions
1492    /// the legacy triple cannot express. `skip_serializing_if` keeps the IR
1493    /// JSON (and its SHA) byte-identical for every pre-v2.26.0 program.
1494    #[serde(skip_serializing_if = "Option::is_none")]
1495    pub cond: Option<IRExpr>,
1496}
1497
1498#[derive(Debug, Clone, Serialize)]
1499pub struct IRForIn {
1500    pub node_type: &'static str,
1501    pub source_line: u32,
1502    pub source_column: u32,
1503    pub variable: String,
1504    pub iterable: String,
1505    pub body: Vec<IRFlowNode>,
1506}
1507
1508#[derive(Debug, Clone, Serialize)]
1509pub struct IRLetBinding {
1510    pub node_type: &'static str,
1511    pub source_line: u32,
1512    pub source_column: u32,
1513    pub target: String,
1514    pub value: String,
1515    /// v1.12.0 — preserves parser tokenization intent.
1516    /// One of "literal" | "reference" | "expression".
1517    pub value_kind: String,
1518    /// v2.26.0 — the lowered expression form of the value, present only for
1519    /// `value_kind == "expression"`. The runtime evaluates it instead of
1520    /// treating the value string as an opaque literal. `skip_serializing_if`
1521    /// keeps the IR byte-identical for every literal / reference let.
1522    #[serde(skip_serializing_if = "Option::is_none")]
1523    pub value_ast: Option<IRExpr>,
1524}
1525
1526#[derive(Debug, Clone, Serialize)]
1527pub struct IRReturnStep {
1528    pub node_type: &'static str,
1529    pub source_line: u32,
1530    pub source_column: u32,
1531    pub value_expr: String,
1532}
1533
1534/// v1.14.0 — `break` keyword IR node. Payload-free (the runner
1535/// raises a sentinel; no value is carried). Mirrors Python's
1536/// ``IRBreak`` (axon/compiler/ir_nodes.py).
1537#[derive(Debug, Clone, Serialize)]
1538pub struct IRBreakStep {
1539    pub node_type: &'static str,
1540    pub source_line: u32,
1541    pub source_column: u32,
1542}
1543
1544/// v1.14.0 — `continue` keyword IR node. Same shape as
1545/// ``IRBreakStep``; the runner uses a different sentinel type to
1546/// distinguish loop-exit (break) from iteration-skip (continue).
1547#[derive(Debug, Clone, Serialize)]
1548pub struct IRContinueStep {
1549    pub node_type: &'static str,
1550    pub source_line: u32,
1551    pub source_column: u32,
1552}
1553
1554#[derive(Debug, Clone, Serialize)]
1555pub struct IRParallelBlock {
1556    pub node_type: &'static str,
1557    pub source_line: u32,
1558    pub source_column: u32,
1559    /// v2.15.0 — the concurrent branches lowered from the AST `par { … }`.
1560    /// Each branch is a flow-IR body run concurrently by the dispatcher's
1561    /// `run_branches_concurrently`. `skip_serializing_if = "Vec::is_empty"` so a
1562    /// payload-free / empty `par` serializes byte-identically to the pre-v2.15.0
1563    /// shape (D5 back-compat); a `par` with real branches carries them.
1564    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1565    pub branches: Vec<Vec<IRFlowNode>>,
1566}
1567
1568#[derive(Debug, Clone, Serialize)]
1569pub struct IRHibernateStep {
1570    pub node_type: &'static str,
1571    pub source_line: u32,
1572    pub source_column: u32,
1573    pub event_name: String,
1574    pub timeout: String,
1575}
1576
1577#[derive(Debug, Clone, Serialize)]
1578pub struct IRDeliberateBlock {
1579    pub node_type: &'static str,
1580    pub source_line: u32,
1581    pub source_column: u32,
1582}
1583
1584#[derive(Debug, Clone, Serialize)]
1585pub struct IRConsensusBlock {
1586    pub node_type: &'static str,
1587    pub source_line: u32,
1588    pub source_column: u32,
1589}
1590
1591/// v2.41.0 — the compiled Directed Creative Synthesis block. This IS the
1592/// "structured IR metadata that the runtime executes as an orchestrated
1593/// pipeline" the README always claimed — pre-v2.41.0 it carried only a source
1594/// location. New fields are `skip_serializing_if`-elided so a program with no
1595/// `forge` stays IR-SHA stable.
1596#[derive(Debug, Clone, Serialize, Default)]
1597pub struct IRForgeBlock {
1598    pub node_type: &'static str,
1599    pub source_line: u32,
1600    pub source_column: u32,
1601    #[serde(default, skip_serializing_if = "String::is_empty")]
1602    pub name: String,
1603    #[serde(default, skip_serializing_if = "String::is_empty")]
1604    pub seed: String,
1605    #[serde(default, skip_serializing_if = "String::is_empty")]
1606    pub output_type: String,
1607    #[serde(default, skip_serializing_if = "String::is_empty")]
1608    pub mode: String,
1609    #[serde(default, skip_serializing_if = "is_default_novelty")]
1610    pub novelty: f64,
1611    #[serde(default, skip_serializing_if = "is_one_i64")]
1612    pub depth: i64,
1613    #[serde(default, skip_serializing_if = "is_one_i64")]
1614    pub branches: i64,
1615    #[serde(default, skip_serializing_if = "String::is_empty")]
1616    pub constraints_ref: String,
1617}
1618
1619fn is_default_novelty(v: &f64) -> bool {
1620    (*v - 0.5).abs() < f64::EPSILON
1621}
1622fn is_one_i64(v: &i64) -> bool {
1623    *v == 1
1624}
1625
1626/// v2.65.0 — the proof-carrying derivative. `original` is the
1627/// differentiated `let`'s expression; `derivatives[i]` = ∂original/∂wrt[i],
1628/// SIMPLIFIED — computed at compile time by the symbolic
1629/// differentiator and re-derived at deploy by PCC `GradientSoundness`.
1630/// `original: None` / empty `derivatives` only in a stale artifact — the
1631/// runtime fails CLOSED on it and the PCC refutes it.
1632#[derive(Debug, Clone, Serialize)]
1633pub struct IRGradStep {
1634    pub node_type: &'static str,
1635    pub source_line: u32,
1636    pub source_column: u32,
1637    /// The prior rich `let` differentiated.
1638    pub target: String,
1639    pub wrt: Vec<String>,
1640    /// Result binding (empty ⇒ `d_<target>`).
1641    pub output: String,
1642    pub original: Option<IRExpr>,
1643    pub derivatives: Vec<IRExpr>,
1644}
1645
1646#[derive(Debug, Clone, Serialize)]
1647pub struct IRFocusStep {
1648    pub node_type: &'static str,
1649    pub source_line: u32,
1650    pub source_column: u32,
1651    pub expression: String,
1652    /// v2.63.0 — the data-plane `where:` (the design decision; empty ⇒ no filter).
1653    pub where_expr: String,
1654    /// v2.63.0 — π: projected columns (empty ⇒ all).
1655    pub select: Vec<String>,
1656    /// v2.63.0 — result binding (`as:`; empty ⇒ the dataspace name).
1657    pub output: String,
1658}
1659
1660#[derive(Debug, Clone, Serialize)]
1661pub struct IRAssociateStep {
1662    pub node_type: &'static str,
1663    pub source_line: u32,
1664    pub source_column: u32,
1665    pub left: String,
1666    pub right: String,
1667    pub using_field: String,
1668    /// v2.63.0 — result binding (`as:`; empty ⇒ `<L>_<R>`).
1669    pub output: String,
1670}
1671
1672#[derive(Debug, Clone, Serialize)]
1673pub struct IRAggregateStep {
1674    pub node_type: &'static str,
1675    pub source_line: u32,
1676    pub source_column: u32,
1677    pub target: String,
1678    pub group_by: Vec<String>,
1679    pub alias: String,
1680    /// v2.63.0 — the closed aggregate catalog entries, raw
1681    /// (`count`, `sum(score)`, …) — canonical spelling, T930-validated.
1682    pub compute: Vec<String>,
1683    /// v2.63.0 — the data-plane `where:`.
1684    pub where_expr: String,
1685}
1686
1687#[derive(Debug, Clone, Serialize)]
1688pub struct IRExploreStep {
1689    pub node_type: &'static str,
1690    pub source_line: u32,
1691    pub source_column: u32,
1692    pub target: String,
1693    pub limit: Option<i64>,
1694    /// v2.63.0 — result binding (`as:`; empty ⇒ the target).
1695    pub output: String,
1696}
1697
1698#[derive(Debug, Clone, Serialize)]
1699pub struct IRIngestStep {
1700    pub node_type: &'static str,
1701    pub source_line: u32,
1702    pub source_column: u32,
1703    pub source: String,
1704    pub target: String,
1705    /// v2.63.0 — the declared wire format (`csv` | `json`, validated
1706    /// by axon-T929). Empty only in a pre-108.c artifact — the runtime
1707    /// handler fails CLOSED on it.
1708    pub format: String,
1709    /// v2.63.0 — bounds enforced on the raw byte stream BEFORE
1710    /// parsing (v2.54.0). `None` ⇒ the engine's conservative defaults.
1711    pub max_bytes: Option<u64>,
1712    pub max_rows: Option<u64>,
1713}
1714
1715/// v2.69.0 — a shield's compiled BREACH POLICY, resolved onto the nodes
1716/// that enforce it (`IRShieldApplyStep` / `IREmit`) at LOWERING — the same
1717/// discipline as `IREmit.shield_ref`: the policy rides the artifact, so every
1718/// dispatch path honors it by construction (no per-ctx shield map a forgotten
1719/// site could miss). Before v2.69.0 the whole `on_breach:` catalog
1720/// (`halt|sanitize_and_retry|escalate|quarantine|deflect`) was documented,
1721/// parsed, type-checked — and the runtime always `halt`ed.
1722#[derive(Debug, Clone, Serialize)]
1723pub struct IRBreachPolicy {
1724    /// The declared policy (validated against `VALID_ON_BREACH_POLICIES`).
1725    pub on_breach: String,
1726    /// The quarantine SINK name (`on_breach: quarantine` requires it, axon-T952).
1727    #[serde(default, skip_serializing_if = "String::is_empty")]
1728    pub quarantine: String,
1729    /// The canned safe reply (`on_breach: deflect` requires it, axon-T952).
1730    #[serde(default, skip_serializing_if = "String::is_empty")]
1731    pub deflect_message: String,
1732    /// Fields masked by `sanitize_and_retry` (requires ≥ 1, axon-T952).
1733    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1734    pub redact: Vec<String>,
1735    /// Re-scan budget for `sanitize_and_retry` (parser default: 3).
1736    pub max_retries: i64,
1737}
1738
1739/// v4.5.0 — a declassification, lowered.
1740///
1741/// It reaches the IR because an act of governance that leaves no trace is
1742/// indistinguishable from a shortcut. The evidence packager reads this: who
1743/// retired which class, through which control, at which line.
1744#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
1745pub struct IRDeclassifyStep {
1746    pub node_type: &'static str,
1747    pub source_line: u32,
1748    pub source_column: u32,
1749    /// The class retired.
1750    pub class: String,
1751    /// The value that entered.
1752    pub source: String,
1753    /// The type it left as.
1754    pub output_type: String,
1755    /// The shield that authorised it.
1756    pub shield: String,
1757    /// v4.5.0 — the RESOLVED PLAN. The compiler has the type graph and the
1758    /// regime table; the runtime has neither, so it does not re-derive what to
1759    /// do — it executes what was decided. Same shape as the breach policy
1760    /// riding a shield-apply: one decision, made where the facts are.
1761    ///
1762    /// The field names the destination type keeps. Everything else is dropped
1763    /// by projection, which IS the suppression: a field the destination does
1764    /// not have cannot survive into it.
1765    pub keep: Vec<String>,
1766    /// `(field, operation)` for the fields kept in REDUCED form. The operation
1767    /// slug comes from the regime table, never from the author.
1768    pub generalise: Vec<(String, String)>,
1769}
1770
1771#[derive(Debug, Clone, Serialize)]
1772pub struct IRShieldApplyStep {
1773    pub node_type: &'static str,
1774    pub source_line: u32,
1775    pub source_column: u32,
1776    pub shield_name: String,
1777    pub target: String,
1778    pub output_type: String,
1779    /// v2.69.0 — the named shield's breach policy, resolved at lowering.
1780    /// `None` ⇒ the shield declares no `on_breach:` (halt, the fail-closed
1781    /// default) — and every pre-v2.69.0 program serializes byte-identically.
1782    #[serde(default, skip_serializing_if = "Option::is_none")]
1783    pub breach_policy: Option<IRBreachPolicy>,
1784    /// v2.89.0 — the named shield's declared `scan:` list, resolved at
1785    /// lowering beside `breach_policy` (same Phase 0 pre-pass).
1786    ///
1787    /// It is the SUBJECT of the v2.89.0 refusal. A shield that declares
1788    /// `scan: [prompt_injection]` is not asking to filter — it is ASSERTING
1789    /// that nothing past this point carries an injection. With no scanner
1790    /// registered under the name, OSS used to pass the value through
1791    /// untouched and bind it under a name that claims the property. That is
1792    /// the `warden` shape (v2.67.0 F12): a clean-looking result for something
1793    /// never examined. The runtime now refuses instead.
1794    ///
1795    /// Empty ⇒ a shield with no declared scan, where v2.83.0's argument still
1796    /// holds (a filter's absence honestly leaves data untouched) and the
1797    /// identity passthrough is kept. `skip_serializing_if` elides it → zero
1798    /// IR-SHA drift for every program whose shields declare no `scan:`.
1799    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1800    pub scan: Vec<String>,
1801}
1802
1803#[derive(Debug, Clone, Serialize)]
1804pub struct IRStreamBlock {
1805    pub node_type: &'static str,
1806    pub source_line: u32,
1807    pub source_column: u32,
1808    /// v2.67.0 — the block's lowered body. ADDITIVE: `skip_serializing_if`
1809    /// elides it when empty, so every pre-111 program's IR JSON stays
1810    /// byte-identical and a legacy IR deserialises to an empty body (which then
1811    /// executes as a no-op, exactly as before — no silent behaviour change for
1812    /// an artifact compiled by an older frontend).
1813    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1814    pub body: Vec<IRFlowNode>,
1815    /// v2.83.0 — the `<T>` in `stream<T>`: the CHUNK type. Elided when the
1816    /// block was written without one.
1817    #[serde(default, skip_serializing_if = "String::is_empty")]
1818    pub chunk_type: String,
1819    /// v2.83.0 — `on_chunk: { … }`, lowered. Run once per chunk with the
1820    /// chunk bound as `chunk`.
1821    #[serde(default, skip_serializing_if = "Option::is_none")]
1822    pub on_chunk: Option<Box<IRStep>>,
1823    /// v2.83.0 — `on_complete: { … }`, lowered. Run once, after the source
1824    /// closes, with the accumulated stream bound as `complete`.
1825    #[serde(default, skip_serializing_if = "Option::is_none")]
1826    pub on_complete: Option<Box<IRStep>>,
1827    /// v2.83.0 — `on_error: { … }`, lowered. Run when the SOURCE fails,
1828    /// with the failure bound as `error`. Never fires for a failure of the
1829    /// author's own handlers, nor for cancellation.
1830    #[serde(default, skip_serializing_if = "Option::is_none")]
1831    pub on_error: Option<Box<IRStep>>,
1832}
1833
1834// ── v2.87.0 — algebraic effects, lowered ──────────────────────────────────
1835//
1836// ⚠️ Names deliberately prefixed `IREffect*`. `axon-rs/src/effects/ir.rs`
1837// already owns `IRPerform` / `IRHandlerFrame` / `IRHandlerClause` / `IRResume`
1838// / `IRAbort` / `IRForward`, and `axon-rs` re-exports THIS module — an
1839// unprefixed name would collide there and, worse, would read as the same type.
1840// It is not: that module's `Instruction` alphabet has an INERT catch-all
1841// (`Passthrough`), so a handler body lowered onto it would silently execute
1842// nothing. the design decision puts the effect machine in the DISPATCHER, whose alphabet is
1843// `IRFlowNode`; these nodes are that alphabet's five new members.
1844
1845/// `handle E { clauses } in { body }`, lowered — one handler frame.
1846#[derive(Debug, Clone, Serialize)]
1847pub struct IREffectHandle {
1848    pub node_type: &'static str,
1849    pub source_line: u32,
1850    pub source_column: u32,
1851    pub effect_names: Vec<String>,
1852    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1853    pub clauses: Vec<IREffectClause>,
1854    /// The `in { … }` block — ORDINARY flow nodes. This field is the design decision.
1855    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1856    pub body: Vec<IRFlowNode>,
1857    /// Per-flow frame identity, allocated in source order. Distinguishes two
1858    /// frames over the same effect so `forward` can name which one it left.
1859    pub frame_id: u32,
1860}
1861
1862/// One clause of an [`IREffectHandle`]: `Op(params) -> { body }`.
1863#[derive(Debug, Clone, Serialize)]
1864pub struct IREffectClause {
1865    pub operation_name: String,
1866    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1867    pub parameter_names: Vec<String>,
1868    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1869    pub body: Vec<IRFlowNode>,
1870    pub source_line: u32,
1871    pub source_column: u32,
1872}
1873
1874/// `perform E.Op(args)`, lowered.
1875///
1876/// `effect_name` is ALWAYS populated here even when the source wrote the bare
1877/// form — the IR generator resolves it against the declared catalog.
1878/// A node that reached this point with an empty `effect_name` is a compiler
1879/// bug, and the dispatcher fails closed on it rather than searching by
1880/// operation name alone (which would let the handler stack pick a frame the
1881/// author never named).
1882#[derive(Debug, Clone, Serialize)]
1883pub struct IREffectPerform {
1884    pub node_type: &'static str,
1885    pub source_line: u32,
1886    pub source_column: u32,
1887    pub effect_name: String,
1888    pub operation_name: String,
1889    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1890    pub arguments: Vec<String>,
1891    /// True when the SOURCE wrote `perform Op(x)` and the catalog supplied the
1892    /// effect. Kept so a diagnostic can quote what the author actually typed.
1893    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1894    pub resolved_from_bare: bool,
1895}
1896
1897/// `resume(v)`, lowered — invoke the captured one-shot continuation (D2).
1898#[derive(Debug, Clone, Serialize)]
1899pub struct IREffectResume {
1900    pub node_type: &'static str,
1901    pub source_line: u32,
1902    pub source_column: u32,
1903    #[serde(default, skip_serializing_if = "String::is_empty")]
1904    pub value_expr: String,
1905}
1906
1907/// `abort(v)`, lowered — leave the enclosing `handle` without resuming.
1908#[derive(Debug, Clone, Serialize)]
1909pub struct IREffectAbort {
1910    pub node_type: &'static str,
1911    pub source_line: u32,
1912    pub source_column: u32,
1913    #[serde(default, skip_serializing_if = "String::is_empty")]
1914    pub value_expr: String,
1915}
1916
1917/// `forward E.Op(args)`, lowered (D12) — propagate to the next OUTER frame.
1918#[derive(Debug, Clone, Serialize)]
1919pub struct IREffectForward {
1920    pub node_type: &'static str,
1921    pub source_line: u32,
1922    pub source_column: u32,
1923    pub effect_name: String,
1924    pub operation_name: String,
1925    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1926    pub arguments: Vec<String>,
1927}
1928
1929#[derive(Debug, Clone, Serialize)]
1930pub struct IRNavigateStep {
1931    /// v2.83.0 — per-navigation depth override → `NavConfig.d_max`.
1932    #[serde(default, skip_serializing_if = "Option::is_none")]
1933    pub depth: Option<i64>,
1934    pub node_type: &'static str,
1935    pub source_line: u32,
1936    pub source_column: u32,
1937    pub pix_ref: String,
1938    pub corpus_ref: String,
1939    pub query: String,
1940    pub trail_enabled: bool,
1941    pub output_name: String,
1942    /// v2.13.0 — MDN corpus-graph navigation: the seed document (`from:`).
1943    #[serde(default, skip_serializing_if = "String::is_empty")]
1944    pub seed: String,
1945    /// v2.13.0 — MDN navigation budget (`budget:` = max documents).
1946    #[serde(default, skip_serializing_if = "Option::is_none")]
1947    pub budget: Option<i64>,
1948    /// v2.17.0 (Q2) — column-scope filter for a `corpus from axonstore`. A raw
1949    /// filter expr threaded to `read_all_store_rows` → `stream_retrieve` for
1950    /// BOTH the documents and edges stores, so the sourced MDN graph is scoped
1951    /// to a sub-tenant column (`where: "tenant_id == '${tenant_id}'"`). The
1952    /// v1.32.0 filter compiler resolves `${name}` → `$N` bind params (injection-
1953    /// safe). Empty = no column filter (axon-tenant RLS scope only).
1954    #[serde(default, skip_serializing_if = "String::is_empty")]
1955    pub where_expr: String,
1956}
1957
1958#[derive(Debug, Clone, Serialize)]
1959pub struct IRDrillStep {
1960    pub node_type: &'static str,
1961    pub source_line: u32,
1962    pub source_column: u32,
1963    pub pix_ref: String,
1964    pub subtree_path: String,
1965    pub query: String,
1966    pub output_name: String,
1967}
1968
1969#[derive(Debug, Clone, Serialize)]
1970pub struct IRTrailStep {
1971    pub node_type: &'static str,
1972    pub source_line: u32,
1973    pub source_column: u32,
1974    pub navigate_ref: String,
1975}
1976
1977#[derive(Debug, Clone, Serialize)]
1978pub struct IRCorroborateStep {
1979    pub node_type: &'static str,
1980    pub source_line: u32,
1981    pub source_column: u32,
1982    pub navigate_ref: String,
1983    pub output_name: String,
1984}
1985
1986#[derive(Debug, Clone, Serialize)]
1987pub struct IROtsApplyStep {
1988    pub node_type: &'static str,
1989    pub source_line: u32,
1990    pub source_column: u32,
1991    pub ots_name: String,
1992    pub target: String,
1993    pub output_type: String,
1994}
1995
1996#[derive(Debug, Clone, Serialize)]
1997pub struct IRMandateApplyStep {
1998    pub node_type: &'static str,
1999    pub source_line: u32,
2000    pub source_column: u32,
2001    pub mandate_name: String,
2002    pub target: String,
2003    pub output_type: String,
2004}
2005
2006#[derive(Debug, Clone, Serialize)]
2007pub struct IRAgentCall {
2008    pub node_type: &'static str,
2009    pub source_line: u32,
2010    pub source_column: u32,
2011    /// The declared `agent` this call invokes. Resolved against
2012    /// `DispatchCtx::agent_specs` at dispatch; an unresolved name fails CLOSED
2013    /// (the v2.67.0 compute doctrine — an empty catalog runs nothing).
2014    pub agent_name: String,
2015    /// Positional v2.83.0 subjects. Resolved against the flow bindings at
2016    /// dispatch, so `TrendAnalyzer(Gather.output)` hands the agent the prior
2017    /// step's VALUE.
2018    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2019    pub arguments: Vec<String>,
2020}
2021
2022#[derive(Debug, Clone, Serialize)]
2023pub struct IRComputeApplyStep {
2024    pub node_type: &'static str,
2025    pub source_line: u32,
2026    pub source_column: u32,
2027    pub compute_name: String,
2028    pub arguments: Vec<String>,
2029    pub output_name: String,
2030}
2031
2032#[derive(Debug, Clone, Serialize)]
2033pub struct IRListenStep {
2034    pub node_type: &'static str,
2035    pub source_line: u32,
2036    pub source_column: u32,
2037    pub channel: String,
2038    /// v1.6.0 D4 — true ⇒ `channel` is a declared
2039    /// `IRChannel` ref; false ⇒ legacy string topic.
2040    pub channel_is_ref: bool,
2041    pub event_alias: String,
2042    /// v2.4.0 — the handler body's lowered flow-steps, executed per event /
2043    /// scheduled tick by the v2.4.0 runtime. `skip_serializing_if` keeps a
2044    /// bodyless `listen`'s JSON byte-identical to the pre-v2.4.0 shape (D8).
2045    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2046    pub body: Vec<IRFlowNode>,
2047}
2048
2049#[derive(Debug, Clone, Serialize)]
2050pub struct IRDaemonStepNode {
2051    pub node_type: &'static str,
2052    pub source_line: u32,
2053    pub source_column: u32,
2054    pub daemon_ref: String,
2055}
2056
2057#[derive(Debug, Clone, Serialize)]
2058pub struct IRPersistStep {
2059    pub node_type: &'static str,
2060    pub source_line: u32,
2061    pub source_column: u32,
2062    pub store_name: String,
2063    /// v1.30.0 — declared `{ col: value }` field block (value
2064    /// expressions kept raw; interpolated at runtime). Empty ⇒ the
2065    /// runtime writes the flow's user bindings (v1.30.0 fallback).
2066    pub fields: Vec<(String, String)>,
2067}
2068
2069#[derive(Debug, Clone, Serialize)]
2070pub struct IRRetrieveStep {
2071    pub node_type: &'static str,
2072    pub source_line: u32,
2073    pub source_column: u32,
2074    pub store_name: String,
2075    pub where_expr: String,
2076    pub alias: String,
2077    /// v2.21.0 — `order_by:` clause (raw `"col [asc|desc], …"`).
2078    /// `skip_serializing_if` empty so a store that doesn't order never
2079    /// perturbs the serialized IR bytes (the v2.4.0 brief-#33 /
2080    /// [[feedback-boot-hydrate-self-heal]] no-drift discipline).
2081    #[serde(skip_serializing_if = "String::is_empty", default)]
2082    pub order_by: String,
2083    /// v2.21.0 — `limit:` clause (raw `"100"` or `"${max}"`).
2084    #[serde(skip_serializing_if = "String::is_empty", default)]
2085    pub limit_expr: String,
2086    /// v2.33.0 — `aggregate:` clause (raw, closed catalog: `count` /
2087    /// `sum(col)` / `avg(col)` / `min(col)` / `max(col)`).
2088    /// `skip_serializing_if` empty so a non-aggregating retrieve never
2089    /// perturbs the serialized IR bytes (the same v2.21.0 no-drift
2090    /// discipline — zero IR-SHA drift for existing programs).
2091    #[serde(skip_serializing_if = "String::is_empty", default)]
2092    pub aggregate: String,
2093    /// v2.33.0 — `group_by:` clause (raw `"col, col2"`).
2094    #[serde(skip_serializing_if = "String::is_empty", default)]
2095    pub group_by: String,
2096    /// v2.40.0 — `cache:` reference (a declared `cache` name). Empty ⇒
2097    /// uncached. Elided when empty (IR-SHA stable for cache-less retrieves).
2098    #[serde(skip_serializing_if = "String::is_empty", default)]
2099    pub cache: String,
2100}
2101
2102#[derive(Debug, Clone, Serialize)]
2103pub struct IRMutateStep {
2104    pub node_type: &'static str,
2105    pub source_line: u32,
2106    pub source_column: u32,
2107    pub store_name: String,
2108    pub where_expr: String,
2109    /// v1.30.0 — declared `{ col: value }` SET assignments (value
2110    /// expressions kept raw; interpolated at runtime). Empty ⇒ the
2111    /// runtime writes the flow's user bindings (v1.31.0 fallback).
2112    pub fields: Vec<(String, String)>,
2113}
2114
2115#[derive(Debug, Clone, Serialize)]
2116pub struct IRPurgeStep {
2117    pub node_type: &'static str,
2118    pub source_line: u32,
2119    pub source_column: u32,
2120    pub store_name: String,
2121    pub where_expr: String,
2122}
2123
2124#[derive(Debug, Clone, Serialize)]
2125pub struct IRTransactBlock {
2126    pub node_type: &'static str,
2127    pub source_line: u32,
2128    pub source_column: u32,
2129}
2130
2131/// v2.43.0 — IR for the `warden` adversarial-analysis block. Carries the
2132/// target, the mandatory `scope_ref`, and the recursively-lowered body so the
2133/// enterprise engine (v2.43.0) can drive the analysis and v2.43.0 can enforce the
2134/// authorization discipline.
2135#[derive(Debug, Clone, Serialize)]
2136pub struct IRWarden {
2137    pub node_type: &'static str,
2138    pub source_line: u32,
2139    pub source_column: u32,
2140    pub target: String,
2141    pub scope_ref: String,
2142    /// Nested flow-body IR (recursively lowered).
2143    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2144    pub body: Vec<IRFlowNode>,
2145}
2146
2147/// v2.43.0 — IR for a `scope` authorization-policy declaration.
2148#[derive(Debug, Clone, Serialize)]
2149pub struct IRScope {
2150    pub node_type: &'static str,
2151    pub source_line: u32,
2152    pub source_column: u32,
2153    pub name: String,
2154    pub targets: Vec<String>,
2155    pub depth: String,
2156    pub approver: String,
2157}
2158
2159/// v2.4.0 — IR for the `quant` cognitive block (Hilbert-space projection).
2160/// Mirrors `ast::QuantBlock`. Optional attributes serialize only when present
2161/// (`skip_serializing_if`) so a bare `quant {}` lowers to a minimal node and
2162/// the JSON stays diff-stable. The body lowers recursively, like `par` branches.
2163#[derive(Debug, Clone, Serialize)]
2164pub struct IRQuant {
2165    pub node_type: &'static str,
2166    pub source_line: u32,
2167    pub source_column: u32,
2168    /// Encoding scheme surface spelling (`amplitude` | `angle`); `None` = default.
2169    #[serde(default, skip_serializing_if = "Option::is_none")]
2170    pub encoding: Option<String>,
2171    /// Referenced `Observable` (Pauli-sum) name; `None` if unspecified.
2172    #[serde(default, skip_serializing_if = "Option::is_none")]
2173    pub observable: Option<String>,
2174    /// Register width n; `None` = inferred.
2175    #[serde(default, skip_serializing_if = "Option::is_none")]
2176    pub qubits: Option<i64>,
2177    /// Variational circuit depth L; `None` = backend default.
2178    #[serde(default, skip_serializing_if = "Option::is_none")]
2179    pub depth: Option<i64>,
2180    /// Projected-kernel bandwidth γ (D7); `None` = backend default.
2181    #[serde(default, skip_serializing_if = "Option::is_none")]
2182    pub bandwidth: Option<f64>,
2183    /// v2.23.0 — data re-uploading layers L (`None`/`1` = no re-uploading).
2184    /// `skip_serializing_if` keeps a non-re-uploading block's IR byte-identical.
2185    #[serde(default, skip_serializing_if = "Option::is_none")]
2186    pub reupload: Option<i64>,
2187    /// Algebraic-effect backend tag (`quant_sim` | `qpu_native`).
2188    pub effect: String,
2189    /// Nested flow-body IR (recursively lowered).
2190    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2191    pub body: Vec<IRFlowNode>,
2192}
2193
2194// ── Tier 2 IR nodes ─────────────────────────────────────────────────────────
2195
2196#[derive(Debug, Clone, Serialize)]
2197pub struct IRAgent {
2198    pub node_type: &'static str,
2199    pub source_line: u32,
2200    pub source_column: u32,
2201    pub name: String,
2202    pub goal: String,
2203    pub tools: Vec<String>,
2204    pub memory_ref: String,
2205    pub strategy: String,
2206    pub on_stuck: String,
2207    pub shield_ref: String,
2208    pub max_iterations: Option<i64>,
2209    pub max_tokens: Option<i64>,
2210    pub max_time: String,
2211    pub max_cost: Option<f64>,
2212    /// `return: T` — the declared result type. The loop's final answer is
2213    /// validated against it when `T` is a declared struct type (4.1.0). Skipped
2214    /// when empty so every pre-4.1.0 IR JSON stays byte-identical.
2215    #[serde(default, skip_serializing_if = "String::is_empty")]
2216    pub return_type: String,
2217    /// The declared fields of `return_type` when it names a declared struct
2218    /// `type` — resolved at lowering so the loop validates the final answer
2219    /// against them with no type table in hand. Empty for builtin / undeclared
2220    /// / generic return types (the answer is then free text, as before).
2221    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2222    pub return_schema: Vec<IRTypeField>,
2223    /// The `custom` policy's step sequence, lowered exactly as a flow body is.
2224    /// Empty for every other strategy (the checker refuses a body there).
2225    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2226    pub body: Vec<IRFlowNode>,
2227}
2228
2229/// v2.27.0 — the lowered temporal execution-window guard. The runtime
2230/// (v2.27.0) evaluates `is_in_window(now, tz, allow)`; the daemon binding +
2231/// coalesced defer ledger are v2.27.0.
2232#[derive(Debug, Clone, Serialize)]
2233pub struct IRWindow {
2234    pub node_type: &'static str,
2235    pub source_line: u32,
2236    pub source_column: u32,
2237    pub name: String,
2238    pub timezone: String,
2239    pub allow: Vec<IRWindowSpan>,
2240    /// v2.27.0 — excluded dates (holidays): ISO `YYYY-MM-DD` literals. A tick
2241    /// whose local date is in this set is OUTSIDE regardless of the hour spans.
2242    /// `skip_serializing_if` keeps a holiday-less window's JSON byte-identical.
2243    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2244    pub exclude: Vec<String>,
2245    pub on_outside: String,
2246}
2247
2248/// v2.27.0 — one allowed day/hour span.
2249#[derive(Debug, Clone, Serialize)]
2250pub struct IRWindowSpan {
2251    pub day_start: String,
2252    pub day_end: String,
2253    pub hour_start: i64,
2254    pub hour_end: i64,
2255}
2256
2257/// v2.28.0 — the `budget { … }` linear-effect rate limit lowered to IR. Each
2258/// quota gates a declared tool's dispatch on a renewable token bucket (the v2.28.0
2259/// `RateLease`); `on_exhausted` is the exhaustion policy.
2260#[derive(Debug, Clone, Serialize)]
2261pub struct IRBudget {
2262    pub node_type: &'static str,
2263    pub source_line: u32,
2264    pub source_column: u32,
2265    /// v2.69.0 — the name of a **top-level** `budget`. Empty ⇒ the anonymous
2266    /// daemon-attached form. Skip-if-empty ⇒ every pre-v2.69.0 program serializes
2267    /// byte-identically (IR-SHA stability).
2268    #[serde(default, skip_serializing_if = "String::is_empty")]
2269    pub name: String,
2270    pub quotas: Vec<IRBudgetQuota>,
2271    /// `block` (fail-closed) | `defer` (reschedule via the v2.27.0 defer ledger) |
2272    /// `shed` (skip the call). An omitted policy lowers to `block` (the safe
2273    /// fail-closed default).
2274    pub on_exhausted: String,
2275}
2276
2277/// v2.28.0 — one quota: `<kind>: <limit> per <period> on Tool(<effect>)`.
2278#[derive(Debug, Clone, Serialize)]
2279pub struct IRBudgetQuota {
2280    /// `rate` (renewable bucket) | `max` (windowed hard cap, no intra-window refill).
2281    pub kind: String,
2282    /// Token allowance per period (> 0, validated by `axon-T831`).
2283    pub limit: i64,
2284    /// `second` | `minute` | `hour` | `day` (closed catalog, `axon-T832`).
2285    pub period: String,
2286    /// The declared tool this quota governs (`on Tool(X)`; resolved by `axon-T830`).
2287    pub effect: String,
2288}
2289
2290/// v4.5.0 — an `attest` declaration, lowered.
2291///
2292/// Deliberately the flattest node in this file: five facts and no structure.
2293/// Everything here was asserted by a person, so there is nothing for the
2294/// artifact to interpret — only to carry, verbatim, to whoever reads the
2295/// evidence and has to tell what was proved from what was signed.
2296#[derive(Debug, Clone, Serialize)]
2297pub struct IRAttestation {
2298    pub node_type: &'static str,
2299    pub source_line: u32,
2300    pub source_column: u32,
2301    pub name: String,
2302    /// The type the determination is about.
2303    pub for_type: String,
2304    /// `safe_harbor` | `expert_determination`.
2305    pub basis: String,
2306    /// The citation for that basis, resolved at lowering from
2307    /// `ATTESTATION_BASES` so the evidence reader does not have to know the
2308    /// regulation to print it.
2309    pub citation: String,
2310    /// The judgements the signer took on.
2311    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2312    pub residual: Vec<String>,
2313    pub by: String,
2314    pub on: String,
2315}
2316#[derive(Debug, Clone, Serialize)]
2317pub struct IRShield {
2318    pub node_type: &'static str,
2319    pub source_line: u32,
2320    pub source_column: u32,
2321    pub name: String,
2322    pub scan: Vec<String>,
2323    pub strategy: String,
2324    pub on_breach: String,
2325    pub severity: String,
2326    pub quarantine: String,
2327    /// section 8.2.h.3 — Python emits concrete 0, not null. AST keeps `Option<i64>`
2328    /// so the parser can distinguish "not set"; IR lowering collapses.
2329    pub max_retries: i64,
2330    pub confidence_threshold: f64,
2331    pub allow_tools: Vec<String>,
2332    pub deny_tools: Vec<String>,
2333    pub sandbox: bool,
2334    pub redact: Vec<String>,
2335    pub log: String,
2336    pub deflect_message: String,
2337    // `taint` exists on `ShieldDefinition` (AST) but Python's reference
2338    // IRShield doesn't emit it. Hidden from JSON output for section 8.2.h parity.
2339    #[serde(skip)]
2340    pub taint: String,
2341    /// ESK — covered regulatory classes for this shield.
2342    pub compliance: Vec<String>,
2343    /// v2.34.0 — egress signing algorithm (`hmac_sha256`; empty = the
2344    /// shield does not sign). Elided from JSON when empty so every pre-v2.34.0
2345    /// program's IR stays byte-identical (zero IR-SHA drift).
2346    #[serde(skip_serializing_if = "String::is_empty")]
2347    pub sign: String,
2348}
2349
2350#[derive(Debug, Clone, Serialize)]
2351pub struct IRPix {
2352    pub node_type: &'static str,
2353    pub source_line: u32,
2354    pub source_column: u32,
2355    pub name: String,
2356    pub source: String,
2357    pub depth: Option<i64>,
2358    pub branching: Option<i64>,
2359    pub model: String,
2360}
2361
2362/// v2.12.0 — the audit-chain (`ledger`) IR node. Same shape as [`IRPix`]
2363/// but a DISTINCT node (`node_type: "ledger"`): a ledger binds a hash-linked
2364/// recorder to an audited surface (`source`), retaining `depth` rows under a
2365/// `branching`-factor Merkle tree, hashed with `model`. Kept separate from
2366/// `IRPix` so the navigator and the audit chain never alias on the wire.
2367#[derive(Debug, Clone, Serialize)]
2368pub struct IRLedger {
2369    pub node_type: &'static str,
2370    pub source_line: u32,
2371    pub source_column: u32,
2372    pub name: String,
2373    pub source: String,
2374    pub depth: Option<i64>,
2375    pub branching: Option<i64>,
2376    pub model: String,
2377}
2378
2379#[derive(Debug, Clone, Serialize)]
2380pub struct IRPsyche {
2381    pub node_type: &'static str,
2382    pub source_line: u32,
2383    pub source_column: u32,
2384    pub name: String,
2385    pub dimensions: Vec<String>,
2386    pub manifold_noise: Option<f64>,
2387    pub manifold_momentum: Option<f64>,
2388    pub safety_constraints: Vec<String>,
2389    pub quantum_enabled: Option<bool>,
2390    pub inference_mode: String,
2391}
2392
2393#[derive(Debug, Clone, Serialize)]
2394pub struct IRCorpus {
2395    pub node_type: &'static str,
2396    pub source_line: u32,
2397    pub source_column: u32,
2398    pub name: String,
2399    pub documents: Vec<String>,
2400    /// v2.13.0 — typed weighted edges. Non-empty ⇒ this corpus is an MDN
2401    /// graph `C = (D, R, τ, ω, σ)`; the runtime builds an `mdn::Corpus` from it.
2402    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2403    pub relations: Vec<IRCorpusRelation>,
2404    /// v2.13.0 — `adaptive: true` enables the memory endofunctor on this
2405    /// corpus's navigations.
2406    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
2407    pub adaptive: bool,
2408    pub mcp_server: String,
2409    pub mcp_resource_uri: String,
2410    /// v2.14.0 — when present, this is a DYNAMIC store-sourced MDN graph: the
2411    /// documents and typed edges are rows in two declared `axonstore`s and the
2412    /// runtime builds the `mdn::Corpus` from the live rows at navigate-time
2413    /// (per-tenant, growing). Absent ⇒ the static v2.13.0 corpus (byte-identical IR).
2414    #[serde(default, skip_serializing_if = "Option::is_none")]
2415    pub store_source: Option<IRCorpusStoreSource>,
2416}
2417
2418/// v2.13.0 — a lowered MDN corpus-graph edge `(from, to, τ, ω)`.
2419#[derive(Debug, Clone, Serialize)]
2420pub struct IRCorpusRelation {
2421    pub etype: String,
2422    pub from: String,
2423    pub to: String,
2424    pub weight: f64,
2425}
2426
2427/// v2.14.0 — the lowered store-mapping of a dynamic, `axonstore`-sourced MDN
2428/// corpus graph. `doc_store(doc_id, doc_title)` maps rows → nodes;
2429/// `edge_store(edge_from, edge_to, edge_type, edge_weight)` maps rows → typed
2430/// weighted edges. The runtime (v2.14.0) reads these stores tenant-scoped at
2431/// navigate-time to build the `mdn::Corpus`.
2432#[derive(Debug, Clone, Serialize)]
2433pub struct IRCorpusStoreSource {
2434    pub doc_store: String,
2435    pub doc_id: String,
2436    pub doc_title: String,
2437    pub edge_store: String,
2438    pub edge_from: String,
2439    pub edge_to: String,
2440    pub edge_type: String,
2441    pub edge_weight: String,
2442}
2443
2444/// v2.63.0 — one compiled dataspace column. `column_type` is the
2445/// CANONICAL catalog name (`Text` / `Int` / `Float` / `Bool` /
2446/// `Timestamp` / `Json`) — aliases are resolved at IR generation, so
2447/// every downstream consumer (the engine's deploy hook, the v2.63.0 PCC
2448/// class) reads one spelling.
2449#[derive(Debug, Clone, Serialize)]
2450pub struct IRDataspaceColumn {
2451    pub name: String,
2452    pub column_type: String,
2453}
2454
2455#[derive(Debug, Clone, Serialize)]
2456pub struct IRDataspace {
2457    pub node_type: &'static str,
2458    pub source_line: u32,
2459    pub source_column: u32,
2460    pub name: String,
2461    /// v2.63.0 — the typed columnar schema (canonical type names).
2462    pub columns: Vec<IRDataspaceColumn>,
2463}
2464
2465#[derive(Debug, Clone, Serialize)]
2466pub struct IROts {
2467    pub node_type: &'static str,
2468    pub source_line: u32,
2469    pub source_column: u32,
2470    pub name: String,
2471    pub teleology: String,
2472    pub homotopy_search: String,
2473    pub loss_function: String,
2474}
2475
2476#[derive(Debug, Clone, Serialize)]
2477pub struct IRMandate {
2478    pub node_type: &'static str,
2479    pub source_line: u32,
2480    pub source_column: u32,
2481    pub name: String,
2482    pub constraint: String,
2483    pub kp: Option<f64>,
2484    pub ki: Option<f64>,
2485    pub kd: Option<f64>,
2486    pub tolerance: Option<f64>,
2487    pub max_steps: Option<i64>,
2488    /// v2.83.0 — the declared stability hypotheses `(D, L)`, carried to the
2489    /// runtime as PROOF OBLIGATIONS: the static band check was conditional on
2490    /// them, so dispatch must verify them against the measured backend or
2491    /// refuse. `None` means nothing was statically promised — absence is
2492    /// visible, never defaulted.
2493    pub drift_bound: Option<f64>,
2494    pub lipschitz: Option<f64>,
2495    pub on_violation: String,
2496}
2497
2498#[derive(Debug, Clone, Serialize)]
2499pub struct IRCompute {
2500    pub node_type: &'static str,
2501    pub source_line: u32,
2502    pub source_column: u32,
2503    pub name: String,
2504    pub shield_ref: String,
2505    /// v2.67.0 — typed parameters. ADDITIVE (`skip_serializing_if`), so every
2506    /// pre-111 program's IR JSON stays byte-identical.
2507    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2508    pub parameters: Vec<IRParameter>,
2509    /// v2.67.0 — the declared result type.
2510    #[serde(default, skip_serializing_if = "String::is_empty")]
2511    pub return_type: String,
2512    /// v2.67.0 — the lowered v2.26.0 expression the runtime evaluates natively.
2513    /// `None` ⇒ the apply is refused (axon-T941): a compute with no body cannot
2514    /// compute, and binding a placeholder string in its place is how the old
2515    /// runtime handed a downstream step the text `"compute:Name(args)"` where it
2516    /// expected a number.
2517    #[serde(default, skip_serializing_if = "Option::is_none")]
2518    pub body: Option<IRExpr>,
2519}
2520
2521#[derive(Debug, Clone, Serialize)]
2522pub struct IRDaemon {
2523    pub node_type: &'static str,
2524    pub source_line: u32,
2525    pub source_column: u32,
2526    pub name: String,
2527    pub goal: String,
2528    pub tools: Vec<String>,
2529    pub memory_ref: String,
2530    pub strategy: String,
2531    pub on_stuck: String,
2532    pub shield_ref: String,
2533    /// v2.27.0 — the `window:` temporal binding (a `window` primitive name).
2534    /// Empty ⇒ no temporal guard; `skip_serializing_if` keeps a windowless
2535    /// daemon's JSON byte-identical (D8 zero-drift).
2536    #[serde(default, skip_serializing_if = "String::is_empty")]
2537    pub window_ref: String,
2538    /// v2.28.0 — the `budget { … }` linear-effect rate limit. `None` ⇒ no
2539    /// budget; `skip_serializing_if` keeps a budgetless daemon's JSON
2540    /// byte-identical (D8 zero-drift).
2541    #[serde(default, skip_serializing_if = "Option::is_none")]
2542    pub budget: Option<IRBudget>,
2543    pub max_tokens: Option<i64>,
2544    pub max_time: String,
2545    pub max_cost: Option<f64>,
2546    /// v2.4.0 — the daemon's `listen` listeners (channel + alias + handler
2547    /// body). Pre-v2.4.0 these were DROPPED at lowering (the IR daemon carried no
2548    /// listeners at all); now they survive so the v2.4.0 runtime can mount + run
2549    /// them and the v2.4.0 enterprise supervisor can extract them per-tenant.
2550    /// `skip_serializing_if` keeps a listenerless daemon's JSON unchanged (D8).
2551    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2552    pub listeners: Vec<IRListenStep>,
2553    /// v2.4.0 — the capability scope the daemon's runs are confined to
2554    /// (`requires: [cap, …]`). The enterprise supervisor mints a per-run
2555    /// principal scoped to exactly these. `skip_serializing_if` keeps a
2556    /// requires-less daemon's JSON byte-identical (D8).
2557    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2558    pub requires_capabilities: Vec<String>,
2559}
2560
2561// ── v2.42.0 — the long-horizon autonomous research primitive ────────────────
2562
2563/// v2.42.0 — a compiled `savant` (long-horizon autonomous research
2564/// primitive). A governed orchestrator: the IR carries the declared surface so
2565/// the enterprise active-inference engine (v2.42.0+) can drive the FEP loop, and
2566/// the v2.42.0 checker can bind `memory` to a declared store, `budget` to a v2.28.0
2567/// linear budget, and the body to a v2.36.0 interruptible session.
2568#[derive(Debug, Clone, Serialize)]
2569pub struct IRSavant {
2570    pub node_type: &'static str,
2571    pub source_line: u32,
2572    pub source_column: u32,
2573    pub name: String,
2574    pub domain: String,
2575    #[serde(default, skip_serializing_if = "Option::is_none")]
2576    pub cognition: Option<IRSavantCognition>,
2577    #[serde(default, skip_serializing_if = "Option::is_none")]
2578    pub memory: Option<IRSavantMemory>,
2579    #[serde(default, skip_serializing_if = "Option::is_none")]
2580    pub budget: Option<IRSavantBudget>,
2581    pub mandates: Vec<IRSavantMandate>,
2582}
2583
2584/// v2.42.0 — the compiled `cognition { … }` sub-block (active-inference
2585/// engine parameters).
2586#[derive(Debug, Clone, Serialize)]
2587pub struct IRSavantCognition {
2588    /// `standard | deep | hyper` — HRR dimensionality tier (validated v2.42.0).
2589    pub depth: String,
2590    /// Expected-Free-Energy convergence bound (`> 0`, v2.42.0). `None` ⇒ default.
2591    #[serde(default, skip_serializing_if = "Option::is_none")]
2592    pub entropic_threshold: Option<f64>,
2593    /// `low | med | high` — explore/exploit balance (validated v2.42.0).
2594    pub divergence: String,
2595}
2596
2597/// v2.42.0 — the compiled `memory { … }` sub-block (retention binding).
2598#[derive(Debug, Clone, Serialize)]
2599pub struct IRSavantMemory {
2600    /// A declared `memory`/`corpus` name (resolved v2.42.0). Empty ⇒ ephemeral.
2601    pub backend: String,
2602    /// Whether to index the corpus as a simplicial-complex graph (topological
2603    /// β_n reading).
2604    pub corpus_graph: bool,
2605    /// Per-tenant tensor partitioning level (enforced by the enterprise engine).
2606    pub isolation_level: String,
2607}
2608
2609/// v2.42.0 — the compiled `budget { … }` sub-block (compute ceiling, bound to
2610/// a v2.28.0 linear budget in v2.42.0).
2611#[derive(Debug, Clone, Serialize)]
2612pub struct IRSavantBudget {
2613    /// Hard ceiling on FEP-loop iterations before the savant pauses.
2614    #[serde(default, skip_serializing_if = "Option::is_none")]
2615    pub max_iterations: Option<i64>,
2616    /// Hard ceiling on `synth` (v2.42.0) tool-creation events per mandate.
2617    #[serde(default, skip_serializing_if = "Option::is_none")]
2618    pub max_tool_synth: Option<i64>,
2619}
2620
2621/// v2.42.0 — a compiled `mandate <Name> { … }` sub-block (one research goal).
2622#[derive(Debug, Clone, Serialize)]
2623pub struct IRSavantMandate {
2624    pub name: String,
2625    pub objective: String,
2626    pub output_type: String,
2627}
2628
2629// ── v2.53.0 — Native Document Synthesis IR ─────────────────────────────────
2630
2631/// v2.53.0 — a compiled document declaration. The runtime `DocumentRenderer`
2632/// tool serialises this to deterministic OOXML bytes (v2.53.0); the
2633/// `DocumentProvenanceSoundness` PCC class (v2.53.0) re-derives the barrier from
2634/// it. `blocks` is the closed-catalog body tree.
2635#[derive(Debug, Clone, Serialize)]
2636pub struct IRDocument {
2637    pub node_type: &'static str,
2638    pub source_line: u32,
2639    pub source_column: u32,
2640    pub name: String,
2641    /// `docx | pptx | xlsx`.
2642    pub target: String,
2643    #[serde(default, skip_serializing_if = "String::is_empty")]
2644    pub template: String,
2645    #[serde(default, skip_serializing_if = "String::is_empty")]
2646    pub provenance: String,
2647    /// The propagated effect row (`io`, `storage`, `sensitive:*`, `legal:*`).
2648    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2649    pub effect_row: Vec<String>,
2650    /// v2.53.0 — the enclosing `epistemic { mode: … }` at compile time
2651    /// (`believe`/`know` vouch the whole document is ≥ believe, satisfying the
2652    /// assertion-laundering barrier without per-field `attribute:`). Empty at
2653    /// top level. Recorded so the `DocumentProvenanceSoundness` PCC class
2654    /// re-derives the barrier identically (no false refutation).
2655    #[serde(default, skip_serializing_if = "String::is_empty")]
2656    pub epistemic_mode: String,
2657    pub blocks: Vec<IRDocBlock>,
2658}
2659
2660/// v2.53.0 — one compiled document block. `fields` preserves declaration
2661/// order (a `Vec` of `(name, value)`), so the render is deterministic.
2662#[derive(Debug, Clone, Serialize)]
2663pub struct IRDocBlock {
2664    pub kind: String,
2665    pub fields: Vec<IRDocField>,
2666    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2667    pub children: Vec<IRDocBlock>,
2668}
2669
2670/// v2.53.0 — a compiled `(field, value)` pair. `kind` tags the value shape
2671/// (`text`|`ref`|`list`|`int`|`bool`) so the renderer + the barrier can
2672/// discriminate a literal from a flow-value reference without re-parsing.
2673#[derive(Debug, Clone, Serialize)]
2674pub struct IRDocField {
2675    pub name: String,
2676    /// `text | ref | list | int | bool`.
2677    pub kind: &'static str,
2678    #[serde(default, skip_serializing_if = "String::is_empty")]
2679    pub value: String,
2680    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2681    pub items: Vec<String>,
2682}
2683
2684// ── v2.60.0 — Governed CRM Delivery IR ─────────────────────────────────────
2685
2686/// v2.60.0 — a compiled delivery declaration. The runtime delivery dispatch
2687/// (`axon::delivery`) transduces this to the configured CRM engine; the
2688/// `DeliveryProvenanceSoundness` PCC class (T920) re-derives the barrier from it.
2689/// `ops` is the closed-catalog operation list.
2690/// v2.66.0 — the compiled `notify` declaration. `epistemic_mode`
2691/// records the enclosing vouch (the v2.53.0/v2.60.0 discipline) so T933
2692/// re-derives identically at deploy (PCC `NotificationProvenanceSoundness`).
2693#[derive(Debug, Clone, Serialize)]
2694pub struct IRNotify {
2695    pub node_type: &'static str,
2696    pub source_line: u32,
2697    pub source_column: u32,
2698    pub name: String,
2699    pub channel: String,
2700    /// The v2.48.0 secret-class ref (the recipient value NEVER rides the IR).
2701    pub to_secret: String,
2702    pub template: String,
2703    pub window: String,
2704    #[serde(default, skip_serializing_if = "String::is_empty")]
2705    pub provenance: String,
2706    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2707    pub effects: Vec<String>,
2708    #[serde(default, skip_serializing_if = "String::is_empty")]
2709    pub epistemic_mode: String,
2710}
2711
2712#[derive(Debug, Clone, Serialize)]
2713pub struct IRDeliver {
2714    pub node_type: &'static str,
2715    pub source_line: u32,
2716    pub source_column: u32,
2717    pub name: String,
2718    /// `crm`.
2719    pub target: String,
2720    /// `attached | cleared` (empty ⇒ `attached`). How field provenance crosses
2721    /// the boundary — the T920 barrier's subject.
2722    #[serde(default, skip_serializing_if = "String::is_empty")]
2723    pub provenance: String,
2724    /// The per-tenant credential key (v2.48.0 custody — resolved at dispatch, never
2725    /// in cognition).
2726    #[serde(default, skip_serializing_if = "String::is_empty")]
2727    pub secret: String,
2728    /// The propagated effect row (`web`, `sensitive:*`, `legal:*`).
2729    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2730    pub effect_row: Vec<String>,
2731    /// The enclosing `epistemic { mode: … }` at compile time (`believe`/`know`
2732    /// vouch the delivered values are ≥ believe, satisfying the T920 barrier for
2733    /// a `provenance: cleared` delivery). Empty at top level. Recorded so the
2734    /// `DeliveryProvenanceSoundness` PCC class re-derives the barrier identically.
2735    #[serde(default, skip_serializing_if = "String::is_empty")]
2736    pub epistemic_mode: String,
2737    pub ops: Vec<IRDeliverOp>,
2738}
2739
2740/// v2.60.0 — one compiled delivery operation. `fields` preserves declaration
2741/// order (a `Vec` of `(name, value)`) so the transduced request is deterministic.
2742/// Reuses [`IRDocField`] — the same `(name, kind, value)` shape a document block
2743/// field carries, so the barrier + transducer discriminate a literal from a
2744/// flow-value `ref` without re-parsing.
2745#[derive(Debug, Clone, Serialize)]
2746pub struct IRDeliverOp {
2747    /// `upsert_contact | create_deal | add_note`.
2748    pub kind: String,
2749    pub fields: Vec<IRDocField>,
2750}
2751
2752/// v2.42.0 — a compiled `synth` dynamic tool-synthesis policy. The IR carries
2753/// the safety envelope so the enterprise Extism/WASM executor (v2.42.0) enforces
2754/// it; OSS ships a deny-by-default `SynthBackend` that refuses execution.
2755#[derive(Debug, Clone, Serialize)]
2756pub struct IRSynth {
2757    pub node_type: &'static str,
2758    pub source_line: u32,
2759    pub source_column: u32,
2760    pub name: String,
2761    pub target: String,
2762    pub risk: String,
2763    #[serde(default, skip_serializing_if = "String::is_empty")]
2764    pub language: String,
2765    pub sandbox: String,
2766    /// `required | none`; an omitted policy lowers to `required` (fail-closed).
2767    pub review: String,
2768    #[serde(default, skip_serializing_if = "Option::is_none")]
2769    pub max_lines: Option<i64>,
2770}
2771
2772// ── v2.5.0 — Closed-catalog extension mechanism ────────────────────────────
2773
2774/// v2.5.0 — one compiled member of an `extension`. For `effects`
2775/// the `name` is a provenance base; `default_confidence` is a CEILING
2776/// (v2.5.0 tainted-overriding). Metadata is elided from JSON when absent
2777/// so the serialised shape stays minimal once the `extensions` field is
2778/// un-skipped alongside the Python IR mirror.
2779#[derive(Debug, Clone, Serialize)]
2780pub struct IRExtensionMember {
2781    pub name: String,
2782    #[serde(default, skip_serializing_if = "Option::is_none")]
2783    pub semantics: Option<String>,
2784    #[serde(default, skip_serializing_if = "Option::is_none")]
2785    pub default_confidence: Option<f64>,
2786}
2787
2788/// v2.5.0 — a compiled `extension` declaration. Rides in the IR (and,
2789/// once un-skipped, the proof bundle) so an independent PCC verifier
2790/// re-derives `is_known_base` against the artifact's own extensions
2791/// (soundness invariant #1). `category` ∈ {`effects`, `scan`} — the
2792/// type-checker (v2.5.0) enforces the closed category + no-shadowing +
2793/// provenance-class invariants before this IR is trusted.
2794#[derive(Debug, Clone, Serialize)]
2795pub struct IRExtension {
2796    pub node_type: &'static str,
2797    pub source_line: u32,
2798    pub source_column: u32,
2799    pub name: String,
2800    pub category: String,
2801    pub members: Vec<IRExtensionMember>,
2802}
2803
2804#[derive(Debug, Clone, Serialize)]
2805pub struct IRAxonStore {
2806    pub node_type: &'static str,
2807    pub source_line: u32,
2808    pub source_column: u32,
2809    pub name: String,
2810    pub backend: String,
2811    /// The DSN. **This is the field that actually runs.**
2812    ///
2813    /// `connection:` → `resolve_dsn` → a real sqlx `PgPool`. It is the sole DSN
2814    /// source for every store op in every deployed flow; there is no
2815    /// global-pool fallback. v2.67.0's census established this, and it is why
2816    /// v2.67.0 is delicate: moving authority to `resource` moves it *away* from
2817    /// the one field that governs anything, *toward* the half that governs
2818    /// nothing. A `resource:` that merely renames this string would be the
2819    /// nominal link — wired and hollow.
2820    ///
2821    /// v2.67.0: still parsed, but **deprecated in favour of [`Self::resource_ref`]**,
2822    /// and a store declared this way is INELIGIBLE for `lease` / `observe` /
2823    /// `reconcile`. *You cannot govern what you did not declare.*
2824    pub connection: String,
2825    /// v2.67.0 — the `resource` this store runs on (`axonstore U { resource: Db }`).
2826    ///
2827    /// When present, the store DERIVES its DSN (`resource.endpoint`), its pool
2828    /// size (`resource.capacity` — a knob that did not exist before v2.67.0; the
2829    /// pool was hardcoded at 10), and its sharing discipline
2830    /// (`resource.lifetime`, `axon-T941`) from the resource. **That derivation
2831    /// — not the reference — is what makes this real.**
2832    ///
2833    /// Empty ⇒ the legacy un-resourced form. Skip-if-empty ⇒ every pre-v2.67.0
2834    /// store serializes byte-identically (the v2.48.0 `class` precedent).
2835    #[serde(default, skip_serializing_if = "String::is_empty")]
2836    pub resource_ref: String,
2837    pub confidence_floor: Option<f64>,
2838    pub isolation: String,
2839    pub on_breach: String,
2840    /// v1.30.0 (D11) — Pillar IV: the capability slug required to
2841    /// access this store (empty = no gate).
2842    pub capability: String,
2843    /// v2.48.0 — the secret-class prefix of a `backend: secrets`
2844    /// metadata store (`rotation_without_revelation`). Non-empty ⇔
2845    /// `backend == "secrets"` (both directions enforced by `axon-T900`
2846    /// before the IR ships). Elided from the wire when empty — every
2847    /// pre-v2.48.0 store serializes byte-identically (IR-SHA stability).
2848    #[serde(default, skip_serializing_if = "String::is_empty")]
2849    pub class: String,
2850    /// v1.31.0 (D1) — the OPTIONAL column-schema declaration. Three
2851    /// closed forms (inline / manifest-ref / env-var). `None` means the
2852    /// 37.x runtime+deploy path applies verbatim (D5 absolute). The
2853    /// v1.31.0 / v1.31.0 type-checker proves every store reference against
2854    /// this when present. v2.48.0: for a `backend: secrets` store the
2855    /// generator synthesizes the FIXED metadata schema here (the artifact
2856    /// is self-describing; PCC and the deploy gate re-derive against it).
2857    #[serde(default, skip_serializing_if = "Option::is_none")]
2858    pub column_schema: Option<IRStoreColumnSchema>,
2859}
2860
2861/// v1.31.0 (D1) — IR mirror of [`crate::store_schema::StoreColumnSchema`].
2862/// Serializes as a tagged union: `{"form": "inline" | "manifest_ref" |
2863/// "env_var", …}`.
2864#[derive(Debug, Clone, Serialize)]
2865#[serde(tag = "form", rename_all = "snake_case")]
2866pub enum IRStoreColumnSchema {
2867    Inline { columns: Vec<IRStoreColumn> },
2868    ManifestRef { qualified_name: String },
2869    EnvVar { var_name: String },
2870}
2871
2872/// v1.31.0 (D1) — IR mirror of [`crate::store_schema::StoreColumn`].
2873/// The serialized `col_type` is the canonical PascalCase name (e.g.
2874/// `"Uuid"`, `"Int"`, `"Timestamptz"`).
2875#[derive(Debug, Clone, Serialize)]
2876pub struct IRStoreColumn {
2877    pub name: String,
2878    pub col_type: String,
2879    #[serde(default, skip_serializing_if = "is_false")]
2880    pub primary_key: bool,
2881    #[serde(default, skip_serializing_if = "is_false")]
2882    pub auto_increment: bool,
2883    #[serde(default, skip_serializing_if = "is_false")]
2884    pub not_null: bool,
2885    #[serde(default, skip_serializing_if = "is_false")]
2886    pub unique: bool,
2887    #[serde(default, skip_serializing_if = "String::is_empty")]
2888    pub default_value: String,
2889    /// v1.31.0 (D2, D5) — `true` iff the column is declared with
2890    /// `GENERATED ALWAYS AS IDENTITY` or `GENERATED BY DEFAULT AS
2891    /// IDENTITY`. Distinct from `auto_increment` (legacy SERIAL via
2892    /// `nextval(...)` default). `skip_serializing_if` keeps IR JSON
2893    /// byte-identical to v1.38.2 for any column where `identity = false`.
2894    #[serde(default, skip_serializing_if = "is_false")]
2895    pub identity: bool,
2896    /// v2.26.0 (D1) — `true` iff the column carries the `index`
2897    /// declaration. Surfaced into the IR so the deployment layer (the
2898    /// enterprise deploy gate) SEES the index as a declared capability and
2899    /// can materialize it (a GIN path index for a `Json`/`Jsonb` column, a
2900    /// b-tree otherwise) — never a silent out-of-band DBA action.
2901    /// `skip_serializing_if` keeps IR JSON byte-identical for any column
2902    /// where `indexed = false`.
2903    #[serde(default, skip_serializing_if = "is_false")]
2904    pub indexed: bool,
2905    /// v2.26.0 (D1) — the OPTIONAL `Json<T>` shape-lens struct name on a
2906    /// `Json`/`Jsonb` column (`payload: Json<UserEvent>` → `Some("UserEvent")`).
2907    /// Surfaced into the IR so the PCC `JsonShapeSoundness` proof can
2908    /// RE-DERIVE, from the artifact alone, that every lens shape resolves
2909    /// to a declared struct `type` — the v2.26.0/v2.26.0 lens well-formedness
2910    /// made an independently-verifiable proof object. `skip_serializing_if`
2911    /// keeps IR JSON byte-identical for any column with no shape lens.
2912    #[serde(default, skip_serializing_if = "Option::is_none")]
2913    pub json_shape: Option<String>,
2914}
2915
2916#[inline]
2917fn is_false(b: &bool) -> bool {
2918    !*b
2919}
2920
2921#[derive(Debug, Clone, Serialize)]
2922pub struct IRAxonEndpoint {
2923    pub node_type: &'static str,
2924    pub source_line: u32,
2925    pub source_column: u32,
2926    pub name: String,
2927    pub method: String,
2928    pub path: String,
2929    pub body_type: String,
2930    pub execute_flow: String,
2931    pub output_type: String,
2932    pub shield_ref: String,
2933    /// section 8.2.h.3 — Python emits concrete `0`; AST stays `Option<i64>`.
2934    pub retries: i64,
2935    pub timeout: String,
2936    /// ESK — κ regulatory class on the boundary.
2937    pub compliance: Vec<String>,
2938    /// v1.32.0 (D1) — Path parameter names extracted from the
2939    /// `path:` string. Mirrors `AxonEndpointDefinition.path_params`.
2940    /// **`skip_serializing_if = Vec::is_empty`** so a pre-v1.38.5 IR
2941    /// JSON snapshot (without the field) is byte-identical to a
2942    /// v1.38.5 IR JSON for the same endpoint — D5 backwards-compat
2943    /// absolute. The runtime + adopter tools that consume the IR
2944    /// JSON parse `path_params` as an absent key → empty Vec via
2945    /// serde's `default` semantics.
2946    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2947    pub path_params: Vec<String>,
2948    /// v1.32.0 (D2) — Query parameters from the inline
2949    /// `query: { … }` block. Mirrors `AxonEndpointDefinition.query_params`
2950    /// using `IRTypeField` (shared with body type fields → uniform
2951    /// downstream tooling). **`skip_serializing_if = Vec::is_empty`**
2952    /// — same D5 IR-JSON byte-identity guarantee as `path_params`.
2953    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2954    pub query_params: Vec<IRTypeField>,
2955    /// v2.4.0 — capability scopes the request bearer must hold
2956    /// (the `requires: [scope.dotted]` declaration, v1.23.0). Mirror
2957    /// of `AxonEndpointDefinition.requires_capabilities`, lowered into
2958    /// the IR so the PCC CapabilityContainment property can prove that
2959    /// the stores this endpoint's flow reaches are all covered by the
2960    /// declared requires. **`skip_serializing_if = Vec::is_empty`** so a
2961    /// pre-v2.4.0 IR-JSON snapshot (no `requires:`) stays byte-identical
2962    /// (D5 backwards-compat — empty key parses back to empty Vec).
2963    #[serde(default, skip_serializing_if = "Vec::is_empty")]
2964    pub requires_capabilities: Vec<String>,
2965    /// v2.38.0 — the `cors: <Name>` reference, or `""` when absent
2966    /// (the design decision: no CORS headers, ever). NEW field on an EXISTING struct —
2967    /// `skip_serializing_if` (not `shield_ref`'s bare/always-emitted
2968    /// historical shape) so a cors-less endpoint's IR stays byte-identical
2969    /// to pre-v2.38.0 (zero IR-SHA drift — the standing v2.33.0 discipline).
2970    #[serde(default, skip_serializing_if = "String::is_empty")]
2971    pub cors_ref: String,
2972    /// v2.44.0 — the explicit authorization-coverage opt-out lowered into
2973    /// the IR so the enterprise runtime (v2.44.0) and the PCC
2974    /// `AuthorizationCoverage` witness (v2.44.0) can read it. `false` (the
2975    /// default + the common case) elides from JSON via `is_false` so a
2976    /// pre-v2.44.0 IR-JSON snapshot stays byte-identical (zero IR-SHA drift —
2977    /// the standing v2.33.0 discipline).
2978    #[serde(default, skip_serializing_if = "is_false")]
2979    pub public: bool,
2980}
2981
2982// ── v1.6.0 — Mobile Typed Channels IR ───────────────────────────────
2983
2984/// Compiled `channel Name { … }` declaration.
2985///
2986/// Direct port of `axon.compiler.ir_nodes.IRChannel`.  Lives in
2987/// `IRProgram.channels`; emit/publish/discover reductions embed in
2988/// their containing flow/listener (paper section 3 + section 4 — π-calc prefix
2989/// discipline preserved structurally, not lifted to top-level ops).
2990#[derive(Debug, Clone, Serialize)]
2991pub struct IRChannel {
2992    pub node_type: &'static str,
2993    pub source_line: u32,
2994    pub source_column: u32,
2995    pub name: String,
2996    pub message: String, // surface spelling — Order | Channel<Order> | …
2997    pub qos: String,
2998    pub lifetime: String,
2999    pub persistence: String,
3000    pub shield_ref: String,
3001    /// v2.34.0 — non-empty ⇒ some `publish <this> within <Shield>` site
3002    /// referenced a SIGNING shield: the channel is an EGRESS channel and
3003    /// its durable events are signed-deliverable to registered external
3004    /// subscribers under this algorithm (first publish site wins;
3005    /// deterministic — the catalog has one algorithm in v1). Elided from
3006    /// JSON when empty (zero IR-SHA drift for pre-v2.34.0 programs).
3007    #[serde(skip_serializing_if = "String::is_empty")]
3008    pub egress_sign: String,
3009}
3010
3011/// v2.3.0 — compiled typed WebSocket transport. `protocol` names the
3012/// `session` it carries; `backpressure_credit` is the typed-resource window
3013/// (`null` if unspecified). axon-rs realises the endpoint over a `tokio` WS,
3014/// crediting/decrementing the window per section 4.2 of the paper.
3015#[derive(Debug, Clone, Serialize)]
3016pub struct IRSocket {
3017    pub node_type: &'static str,
3018    pub source_line: u32,
3019    pub source_column: u32,
3020    pub name: String,
3021    pub protocol: String,
3022    pub backpressure_credit: Option<i64>,
3023    pub reconnect: bool,
3024    pub legal_basis: Option<String>,
3025}
3026
3027/// v2.37.0 — compiled outbound vendor connection (the client dual of
3028/// [`IRSocket`]). `protocol`/`role` bind the axon-facing session interface;
3029/// `resolve`/`secret` are per-tenant config keys (never literals — T850);
3030/// `map` is the compile-time-total wire↔session projection (T849). Optional
3031/// fields elide when absent so the IR shape is purely additive.
3032#[derive(Debug, Clone, Serialize)]
3033pub struct IRUpstream {
3034    pub node_type: &'static str,
3035    pub source_line: u32,
3036    pub source_column: u32,
3037    pub name: String,
3038    pub transport: String,
3039    pub protocol: String,
3040    pub role: String,
3041    pub resolve: String,
3042    /// v2.69.0 — the `resource` this upstream's channel rides. When set,
3043    /// `resolve` above was DERIVED from the resource's `endpoint` at LOWERING
3044    /// (the v2.69.0 shield-egress discipline: derivation stamped into the
3045    /// artifact reaches every dial path by construction — no per-site wiring
3046    /// to forget) and `capacity` below carries the resource's bound. Elided
3047    /// when empty — every pre-v2.69.0 upstream serializes byte-identically.
3048    #[serde(default, skip_serializing_if = "String::is_empty")]
3049    pub resource_ref: String,
3050    /// v2.69.0 — **max concurrent connection INSTANCES** of this upstream
3051    /// (from `resource.capacity`). Frames are already flow-controlled by
3052    /// `backpressure_credit`; this bounds CONNECTIONS. The runtime holds a
3053    /// per-process semaphore permit for the life of each dialed handle —
3054    /// the same in-memory/per-process bound v2.69.0 documented for tools.
3055    #[serde(default, skip_serializing_if = "Option::is_none")]
3056    pub capacity: Option<i64>,
3057    pub secret: String,
3058    pub auth_kind: String,
3059    #[serde(default, skip_serializing_if = "Option::is_none")]
3060    pub auth_name: Option<String>,
3061    #[serde(default, skip_serializing_if = "Option::is_none")]
3062    pub auth_prefix: Option<String>,
3063    pub map: Vec<IRUpstreamMapRule>,
3064    #[serde(default, skip_serializing_if = "Option::is_none")]
3065    pub reconnect: Option<IRUpstreamReconnect>,
3066    #[serde(default, skip_serializing_if = "Option::is_none")]
3067    pub overflow: Option<String>,
3068    #[serde(default, skip_serializing_if = "Option::is_none")]
3069    pub backpressure_credit: Option<i64>,
3070    /// v2.37.0 — the `Preset@vN` reference this declaration was expanded from
3071    /// (provenance for the compliance reviewer); absent for hand-written ones.
3072    #[serde(default, skip_serializing_if = "Option::is_none")]
3073    pub preset: Option<String>,
3074}
3075
3076/// v2.37.0 — one compiled `map:` projection rule.
3077#[derive(Debug, Clone, Serialize)]
3078pub struct IRUpstreamMapRule {
3079    pub node_type: &'static str,
3080    pub direction: String,
3081    pub message: String,
3082    pub framing: String,
3083    #[serde(default, skip_serializing_if = "Option::is_none")]
3084    pub tag: Option<String>,
3085    #[serde(default, skip_serializing_if = "Option::is_none")]
3086    pub when_field: Option<String>,
3087    #[serde(default, skip_serializing_if = "Option::is_none")]
3088    pub when_value: Option<String>,
3089}
3090
3091/// v2.37.0 — compiled reconnect policy (all three fields required by the
3092/// parser — a reconnection policy with a hole is not a policy).
3093#[derive(Debug, Clone, Serialize)]
3094pub struct IRUpstreamReconnect {
3095    pub backoff_ms: i64,
3096    pub max_attempts: i64,
3097    pub on_exhausted: String,
3098}
3099
3100/// v2.38.0 — a named, referenced browser-origin policy. Mirrors
3101/// `IRShield`'s field-for-field shape; consumed by `IRAxonEndpoint.cors_ref`.
3102/// Wildcard+credentials (T853), origin-glob shape (T854), and closed-method
3103/// (T855) violations are all rejected before this node is ever lowered — the
3104/// checker re-derives the same closed catalogs at deploy time (v2.38.0,
3105/// `CorsPolicyConsistency`), so an IR that reaches the runtime is already
3106/// proven consistent.
3107#[derive(Debug, Clone, Serialize)]
3108pub struct IRCors {
3109    pub node_type: &'static str,
3110    pub source_line: u32,
3111    pub source_column: u32,
3112    pub name: String,
3113    pub allow_origins: Vec<String>,
3114    pub allow_methods: Vec<String>,
3115    pub allow_headers: Vec<String>,
3116    pub allow_credentials: bool,
3117    /// Duration literal (`"3600s"`) — same string-carries-the-unit
3118    /// convention as `axonendpoint.timeout`; the consumer (enterprise's
3119    /// dynamic CORS middleware) parses it into seconds at request time.
3120    #[serde(default, skip_serializing_if = "Option::is_none")]
3121    pub max_age: Option<String>,
3122    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3123    pub expose_headers: Vec<String>,
3124}
3125
3126/// v2.40.0 — compiled `cache` policy. The checker (v2.40.0) re-derives the
3127/// same laws at the deploy gate (`CacheSoundness`), so an IR that reaches the
3128/// runtime is already proven sound (one default max, non-pure ⇒ finite ttl,
3129/// references resolve). Every optional field is `skip_serializing_if` so a
3130/// bundle using `cache` only pays IR bytes for what it declares, and a bundle
3131/// with no `cache` never emits a `caches` key (IR-SHA stable, v2.33.0).
3132#[derive(Debug, Clone, Serialize)]
3133pub struct IRCache {
3134    pub node_type: &'static str,
3135    pub source_line: u32,
3136    pub source_column: u32,
3137    pub name: String,
3138    /// `"redis"` | `"in_process"`; empty ⇒ runtime default (`in_process`).
3139    #[serde(default, skip_serializing_if = "String::is_empty")]
3140    pub backend: String,
3141    /// Duration literal (`"10s"`) — same string-carries-the-unit convention as
3142    /// `cors.max_age`. `None` ⇒ cache-forever (sound only for a `pure` cache).
3143    #[serde(default, skip_serializing_if = "Option::is_none")]
3144    pub ttl: Option<String>,
3145    /// The parameter-name subset forming the key; empty ⇒ all bound params.
3146    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3147    pub key_params: Vec<String>,
3148    /// `true` ⇒ auto-covers every eligible tool (at most one per module).
3149    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
3150    pub default_policy: bool,
3151    /// Effect classes this cache memoises; empty ⇒ `["pure"]`.
3152    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3153    pub apply_to_effects: Vec<String>,
3154    /// Channel names whose `emit` flushes this cache's namespace.
3155    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3156    pub invalidate_on: Vec<String>,
3157}
3158
3159/// Compiled emit step — `c⟨v⟩.P` (Chan-Output / Chan-Mobility).
3160///
3161/// `value_is_channel = true` ⇒ resolved at lowering time as a channel
3162/// handle (second-order mobility, paper section 3.2); the runtime dispatches
3163/// on this flag without re-resolving symbols.
3164#[derive(Debug, Clone, Serialize)]
3165pub struct IREmit {
3166    pub node_type: &'static str,
3167    pub source_line: u32,
3168    pub source_column: u32,
3169    pub channel_ref: String,
3170    pub value_ref: String,
3171    pub value_is_channel: bool,
3172    /// v2.69.0 (owed) — the σ-shield the target `channel` declares
3173    /// (`channel C { … shield: S }`), RESOLVED here at lowering (Phase 0
3174    /// pre-pass, like `IRPublish.sign`) so the runtime `run_emit` scans the
3175    /// emitted value through S on EVERY dispatch path without re-deriving the
3176    /// channel↔shield map. Empty ⇒ an unshielded channel (byte-identical to a
3177    /// pre-v2.69.0 emit: `skip_serializing_if` elides it → zero IR-SHA drift for
3178    /// programs whose channels declare no shield).
3179    #[serde(default, skip_serializing_if = "String::is_empty")]
3180    pub shield_ref: String,
3181    /// v2.69.0 — the σ-shield's breach policy, resolved at lowering beside
3182    /// `shield_ref` (same Phase 0 pre-pass). `None` ⇒ no `on_breach:` declared
3183    /// (halt, the fail-closed default); elided → zero IR-SHA drift.
3184    #[serde(default, skip_serializing_if = "Option::is_none")]
3185    pub breach_policy: Option<IRBreachPolicy>,
3186    /// v2.89.0 — the σ-shield's declared `scan:` list, resolved at lowering
3187    /// beside `shield_ref` (same Phase 0 pre-pass). See
3188    /// [`IRShieldApplyStep::scan`] for the argument; the egress site carries it
3189    /// for the same reason the apply site does, because an `emit` through a
3190    /// scanning channel makes the same assertion about the value that leaves.
3191    /// Empty ⇒ unshielded, or shielded by a shield declaring no `scan:`.
3192    #[serde(default, skip_serializing_if = "Vec::is_empty")]
3193    pub scan: Vec<String>,
3194}
3195
3196/// v2.46.0 — compiled `credential` contract. The TTL is carried as
3197/// SECONDS (converted at lowering from the duration literal) so every
3198/// consumer — the OSS minter port, the enterprise PASETO minter, the
3199/// deploy gate — shares one arithmetic-ready representation.
3200#[derive(Debug, Clone, Serialize)]
3201pub struct IRCredential {
3202    pub node_type: &'static str,
3203    pub source_line: u32,
3204    pub source_column: u32,
3205    pub name: String,
3206    /// The bearer lifetime in seconds (from the `ttl:` duration literal;
3207    /// `0` = unparseable, rejected by `axon-T894` before the IR ships).
3208    pub ttl_secs: u64,
3209    /// The capability slugs the minted bearer carries (validated dotted
3210    /// slugs; non-empty per `axon-T893`).
3211    pub grants: Vec<String>,
3212}
3213
3214/// v2.46.0 — compiled `mint <Credential> as <binding>` step. The runtime
3215/// resolves the contract, enforces the attenuation law
3216/// (`grants ⊆ capabilities(minter)`, fail-closed), mints via the
3217/// `CredentialMinter` port, and binds the raw bearer under `binding`.
3218#[derive(Debug, Clone, Serialize)]
3219pub struct IRMintStep {
3220    pub node_type: &'static str,
3221    pub source_line: u32,
3222    pub source_column: u32,
3223    pub credential_ref: String,
3224    pub binding: String,
3225}
3226
3227/// v2.48.0 — compiled `rotate <SecretsStore> [where "…"] with <Tool>
3228/// as <binding>` step. The runtime enumerates the custody entries of the
3229/// store's class matching `where_expr` (whole class when empty), performs
3230/// ONE mediated exchange per key through the named tool (reveal → tool
3231/// renews → CAS commit at version+1), and binds the METADATA-ONLY
3232/// summary. Fail-closed without a custody port; each per-key failure
3233/// degrades with a witness, never destructively.
3234#[derive(Debug, Clone, Serialize)]
3235pub struct IRRotateStep {
3236    pub node_type: &'static str,
3237    pub source_line: u32,
3238    pub source_column: u32,
3239    pub store_ref: String,
3240    /// v2.21.0 metadata filter; empty = the whole class (elided from the wire).
3241    #[serde(default, skip_serializing_if = "String::is_empty")]
3242    pub where_expr: String,
3243    pub tool_ref: String,
3244    pub binding: String,
3245}
3246
3247/// Compiled publish step — capability extrusion (Publish-Ext, paper section 4.3).
3248#[derive(Debug, Clone, Serialize)]
3249pub struct IRPublish {
3250    pub node_type: &'static str,
3251    pub source_line: u32,
3252    pub source_column: u32,
3253    pub channel_ref: String,
3254    pub shield_ref: String,
3255    /// v2.34.0 — the referenced shield's `sign:` algorithm, RESOLVED at
3256    /// lowering (order-independent pre-pass over every declared shield).
3257    /// Non-empty ⇒ this publish is an EGRESS declaration: the channel's
3258    /// events are signed-deliverable to registered external subscribers.
3259    /// Elided from JSON when empty (zero IR-SHA drift for pre-v2.34.0 programs).
3260    #[serde(skip_serializing_if = "String::is_empty")]
3261    pub sign: String,
3262}
3263
3264/// Compiled discover step — dual of publish.
3265#[derive(Debug, Clone, Serialize)]
3266pub struct IRDiscover {
3267    pub node_type: &'static str,
3268    pub source_line: u32,
3269    pub source_column: u32,
3270    pub capability_ref: String,
3271    pub alias: String,
3272}