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