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