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