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