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