axon_frontend/ir_nodes.rs
1//! AXON IR node definitions — direct port of axon/compiler/ir_nodes.py.
2//!
3//! All nodes serialize to JSON matching the Python IR output format exactly.
4
5#![allow(dead_code)]
6
7use serde::Serialize;
8
9// ── Program root ─────────────────────────────────────────────────────────────
10
11/// §Fase 112.b — `Clone` is additive and every member IR type already derives it.
12/// The Cognitive-I/O supervisor owns the compiled program it drives (it outlives
13/// the deploy call that built it), and a lifetime-bound supervisor would have to be
14/// threaded through `ServerState` for no benefit.
15#[derive(Debug, Clone, Serialize)]
16pub struct IRProgram {
17 pub node_type: &'static str,
18 pub source_line: u32,
19 pub source_column: u32,
20 pub personas: Vec<IRPersona>,
21 pub contexts: Vec<IRContext>,
22 pub anchors: Vec<IRAnchor>,
23 pub tools: Vec<IRToolSpec>,
24 pub memories: Vec<IRMemory>,
25 pub types: Vec<IRType>,
26 pub flows: Vec<IRFlow>,
27 pub runs: Vec<IRRun>,
28 pub imports: Vec<IRImport>,
29 pub agents: Vec<IRAgent>,
30 pub shields: Vec<IRShield>,
31 /// §Fase 71.a — temporal execution-window guards.
32 pub windows: Vec<IRWindow>,
33 /// §Fase 114.a — top-level `budget` declarations. A daemon's anonymous budget
34 /// stays on the daemon; these govern EVERY flow that calls the tools they
35 /// name, including the HTTP endpoints adopters actually deploy.
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
37 pub budgets: Vec<IRBudget>,
38 pub daemons: Vec<IRDaemon>,
39 pub ots_specs: Vec<IROts>,
40 pub pix_specs: Vec<IRPix>,
41 /// §Fase 62.0 — audit-chain (`ledger`) declarations. Distinct from
42 /// `pix_specs` (the retrieval navigator); a ledger binds a hash-linked
43 /// recorder to an audited surface.
44 pub ledger_specs: Vec<IRLedger>,
45 pub corpus_specs: Vec<IRCorpus>,
46 pub psyche_specs: Vec<IRPsyche>,
47 pub mandate_specs: Vec<IRMandate>,
48 pub lambda_data_specs: Vec<IRLambdaData>,
49 pub compute_specs: Vec<IRCompute>,
50 pub axonstore_specs: Vec<IRAxonStore>,
51 pub endpoints: Vec<IRAxonEndpoint>,
52 /// §Fase 53 — closed-catalog extension declarations (compiled).
53 /// `#[serde(skip)]` so the field is NOT emitted into the IR JSON —
54 /// this keeps the static IR-JSON drift-gate fixtures green without
55 /// regenerating them (the pattern `dataspace_specs` also used until
56 /// §108.b un-skipped it).
57 /// The in-memory field feeds the §53.c type-checker + §53.d PCC (both
58 /// read `&IRProgram`); soundness invariant #1 holds via SOURCE
59 /// re-derivation — both the prover and the verifier read the
60 /// source-derived IR, which carries the extensions. §53.x hardening
61 /// (optional): un-skip + regenerate fixtures + bind extensions into
62 /// the PCC `artifact_digest` (today the digest omits them; the
63 /// witness still binds them by re-derivation). Deterministically
64 /// sorted by `name` at the end of IR generation (§53.b founder
65 /// refinement B) so multi-file declaration order can never perturb
66 /// the proof-bundle hash.
67 #[serde(skip)]
68 pub extensions: Vec<IRExtension>,
69 /// §Fase 108.b — the compiled dataspace schemas, SERIALIZED into the
70 /// IR JSON (un-skipped). History: this field was `#[serde(skip)]` for
71 /// byte-identical parity with the retired Python reference frontend
72 /// (§8.2.h.1) — which meant the runtime literally could not see a
73 /// declared dataspace (the §108 ground-truth finding). The parity
74 /// constraint is gone; the deploy hook walks this field to
75 /// instantiate the deterministic columnar engine's stores. Additive
76 /// for consumers: no IR deserializer uses `deny_unknown_fields`
77 /// (verified 2026-07-12), and `IRProgram` is `Serialize`-only —
78 /// consumers re-derive from source.
79 pub dataspace_specs: Vec<IRDataspace>,
80 /// §λ-L-E Fase 1 — I/O cognitivo primitives (compiled).
81 pub resources: Vec<IRResource>,
82 pub fabrics: Vec<IRFabric>,
83 pub manifests: Vec<IRManifest>,
84 pub observations: Vec<IRObserve>,
85 /// §λ-L-E Fase 1 (Free Monad root) — populated when the program
86 /// declares manifests/observes. `None` ⇒ serialises as `null`
87 /// (matches Python when the field is `None`).
88 pub intention_tree: Option<IRIntentionTree>,
89 /// §λ-L-E Fase 3 — Control cognitivo primitives (compiled).
90 pub reconciles: Vec<IRReconcile>,
91 pub leases: Vec<IRLease>,
92 pub ensembles: Vec<IREnsemble>,
93 /// §λ-L-E Fase 4 — Topology + Session (compiled).
94 pub sessions: Vec<IRSession>,
95 pub topologies: Vec<IRTopology>,
96 /// §λ-L-E Fase 5 — Immune system (compiled).
97 pub immunes: Vec<IRImmune>,
98 pub reflexes: Vec<IRReflex>,
99 pub heals: Vec<IRHeal>,
100 /// §λ-L-E Fase 9 — UI cognitiva declarativa (compiled).
101 pub components: Vec<IRComponent>,
102 pub views: Vec<IRView>,
103 /// §λ-L-E Fase 13 — Mobile typed channels (compiled).
104 pub channels: Vec<IRChannel>,
105 /// §Fase 41.b — typed WebSocket transports (compiled). Each carries its
106 /// referenced `session` protocol + the credit-window backpressure so
107 /// axon-rs can realise the typed endpoint over a `tokio` WebSocket.
108 pub sockets: Vec<IRSocket>,
109 /// §Fase 51.c.2 — Pauli-sum observable declarations (compiled). Each carries
110 /// its real-coefficient × Pauli-string terms so axon-rs can build the
111 /// Hermitian measurement operator `M = Σ cₖ Pₖ` a `quant` block measures
112 /// against. `#[serde(skip)]` (like `extensions` / `dataspace_specs`) so the
113 /// static IR-JSON drift fixtures stay green; the in-memory field feeds the
114 /// §51.c.2 checker + the §51.d/e runtime. The checker resolves
115 /// `quant(observable: …)` against the AST symbol table, not this field.
116 #[serde(skip)]
117 pub observables: Vec<IRObservable>,
118 /// §Fase 69.a — Advantage-Witness declarations. `skip_serializing_if = empty`
119 /// keeps a witness-less program's IR JSON byte-identical (zero IR-SHA drift,
120 /// the §52/§67 pattern); when present it rides the IR to the enterprise
121 /// deploy/runtime evaluator (§69.b+).
122 #[serde(default, skip_serializing_if = "Vec::is_empty")]
123 pub witnesses: Vec<IRWitness>,
124 /// §Fase 80.b — outbound vendor connections (compiled). Each carries its
125 /// axon-facing session binding (`protocol`/`role`), the per-tenant config
126 /// keys (`resolve`/`secret`), the auth handshake, the total wire↔session
127 /// projection (`map`) and the reconnect/overflow policies, so axon-rs can
128 /// dial + transcode without vendor-specific code. `skip_serializing_if =
129 /// empty` keeps an upstream-less program's IR JSON byte-identical (zero
130 /// IR-SHA drift — the standing §76.d discipline).
131 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub upstreams: Vec<IRUpstream>,
133 /// §Fase 83.a — named, referenced browser-origin policies. `skip_serializing_if
134 /// = empty` keeps a cors-less program's IR JSON byte-identical (zero IR-SHA
135 /// drift — the standing §76.d discipline).
136 #[serde(default, skip_serializing_if = "Vec::is_empty")]
137 pub cors_policies: Vec<IRCors>,
138 /// §Fase 85.b — named, referenced result-memoization policies. Same
139 /// `skip_serializing_if = empty` IR-SHA discipline as `cors_policies`.
140 #[serde(default, skip_serializing_if = "Vec::is_empty")]
141 pub caches: Vec<IRCache>,
142 /// §Fase 92.a — ephemeral-credential contracts (`credential { ttl:
143 /// grants: }`), minted at runtime by the `mint` flow verb under the
144 /// attenuation law (`authority_only_attenuates`). Same
145 /// `skip_serializing_if = empty` IR-SHA discipline.
146 #[serde(default, skip_serializing_if = "Vec::is_empty")]
147 pub credentials: Vec<IRCredential>,
148 /// §Fase 87.a — long-horizon autonomous research primitives (compiled). Each
149 /// carries its domain, cognition params, memory binding, compute budget and
150 /// mandates so the enterprise engine (§87.h+) can drive the active-inference
151 /// loop. Same `skip_serializing_if = empty` IR-SHA discipline as `caches`
152 /// (a savant-less program's IR JSON stays byte-identical — zero drift).
153 #[serde(default, skip_serializing_if = "Vec::is_empty")]
154 pub savants: Vec<IRSavant>,
155 /// §Fase 99.b — compiled document declarations. Same
156 /// `skip_serializing_if = empty` IR-SHA discipline (a document-less
157 /// program's IR JSON stays byte-identical). Consumed by the runtime
158 /// `DocumentRenderer` tool + the `DocumentProvenanceSoundness` PCC class.
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
160 pub documents: Vec<IRDocument>,
161 /// §Fase 105 — compiled CRM delivery declarations. Same
162 /// `skip_serializing_if = empty` IR-SHA discipline (a delivery-less program's
163 /// IR JSON stays byte-identical). Consumed by the runtime delivery dispatch
164 /// (`axon::delivery`) + the `DeliveryProvenanceSoundness` PCC class (T920).
165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
166 pub deliveries: Vec<IRDeliver>,
167 /// §Fase 110 — governed human notifications (the third egress dual).
168 pub notifications: Vec<IRNotify>,
169 /// §Fase 87.d — dynamic tool-synthesis policies (compiled). Same
170 /// `skip_serializing_if = empty` IR-SHA discipline as `savants`.
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub synths: Vec<IRSynth>,
173 /// §Fase 88.a — authorization-scope policies (compiled). Same
174 /// `skip_serializing_if = empty` IR-SHA discipline as `synths`.
175 #[serde(default, skip_serializing_if = "Vec::is_empty")]
176 pub scopes: Vec<IRScope>,
177 /// §Fase 23 — algebraic effect declarations (compiled).
178 /// Each declared effect persists into IR so axon-rs can build the
179 /// per-effect operation table at startup. The CPS state graph for
180 /// perform/handle sites lives inline within IRFlow.steps (each
181 /// IRPerform / IRHandlerFrame carries its assigned state_id /
182 /// frame_id).
183 ///
184 /// **§Fase 120 — 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 /// §Fase 120 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 D120.2 resolves a bare
196 /// `perform Emit(x)` over.
197 ///
198 /// ⚠️ Reusing this field rather than adding one was a deliberate correction
199 /// mid-fase: a parallel `effect_specs` would have made TWO compiled
200 /// catalogs of one concept in one artifact — the §119.m.2 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 /// §Fase 115.e — 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 §76.d discipline).
212 #[serde(default, skip_serializing_if = "Vec::is_empty")]
213 pub modules: Vec<IRModuleProvenance>,
214}
215
216/// §Fase 115.e — 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/// §Fase 51.d.2 — 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/// §Fase 51.c.2 — 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/// §Fase 51.c.2 — 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/// §Fase 69.a — 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// ── §Fase 23 / §Fase 120 — Algebraic effect declarations ────────────────────
350//
351// §Fase 23 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: []`. §Fase 120 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// ── §λ-L-E Fase 1 — 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 /// §Fase 113 — the pool size. Until §113 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 /// §Fase 113 — **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 §113, 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 /// §Fase 113 — 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-§113 program
454 /// serializes byte-identically (IR-SHA stability, the §94.a `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// ── §λ-L-E Fase 1 — 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// ── §λ-L-E Fase 1 — 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// ── §λ-L-E Fase 1 — IRObserve ───────────────────────────────────────────────
511
512// ── §λ-L-E Fase 1 — 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 (Fase 2) 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/// Fase 1; 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// ── §λ-L-E Fase 3 — 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// ── §λ-L-E Fase 4 — IRSession / IRTopology ──────────────────────────────────
597
598/// One operation in a compiled session protocol
599/// (send / receive / loop / end / select / branch — §Fase 41.b 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 /// §Fase 41.b — labelled branches (only for `op == "select" | "branch"`;
608 /// §Fase 79.b reuses them for `op == "interrupt"`: `body` + `handler` arms).
609 #[serde(skip_serializing_if = "Vec::is_empty", default)]
610 pub branches: Vec<IRSessionBranch>,
611 /// §Fase 79.b — `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 §76.d/§77.a additive-only discipline).
614 #[serde(skip_serializing_if = "String::is_empty", default)]
615 pub binder: String,
616 /// §Fase 79.b — `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/// §Fase 41.b — 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// ── §λ-L-E Fase 5 — 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// ── §λ-L-E Fase 9 — 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 /// §Fase 115.e — `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-§115 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 /// §Fase 115.e — 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 /// §Fase 91.a — the frame's declared cognitive timezone (IANA name).
804 /// Elided when absent → pre-§91 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/// §Fase 58.c — 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 (§58 D1, single source of
833/// truth). Lossless round-trip is gated in §58.i.
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/// §Fase 58.c — 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 (§58.e) 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 /// §Fase 60 — `"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 /// §Fase 114.c — the `resource` this tool's channel runs on. Empty ⇒ legacy
869 /// form. Skip-if-empty ⇒ every pre-§114 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 /// §Fase 58.c — the tool's typed INPUT SCHEMA (D1). Distinct from the §32
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 (§58.d) and the runtime binds
879 /// structured args against (§58.e). Empty for a schema-less tool (D5).
880 pub parameters: Vec<IRToolParam>,
881 /// §Fase 58.c — 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 /// §Fase 116.a (D116.9) — the authorization scopes this tool's operation
886 /// requires: flat capability atoms, the `credential.grants` (§92) /
887 /// `requires_capabilities` (§51.x) vocabulary. `axon-T956` enforces subset
888 /// coverage at compile; the PCC `ScopeCoverage` witness rides the linked
889 /// IR. Elided when empty — every pre-§116 tool serializes byte-identically
890 /// (IR-SHA stability), and stored FlowIr hydrates via `default` (§49.f
891 /// mirror discipline).
892 #[serde(default, skip_serializing_if = "Vec::is_empty")]
893 pub requires: Vec<String>,
894 /// §Fase 94.c — 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-§94 tool serializes byte-identically
898 /// (IR-SHA stability).
899 #[serde(default, skip_serializing_if = "String::is_empty")]
900 pub secret: String,
901 /// §Fase 95.a — 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 §94 and
904 /// pre-§94 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 /// §Fase 84.b — Remote Hands. All three fields are `skip_serializing_if`
911 /// so a program using none of them serialises **byte-identically** to the
912 /// pre-§84 IR (the §76.d 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 /// §Fase 85.b — 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 /// §Fase 98.b — the closed-catalog web-acquisition config. `None` for
926 /// every non-scrape tool, and `skip_serializing_if` so the entire
927 /// pre-§98 corpus serialises byte-identically (the §76.d IR-SHA /
928 /// additive-only gate). Present ⇒ this tool acquires open-web content
929 /// (born Untrusted, D98.1) and its `effect_row` carries `web`.
930 #[serde(skip_serializing_if = "Option::is_none")]
931 pub scrape: Option<IRScrapeSpec>,
932}
933
934/// §Fase 98.b — 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 Fase 6.1 — κ 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 /// §Fase 68.b — the step's model-capability requirement (context window in
1053 /// tokens). `skip_serializing_if = Option::is_none` keeps every pre-§68 step's
1054 /// IR JSON byte-identical (no IR-SHA drift, D68.4); a legacy IR deserialises
1055 /// to `None` → the §68.c resolver picks the backend default exactly as today.
1056 #[serde(default, skip_serializing_if = "Option::is_none")]
1057 pub requires_context: Option<u32>,
1058 /// §Fase 91.a — 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-§91 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 /// §Fase 119 (D119.4) — governance applications scoped to this step.
1065 /// Elided when empty so every pre-§119 program's IR JSON stays
1066 /// byte-identical (no IR-SHA drift — the §68.b/§91.a discipline).
1067 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1068 pub guards: Vec<IRStepGuard>,
1069 /// §Fase 119.f — 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-§119.f program's IR JSON is byte-identical.
1074 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1075 pub pix_ops: Vec<IRFlowNode>,
1076 /// §Fase 119.n — 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-§119.n 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 /// §Fase 120 — 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 /// `fase_23` §3.1's `perform Emit(response.token)` performs the step's own
1087 /// result. Elided when empty, so every pre-§120 program's IR JSON stays
1088 /// byte-identical (the §68.b/§91.a/§119.n 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/// §Fase 119 (D119.4) — one step-scoped governance application. For a
1095/// `mandate` guard, dispatch must run the §119.b 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 /// Fase 19.e — 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 /// Fase 19.e — 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 /// §Fase 109 — 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 /// §Fase 120 — `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 /// §Fase 120 — `perform E.Op(args)`.
1218 Perform(IREffectPerform),
1219 /// §Fase 120 — `resume(v)`, the one-shot continuation invocation (D2).
1220 Resume(IREffectResume),
1221 /// §Fase 120 — `abort(v)`.
1222 Abort(IREffectAbort),
1223 /// §Fase 120 — `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 /// §Fase 119.m.3 — `<Agent>(arg, …)`. The 46th variant, and the one that
1233 /// makes §119.m.1's bounded control loop reachable from source.
1234 AgentCall(IRAgentCall),
1235 Listen(IRListenStep),
1236 DaemonStep(IRDaemonStepNode),
1237 /// §λ-L-E Fase 13 — π-calc output prefix (Chan-Output / Chan-Mobility).
1238 Emit(IREmit),
1239 /// §Fase 92.b — ephemeral-credential minting (attenuated, TTL-bounded).
1240 Mint(IRMintStep),
1241 /// §Fase 94.b — mediated secret renewal (`rotation_without_revelation`).
1242 Rotate(IRRotateStep),
1243 /// §λ-L-E Fase 13 — capability extrusion (Publish-Ext).
1244 Publish(IRPublish),
1245 /// §λ-L-E Fase 13 — 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 /// §Fase 88.a — the `warden` adversarial security-analysis block.
1253 Warden(IRWarden),
1254 /// §Fase 51.a — the `quant` cognitive block (Hilbert-space projection).
1255 Quant(IRQuant),
1256 /// §Fase 51.d.2 — the `yield` measurement point inside a `quant` block.
1257 Yield(IRYield),
1258 /// §Fase 52.c — `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 /// §Fase 119.f.8 — the evidence the deliberation reasons OVER, resolved
1279 /// against the flow bindings at dispatch. Elided when empty so every
1280 /// pre-§119.f.8 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 /// §Fase 119.f.8 — the deliberation's question. The README writes it in
1284 /// every one of its sixteen `reason { … }` blocks and, before this fase, it
1285 /// reached the model in none of them.
1286 #[serde(default, skip_serializing_if = "String::is_empty")]
1287 pub ask: String,
1288 /// §Fase 119.f.8 — 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}
1302
1303#[derive(Debug, Clone, Serialize)]
1304pub struct IRRefineStep {
1305 pub node_type: &'static str,
1306 pub source_line: u32,
1307 pub source_column: u32,
1308 pub target: String,
1309 pub strategy: String,
1310}
1311
1312#[derive(Debug, Clone, Serialize)]
1313pub struct IRWeaveStep {
1314 pub node_type: &'static str,
1315 pub source_line: u32,
1316 pub source_column: u32,
1317 pub sources: Vec<String>,
1318 pub target: String,
1319 pub format_type: String,
1320 pub priority: Vec<String>,
1321 pub style: String,
1322 /// §Fase 119.f.9 — the parts the synthesis must contain
1323 /// (`include: [summary, risks, recommendations]`). Elided when empty so
1324 /// every pre-§119.f.9 program's IR JSON is byte-identical (no IR-SHA drift).
1325 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1326 pub include: Vec<String>,
1327}
1328
1329#[derive(Debug, Clone, Serialize)]
1330pub struct IRUseToolStep {
1331 pub node_type: &'static str,
1332 pub source_line: u32,
1333 pub source_column: u32,
1334 pub tool_name: String,
1335 pub argument: String,
1336 /// §Fase 58.c — the bound keyword args of `use Tool(k = v, …)` (W1: the
1337 /// structured args survive to the IR, no longer collapsed to one opaque
1338 /// string). Empty for the legacy single-`on <arg>` form (`argument`
1339 /// carries that, D5).
1340 pub named_args: Vec<IRNamedArg>,
1341}
1342
1343#[derive(Debug, Clone, Serialize)]
1344pub struct IRRememberStep {
1345 pub node_type: &'static str,
1346 pub source_line: u32,
1347 pub source_column: u32,
1348 pub expression: String,
1349 pub memory_target: String,
1350}
1351
1352#[derive(Debug, Clone, Serialize)]
1353pub struct IRRecallStep {
1354 pub node_type: &'static str,
1355 pub source_line: u32,
1356 pub source_column: u32,
1357 pub query: String,
1358 pub memory_source: String,
1359}
1360
1361/// §Fase 70.a — the lowered form of a pure expression (`Expr`). Carried in the
1362/// IR for conditions the legacy `(condition, op, value)` triple cannot express.
1363/// Operators are canonical lowercase strings so the JSON is stable + readable;
1364/// the runtime evaluator (§70.f) matches on them. Externally-tagged by `kind`.
1365#[derive(Debug, Clone, Serialize)]
1366#[serde(tag = "kind", rename_all = "snake_case")]
1367pub enum IRExpr {
1368 /// A typed literal.
1369 Lit { lit: IRExprLit },
1370 /// A reference to a binding / dotted path.
1371 Ref { path: String },
1372 /// Unary op — `op ∈ {neg, not}`.
1373 Unary { op: String, operand: Box<IRExpr> },
1374 /// Binary op — `op ∈ {add,sub,mul,div,mod,eq,ne,lt,le,gt,ge,and,or}`.
1375 Binary {
1376 op: String,
1377 lhs: Box<IRExpr>,
1378 rhs: Box<IRExpr>,
1379 },
1380 /// §Fase 70.c — a closed-catalog builtin call. `args[0]` is the receiver.
1381 /// `builtin ∈ {length,count,is_empty,is_null,contains,starts_with,ends_with}`.
1382 Call {
1383 builtin: String,
1384 args: Vec<IRExpr>,
1385 },
1386 /// §Fase 70.d — field access on a non-reference base (the JSONB seam).
1387 Field {
1388 base: Box<IRExpr>,
1389 field: String,
1390 },
1391 /// §Fase 70.d — index access `base[index]`.
1392 Index {
1393 base: Box<IRExpr>,
1394 index: Box<IRExpr>,
1395 },
1396 /// §Fase 119.o — `let <name> = <value>` scoped over `<body>`. The lowered
1397 /// form of a `logic { let … return … }` chain: one `Let` per binding,
1398 /// nested, so each bound term is evaluated exactly once and shadowing falls
1399 /// out of the nesting.
1400 Let {
1401 name: String,
1402 value: Box<IRExpr>,
1403 body: Box<IRExpr>,
1404 },
1405}
1406
1407/// §Fase 70.a — a literal inside an [`IRExpr`].
1408#[derive(Debug, Clone, Serialize)]
1409#[serde(tag = "ty", rename_all = "snake_case")]
1410pub enum IRExprLit {
1411 Int { value: i64 },
1412 Float { value: f64 },
1413 Bool { value: bool },
1414 Str { value: String },
1415}
1416
1417#[derive(Debug, Clone, Serialize)]
1418pub struct IRConditional {
1419 pub node_type: &'static str,
1420 pub source_line: u32,
1421 pub source_column: u32,
1422 pub condition: String,
1423 pub comparison_op: String,
1424 pub comparison_value: String,
1425 pub then_body: Vec<IRFlowNode>,
1426 pub else_body: Vec<IRFlowNode>,
1427 pub conditions: Vec<(String, String, String)>,
1428 pub conjunctor: String,
1429 /// §Fase 70.a — the lowered expression form, present only for conditions
1430 /// the legacy triple cannot express. `skip_serializing_if` keeps the IR
1431 /// JSON (and its SHA) byte-identical for every pre-§70 program.
1432 #[serde(skip_serializing_if = "Option::is_none")]
1433 pub cond: Option<IRExpr>,
1434}
1435
1436#[derive(Debug, Clone, Serialize)]
1437pub struct IRForIn {
1438 pub node_type: &'static str,
1439 pub source_line: u32,
1440 pub source_column: u32,
1441 pub variable: String,
1442 pub iterable: String,
1443 pub body: Vec<IRFlowNode>,
1444}
1445
1446#[derive(Debug, Clone, Serialize)]
1447pub struct IRLetBinding {
1448 pub node_type: &'static str,
1449 pub source_line: u32,
1450 pub source_column: u32,
1451 pub target: String,
1452 pub value: String,
1453 /// Fase 17.a — preserves parser tokenization intent.
1454 /// One of "literal" | "reference" | "expression".
1455 pub value_kind: String,
1456 /// §Fase 70.f — the lowered expression form of the value, present only for
1457 /// `value_kind == "expression"`. The runtime evaluates it instead of
1458 /// treating the value string as an opaque literal. `skip_serializing_if`
1459 /// keeps the IR byte-identical for every literal / reference let.
1460 #[serde(skip_serializing_if = "Option::is_none")]
1461 pub value_ast: Option<IRExpr>,
1462}
1463
1464#[derive(Debug, Clone, Serialize)]
1465pub struct IRReturnStep {
1466 pub node_type: &'static str,
1467 pub source_line: u32,
1468 pub source_column: u32,
1469 pub value_expr: String,
1470}
1471
1472/// Fase 19.e — `break` keyword IR node. Payload-free (the runner
1473/// raises a sentinel; no value is carried). Mirrors Python's
1474/// ``IRBreak`` (axon/compiler/ir_nodes.py).
1475#[derive(Debug, Clone, Serialize)]
1476pub struct IRBreakStep {
1477 pub node_type: &'static str,
1478 pub source_line: u32,
1479 pub source_column: u32,
1480}
1481
1482/// Fase 19.e — `continue` keyword IR node. Same shape as
1483/// ``IRBreakStep``; the runner uses a different sentinel type to
1484/// distinguish loop-exit (break) from iteration-skip (continue).
1485#[derive(Debug, Clone, Serialize)]
1486pub struct IRContinueStep {
1487 pub node_type: &'static str,
1488 pub source_line: u32,
1489 pub source_column: u32,
1490}
1491
1492#[derive(Debug, Clone, Serialize)]
1493pub struct IRParallelBlock {
1494 pub node_type: &'static str,
1495 pub source_line: u32,
1496 pub source_column: u32,
1497 /// §Fase 65 — the concurrent branches lowered from the AST `par { … }`.
1498 /// Each branch is a flow-IR body run concurrently by the dispatcher's
1499 /// `run_branches_concurrently`. `skip_serializing_if = "Vec::is_empty"` so a
1500 /// payload-free / empty `par` serializes byte-identically to the pre-§65
1501 /// shape (D5 back-compat); a `par` with real branches carries them.
1502 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1503 pub branches: Vec<Vec<IRFlowNode>>,
1504}
1505
1506#[derive(Debug, Clone, Serialize)]
1507pub struct IRHibernateStep {
1508 pub node_type: &'static str,
1509 pub source_line: u32,
1510 pub source_column: u32,
1511 pub event_name: String,
1512 pub timeout: String,
1513}
1514
1515#[derive(Debug, Clone, Serialize)]
1516pub struct IRDeliberateBlock {
1517 pub node_type: &'static str,
1518 pub source_line: u32,
1519 pub source_column: u32,
1520}
1521
1522#[derive(Debug, Clone, Serialize)]
1523pub struct IRConsensusBlock {
1524 pub node_type: &'static str,
1525 pub source_line: u32,
1526 pub source_column: u32,
1527}
1528
1529/// §Fase 86 — the compiled Directed Creative Synthesis block. This IS the
1530/// "structured IR metadata that the runtime executes as an orchestrated
1531/// pipeline" the README always claimed — pre-§86 it carried only a source
1532/// location. New fields are `skip_serializing_if`-elided so a program with no
1533/// `forge` stays IR-SHA stable.
1534#[derive(Debug, Clone, Serialize, Default)]
1535pub struct IRForgeBlock {
1536 pub node_type: &'static str,
1537 pub source_line: u32,
1538 pub source_column: u32,
1539 #[serde(default, skip_serializing_if = "String::is_empty")]
1540 pub name: String,
1541 #[serde(default, skip_serializing_if = "String::is_empty")]
1542 pub seed: String,
1543 #[serde(default, skip_serializing_if = "String::is_empty")]
1544 pub output_type: String,
1545 #[serde(default, skip_serializing_if = "String::is_empty")]
1546 pub mode: String,
1547 #[serde(default, skip_serializing_if = "is_default_novelty")]
1548 pub novelty: f64,
1549 #[serde(default, skip_serializing_if = "is_one_i64")]
1550 pub depth: i64,
1551 #[serde(default, skip_serializing_if = "is_one_i64")]
1552 pub branches: i64,
1553 #[serde(default, skip_serializing_if = "String::is_empty")]
1554 pub constraints_ref: String,
1555}
1556
1557fn is_default_novelty(v: &f64) -> bool {
1558 (*v - 0.5).abs() < f64::EPSILON
1559}
1560fn is_one_i64(v: &i64) -> bool {
1561 *v == 1
1562}
1563
1564/// §Fase 109.a — the proof-carrying derivative. `original` is the
1565/// differentiated `let`'s expression; `derivatives[i]` = ∂original/∂wrt[i],
1566/// SIMPLIFIED (D109.4) — computed at compile time by the symbolic
1567/// differentiator and re-derived at deploy by PCC `GradientSoundness`.
1568/// `original: None` / empty `derivatives` only in a stale artifact — the
1569/// runtime fails CLOSED on it and the PCC refutes it.
1570#[derive(Debug, Clone, Serialize)]
1571pub struct IRGradStep {
1572 pub node_type: &'static str,
1573 pub source_line: u32,
1574 pub source_column: u32,
1575 /// The prior rich `let` differentiated.
1576 pub target: String,
1577 pub wrt: Vec<String>,
1578 /// Result binding (empty ⇒ `d_<target>`).
1579 pub output: String,
1580 pub original: Option<IRExpr>,
1581 pub derivatives: Vec<IRExpr>,
1582}
1583
1584#[derive(Debug, Clone, Serialize)]
1585pub struct IRFocusStep {
1586 pub node_type: &'static str,
1587 pub source_line: u32,
1588 pub source_column: u32,
1589 pub expression: String,
1590 /// §Fase 108.d — the data-plane `where:` (D108.9; empty ⇒ no filter).
1591 pub where_expr: String,
1592 /// §Fase 108.d — π: projected columns (empty ⇒ all).
1593 pub select: Vec<String>,
1594 /// §Fase 108.d — result binding (`as:`; empty ⇒ the dataspace name).
1595 pub output: String,
1596}
1597
1598#[derive(Debug, Clone, Serialize)]
1599pub struct IRAssociateStep {
1600 pub node_type: &'static str,
1601 pub source_line: u32,
1602 pub source_column: u32,
1603 pub left: String,
1604 pub right: String,
1605 pub using_field: String,
1606 /// §Fase 108.d — result binding (`as:`; empty ⇒ `<L>_<R>`).
1607 pub output: String,
1608}
1609
1610#[derive(Debug, Clone, Serialize)]
1611pub struct IRAggregateStep {
1612 pub node_type: &'static str,
1613 pub source_line: u32,
1614 pub source_column: u32,
1615 pub target: String,
1616 pub group_by: Vec<String>,
1617 pub alias: String,
1618 /// §Fase 108.d — the closed aggregate catalog entries, raw
1619 /// (`count`, `sum(score)`, …) — canonical spelling, T930-validated.
1620 pub compute: Vec<String>,
1621 /// §Fase 108.d — the data-plane `where:` (D108.9).
1622 pub where_expr: String,
1623}
1624
1625#[derive(Debug, Clone, Serialize)]
1626pub struct IRExploreStep {
1627 pub node_type: &'static str,
1628 pub source_line: u32,
1629 pub source_column: u32,
1630 pub target: String,
1631 pub limit: Option<i64>,
1632 /// §Fase 108.d — result binding (`as:`; empty ⇒ the target).
1633 pub output: String,
1634}
1635
1636#[derive(Debug, Clone, Serialize)]
1637pub struct IRIngestStep {
1638 pub node_type: &'static str,
1639 pub source_line: u32,
1640 pub source_column: u32,
1641 pub source: String,
1642 pub target: String,
1643 /// §Fase 108.c — the declared wire format (`csv` | `json`, validated
1644 /// by axon-T929). Empty only in a pre-108.c artifact — the runtime
1645 /// handler fails CLOSED on it.
1646 pub format: String,
1647 /// §Fase 108.c — bounds enforced on the raw byte stream BEFORE
1648 /// parsing (§100). `None` ⇒ the engine's conservative defaults.
1649 pub max_bytes: Option<u64>,
1650 pub max_rows: Option<u64>,
1651}
1652
1653/// §Fase 114.w — a shield's compiled BREACH POLICY, resolved onto the nodes
1654/// that enforce it (`IRShieldApplyStep` / `IREmit`) at LOWERING — the same
1655/// discipline as `IREmit.shield_ref`: the policy rides the artifact, so every
1656/// dispatch path honors it by construction (no per-ctx shield map a forgotten
1657/// site could miss). Before §114.w the whole `on_breach:` catalog
1658/// (`halt|sanitize_and_retry|escalate|quarantine|deflect`) was documented,
1659/// parsed, type-checked — and the runtime always `halt`ed.
1660#[derive(Debug, Clone, Serialize)]
1661pub struct IRBreachPolicy {
1662 /// The declared policy (validated against `VALID_ON_BREACH_POLICIES`).
1663 pub on_breach: String,
1664 /// The quarantine SINK name (`on_breach: quarantine` requires it, axon-T952).
1665 #[serde(default, skip_serializing_if = "String::is_empty")]
1666 pub quarantine: String,
1667 /// The canned safe reply (`on_breach: deflect` requires it, axon-T952).
1668 #[serde(default, skip_serializing_if = "String::is_empty")]
1669 pub deflect_message: String,
1670 /// Fields masked by `sanitize_and_retry` (requires ≥ 1, axon-T952).
1671 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1672 pub redact: Vec<String>,
1673 /// Re-scan budget for `sanitize_and_retry` (parser default: 3).
1674 pub max_retries: i64,
1675}
1676
1677#[derive(Debug, Clone, Serialize)]
1678pub struct IRShieldApplyStep {
1679 pub node_type: &'static str,
1680 pub source_line: u32,
1681 pub source_column: u32,
1682 pub shield_name: String,
1683 pub target: String,
1684 pub output_type: String,
1685 /// §Fase 114.w — the named shield's breach policy, resolved at lowering.
1686 /// `None` ⇒ the shield declares no `on_breach:` (halt, the fail-closed
1687 /// default) — and every pre-§114.w program serializes byte-identically.
1688 #[serde(default, skip_serializing_if = "Option::is_none")]
1689 pub breach_policy: Option<IRBreachPolicy>,
1690}
1691
1692#[derive(Debug, Clone, Serialize)]
1693pub struct IRStreamBlock {
1694 pub node_type: &'static str,
1695 pub source_line: u32,
1696 pub source_column: u32,
1697 /// §Fase 111.e — the block's lowered body. ADDITIVE: `skip_serializing_if`
1698 /// elides it when empty, so every pre-111 program's IR JSON stays
1699 /// byte-identical and a legacy IR deserialises to an empty body (which then
1700 /// executes as a no-op, exactly as before — no silent behaviour change for
1701 /// an artifact compiled by an older frontend).
1702 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1703 pub body: Vec<IRFlowNode>,
1704 /// §Fase 119.n — the `<T>` in `stream<T>`: the CHUNK type. Elided when the
1705 /// block was written without one.
1706 #[serde(default, skip_serializing_if = "String::is_empty")]
1707 pub chunk_type: String,
1708 /// §Fase 119.n — `on_chunk: { … }`, lowered. Run once per chunk with the
1709 /// chunk bound as `chunk`.
1710 #[serde(default, skip_serializing_if = "Option::is_none")]
1711 pub on_chunk: Option<Box<IRStep>>,
1712 /// §Fase 119.n — `on_complete: { … }`, lowered. Run once, after the source
1713 /// closes, with the accumulated stream bound as `complete`.
1714 #[serde(default, skip_serializing_if = "Option::is_none")]
1715 pub on_complete: Option<Box<IRStep>>,
1716 /// §Fase 119.n.3 — `on_error: { … }`, lowered. Run when the SOURCE fails,
1717 /// with the failure bound as `error`. Never fires for a failure of the
1718 /// author's own handlers, nor for cancellation.
1719 #[serde(default, skip_serializing_if = "Option::is_none")]
1720 pub on_error: Option<Box<IRStep>>,
1721}
1722
1723// ── §Fase 120 — algebraic effects, lowered ──────────────────────────────────
1724//
1725// ⚠️ Names deliberately prefixed `IREffect*`. `axon-rs/src/effects/ir.rs`
1726// already owns `IRPerform` / `IRHandlerFrame` / `IRHandlerClause` / `IRResume`
1727// / `IRAbort` / `IRForward`, and `axon-rs` re-exports THIS module — an
1728// unprefixed name would collide there and, worse, would read as the same type.
1729// It is not: that module's `Instruction` alphabet has an INERT catch-all
1730// (`Passthrough`), so a handler body lowered onto it would silently execute
1731// nothing. D120.1 puts the effect machine in the DISPATCHER, whose alphabet is
1732// `IRFlowNode`; these nodes are that alphabet's five new members.
1733
1734/// `handle E { clauses } in { body }`, lowered — one handler frame.
1735#[derive(Debug, Clone, Serialize)]
1736pub struct IREffectHandle {
1737 pub node_type: &'static str,
1738 pub source_line: u32,
1739 pub source_column: u32,
1740 pub effect_names: Vec<String>,
1741 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1742 pub clauses: Vec<IREffectClause>,
1743 /// The `in { … }` block — ORDINARY flow nodes. This field is D120.1.
1744 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1745 pub body: Vec<IRFlowNode>,
1746 /// Per-flow frame identity, allocated in source order. Distinguishes two
1747 /// frames over the same effect so `forward` can name which one it left.
1748 pub frame_id: u32,
1749}
1750
1751/// One clause of an [`IREffectHandle`]: `Op(params) -> { body }`.
1752#[derive(Debug, Clone, Serialize)]
1753pub struct IREffectClause {
1754 pub operation_name: String,
1755 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1756 pub parameter_names: Vec<String>,
1757 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1758 pub body: Vec<IRFlowNode>,
1759 pub source_line: u32,
1760 pub source_column: u32,
1761}
1762
1763/// `perform E.Op(args)`, lowered.
1764///
1765/// `effect_name` is ALWAYS populated here even when the source wrote the bare
1766/// form — the IR generator resolves it against the declared catalog (D120.2).
1767/// A node that reached this point with an empty `effect_name` is a compiler
1768/// bug, and the dispatcher fails closed on it rather than searching by
1769/// operation name alone (which would let the handler stack pick a frame the
1770/// author never named).
1771#[derive(Debug, Clone, Serialize)]
1772pub struct IREffectPerform {
1773 pub node_type: &'static str,
1774 pub source_line: u32,
1775 pub source_column: u32,
1776 pub effect_name: String,
1777 pub operation_name: String,
1778 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1779 pub arguments: Vec<String>,
1780 /// True when the SOURCE wrote `perform Op(x)` and the catalog supplied the
1781 /// effect. Kept so a diagnostic can quote what the author actually typed.
1782 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1783 pub resolved_from_bare: bool,
1784}
1785
1786/// `resume(v)`, lowered — invoke the captured one-shot continuation (D2).
1787#[derive(Debug, Clone, Serialize)]
1788pub struct IREffectResume {
1789 pub node_type: &'static str,
1790 pub source_line: u32,
1791 pub source_column: u32,
1792 #[serde(default, skip_serializing_if = "String::is_empty")]
1793 pub value_expr: String,
1794}
1795
1796/// `abort(v)`, lowered — leave the enclosing `handle` without resuming.
1797#[derive(Debug, Clone, Serialize)]
1798pub struct IREffectAbort {
1799 pub node_type: &'static str,
1800 pub source_line: u32,
1801 pub source_column: u32,
1802 #[serde(default, skip_serializing_if = "String::is_empty")]
1803 pub value_expr: String,
1804}
1805
1806/// `forward E.Op(args)`, lowered (D12) — propagate to the next OUTER frame.
1807#[derive(Debug, Clone, Serialize)]
1808pub struct IREffectForward {
1809 pub node_type: &'static str,
1810 pub source_line: u32,
1811 pub source_column: u32,
1812 pub effect_name: String,
1813 pub operation_name: String,
1814 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1815 pub arguments: Vec<String>,
1816}
1817
1818#[derive(Debug, Clone, Serialize)]
1819pub struct IRNavigateStep {
1820 /// §Fase 119.f — per-navigation depth override → `NavConfig.d_max`.
1821 #[serde(default, skip_serializing_if = "Option::is_none")]
1822 pub depth: Option<i64>,
1823 pub node_type: &'static str,
1824 pub source_line: u32,
1825 pub source_column: u32,
1826 pub pix_ref: String,
1827 pub corpus_ref: String,
1828 pub query: String,
1829 pub trail_enabled: bool,
1830 pub output_name: String,
1831 /// §Fase 63.B — MDN corpus-graph navigation: the seed document (`from:`).
1832 #[serde(default, skip_serializing_if = "String::is_empty")]
1833 pub seed: String,
1834 /// §Fase 63.B — MDN navigation budget (`budget:` = max documents).
1835 #[serde(default, skip_serializing_if = "Option::is_none")]
1836 pub budget: Option<i64>,
1837 /// §Fase 66 (Q2) — column-scope filter for a `corpus from axonstore`. A raw
1838 /// filter expr threaded to `read_all_store_rows` → `stream_retrieve` for
1839 /// BOTH the documents and edges stores, so the sourced MDN graph is scoped
1840 /// to a sub-tenant column (`where: "tenant_id == '${tenant_id}'"`). The
1841 /// §37.d filter compiler resolves `${name}` → `$N` bind params (injection-
1842 /// safe). Empty = no column filter (axon-tenant RLS scope only).
1843 #[serde(default, skip_serializing_if = "String::is_empty")]
1844 pub where_expr: String,
1845}
1846
1847#[derive(Debug, Clone, Serialize)]
1848pub struct IRDrillStep {
1849 pub node_type: &'static str,
1850 pub source_line: u32,
1851 pub source_column: u32,
1852 pub pix_ref: String,
1853 pub subtree_path: String,
1854 pub query: String,
1855 pub output_name: String,
1856}
1857
1858#[derive(Debug, Clone, Serialize)]
1859pub struct IRTrailStep {
1860 pub node_type: &'static str,
1861 pub source_line: u32,
1862 pub source_column: u32,
1863 pub navigate_ref: String,
1864}
1865
1866#[derive(Debug, Clone, Serialize)]
1867pub struct IRCorroborateStep {
1868 pub node_type: &'static str,
1869 pub source_line: u32,
1870 pub source_column: u32,
1871 pub navigate_ref: String,
1872 pub output_name: String,
1873}
1874
1875#[derive(Debug, Clone, Serialize)]
1876pub struct IROtsApplyStep {
1877 pub node_type: &'static str,
1878 pub source_line: u32,
1879 pub source_column: u32,
1880 pub ots_name: String,
1881 pub target: String,
1882 pub output_type: String,
1883}
1884
1885#[derive(Debug, Clone, Serialize)]
1886pub struct IRMandateApplyStep {
1887 pub node_type: &'static str,
1888 pub source_line: u32,
1889 pub source_column: u32,
1890 pub mandate_name: String,
1891 pub target: String,
1892 pub output_type: String,
1893}
1894
1895#[derive(Debug, Clone, Serialize)]
1896pub struct IRAgentCall {
1897 pub node_type: &'static str,
1898 pub source_line: u32,
1899 pub source_column: u32,
1900 /// The declared `agent` this call invokes. Resolved against
1901 /// `DispatchCtx::agent_specs` at dispatch; an unresolved name fails CLOSED
1902 /// (the §111.f compute doctrine — an empty catalog runs nothing).
1903 pub agent_name: String,
1904 /// Positional §119.f.10 subjects. Resolved against the flow bindings at
1905 /// dispatch, so `TrendAnalyzer(Gather.output)` hands the agent the prior
1906 /// step's VALUE.
1907 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1908 pub arguments: Vec<String>,
1909}
1910
1911#[derive(Debug, Clone, Serialize)]
1912pub struct IRComputeApplyStep {
1913 pub node_type: &'static str,
1914 pub source_line: u32,
1915 pub source_column: u32,
1916 pub compute_name: String,
1917 pub arguments: Vec<String>,
1918 pub output_name: String,
1919}
1920
1921#[derive(Debug, Clone, Serialize)]
1922pub struct IRListenStep {
1923 pub node_type: &'static str,
1924 pub source_line: u32,
1925 pub source_column: u32,
1926 pub channel: String,
1927 /// §λ-L-E Fase 13 D4 — true ⇒ `channel` is a declared
1928 /// `IRChannel` ref; false ⇒ legacy string topic.
1929 pub channel_is_ref: bool,
1930 pub event_alias: String,
1931 /// §Fase 52.a — the handler body's lowered flow-steps, executed per event /
1932 /// scheduled tick by the §52.c runtime. `skip_serializing_if` keeps a
1933 /// bodyless `listen`'s JSON byte-identical to the pre-§52.a shape (D8).
1934 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1935 pub body: Vec<IRFlowNode>,
1936}
1937
1938#[derive(Debug, Clone, Serialize)]
1939pub struct IRDaemonStepNode {
1940 pub node_type: &'static str,
1941 pub source_line: u32,
1942 pub source_column: u32,
1943 pub daemon_ref: String,
1944}
1945
1946#[derive(Debug, Clone, Serialize)]
1947pub struct IRPersistStep {
1948 pub node_type: &'static str,
1949 pub source_line: u32,
1950 pub source_column: u32,
1951 pub store_name: String,
1952 /// §Fase 35.o — declared `{ col: value }` field block (value
1953 /// expressions kept raw; interpolated at runtime). Empty ⇒ the
1954 /// runtime writes the flow's user bindings (v1.30.0 fallback).
1955 pub fields: Vec<(String, String)>,
1956}
1957
1958#[derive(Debug, Clone, Serialize)]
1959pub struct IRRetrieveStep {
1960 pub node_type: &'static str,
1961 pub source_line: u32,
1962 pub source_column: u32,
1963 pub store_name: String,
1964 pub where_expr: String,
1965 pub alias: String,
1966 /// §Fase 67.b — `order_by:` clause (raw `"col [asc|desc], …"`).
1967 /// `skip_serializing_if` empty so a store that doesn't order never
1968 /// perturbs the serialized IR bytes (the §52 brief-#33 /
1969 /// [[feedback-boot-hydrate-self-heal]] no-drift discipline).
1970 #[serde(skip_serializing_if = "String::is_empty", default)]
1971 pub order_by: String,
1972 /// §Fase 67.b — `limit:` clause (raw `"100"` or `"${max}"`).
1973 #[serde(skip_serializing_if = "String::is_empty", default)]
1974 pub limit_expr: String,
1975 /// §Fase 76.d — `aggregate:` clause (raw, closed catalog: `count` /
1976 /// `sum(col)` / `avg(col)` / `min(col)` / `max(col)`).
1977 /// `skip_serializing_if` empty so a non-aggregating retrieve never
1978 /// perturbs the serialized IR bytes (the same §67.b no-drift
1979 /// discipline — zero IR-SHA drift for existing programs).
1980 #[serde(skip_serializing_if = "String::is_empty", default)]
1981 pub aggregate: String,
1982 /// §Fase 76.d — `group_by:` clause (raw `"col, col2"`).
1983 #[serde(skip_serializing_if = "String::is_empty", default)]
1984 pub group_by: String,
1985 /// §Fase 85.b — `cache:` reference (a declared `cache` name). Empty ⇒
1986 /// uncached. Elided when empty (IR-SHA stable for cache-less retrieves).
1987 #[serde(skip_serializing_if = "String::is_empty", default)]
1988 pub cache: String,
1989}
1990
1991#[derive(Debug, Clone, Serialize)]
1992pub struct IRMutateStep {
1993 pub node_type: &'static str,
1994 pub source_line: u32,
1995 pub source_column: u32,
1996 pub store_name: String,
1997 pub where_expr: String,
1998 /// §Fase 35.p — declared `{ col: value }` SET assignments (value
1999 /// expressions kept raw; interpolated at runtime). Empty ⇒ the
2000 /// runtime writes the flow's user bindings (v1.31.0 fallback).
2001 pub fields: Vec<(String, String)>,
2002}
2003
2004#[derive(Debug, Clone, Serialize)]
2005pub struct IRPurgeStep {
2006 pub node_type: &'static str,
2007 pub source_line: u32,
2008 pub source_column: u32,
2009 pub store_name: String,
2010 pub where_expr: String,
2011}
2012
2013#[derive(Debug, Clone, Serialize)]
2014pub struct IRTransactBlock {
2015 pub node_type: &'static str,
2016 pub source_line: u32,
2017 pub source_column: u32,
2018}
2019
2020/// §Fase 88.a — IR for the `warden` adversarial-analysis block. Carries the
2021/// target, the mandatory `scope_ref`, and the recursively-lowered body so the
2022/// enterprise engine (§88.f) can drive the analysis and §88.c can enforce the
2023/// authorization discipline.
2024#[derive(Debug, Clone, Serialize)]
2025pub struct IRWarden {
2026 pub node_type: &'static str,
2027 pub source_line: u32,
2028 pub source_column: u32,
2029 pub target: String,
2030 pub scope_ref: String,
2031 /// Nested flow-body IR (recursively lowered).
2032 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2033 pub body: Vec<IRFlowNode>,
2034}
2035
2036/// §Fase 88.a — IR for a `scope` authorization-policy declaration.
2037#[derive(Debug, Clone, Serialize)]
2038pub struct IRScope {
2039 pub node_type: &'static str,
2040 pub source_line: u32,
2041 pub source_column: u32,
2042 pub name: String,
2043 pub targets: Vec<String>,
2044 pub depth: String,
2045 pub approver: String,
2046}
2047
2048/// §Fase 51.a — IR for the `quant` cognitive block (Hilbert-space projection).
2049/// Mirrors `ast::QuantBlock`. Optional attributes serialize only when present
2050/// (`skip_serializing_if`) so a bare `quant {}` lowers to a minimal node and
2051/// the JSON stays diff-stable. The body lowers recursively, like `par` branches.
2052#[derive(Debug, Clone, Serialize)]
2053pub struct IRQuant {
2054 pub node_type: &'static str,
2055 pub source_line: u32,
2056 pub source_column: u32,
2057 /// Encoding scheme surface spelling (`amplitude` | `angle`); `None` = default.
2058 #[serde(default, skip_serializing_if = "Option::is_none")]
2059 pub encoding: Option<String>,
2060 /// Referenced `Observable` (Pauli-sum) name; `None` if unspecified.
2061 #[serde(default, skip_serializing_if = "Option::is_none")]
2062 pub observable: Option<String>,
2063 /// Register width n; `None` = inferred.
2064 #[serde(default, skip_serializing_if = "Option::is_none")]
2065 pub qubits: Option<i64>,
2066 /// Variational circuit depth L; `None` = backend default.
2067 #[serde(default, skip_serializing_if = "Option::is_none")]
2068 pub depth: Option<i64>,
2069 /// Projected-kernel bandwidth γ (D7); `None` = backend default.
2070 #[serde(default, skip_serializing_if = "Option::is_none")]
2071 pub bandwidth: Option<f64>,
2072 /// §Fase 69.c — data re-uploading layers L (`None`/`1` = no re-uploading).
2073 /// `skip_serializing_if` keeps a non-re-uploading block's IR byte-identical.
2074 #[serde(default, skip_serializing_if = "Option::is_none")]
2075 pub reupload: Option<i64>,
2076 /// Algebraic-effect backend tag (`quant_sim` | `qpu_native`).
2077 pub effect: String,
2078 /// Nested flow-body IR (recursively lowered).
2079 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2080 pub body: Vec<IRFlowNode>,
2081}
2082
2083// ── Tier 2 IR nodes ─────────────────────────────────────────────────────────
2084
2085#[derive(Debug, Clone, Serialize)]
2086pub struct IRAgent {
2087 pub node_type: &'static str,
2088 pub source_line: u32,
2089 pub source_column: u32,
2090 pub name: String,
2091 pub goal: String,
2092 pub tools: Vec<String>,
2093 pub memory_ref: String,
2094 pub strategy: String,
2095 pub on_stuck: String,
2096 pub shield_ref: String,
2097 pub max_iterations: Option<i64>,
2098 pub max_tokens: Option<i64>,
2099 pub max_time: String,
2100 pub max_cost: Option<f64>,
2101}
2102
2103/// §Fase 71.a — the lowered temporal execution-window guard. The runtime
2104/// (§71.b) evaluates `is_in_window(now, tz, allow)`; the daemon binding +
2105/// coalesced defer ledger are §71.c/d.
2106#[derive(Debug, Clone, Serialize)]
2107pub struct IRWindow {
2108 pub node_type: &'static str,
2109 pub source_line: u32,
2110 pub source_column: u32,
2111 pub name: String,
2112 pub timezone: String,
2113 pub allow: Vec<IRWindowSpan>,
2114 /// §Fase 71.e — excluded dates (holidays): ISO `YYYY-MM-DD` literals. A tick
2115 /// whose local date is in this set is OUTSIDE regardless of the hour spans.
2116 /// `skip_serializing_if` keeps a holiday-less window's JSON byte-identical.
2117 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2118 pub exclude: Vec<String>,
2119 pub on_outside: String,
2120}
2121
2122/// §Fase 71.a — one allowed day/hour span.
2123#[derive(Debug, Clone, Serialize)]
2124pub struct IRWindowSpan {
2125 pub day_start: String,
2126 pub day_end: String,
2127 pub hour_start: i64,
2128 pub hour_end: i64,
2129}
2130
2131/// §Fase 72.a — the `budget { … }` linear-effect rate limit lowered to IR. Each
2132/// quota gates a declared tool's dispatch on a renewable token bucket (the §72.b
2133/// `RateLease`); `on_exhausted` is the exhaustion policy.
2134#[derive(Debug, Clone, Serialize)]
2135pub struct IRBudget {
2136 pub node_type: &'static str,
2137 pub source_line: u32,
2138 pub source_column: u32,
2139 /// §Fase 114.a — the name of a **top-level** `budget`. Empty ⇒ the anonymous
2140 /// daemon-attached form. Skip-if-empty ⇒ every pre-§114 program serializes
2141 /// byte-identically (IR-SHA stability).
2142 #[serde(default, skip_serializing_if = "String::is_empty")]
2143 pub name: String,
2144 pub quotas: Vec<IRBudgetQuota>,
2145 /// `block` (fail-closed) | `defer` (reschedule via the §71 defer ledger) |
2146 /// `shed` (skip the call). An omitted policy lowers to `block` (the safe
2147 /// fail-closed default).
2148 pub on_exhausted: String,
2149}
2150
2151/// §Fase 72.a — one quota: `<kind>: <limit> per <period> on Tool(<effect>)`.
2152#[derive(Debug, Clone, Serialize)]
2153pub struct IRBudgetQuota {
2154 /// `rate` (renewable bucket) | `max` (windowed hard cap, no intra-window refill).
2155 pub kind: String,
2156 /// Token allowance per period (> 0, validated by `axon-T831`).
2157 pub limit: i64,
2158 /// `second` | `minute` | `hour` | `day` (closed catalog, `axon-T832`).
2159 pub period: String,
2160 /// The declared tool this quota governs (`on Tool(X)`; resolved by `axon-T830`).
2161 pub effect: String,
2162}
2163
2164#[derive(Debug, Clone, Serialize)]
2165pub struct IRShield {
2166 pub node_type: &'static str,
2167 pub source_line: u32,
2168 pub source_column: u32,
2169 pub name: String,
2170 pub scan: Vec<String>,
2171 pub strategy: String,
2172 pub on_breach: String,
2173 pub severity: String,
2174 pub quarantine: String,
2175 /// §8.2.h.3 — Python emits concrete 0, not null. AST keeps `Option<i64>`
2176 /// so the parser can distinguish "not set"; IR lowering collapses.
2177 pub max_retries: i64,
2178 pub confidence_threshold: f64,
2179 pub allow_tools: Vec<String>,
2180 pub deny_tools: Vec<String>,
2181 pub sandbox: bool,
2182 pub redact: Vec<String>,
2183 pub log: String,
2184 pub deflect_message: String,
2185 // `taint` exists on `ShieldDefinition` (AST) but Python's reference
2186 // IRShield doesn't emit it. Hidden from JSON output for §8.2.h parity.
2187 #[serde(skip)]
2188 pub taint: String,
2189 /// §ESK Fase 6.1 — covered regulatory classes for this shield.
2190 pub compliance: Vec<String>,
2191 /// §Fase 77.a — egress signing algorithm (`hmac_sha256`; empty = the
2192 /// shield does not sign). Elided from JSON when empty so every pre-§77
2193 /// program's IR stays byte-identical (zero IR-SHA drift).
2194 #[serde(skip_serializing_if = "String::is_empty")]
2195 pub sign: String,
2196}
2197
2198#[derive(Debug, Clone, Serialize)]
2199pub struct IRPix {
2200 pub node_type: &'static str,
2201 pub source_line: u32,
2202 pub source_column: u32,
2203 pub name: String,
2204 pub source: String,
2205 pub depth: Option<i64>,
2206 pub branching: Option<i64>,
2207 pub model: String,
2208}
2209
2210/// §Fase 62.0 — the audit-chain (`ledger`) IR node. Same shape as [`IRPix`]
2211/// but a DISTINCT node (`node_type: "ledger"`): a ledger binds a hash-linked
2212/// recorder to an audited surface (`source`), retaining `depth` rows under a
2213/// `branching`-factor Merkle tree, hashed with `model`. Kept separate from
2214/// `IRPix` so the navigator and the audit chain never alias on the wire.
2215#[derive(Debug, Clone, Serialize)]
2216pub struct IRLedger {
2217 pub node_type: &'static str,
2218 pub source_line: u32,
2219 pub source_column: u32,
2220 pub name: String,
2221 pub source: String,
2222 pub depth: Option<i64>,
2223 pub branching: Option<i64>,
2224 pub model: String,
2225}
2226
2227#[derive(Debug, Clone, Serialize)]
2228pub struct IRPsyche {
2229 pub node_type: &'static str,
2230 pub source_line: u32,
2231 pub source_column: u32,
2232 pub name: String,
2233 pub dimensions: Vec<String>,
2234 pub manifold_noise: Option<f64>,
2235 pub manifold_momentum: Option<f64>,
2236 pub safety_constraints: Vec<String>,
2237 pub quantum_enabled: Option<bool>,
2238 pub inference_mode: String,
2239}
2240
2241#[derive(Debug, Clone, Serialize)]
2242pub struct IRCorpus {
2243 pub node_type: &'static str,
2244 pub source_line: u32,
2245 pub source_column: u32,
2246 pub name: String,
2247 pub documents: Vec<String>,
2248 /// §Fase 63.A — typed weighted edges. Non-empty ⇒ this corpus is an MDN
2249 /// graph `C = (D, R, τ, ω, σ)`; the runtime builds an `mdn::Corpus` from it.
2250 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2251 pub relations: Vec<IRCorpusRelation>,
2252 /// §Fase 63.C — `adaptive: true` enables the memory endofunctor on this
2253 /// corpus's navigations.
2254 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
2255 pub adaptive: bool,
2256 pub mcp_server: String,
2257 pub mcp_resource_uri: String,
2258 /// §Fase 64.A — when present, this is a DYNAMIC store-sourced MDN graph: the
2259 /// documents and typed edges are rows in two declared `axonstore`s and the
2260 /// runtime builds the `mdn::Corpus` from the live rows at navigate-time
2261 /// (per-tenant, growing). Absent ⇒ the static §63 corpus (byte-identical IR).
2262 #[serde(default, skip_serializing_if = "Option::is_none")]
2263 pub store_source: Option<IRCorpusStoreSource>,
2264}
2265
2266/// §Fase 63.A — a lowered MDN corpus-graph edge `(from, to, τ, ω)`.
2267#[derive(Debug, Clone, Serialize)]
2268pub struct IRCorpusRelation {
2269 pub etype: String,
2270 pub from: String,
2271 pub to: String,
2272 pub weight: f64,
2273}
2274
2275/// §Fase 64.A — the lowered store-mapping of a dynamic, `axonstore`-sourced MDN
2276/// corpus graph. `doc_store(doc_id, doc_title)` maps rows → nodes;
2277/// `edge_store(edge_from, edge_to, edge_type, edge_weight)` maps rows → typed
2278/// weighted edges. The runtime (§64.B) reads these stores tenant-scoped at
2279/// navigate-time to build the `mdn::Corpus`.
2280#[derive(Debug, Clone, Serialize)]
2281pub struct IRCorpusStoreSource {
2282 pub doc_store: String,
2283 pub doc_id: String,
2284 pub doc_title: String,
2285 pub edge_store: String,
2286 pub edge_from: String,
2287 pub edge_to: String,
2288 pub edge_type: String,
2289 pub edge_weight: String,
2290}
2291
2292/// §Fase 108.b — one compiled dataspace column. `column_type` is the
2293/// CANONICAL catalog name (`Text` / `Int` / `Float` / `Bool` /
2294/// `Timestamp` / `Json`) — aliases are resolved at IR generation, so
2295/// every downstream consumer (the engine's deploy hook, the §108.d PCC
2296/// class) reads one spelling.
2297#[derive(Debug, Clone, Serialize)]
2298pub struct IRDataspaceColumn {
2299 pub name: String,
2300 pub column_type: String,
2301}
2302
2303#[derive(Debug, Clone, Serialize)]
2304pub struct IRDataspace {
2305 pub node_type: &'static str,
2306 pub source_line: u32,
2307 pub source_column: u32,
2308 pub name: String,
2309 /// §Fase 108.b — the typed columnar schema (canonical type names).
2310 pub columns: Vec<IRDataspaceColumn>,
2311}
2312
2313#[derive(Debug, Clone, Serialize)]
2314pub struct IROts {
2315 pub node_type: &'static str,
2316 pub source_line: u32,
2317 pub source_column: u32,
2318 pub name: String,
2319 pub teleology: String,
2320 pub homotopy_search: String,
2321 pub loss_function: String,
2322}
2323
2324#[derive(Debug, Clone, Serialize)]
2325pub struct IRMandate {
2326 pub node_type: &'static str,
2327 pub source_line: u32,
2328 pub source_column: u32,
2329 pub name: String,
2330 pub constraint: String,
2331 pub kp: Option<f64>,
2332 pub ki: Option<f64>,
2333 pub kd: Option<f64>,
2334 pub tolerance: Option<f64>,
2335 pub max_steps: Option<i64>,
2336 /// §Fase 119.b — the declared stability hypotheses `(D, L)`, carried to the
2337 /// runtime as PROOF OBLIGATIONS: the static band check was conditional on
2338 /// them, so dispatch must verify them against the measured backend or
2339 /// refuse. `None` means nothing was statically promised — absence is
2340 /// visible, never defaulted.
2341 pub drift_bound: Option<f64>,
2342 pub lipschitz: Option<f64>,
2343 pub on_violation: String,
2344}
2345
2346#[derive(Debug, Clone, Serialize)]
2347pub struct IRCompute {
2348 pub node_type: &'static str,
2349 pub source_line: u32,
2350 pub source_column: u32,
2351 pub name: String,
2352 pub shield_ref: String,
2353 /// §Fase 111.f — typed parameters. ADDITIVE (`skip_serializing_if`), so every
2354 /// pre-111 program's IR JSON stays byte-identical.
2355 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2356 pub parameters: Vec<IRParameter>,
2357 /// §Fase 111.f — the declared result type.
2358 #[serde(default, skip_serializing_if = "String::is_empty")]
2359 pub return_type: String,
2360 /// §Fase 111.f — the lowered §70 expression the runtime evaluates natively.
2361 /// `None` ⇒ the apply is refused (axon-T941): a compute with no body cannot
2362 /// compute, and binding a placeholder string in its place is how the old
2363 /// runtime handed a downstream step the text `"compute:Name(args)"` where it
2364 /// expected a number.
2365 #[serde(default, skip_serializing_if = "Option::is_none")]
2366 pub body: Option<IRExpr>,
2367}
2368
2369#[derive(Debug, Clone, Serialize)]
2370pub struct IRDaemon {
2371 pub node_type: &'static str,
2372 pub source_line: u32,
2373 pub source_column: u32,
2374 pub name: String,
2375 pub goal: String,
2376 pub tools: Vec<String>,
2377 pub memory_ref: String,
2378 pub strategy: String,
2379 pub on_stuck: String,
2380 pub shield_ref: String,
2381 /// §Fase 71.c — the `window:` temporal binding (a `window` primitive name).
2382 /// Empty ⇒ no temporal guard; `skip_serializing_if` keeps a windowless
2383 /// daemon's JSON byte-identical (D8 zero-drift).
2384 #[serde(default, skip_serializing_if = "String::is_empty")]
2385 pub window_ref: String,
2386 /// §Fase 72.a — the `budget { … }` linear-effect rate limit. `None` ⇒ no
2387 /// budget; `skip_serializing_if` keeps a budgetless daemon's JSON
2388 /// byte-identical (D8 zero-drift).
2389 #[serde(default, skip_serializing_if = "Option::is_none")]
2390 pub budget: Option<IRBudget>,
2391 pub max_tokens: Option<i64>,
2392 pub max_time: String,
2393 pub max_cost: Option<f64>,
2394 /// §Fase 52.a — the daemon's `listen` listeners (channel + alias + handler
2395 /// body). Pre-§52.a these were DROPPED at lowering (the IR daemon carried no
2396 /// listeners at all); now they survive so the §52.c runtime can mount + run
2397 /// them and the §52.d enterprise supervisor can extract them per-tenant.
2398 /// `skip_serializing_if` keeps a listenerless daemon's JSON unchanged (D8).
2399 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2400 pub listeners: Vec<IRListenStep>,
2401 /// §Fase 52.d — the capability scope the daemon's runs are confined to
2402 /// (`requires: [cap, …]`). The enterprise supervisor mints a per-run
2403 /// principal scoped to exactly these. `skip_serializing_if` keeps a
2404 /// requires-less daemon's JSON byte-identical (D8).
2405 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2406 pub requires_capabilities: Vec<String>,
2407}
2408
2409// ── §Fase 87 — the long-horizon autonomous research primitive ────────────────
2410
2411/// §Fase 87.a — a compiled `savant` (long-horizon autonomous research
2412/// primitive). A governed orchestrator: the IR carries the declared surface so
2413/// the enterprise active-inference engine (§87.h+) can drive the FEP loop, and
2414/// the §87.c checker can bind `memory` to a declared store, `budget` to a §72
2415/// linear budget, and the body to a §79 interruptible session.
2416#[derive(Debug, Clone, Serialize)]
2417pub struct IRSavant {
2418 pub node_type: &'static str,
2419 pub source_line: u32,
2420 pub source_column: u32,
2421 pub name: String,
2422 pub domain: String,
2423 #[serde(default, skip_serializing_if = "Option::is_none")]
2424 pub cognition: Option<IRSavantCognition>,
2425 #[serde(default, skip_serializing_if = "Option::is_none")]
2426 pub memory: Option<IRSavantMemory>,
2427 #[serde(default, skip_serializing_if = "Option::is_none")]
2428 pub budget: Option<IRSavantBudget>,
2429 pub mandates: Vec<IRSavantMandate>,
2430}
2431
2432/// §Fase 87.a — the compiled `cognition { … }` sub-block (active-inference
2433/// engine parameters).
2434#[derive(Debug, Clone, Serialize)]
2435pub struct IRSavantCognition {
2436 /// `standard | deep | hyper` — HRR dimensionality tier (validated §87.b).
2437 pub depth: String,
2438 /// Expected-Free-Energy convergence bound (`> 0`, §87.b). `None` ⇒ default.
2439 #[serde(default, skip_serializing_if = "Option::is_none")]
2440 pub entropic_threshold: Option<f64>,
2441 /// `low | med | high` — explore/exploit balance (validated §87.b).
2442 pub divergence: String,
2443}
2444
2445/// §Fase 87.a — the compiled `memory { … }` sub-block (retention binding).
2446#[derive(Debug, Clone, Serialize)]
2447pub struct IRSavantMemory {
2448 /// A declared `memory`/`corpus` name (resolved §87.c). Empty ⇒ ephemeral.
2449 pub backend: String,
2450 /// Whether to index the corpus as a simplicial-complex graph (topological
2451 /// β_n reading).
2452 pub corpus_graph: bool,
2453 /// Per-tenant tensor partitioning level (enforced by the enterprise engine).
2454 pub isolation_level: String,
2455}
2456
2457/// §Fase 87.a — the compiled `budget { … }` sub-block (compute ceiling, bound to
2458/// a §72 linear budget in §87.c).
2459#[derive(Debug, Clone, Serialize)]
2460pub struct IRSavantBudget {
2461 /// Hard ceiling on FEP-loop iterations before the savant pauses.
2462 #[serde(default, skip_serializing_if = "Option::is_none")]
2463 pub max_iterations: Option<i64>,
2464 /// Hard ceiling on `synth` (§87.d) tool-creation events per mandate.
2465 #[serde(default, skip_serializing_if = "Option::is_none")]
2466 pub max_tool_synth: Option<i64>,
2467}
2468
2469/// §Fase 87.a — a compiled `mandate <Name> { … }` sub-block (one research goal).
2470#[derive(Debug, Clone, Serialize)]
2471pub struct IRSavantMandate {
2472 pub name: String,
2473 pub objective: String,
2474 pub output_type: String,
2475}
2476
2477// ── §Fase 99.b — Native Document Synthesis IR ─────────────────────────────────
2478
2479/// §Fase 99.b — a compiled document declaration. The runtime `DocumentRenderer`
2480/// tool serialises this to deterministic OOXML bytes (§99.e); the
2481/// `DocumentProvenanceSoundness` PCC class (§99.d) re-derives the barrier from
2482/// it. `blocks` is the closed-catalog body tree.
2483#[derive(Debug, Clone, Serialize)]
2484pub struct IRDocument {
2485 pub node_type: &'static str,
2486 pub source_line: u32,
2487 pub source_column: u32,
2488 pub name: String,
2489 /// `docx | pptx | xlsx`.
2490 pub target: String,
2491 #[serde(default, skip_serializing_if = "String::is_empty")]
2492 pub template: String,
2493 #[serde(default, skip_serializing_if = "String::is_empty")]
2494 pub provenance: String,
2495 /// The propagated effect row (`io`, `storage`, `sensitive:*`, `legal:*`).
2496 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2497 pub effect_row: Vec<String>,
2498 /// §Fase 99.d — the enclosing `epistemic { mode: … }` at compile time
2499 /// (`believe`/`know` vouch the whole document is ≥ believe, satisfying the
2500 /// assertion-laundering barrier without per-field `attribute:`). Empty at
2501 /// top level. Recorded so the `DocumentProvenanceSoundness` PCC class
2502 /// re-derives the barrier identically (no false refutation).
2503 #[serde(default, skip_serializing_if = "String::is_empty")]
2504 pub epistemic_mode: String,
2505 pub blocks: Vec<IRDocBlock>,
2506}
2507
2508/// §Fase 99.b — one compiled document block. `fields` preserves declaration
2509/// order (a `Vec` of `(name, value)`), so the render is deterministic.
2510#[derive(Debug, Clone, Serialize)]
2511pub struct IRDocBlock {
2512 pub kind: String,
2513 pub fields: Vec<IRDocField>,
2514 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2515 pub children: Vec<IRDocBlock>,
2516}
2517
2518/// §Fase 99.b — a compiled `(field, value)` pair. `kind` tags the value shape
2519/// (`text`|`ref`|`list`|`int`|`bool`) so the renderer + the barrier can
2520/// discriminate a literal from a flow-value reference without re-parsing.
2521#[derive(Debug, Clone, Serialize)]
2522pub struct IRDocField {
2523 pub name: String,
2524 /// `text | ref | list | int | bool`.
2525 pub kind: &'static str,
2526 #[serde(default, skip_serializing_if = "String::is_empty")]
2527 pub value: String,
2528 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2529 pub items: Vec<String>,
2530}
2531
2532// ── §Fase 105 — Governed CRM Delivery IR ─────────────────────────────────────
2533
2534/// §Fase 105 — a compiled delivery declaration. The runtime delivery dispatch
2535/// (`axon::delivery`) transduces this to the configured CRM engine; the
2536/// `DeliveryProvenanceSoundness` PCC class (T920) re-derives the barrier from it.
2537/// `ops` is the closed-catalog operation list.
2538/// §Fase 110 — the compiled `notify` declaration. `epistemic_mode`
2539/// records the enclosing vouch (the §99.d/§105 discipline) so T933
2540/// re-derives identically at deploy (PCC `NotificationProvenanceSoundness`).
2541#[derive(Debug, Clone, Serialize)]
2542pub struct IRNotify {
2543 pub node_type: &'static str,
2544 pub source_line: u32,
2545 pub source_column: u32,
2546 pub name: String,
2547 pub channel: String,
2548 /// The §94 secret-class ref (the recipient value NEVER rides the IR).
2549 pub to_secret: String,
2550 pub template: String,
2551 pub window: String,
2552 #[serde(default, skip_serializing_if = "String::is_empty")]
2553 pub provenance: String,
2554 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2555 pub effects: Vec<String>,
2556 #[serde(default, skip_serializing_if = "String::is_empty")]
2557 pub epistemic_mode: String,
2558}
2559
2560#[derive(Debug, Clone, Serialize)]
2561pub struct IRDeliver {
2562 pub node_type: &'static str,
2563 pub source_line: u32,
2564 pub source_column: u32,
2565 pub name: String,
2566 /// `crm`.
2567 pub target: String,
2568 /// `attached | cleared` (empty ⇒ `attached`). How field provenance crosses
2569 /// the boundary (D105.2) — the T920 barrier's subject.
2570 #[serde(default, skip_serializing_if = "String::is_empty")]
2571 pub provenance: String,
2572 /// The per-tenant credential key (§94 custody — resolved at dispatch, never
2573 /// in cognition).
2574 #[serde(default, skip_serializing_if = "String::is_empty")]
2575 pub secret: String,
2576 /// The propagated effect row (`web`, `sensitive:*`, `legal:*`).
2577 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2578 pub effect_row: Vec<String>,
2579 /// The enclosing `epistemic { mode: … }` at compile time (`believe`/`know`
2580 /// vouch the delivered values are ≥ believe, satisfying the T920 barrier for
2581 /// a `provenance: cleared` delivery). Empty at top level. Recorded so the
2582 /// `DeliveryProvenanceSoundness` PCC class re-derives the barrier identically.
2583 #[serde(default, skip_serializing_if = "String::is_empty")]
2584 pub epistemic_mode: String,
2585 pub ops: Vec<IRDeliverOp>,
2586}
2587
2588/// §Fase 105 — one compiled delivery operation. `fields` preserves declaration
2589/// order (a `Vec` of `(name, value)`) so the transduced request is deterministic.
2590/// Reuses [`IRDocField`] — the same `(name, kind, value)` shape a document block
2591/// field carries, so the barrier + transducer discriminate a literal from a
2592/// flow-value `ref` without re-parsing.
2593#[derive(Debug, Clone, Serialize)]
2594pub struct IRDeliverOp {
2595 /// `upsert_contact | create_deal | add_note`.
2596 pub kind: String,
2597 pub fields: Vec<IRDocField>,
2598}
2599
2600/// §Fase 87.d — a compiled `synth` dynamic tool-synthesis policy. The IR carries
2601/// the safety envelope so the enterprise Extism/WASM executor (§87.j) enforces
2602/// it; OSS ships a deny-by-default `SynthBackend` that refuses execution.
2603#[derive(Debug, Clone, Serialize)]
2604pub struct IRSynth {
2605 pub node_type: &'static str,
2606 pub source_line: u32,
2607 pub source_column: u32,
2608 pub name: String,
2609 pub target: String,
2610 pub risk: String,
2611 #[serde(default, skip_serializing_if = "String::is_empty")]
2612 pub language: String,
2613 pub sandbox: String,
2614 /// `required | none`; an omitted policy lowers to `required` (fail-closed).
2615 pub review: String,
2616 #[serde(default, skip_serializing_if = "Option::is_none")]
2617 pub max_lines: Option<i64>,
2618}
2619
2620// ── §Fase 53 — Closed-catalog extension mechanism ────────────────────────────
2621
2622/// §Fase 53 — one compiled member of an `extension`. For `effects`
2623/// the `name` is a provenance base; `default_confidence` is a CEILING
2624/// (§53.d tainted-overriding). Metadata is elided from JSON when absent
2625/// so the serialised shape stays minimal once the `extensions` field is
2626/// un-skipped alongside the Python IR mirror.
2627#[derive(Debug, Clone, Serialize)]
2628pub struct IRExtensionMember {
2629 pub name: String,
2630 #[serde(default, skip_serializing_if = "Option::is_none")]
2631 pub semantics: Option<String>,
2632 #[serde(default, skip_serializing_if = "Option::is_none")]
2633 pub default_confidence: Option<f64>,
2634}
2635
2636/// §Fase 53 — a compiled `extension` declaration. Rides in the IR (and,
2637/// once un-skipped, the proof bundle) so an independent PCC verifier
2638/// re-derives `is_known_base` against the artifact's own extensions
2639/// (soundness invariant #1). `category` ∈ {`effects`, `scan`} — the
2640/// type-checker (§53.c) enforces the closed category + no-shadowing +
2641/// provenance-class invariants before this IR is trusted.
2642#[derive(Debug, Clone, Serialize)]
2643pub struct IRExtension {
2644 pub node_type: &'static str,
2645 pub source_line: u32,
2646 pub source_column: u32,
2647 pub name: String,
2648 pub category: String,
2649 pub members: Vec<IRExtensionMember>,
2650}
2651
2652#[derive(Debug, Clone, Serialize)]
2653pub struct IRAxonStore {
2654 pub node_type: &'static str,
2655 pub source_line: u32,
2656 pub source_column: u32,
2657 pub name: String,
2658 pub backend: String,
2659 /// The DSN. **This is the field that actually runs.**
2660 ///
2661 /// `connection:` → `resolve_dsn` → a real sqlx `PgPool`. It is the sole DSN
2662 /// source for every store op in every deployed flow; there is no
2663 /// global-pool fallback. §113's census established this, and it is why
2664 /// §113 is delicate: moving authority to `resource` moves it *away* from
2665 /// the one field that governs anything, *toward* the half that governs
2666 /// nothing. A `resource:` that merely renames this string would be the
2667 /// nominal link — wired and hollow.
2668 ///
2669 /// §113: still parsed, but **deprecated in favour of [`Self::resource_ref`]**,
2670 /// and a store declared this way is INELIGIBLE for `lease` / `observe` /
2671 /// `reconcile`. *You cannot govern what you did not declare.*
2672 pub connection: String,
2673 /// §Fase 113 — the `resource` this store runs on (`axonstore U { resource: Db }`).
2674 ///
2675 /// When present, the store DERIVES its DSN (`resource.endpoint`), its pool
2676 /// size (`resource.capacity` — a knob that did not exist before §113; the
2677 /// pool was hardcoded at 10), and its sharing discipline
2678 /// (`resource.lifetime`, `axon-T941`) from the resource. **That derivation
2679 /// — not the reference — is what makes this real.**
2680 ///
2681 /// Empty ⇒ the legacy un-resourced form. Skip-if-empty ⇒ every pre-§113
2682 /// store serializes byte-identically (the §94.a `class` precedent).
2683 #[serde(default, skip_serializing_if = "String::is_empty")]
2684 pub resource_ref: String,
2685 pub confidence_floor: Option<f64>,
2686 pub isolation: String,
2687 pub on_breach: String,
2688 /// §Fase 35.j (D11) — Pillar IV: the capability slug required to
2689 /// access this store (empty = no gate).
2690 pub capability: String,
2691 /// §Fase 94.a — the secret-class prefix of a `backend: secrets`
2692 /// metadata store (`rotation_without_revelation`). Non-empty ⇔
2693 /// `backend == "secrets"` (both directions enforced by `axon-T900`
2694 /// before the IR ships). Elided from the wire when empty — every
2695 /// pre-§94 store serializes byte-identically (IR-SHA stability).
2696 #[serde(default, skip_serializing_if = "String::is_empty")]
2697 pub class: String,
2698 /// §Fase 38.b (D1) — the OPTIONAL column-schema declaration. Three
2699 /// closed forms (inline / manifest-ref / env-var). `None` means the
2700 /// 37.x runtime+deploy path applies verbatim (D5 absolute). The
2701 /// §38.d / §38.e type-checker proves every store reference against
2702 /// this when present. §Fase 94.a: for a `backend: secrets` store the
2703 /// generator synthesizes the FIXED metadata schema here (the artifact
2704 /// is self-describing; PCC and the deploy gate re-derive against it).
2705 #[serde(default, skip_serializing_if = "Option::is_none")]
2706 pub column_schema: Option<IRStoreColumnSchema>,
2707}
2708
2709/// §Fase 38.b (D1) — IR mirror of [`crate::store_schema::StoreColumnSchema`].
2710/// Serializes as a tagged union: `{"form": "inline" | "manifest_ref" |
2711/// "env_var", …}`.
2712#[derive(Debug, Clone, Serialize)]
2713#[serde(tag = "form", rename_all = "snake_case")]
2714pub enum IRStoreColumnSchema {
2715 Inline { columns: Vec<IRStoreColumn> },
2716 ManifestRef { qualified_name: String },
2717 EnvVar { var_name: String },
2718}
2719
2720/// §Fase 38.b (D1) — IR mirror of [`crate::store_schema::StoreColumn`].
2721/// The serialized `col_type` is the canonical PascalCase name (e.g.
2722/// `"Uuid"`, `"Int"`, `"Timestamptz"`).
2723#[derive(Debug, Clone, Serialize)]
2724pub struct IRStoreColumn {
2725 pub name: String,
2726 pub col_type: String,
2727 #[serde(default, skip_serializing_if = "is_false")]
2728 pub primary_key: bool,
2729 #[serde(default, skip_serializing_if = "is_false")]
2730 pub auto_increment: bool,
2731 #[serde(default, skip_serializing_if = "is_false")]
2732 pub not_null: bool,
2733 #[serde(default, skip_serializing_if = "is_false")]
2734 pub unique: bool,
2735 #[serde(default, skip_serializing_if = "String::is_empty")]
2736 pub default_value: String,
2737 /// §Fase 38.x.c (D2, D5) — `true` iff the column is declared with
2738 /// `GENERATED ALWAYS AS IDENTITY` or `GENERATED BY DEFAULT AS
2739 /// IDENTITY`. Distinct from `auto_increment` (legacy SERIAL via
2740 /// `nextval(...)` default). `skip_serializing_if` keeps IR JSON
2741 /// byte-identical to v1.38.2 for any column where `identity = false`.
2742 #[serde(default, skip_serializing_if = "is_false")]
2743 pub identity: bool,
2744 /// §Fase 73.f (D1) — `true` iff the column carries the `index`
2745 /// declaration. Surfaced into the IR so the deployment layer (the
2746 /// enterprise deploy gate) SEES the index as a declared capability and
2747 /// can materialize it (a GIN path index for a `Json`/`Jsonb` column, a
2748 /// b-tree otherwise) — never a silent out-of-band DBA action.
2749 /// `skip_serializing_if` keeps IR JSON byte-identical for any column
2750 /// where `indexed = false`.
2751 #[serde(default, skip_serializing_if = "is_false")]
2752 pub indexed: bool,
2753 /// §Fase 73.g (D1) — the OPTIONAL `Json<T>` shape-lens struct name on a
2754 /// `Json`/`Jsonb` column (`payload: Json<UserEvent>` → `Some("UserEvent")`).
2755 /// Surfaced into the IR so the PCC `JsonShapeSoundness` proof can
2756 /// RE-DERIVE, from the artifact alone, that every lens shape resolves
2757 /// to a declared struct `type` — the §73.a/§73.e lens well-formedness
2758 /// made an independently-verifiable proof object. `skip_serializing_if`
2759 /// keeps IR JSON byte-identical for any column with no shape lens.
2760 #[serde(default, skip_serializing_if = "Option::is_none")]
2761 pub json_shape: Option<String>,
2762}
2763
2764#[inline]
2765fn is_false(b: &bool) -> bool {
2766 !*b
2767}
2768
2769#[derive(Debug, Clone, Serialize)]
2770pub struct IRAxonEndpoint {
2771 pub node_type: &'static str,
2772 pub source_line: u32,
2773 pub source_column: u32,
2774 pub name: String,
2775 pub method: String,
2776 pub path: String,
2777 pub body_type: String,
2778 pub execute_flow: String,
2779 pub output_type: String,
2780 pub shield_ref: String,
2781 /// §8.2.h.3 — Python emits concrete `0`; AST stays `Option<i64>`.
2782 pub retries: i64,
2783 pub timeout: String,
2784 /// §ESK Fase 6.1 — κ regulatory class on the boundary.
2785 pub compliance: Vec<String>,
2786 /// §Fase 37.y (D1) — Path parameter names extracted from the
2787 /// `path:` string. Mirrors `AxonEndpointDefinition.path_params`.
2788 /// **`skip_serializing_if = Vec::is_empty`** so a pre-v1.38.5 IR
2789 /// JSON snapshot (without the field) is byte-identical to a
2790 /// v1.38.5 IR JSON for the same endpoint — D5 backwards-compat
2791 /// absolute. The runtime + adopter tools that consume the IR
2792 /// JSON parse `path_params` as an absent key → empty Vec via
2793 /// serde's `default` semantics.
2794 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2795 pub path_params: Vec<String>,
2796 /// §Fase 37.y (D2) — Query parameters from the inline
2797 /// `query: { … }` block. Mirrors `AxonEndpointDefinition.query_params`
2798 /// using `IRTypeField` (shared with body type fields → uniform
2799 /// downstream tooling). **`skip_serializing_if = Vec::is_empty`**
2800 /// — same D5 IR-JSON byte-identity guarantee as `path_params`.
2801 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2802 pub query_params: Vec<IRTypeField>,
2803 /// §Fase 51.x — capability scopes the request bearer must hold
2804 /// (the `requires: [scope.dotted]` declaration, §Fase 32.g). Mirror
2805 /// of `AxonEndpointDefinition.requires_capabilities`, lowered into
2806 /// the IR so the PCC CapabilityContainment property can prove that
2807 /// the stores this endpoint's flow reaches are all covered by the
2808 /// declared requires. **`skip_serializing_if = Vec::is_empty`** so a
2809 /// pre-§51.x IR-JSON snapshot (no `requires:`) stays byte-identical
2810 /// (D5 backwards-compat — empty key parses back to empty Vec).
2811 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2812 pub requires_capabilities: Vec<String>,
2813 /// §Fase 83.a — the `cors: <Name>` reference, or `""` when absent
2814 /// (D83.5: no CORS headers, ever). NEW field on an EXISTING struct —
2815 /// `skip_serializing_if` (not `shield_ref`'s bare/always-emitted
2816 /// historical shape) so a cors-less endpoint's IR stays byte-identical
2817 /// to pre-§83 (zero IR-SHA drift — the standing §76.d discipline).
2818 #[serde(default, skip_serializing_if = "String::is_empty")]
2819 pub cors_ref: String,
2820 /// §Fase 89.a — the explicit authorization-coverage opt-out lowered into
2821 /// the IR so the enterprise runtime (§89.d) and the PCC
2822 /// `AuthorizationCoverage` witness (§89.c) can read it. `false` (the
2823 /// default + the common case) elides from JSON via `is_false` so a
2824 /// pre-§89 IR-JSON snapshot stays byte-identical (zero IR-SHA drift —
2825 /// the standing §76.d discipline).
2826 #[serde(default, skip_serializing_if = "is_false")]
2827 pub public: bool,
2828}
2829
2830// ── §λ-L-E Fase 13 — Mobile Typed Channels IR ───────────────────────────────
2831
2832/// Compiled `channel Name { … }` declaration.
2833///
2834/// Direct port of `axon.compiler.ir_nodes.IRChannel`. Lives in
2835/// `IRProgram.channels`; emit/publish/discover reductions embed in
2836/// their containing flow/listener (paper §3 + §4 — π-calc prefix
2837/// discipline preserved structurally, not lifted to top-level ops).
2838#[derive(Debug, Clone, Serialize)]
2839pub struct IRChannel {
2840 pub node_type: &'static str,
2841 pub source_line: u32,
2842 pub source_column: u32,
2843 pub name: String,
2844 pub message: String, // surface spelling — Order | Channel<Order> | …
2845 pub qos: String,
2846 pub lifetime: String,
2847 pub persistence: String,
2848 pub shield_ref: String,
2849 /// §Fase 77.b — non-empty ⇒ some `publish <this> within <Shield>` site
2850 /// referenced a SIGNING shield: the channel is an EGRESS channel and
2851 /// its durable events are signed-deliverable to registered external
2852 /// subscribers under this algorithm (first publish site wins;
2853 /// deterministic — the catalog has one algorithm in v1). Elided from
2854 /// JSON when empty (zero IR-SHA drift for pre-§77 programs).
2855 #[serde(skip_serializing_if = "String::is_empty")]
2856 pub egress_sign: String,
2857}
2858
2859/// §Fase 41.b — compiled typed WebSocket transport. `protocol` names the
2860/// `session` it carries; `backpressure_credit` is the typed-resource window
2861/// (`null` if unspecified). axon-rs realises the endpoint over a `tokio` WS,
2862/// crediting/decrementing the window per §4.2 of the paper.
2863#[derive(Debug, Clone, Serialize)]
2864pub struct IRSocket {
2865 pub node_type: &'static str,
2866 pub source_line: u32,
2867 pub source_column: u32,
2868 pub name: String,
2869 pub protocol: String,
2870 pub backpressure_credit: Option<i64>,
2871 pub reconnect: bool,
2872 pub legal_basis: Option<String>,
2873}
2874
2875/// §Fase 80.b — compiled outbound vendor connection (the client dual of
2876/// [`IRSocket`]). `protocol`/`role` bind the axon-facing session interface;
2877/// `resolve`/`secret` are per-tenant config keys (never literals — T850);
2878/// `map` is the compile-time-total wire↔session projection (T849). Optional
2879/// fields elide when absent so the IR shape is purely additive.
2880#[derive(Debug, Clone, Serialize)]
2881pub struct IRUpstream {
2882 pub node_type: &'static str,
2883 pub source_line: u32,
2884 pub source_column: u32,
2885 pub name: String,
2886 pub transport: String,
2887 pub protocol: String,
2888 pub role: String,
2889 pub resolve: String,
2890 /// §Fase 114.u — the `resource` this upstream's channel rides. When set,
2891 /// `resolve` above was DERIVED from the resource's `endpoint` at LOWERING
2892 /// (the §114 shield-egress discipline: derivation stamped into the
2893 /// artifact reaches every dial path by construction — no per-site wiring
2894 /// to forget) and `capacity` below carries the resource's bound. Elided
2895 /// when empty — every pre-§114.u upstream serializes byte-identically.
2896 #[serde(default, skip_serializing_if = "String::is_empty")]
2897 pub resource_ref: String,
2898 /// §Fase 114.u — **max concurrent connection INSTANCES** of this upstream
2899 /// (from `resource.capacity`). Frames are already flow-controlled by
2900 /// `backpressure_credit`; this bounds CONNECTIONS. The runtime holds a
2901 /// per-process semaphore permit for the life of each dialed handle —
2902 /// the same in-memory/per-process bound §114.e documented for tools.
2903 #[serde(default, skip_serializing_if = "Option::is_none")]
2904 pub capacity: Option<i64>,
2905 pub secret: String,
2906 pub auth_kind: String,
2907 #[serde(default, skip_serializing_if = "Option::is_none")]
2908 pub auth_name: Option<String>,
2909 #[serde(default, skip_serializing_if = "Option::is_none")]
2910 pub auth_prefix: Option<String>,
2911 pub map: Vec<IRUpstreamMapRule>,
2912 #[serde(default, skip_serializing_if = "Option::is_none")]
2913 pub reconnect: Option<IRUpstreamReconnect>,
2914 #[serde(default, skip_serializing_if = "Option::is_none")]
2915 pub overflow: Option<String>,
2916 #[serde(default, skip_serializing_if = "Option::is_none")]
2917 pub backpressure_credit: Option<i64>,
2918 /// §80.f — the `Preset@vN` reference this declaration was expanded from
2919 /// (provenance for the compliance reviewer); absent for hand-written ones.
2920 #[serde(default, skip_serializing_if = "Option::is_none")]
2921 pub preset: Option<String>,
2922}
2923
2924/// §Fase 80.b — one compiled `map:` projection rule.
2925#[derive(Debug, Clone, Serialize)]
2926pub struct IRUpstreamMapRule {
2927 pub node_type: &'static str,
2928 pub direction: String,
2929 pub message: String,
2930 pub framing: String,
2931 #[serde(default, skip_serializing_if = "Option::is_none")]
2932 pub tag: Option<String>,
2933 #[serde(default, skip_serializing_if = "Option::is_none")]
2934 pub when_field: Option<String>,
2935 #[serde(default, skip_serializing_if = "Option::is_none")]
2936 pub when_value: Option<String>,
2937}
2938
2939/// §Fase 80.b — compiled reconnect policy (all three fields required by the
2940/// parser — a reconnection policy with a hole is not a policy).
2941#[derive(Debug, Clone, Serialize)]
2942pub struct IRUpstreamReconnect {
2943 pub backoff_ms: i64,
2944 pub max_attempts: i64,
2945 pub on_exhausted: String,
2946}
2947
2948/// §Fase 83.a — a named, referenced browser-origin policy. Mirrors
2949/// `IRShield`'s field-for-field shape; consumed by `IRAxonEndpoint.cors_ref`.
2950/// Wildcard+credentials (T853), origin-glob shape (T854), and closed-method
2951/// (T855) violations are all rejected before this node is ever lowered — the
2952/// checker re-derives the same closed catalogs at deploy time (§83.c,
2953/// `CorsPolicyConsistency`), so an IR that reaches the runtime is already
2954/// proven consistent.
2955#[derive(Debug, Clone, Serialize)]
2956pub struct IRCors {
2957 pub node_type: &'static str,
2958 pub source_line: u32,
2959 pub source_column: u32,
2960 pub name: String,
2961 pub allow_origins: Vec<String>,
2962 pub allow_methods: Vec<String>,
2963 pub allow_headers: Vec<String>,
2964 pub allow_credentials: bool,
2965 /// Duration literal (`"3600s"`) — same string-carries-the-unit
2966 /// convention as `axonendpoint.timeout`; the consumer (enterprise's
2967 /// dynamic CORS middleware) parses it into seconds at request time.
2968 #[serde(default, skip_serializing_if = "Option::is_none")]
2969 pub max_age: Option<String>,
2970 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2971 pub expose_headers: Vec<String>,
2972}
2973
2974/// §Fase 85.b — compiled `cache` policy. The checker (§85.c) re-derives the
2975/// same laws at the deploy gate (`CacheSoundness`), so an IR that reaches the
2976/// runtime is already proven sound (one default max, non-pure ⇒ finite ttl,
2977/// references resolve). Every optional field is `skip_serializing_if` so a
2978/// bundle using `cache` only pays IR bytes for what it declares, and a bundle
2979/// with no `cache` never emits a `caches` key (IR-SHA stable, §76.d).
2980#[derive(Debug, Clone, Serialize)]
2981pub struct IRCache {
2982 pub node_type: &'static str,
2983 pub source_line: u32,
2984 pub source_column: u32,
2985 pub name: String,
2986 /// `"redis"` | `"in_process"`; empty ⇒ runtime default (`in_process`).
2987 #[serde(default, skip_serializing_if = "String::is_empty")]
2988 pub backend: String,
2989 /// Duration literal (`"10s"`) — same string-carries-the-unit convention as
2990 /// `cors.max_age`. `None` ⇒ cache-forever (sound only for a `pure` cache).
2991 #[serde(default, skip_serializing_if = "Option::is_none")]
2992 pub ttl: Option<String>,
2993 /// The parameter-name subset forming the key; empty ⇒ all bound params.
2994 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2995 pub key_params: Vec<String>,
2996 /// `true` ⇒ auto-covers every eligible tool (at most one per module).
2997 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
2998 pub default_policy: bool,
2999 /// Effect classes this cache memoises; empty ⇒ `["pure"]`.
3000 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3001 pub apply_to_effects: Vec<String>,
3002 /// Channel names whose `emit` flushes this cache's namespace.
3003 #[serde(default, skip_serializing_if = "Vec::is_empty")]
3004 pub invalidate_on: Vec<String>,
3005}
3006
3007/// Compiled emit step — `c⟨v⟩.P` (Chan-Output / Chan-Mobility).
3008///
3009/// `value_is_channel = true` ⇒ resolved at lowering time as a channel
3010/// handle (second-order mobility, paper §3.2); the runtime dispatches
3011/// on this flag without re-resolving symbols.
3012#[derive(Debug, Clone, Serialize)]
3013pub struct IREmit {
3014 pub node_type: &'static str,
3015 pub source_line: u32,
3016 pub source_column: u32,
3017 pub channel_ref: String,
3018 pub value_ref: String,
3019 pub value_is_channel: bool,
3020 /// §Fase 114 (owed) — the σ-shield the target `channel` declares
3021 /// (`channel C { … shield: S }`), RESOLVED here at lowering (Phase 0
3022 /// pre-pass, like `IRPublish.sign`) so the runtime `run_emit` scans the
3023 /// emitted value through S on EVERY dispatch path without re-deriving the
3024 /// channel↔shield map. Empty ⇒ an unshielded channel (byte-identical to a
3025 /// pre-§114 emit: `skip_serializing_if` elides it → zero IR-SHA drift for
3026 /// programs whose channels declare no shield).
3027 #[serde(default, skip_serializing_if = "String::is_empty")]
3028 pub shield_ref: String,
3029 /// §Fase 114.w — the σ-shield's breach policy, resolved at lowering beside
3030 /// `shield_ref` (same Phase 0 pre-pass). `None` ⇒ no `on_breach:` declared
3031 /// (halt, the fail-closed default); elided → zero IR-SHA drift.
3032 #[serde(default, skip_serializing_if = "Option::is_none")]
3033 pub breach_policy: Option<IRBreachPolicy>,
3034}
3035
3036/// §Fase 92.a — compiled `credential` contract. The TTL is carried as
3037/// SECONDS (converted at lowering from the duration literal) so every
3038/// consumer — the OSS minter port, the enterprise PASETO minter, the
3039/// deploy gate — shares one arithmetic-ready representation.
3040#[derive(Debug, Clone, Serialize)]
3041pub struct IRCredential {
3042 pub node_type: &'static str,
3043 pub source_line: u32,
3044 pub source_column: u32,
3045 pub name: String,
3046 /// The bearer lifetime in seconds (from the `ttl:` duration literal;
3047 /// `0` = unparseable, rejected by `axon-T894` before the IR ships).
3048 pub ttl_secs: u64,
3049 /// The capability slugs the minted bearer carries (validated dotted
3050 /// slugs; non-empty per `axon-T893`).
3051 pub grants: Vec<String>,
3052}
3053
3054/// §Fase 92.b — compiled `mint <Credential> as <binding>` step. The runtime
3055/// resolves the contract, enforces the attenuation law
3056/// (`grants ⊆ capabilities(minter)`, fail-closed), mints via the
3057/// `CredentialMinter` port, and binds the raw bearer under `binding`.
3058#[derive(Debug, Clone, Serialize)]
3059pub struct IRMintStep {
3060 pub node_type: &'static str,
3061 pub source_line: u32,
3062 pub source_column: u32,
3063 pub credential_ref: String,
3064 pub binding: String,
3065}
3066
3067/// §Fase 94.b — compiled `rotate <SecretsStore> [where "…"] with <Tool>
3068/// as <binding>` step. The runtime enumerates the custody entries of the
3069/// store's class matching `where_expr` (whole class when empty), performs
3070/// ONE mediated exchange per key through the named tool (reveal → tool
3071/// renews → CAS commit at version+1), and binds the METADATA-ONLY
3072/// summary. Fail-closed without a custody port; each per-key failure
3073/// degrades with a witness, never destructively.
3074#[derive(Debug, Clone, Serialize)]
3075pub struct IRRotateStep {
3076 pub node_type: &'static str,
3077 pub source_line: u32,
3078 pub source_column: u32,
3079 pub store_ref: String,
3080 /// §67 metadata filter; empty = the whole class (elided from the wire).
3081 #[serde(default, skip_serializing_if = "String::is_empty")]
3082 pub where_expr: String,
3083 pub tool_ref: String,
3084 pub binding: String,
3085}
3086
3087/// Compiled publish step — capability extrusion (Publish-Ext, paper §4.3).
3088#[derive(Debug, Clone, Serialize)]
3089pub struct IRPublish {
3090 pub node_type: &'static str,
3091 pub source_line: u32,
3092 pub source_column: u32,
3093 pub channel_ref: String,
3094 pub shield_ref: String,
3095 /// §Fase 77.b — the referenced shield's `sign:` algorithm, RESOLVED at
3096 /// lowering (order-independent pre-pass over every declared shield).
3097 /// Non-empty ⇒ this publish is an EGRESS declaration: the channel's
3098 /// events are signed-deliverable to registered external subscribers.
3099 /// Elided from JSON when empty (zero IR-SHA drift for pre-§77 programs).
3100 #[serde(skip_serializing_if = "String::is_empty")]
3101 pub sign: String,
3102}
3103
3104/// Compiled discover step — dual of publish.
3105#[derive(Debug, Clone, Serialize)]
3106pub struct IRDiscover {
3107 pub node_type: &'static str,
3108 pub source_line: u32,
3109 pub source_column: u32,
3110 pub capability_ref: String,
3111 pub alias: String,
3112}