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