1use std::collections::HashMap;
10
11use crate::ast::*;
12use crate::ir_nodes::*;
13use crate::store_schema::{StoreColumn, StoreColumnSchema};
14
15fn lower_column_schema(s: &StoreColumnSchema) -> IRStoreColumnSchema {
18 match s {
19 StoreColumnSchema::Inline { columns, .. } => IRStoreColumnSchema::Inline {
20 columns: columns.iter().map(lower_column).collect(),
21 },
22 StoreColumnSchema::ManifestRef { qualified_name, .. } => {
23 IRStoreColumnSchema::ManifestRef {
24 qualified_name: qualified_name.clone(),
25 }
26 }
27 StoreColumnSchema::EnvVar { var_name, .. } => IRStoreColumnSchema::EnvVar {
28 var_name: var_name.clone(),
29 },
30 }
31}
32
33fn lower_column(c: &StoreColumn) -> IRStoreColumn {
34 IRStoreColumn {
35 name: c.name.clone(),
36 col_type: c.col_type.canonical_name().to_string(),
39 primary_key: c.primary_key,
40 auto_increment: c.auto_increment,
41 not_null: c.not_null,
42 unique: c.unique,
43 default_value: c.default_value.clone(),
44 identity: c.identity,
46 }
47}
48
49pub struct IRGenerator {
50 personas: HashMap<String, IRPersona>,
51 contexts: HashMap<String, IRContext>,
52 anchors: HashMap<String, IRAnchor>,
53 flows: HashMap<String, IRFlow>,
54 lambda_data_specs: HashMap<String, IRLambdaData>,
55 intention_ops: Vec<IRIntentionOperation>,
58 program_line: u32,
60 program_column: u32,
61 channel_names: std::collections::HashSet<String>,
66}
67
68impl IRGenerator {
69 pub fn new() -> Self {
70 IRGenerator {
71 personas: HashMap::new(),
72 contexts: HashMap::new(),
73 anchors: HashMap::new(),
74 flows: HashMap::new(),
75 lambda_data_specs: HashMap::new(),
76 intention_ops: Vec::new(),
77 program_line: 1,
78 program_column: 1,
79 channel_names: std::collections::HashSet::new(),
80 }
81 }
82
83 pub fn generate(mut self, program: &Program) -> IRProgram {
84 let mut ir = IRProgram::new();
85 self.program_line = program.loc.line;
86 self.program_column = program.loc.column;
87
88 for decl in &program.declarations {
90 self.visit_declaration(decl, &mut ir);
91 }
92
93 for run in &mut ir.runs {
95 if let Some(flow) = self.flows.get(&run.flow_name) {
96 run.resolved_flow = Some(flow.clone());
97 }
98 if let Some(persona) = self.personas.get(&run.persona_name) {
99 run.resolved_persona = Some(persona.clone());
100 }
101 if let Some(context) = self.contexts.get(&run.context_name) {
102 run.resolved_context = Some(context.clone());
103 }
104 for anchor_name in &run.anchor_names {
105 if let Some(anchor) = self.anchors.get(anchor_name) {
106 run.resolved_anchors.push(anchor.clone());
107 }
108 }
109 }
110
111 if !self.intention_ops.is_empty() {
115 ir.intention_tree = Some(IRIntentionTree {
116 node_type: "intention_tree",
117 source_line: self.program_line,
118 source_column: self.program_column,
119 operations: std::mem::take(&mut self.intention_ops),
120 });
121 }
122
123 ir.extensions.sort_by(|a, b| a.name.cmp(&b.name));
132
133 ir
134 }
135
136 fn visit_declaration(&mut self, decl: &Declaration, ir: &mut IRProgram) {
137 match decl {
138 Declaration::Import(n) => ir.imports.push(self.visit_import(n)),
139 Declaration::Persona(n) => {
140 let node = self.visit_persona(n);
141 self.personas.insert(node.name.clone(), node.clone());
142 ir.personas.push(node);
143 }
144 Declaration::Context(n) => {
145 let node = self.visit_context(n);
146 self.contexts.insert(node.name.clone(), node.clone());
147 ir.contexts.push(node);
148 }
149 Declaration::Anchor(n) => {
150 let node = self.visit_anchor(n);
151 self.anchors.insert(node.name.clone(), node.clone());
152 ir.anchors.push(node);
153 }
154 Declaration::Memory(n) => ir.memories.push(self.visit_memory(n)),
155 Declaration::Tool(n) => ir.tools.push(self.visit_tool(n)),
156 Declaration::Type(n) => ir.types.push(self.visit_type(n)),
157 Declaration::Flow(n) => {
158 let node = self.visit_flow(n);
159 self.flows.insert(node.name.clone(), node.clone());
160 ir.flows.push(node);
161 }
162 Declaration::Intent(_) => {} Declaration::Run(n) => ir.runs.push(self.visit_run(n)),
164 Declaration::LambdaData(n) => {
165 let node = self.visit_lambda_data(n);
166 self.lambda_data_specs
167 .insert(node.name.clone(), node.clone());
168 ir.lambda_data_specs.push(node);
169 }
170 Declaration::Agent(n) => ir.agents.push(self.visit_agent(n)),
171 Declaration::Shield(n) => ir.shields.push(self.visit_shield(n)),
172 Declaration::Pix(n) => ir.pix_specs.push(self.visit_pix(n)),
173 Declaration::Psyche(n) => ir.psyche_specs.push(self.visit_psyche(n)),
174 Declaration::Corpus(n) => ir.corpus_specs.push(self.visit_corpus(n)),
175 Declaration::Dataspace(n) => ir.dataspace_specs.push(self.visit_dataspace(n)),
176 Declaration::Ots(n) => ir.ots_specs.push(self.visit_ots(n)),
177 Declaration::Mandate(n) => ir.mandate_specs.push(self.visit_mandate(n)),
178 Declaration::Compute(n) => ir.compute_specs.push(self.visit_compute(n)),
179 Declaration::Daemon(n) => ir.daemons.push(self.visit_daemon(n)),
180 Declaration::AxonStore(n) => ir.axonstore_specs.push(self.visit_axonstore(n)),
181 Declaration::AxonEndpoint(n) => ir.endpoints.push(self.visit_axonendpoint(n)),
182 Declaration::Extension(n) => ir.extensions.push(self.visit_extension(n)),
186 Declaration::Resource(n) => ir.resources.push(self.visit_resource(n)),
187 Declaration::Fabric(n) => ir.fabrics.push(self.visit_fabric(n)),
188 Declaration::Manifest(n) => {
189 let m = self.visit_manifest(n);
190 self.intention_ops
193 .push(IRIntentionOperation::Manifest(m.clone()));
194 ir.manifests.push(m);
195 }
196 Declaration::Observe(n) => {
197 let o = self.visit_observe(n);
198 self.intention_ops
200 .push(IRIntentionOperation::Observe(o.clone()));
201 ir.observations.push(o);
202 }
203 Declaration::Reconcile(n) => ir.reconciles.push(self.visit_reconcile(n)),
204 Declaration::Lease(n) => ir.leases.push(self.visit_lease(n)),
205 Declaration::Ensemble(n) => ir.ensembles.push(self.visit_ensemble(n)),
206 Declaration::Session(n) => ir.sessions.push(self.visit_session(n)),
207 Declaration::Topology(n) => ir.topologies.push(self.visit_topology(n)),
208 Declaration::Socket(n) => ir.sockets.push(self.visit_socket(n)),
209 Declaration::Immune(n) => ir.immunes.push(self.visit_immune(n)),
210 Declaration::Reflex(n) => ir.reflexes.push(self.visit_reflex(n)),
211 Declaration::Heal(n) => ir.heals.push(self.visit_heal(n)),
212 Declaration::Component(n) => ir.components.push(self.visit_component(n)),
213 Declaration::View(n) => ir.views.push(self.visit_view(n)),
214 Declaration::Channel(n) => {
220 self.channel_names.insert(n.name.clone());
221 ir.channels.push(self.visit_channel(n));
222 }
223 Declaration::Epistemic(eb) => {
224 for child in &eb.body {
225 self.visit_declaration(child, ir);
226 }
227 }
228 Declaration::Let(_) => {}
229 Declaration::Generic(g) => {
230 let val = serde_json::json!({
232 "node_type": g.keyword,
233 "source_line": g.loc.line,
234 "source_column": g.loc.column,
235 "name": g.name,
236 });
237 let _ = val; }
240 }
241 }
242
243 fn visit_import(&self, n: &ImportNode) -> IRImport {
246 IRImport {
247 node_type: "import",
248 source_line: n.loc.line,
249 source_column: n.loc.column,
250 module_path: n.module_path.clone(),
251 names: n.names.clone(),
252 }
253 }
254
255 fn visit_persona(&self, n: &PersonaDefinition) -> IRPersona {
256 IRPersona {
257 node_type: "persona",
258 source_line: n.loc.line,
259 source_column: n.loc.column,
260 name: n.name.clone(),
261 domain: n.domain.clone(),
262 tone: n.tone.clone(),
263 confidence_threshold: n.confidence_threshold,
264 cite_sources: n.cite_sources,
265 refuse_if: n.refuse_if.clone(),
266 language: n.language.clone(),
267 description: n.description.clone(),
268 }
269 }
270
271 fn visit_context(&self, n: &ContextDefinition) -> IRContext {
272 IRContext {
273 node_type: "context",
274 source_line: n.loc.line,
275 source_column: n.loc.column,
276 name: n.name.clone(),
277 memory_scope: n.memory_scope.clone(),
278 language: n.language.clone(),
279 depth: n.depth.clone(),
280 max_tokens: n.max_tokens,
281 temperature: n.temperature,
282 cite_sources: n.cite_sources,
283 }
284 }
285
286 fn visit_anchor(&self, n: &AnchorConstraint) -> IRAnchor {
287 IRAnchor {
288 node_type: "anchor",
289 source_line: n.loc.line,
290 source_column: n.loc.column,
291 name: n.name.clone(),
292 description: n.description.clone(),
293 require: n.require.clone(),
294 reject: n.reject.clone(),
295 enforce: n.enforce.clone(),
296 confidence_floor: n.confidence_floor,
297 unknown_response: n.unknown_response.clone(),
298 on_violation: n.on_violation.clone(),
299 on_violation_target: n.on_violation_target.clone(),
300 }
301 }
302
303 fn visit_memory(&self, n: &MemoryDefinition) -> IRMemory {
304 IRMemory {
305 node_type: "memory",
306 source_line: n.loc.line,
307 source_column: n.loc.column,
308 name: n.name.clone(),
309 store: n.store.clone(),
310 backend: n.backend.clone(),
311 retrieval: n.retrieval.clone(),
312 decay: n.decay.clone(),
313 }
314 }
315
316 fn visit_tool(&self, n: &ToolDefinition) -> IRToolSpec {
317 let effect_row = match &n.effects {
318 Some(eff) => {
319 let mut row = eff.effects.clone();
320 if !eff.epistemic_level.is_empty() {
321 row.push(format!("epistemic:{}", eff.epistemic_level));
322 }
323 row
324 }
325 None => Vec::new(),
326 };
327
328 IRToolSpec {
329 node_type: "tool_spec",
330 source_line: n.loc.line,
331 source_column: n.loc.column,
332 name: n.name.clone(),
333 provider: n.provider.clone(),
334 max_results: n.max_results,
335 filter_expr: n.filter_expr.clone(),
336 timeout: n.timeout.clone(),
337 runtime: n.runtime.clone(),
338 sandbox: n.sandbox,
339 input_schema: Vec::new(),
340 output_schema: String::new(),
341 parameters: n
345 .parameters
346 .iter()
347 .map(|p| {
348 let mut type_name = p.type_expr.name.clone();
349 if !p.type_expr.generic_param.is_empty() {
350 type_name.push('<');
351 type_name.push_str(&p.type_expr.generic_param);
352 type_name.push('>');
353 }
354 crate::ir_nodes::IRToolParam {
355 name: p.name.clone(),
356 type_name,
357 optional: p.type_expr.optional,
358 }
359 })
360 .collect(),
361 output_type: n.output_type.clone(),
362 effect_row,
363 }
364 }
365
366 fn visit_type(&self, n: &TypeDefinition) -> IRType {
367 let fields = n
368 .fields
369 .iter()
370 .map(|f| IRTypeField {
371 node_type: "type_field",
372 source_line: f.loc.line,
373 source_column: f.loc.column,
374 name: f.name.clone(),
375 type_name: f.type_expr.name.clone(),
376 generic_param: f.type_expr.generic_param.clone(),
377 optional: f.type_expr.optional,
378 })
379 .collect();
380
381 let (range_min, range_max) = match &n.range_constraint {
382 Some(rc) => (Some(rc.min_value), Some(rc.max_value)),
383 None => (None, None),
384 };
385
386 let where_expression = match &n.where_clause {
387 Some(wc) => wc.expression.clone(),
388 None => String::new(),
389 };
390
391 IRType {
392 node_type: "type_def",
393 source_line: n.loc.line,
394 source_column: n.loc.column,
395 name: n.name.clone(),
396 fields,
397 range_min,
398 range_max,
399 where_expression,
400 compliance: n.compliance.clone(),
401 }
402 }
403
404 fn visit_flow(&self, n: &FlowDefinition) -> IRFlow {
405 let parameters: Vec<IRParameter> = n
406 .parameters
407 .iter()
408 .map(|p| IRParameter {
409 node_type: "parameter",
410 source_line: p.loc.line,
411 source_column: p.loc.column,
412 name: p.name.clone(),
413 type_name: p.type_expr.name.clone(),
414 generic_param: p.type_expr.generic_param.clone(),
415 optional: p.type_expr.optional,
416 })
417 .collect();
418
419 let (return_type_name, return_type_generic, return_type_optional) = match &n.return_type {
420 Some(rt) => (rt.name.clone(), rt.generic_param.clone(), rt.optional),
421 None => (String::new(), String::new(), false),
422 };
423
424 let steps: Vec<IRFlowNode> = n.body.iter().map(|fs| self.visit_flow_step(fs)).collect();
426
427 let mut edges: Vec<IRDataEdge> = Vec::new();
429 let step_names: Vec<String> = steps
430 .iter()
431 .filter_map(|n| {
432 if let IRFlowNode::Step(s) = n {
433 Some(s.name.clone())
434 } else {
435 None
436 }
437 })
438 .collect();
439 for node in &steps {
440 if let IRFlowNode::Step(step) = node {
441 if !step.given.is_empty() {
442 let given_root = step.given.split('.').next().unwrap_or("");
443 if step_names.contains(&given_root.to_string()) && given_root != step.name {
444 edges.push(IRDataEdge {
445 node_type: "data_edge",
446 source_line: step.source_line,
447 source_column: step.source_column,
448 source_step: given_root.to_string(),
449 target_step: step.name.clone(),
450 type_name: "Any".to_string(),
451 });
452 }
453 }
454 }
455 }
456
457 let execution_levels = self.compute_execution_levels(&steps, &edges);
459
460 IRFlow {
461 node_type: "flow",
462 source_line: n.loc.line,
463 source_column: n.loc.column,
464 name: n.name.clone(),
465 parameters,
466 return_type_name,
467 return_type_generic,
468 return_type_optional,
469 steps,
470 edges,
471 execution_levels,
472 }
473 }
474
475 fn visit_flow_step(&self, fs: &FlowStep) -> IRFlowNode {
476 match fs {
477 FlowStep::Step(s) => IRFlowNode::Step(IRStep {
478 node_type: "step",
479 source_line: s.loc.line,
480 source_column: s.loc.column,
481 name: s.name.clone(),
482 persona_ref: s.persona_ref.clone(),
483 given: s.given.clone(),
484 ask: s.ask.clone(),
485 use_tool: None,
486 probe: None,
487 reason: None,
488 weave: None,
489 output_type: s.output_type.clone(),
490 confidence_floor: s.confidence_floor,
491 navigate_ref: s.navigate_ref.clone(),
492 apply_ref: s.apply_ref.clone(),
493 body: Vec::new(),
494 }),
495 FlowStep::Probe(s) => IRFlowNode::Probe(IRProbe {
496 node_type: "probe",
497 source_line: s.loc.line,
498 source_column: s.loc.column,
499 target: s.target.clone(),
500 }),
501 FlowStep::Reason(s) => IRFlowNode::Reason(IRReasonStep {
502 node_type: "reason",
503 source_line: s.loc.line,
504 source_column: s.loc.column,
505 strategy: s.strategy.clone(),
506 target: s.target.clone(),
507 }),
508 FlowStep::Validate(s) => IRFlowNode::Validate(IRValidateStep {
509 node_type: "validate",
510 source_line: s.loc.line,
511 source_column: s.loc.column,
512 target: s.target.clone(),
513 rule: s.rule.clone(),
514 }),
515 FlowStep::Refine(s) => IRFlowNode::Refine(IRRefineStep {
516 node_type: "refine",
517 source_line: s.loc.line,
518 source_column: s.loc.column,
519 target: s.target.clone(),
520 strategy: s.strategy.clone(),
521 }),
522 FlowStep::Weave(s) => IRFlowNode::Weave(IRWeaveStep {
523 node_type: "weave",
524 source_line: s.loc.line,
525 source_column: s.loc.column,
526 sources: s.sources.clone(),
527 target: s.target.clone(),
528 format_type: s.format_type.clone(),
529 priority: s.priority.clone(),
530 style: s.style.clone(),
531 }),
532 FlowStep::UseTool(s) => IRFlowNode::UseTool(IRUseToolStep {
533 node_type: "use_tool",
534 source_line: s.loc.line,
535 source_column: s.loc.column,
536 tool_name: s.tool_name.clone(),
537 argument: s.args.legacy_argument(),
541 named_args: match &s.args {
543 UseArgs::Named(pairs) => pairs
544 .iter()
545 .map(|(name, value, value_kind)| crate::ir_nodes::IRNamedArg {
546 name: name.clone(),
547 value: value.clone(),
548 value_kind: value_kind.clone(),
549 })
550 .collect(),
551 UseArgs::LegacyPositional(_) => Vec::new(),
552 },
553 }),
554 FlowStep::Remember(s) => IRFlowNode::Remember(IRRememberStep {
555 node_type: "remember",
556 source_line: s.loc.line,
557 source_column: s.loc.column,
558 expression: s.expression.clone(),
559 memory_target: s.memory_target.clone(),
560 }),
561 FlowStep::Recall(s) => IRFlowNode::Recall(IRRecallStep {
562 node_type: "recall",
563 source_line: s.loc.line,
564 source_column: s.loc.column,
565 query: s.query.clone(),
566 memory_source: s.memory_source.clone(),
567 }),
568 FlowStep::If(s) => IRFlowNode::Conditional(IRConditional {
569 node_type: "conditional",
570 source_line: s.loc.line,
571 source_column: s.loc.column,
572 condition: s.condition.clone(),
573 comparison_op: s.comparison_op.clone(),
574 comparison_value: s.comparison_value.clone(),
575 then_body: s
576 .then_body
577 .iter()
578 .map(|fs| self.visit_flow_step(fs))
579 .collect(),
580 else_body: s
581 .else_body
582 .iter()
583 .map(|fs| self.visit_flow_step(fs))
584 .collect(),
585 conditions: s.conditions.clone(),
586 conjunctor: s.conjunctor.clone(),
587 }),
588 FlowStep::ForIn(s) => IRFlowNode::ForIn(IRForIn {
589 node_type: "for_in",
590 source_line: s.loc.line,
591 source_column: s.loc.column,
592 variable: s.variable.clone(),
593 iterable: s.iterable.clone(),
594 body: s.body.iter().map(|fs| self.visit_flow_step(fs)).collect(),
595 }),
596 FlowStep::Let(s) => IRFlowNode::Let(IRLetBinding {
597 node_type: "let_binding",
598 source_line: s.loc.line,
599 source_column: s.loc.column,
600 target: s.identifier.clone(),
601 value: s.value_expr.clone(),
602 value_kind: if s.value_kind.is_empty() {
603 "literal".to_string()
604 } else {
605 s.value_kind.clone()
606 },
607 }),
608 FlowStep::Return(s) => IRFlowNode::Return(IRReturnStep {
609 node_type: "return",
610 source_line: s.loc.line,
611 source_column: s.loc.column,
612 value_expr: s.value_expr.clone(),
613 }),
614 FlowStep::Break(s) => IRFlowNode::Break(IRBreakStep {
618 node_type: "break",
619 source_line: s.loc.line,
620 source_column: s.loc.column,
621 }),
622 FlowStep::Continue(s) => IRFlowNode::Continue(IRContinueStep {
623 node_type: "continue",
624 source_line: s.loc.line,
625 source_column: s.loc.column,
626 }),
627 FlowStep::LambdaDataApply(s) => IRFlowNode::LambdaDataApply(IRLambdaDataApply {
628 node_type: "lambda_data_apply",
629 source_line: s.loc.line,
630 source_column: s.loc.column,
631 lambda_data_name: s.lambda_data_name.clone(),
632 target: s.target.clone(),
633 output_type: s.output_type.clone(),
634 }),
635 FlowStep::Par(s) => IRFlowNode::Par(IRParallelBlock {
636 node_type: "parallel_block",
637 source_line: s.loc.line,
638 source_column: s.loc.column,
639 }),
640 FlowStep::Hibernate(s) => IRFlowNode::Hibernate(IRHibernateStep {
641 node_type: "hibernate",
642 source_line: s.loc.line,
643 source_column: s.loc.column,
644 event_name: s.event_name.clone(),
645 timeout: s.timeout.clone(),
646 }),
647 FlowStep::Deliberate(s) => IRFlowNode::Deliberate(IRDeliberateBlock {
648 node_type: "deliberate",
649 source_line: s.loc.line,
650 source_column: s.loc.column,
651 }),
652 FlowStep::Consensus(s) => IRFlowNode::Consensus(IRConsensusBlock {
653 node_type: "consensus",
654 source_line: s.loc.line,
655 source_column: s.loc.column,
656 }),
657 FlowStep::Forge(s) => IRFlowNode::Forge(IRForgeBlock {
658 node_type: "forge",
659 source_line: s.loc.line,
660 source_column: s.loc.column,
661 }),
662 FlowStep::Focus(s) => IRFlowNode::Focus(IRFocusStep {
663 node_type: "focus",
664 source_line: s.loc.line,
665 source_column: s.loc.column,
666 expression: s.expression.clone(),
667 }),
668 FlowStep::Associate(s) => IRFlowNode::Associate(IRAssociateStep {
669 node_type: "associate",
670 source_line: s.loc.line,
671 source_column: s.loc.column,
672 left: s.left.clone(),
673 right: s.right.clone(),
674 using_field: s.using_field.clone(),
675 }),
676 FlowStep::Aggregate(s) => IRFlowNode::Aggregate(IRAggregateStep {
677 node_type: "aggregate",
678 source_line: s.loc.line,
679 source_column: s.loc.column,
680 target: s.target.clone(),
681 group_by: s.group_by.clone(),
682 alias: s.alias.clone(),
683 }),
684 FlowStep::ExploreStep(s) => IRFlowNode::Explore(IRExploreStep {
685 node_type: "explore",
686 source_line: s.loc.line,
687 source_column: s.loc.column,
688 target: s.target.clone(),
689 limit: s.limit,
690 }),
691 FlowStep::Ingest(s) => IRFlowNode::Ingest(IRIngestStep {
692 node_type: "ingest",
693 source_line: s.loc.line,
694 source_column: s.loc.column,
695 source: s.source.clone(),
696 target: s.target.clone(),
697 }),
698 FlowStep::ShieldApply(s) => IRFlowNode::ShieldApply(IRShieldApplyStep {
699 node_type: "shield_apply",
700 source_line: s.loc.line,
701 source_column: s.loc.column,
702 shield_name: s.shield_name.clone(),
703 target: s.target.clone(),
704 output_type: s.output_type.clone(),
705 }),
706 FlowStep::Stream(s) => IRFlowNode::Stream(IRStreamBlock {
707 node_type: "stream",
708 source_line: s.loc.line,
709 source_column: s.loc.column,
710 }),
711 FlowStep::Navigate(s) => IRFlowNode::Navigate(IRNavigateStep {
712 node_type: "navigate",
713 source_line: s.loc.line,
714 source_column: s.loc.column,
715 pix_ref: s.pix_name.clone(),
716 corpus_ref: s.corpus_name.clone(),
717 query: s.query_expr.clone(),
718 trail_enabled: s.trail_enabled,
719 output_name: s.output_name.clone(),
720 }),
721 FlowStep::Drill(s) => IRFlowNode::Drill(IRDrillStep {
722 node_type: "drill",
723 source_line: s.loc.line,
724 source_column: s.loc.column,
725 pix_ref: s.pix_name.clone(),
726 subtree_path: s.subtree_path.clone(),
727 query: s.query_expr.clone(),
728 output_name: s.output_name.clone(),
729 }),
730 FlowStep::Trail(s) => IRFlowNode::Trail(IRTrailStep {
731 node_type: "trail",
732 source_line: s.loc.line,
733 source_column: s.loc.column,
734 navigate_ref: s.navigate_ref.clone(),
735 }),
736 FlowStep::Corroborate(s) => IRFlowNode::Corroborate(IRCorroborateStep {
737 node_type: "corroborate",
738 source_line: s.loc.line,
739 source_column: s.loc.column,
740 navigate_ref: s.navigate_ref.clone(),
741 output_name: s.output_name.clone(),
742 }),
743 FlowStep::OtsApply(s) => IRFlowNode::OtsApply(IROtsApplyStep {
744 node_type: "ots_apply",
745 source_line: s.loc.line,
746 source_column: s.loc.column,
747 ots_name: s.ots_name.clone(),
748 target: s.target.clone(),
749 output_type: s.output_type.clone(),
750 }),
751 FlowStep::MandateApply(s) => IRFlowNode::MandateApply(IRMandateApplyStep {
752 node_type: "mandate_apply",
753 source_line: s.loc.line,
754 source_column: s.loc.column,
755 mandate_name: s.mandate_name.clone(),
756 target: s.target.clone(),
757 output_type: s.output_type.clone(),
758 }),
759 FlowStep::ComputeApply(s) => IRFlowNode::ComputeApply(IRComputeApplyStep {
760 node_type: "compute_apply",
761 source_line: s.loc.line,
762 source_column: s.loc.column,
763 compute_name: s.compute_name.clone(),
764 arguments: s.arguments.clone(),
765 output_name: s.output_name.clone(),
766 }),
767 FlowStep::Listen(s) => IRFlowNode::Listen(IRListenStep {
768 node_type: "listen",
769 source_line: s.loc.line,
770 source_column: s.loc.column,
771 channel: s.channel.clone(),
772 channel_is_ref: s.channel_is_ref,
773 event_alias: s.event_alias.clone(),
774 }),
775 FlowStep::Emit(s) => IRFlowNode::Emit(IREmit {
777 node_type: "emit",
778 source_line: s.loc.line,
779 source_column: s.loc.column,
780 channel_ref: s.channel_ref.clone(),
781 value_ref: s.value_ref.clone(),
782 value_is_channel: self.channel_names.contains(&s.value_ref),
783 }),
784 FlowStep::Publish(s) => IRFlowNode::Publish(IRPublish {
785 node_type: "publish",
786 source_line: s.loc.line,
787 source_column: s.loc.column,
788 channel_ref: s.channel_ref.clone(),
789 shield_ref: s.shield_ref.clone(),
790 }),
791 FlowStep::Discover(s) => IRFlowNode::Discover(IRDiscover {
792 node_type: "discover",
793 source_line: s.loc.line,
794 source_column: s.loc.column,
795 capability_ref: s.capability_ref.clone(),
796 alias: s.alias.clone(),
797 }),
798 FlowStep::DaemonStep(s) => IRFlowNode::DaemonStep(IRDaemonStepNode {
799 node_type: "daemon",
800 source_line: s.loc.line,
801 source_column: s.loc.column,
802 daemon_ref: s.daemon_ref.clone(),
803 }),
804 FlowStep::Persist(s) => IRFlowNode::Persist(IRPersistStep {
805 node_type: "persist",
806 source_line: s.loc.line,
807 source_column: s.loc.column,
808 store_name: s.store_name.clone(),
809 fields: s.fields.clone(),
810 }),
811 FlowStep::Retrieve(s) => IRFlowNode::Retrieve(IRRetrieveStep {
812 node_type: "retrieve",
813 source_line: s.loc.line,
814 source_column: s.loc.column,
815 store_name: s.store_name.clone(),
816 where_expr: s.where_expr.clone(),
817 alias: s.alias.clone(),
818 }),
819 FlowStep::Mutate(s) => IRFlowNode::Mutate(IRMutateStep {
820 node_type: "mutate",
821 source_line: s.loc.line,
822 source_column: s.loc.column,
823 store_name: s.store_name.clone(),
824 where_expr: s.where_expr.clone(),
825 fields: s.fields.clone(),
826 }),
827 FlowStep::Purge(s) => IRFlowNode::Purge(IRPurgeStep {
828 node_type: "purge",
829 source_line: s.loc.line,
830 source_column: s.loc.column,
831 store_name: s.store_name.clone(),
832 where_expr: s.where_expr.clone(),
833 }),
834 FlowStep::Transact(s) => IRFlowNode::Transact(IRTransactBlock {
835 node_type: "transact",
836 source_line: s.loc.line,
837 source_column: s.loc.column,
838 }),
839 FlowStep::GenericStep(_) => {
840 IRFlowNode::Step(IRStep {
842 node_type: "step",
843 source_line: 0,
844 source_column: 0,
845 name: String::new(),
846 persona_ref: String::new(),
847 given: String::new(),
848 ask: String::new(),
849 use_tool: None,
850 probe: None,
851 reason: None,
852 weave: None,
853 output_type: String::new(),
854 confidence_floor: None,
855 navigate_ref: String::new(),
856 apply_ref: String::new(),
857 body: Vec::new(),
858 })
859 }
860 }
861 }
862
863 fn compute_execution_levels(
864 &self,
865 steps: &[IRFlowNode],
866 edges: &[IRDataEdge],
867 ) -> Vec<Vec<String>> {
868 let step_nodes: Vec<&IRStep> = steps
870 .iter()
871 .filter_map(|n| {
872 if let IRFlowNode::Step(s) = n {
873 Some(s)
874 } else {
875 None
876 }
877 })
878 .collect();
879
880 if step_nodes.is_empty() {
881 return Vec::new();
882 }
883
884 let mut deps: HashMap<String, Vec<String>> = HashMap::new();
886 for step in &step_nodes {
887 deps.insert(step.name.clone(), Vec::new());
888 }
889 for edge in edges {
890 deps.entry(edge.target_step.clone())
891 .or_default()
892 .push(edge.source_step.clone());
893 }
894
895 let mut levels: Vec<Vec<String>> = Vec::new();
896 let mut placed: Vec<String> = Vec::new();
897
898 loop {
899 let mut level: Vec<String> = Vec::new();
900 for step in &step_nodes {
901 if placed.contains(&step.name) {
902 continue;
903 }
904 let step_deps = deps.get(&step.name).cloned().unwrap_or_default();
905 if step_deps.iter().all(|d| placed.contains(d)) {
906 level.push(step.name.clone());
907 }
908 }
909 if level.is_empty() {
910 break;
911 }
912 placed.extend(level.clone());
913 levels.push(level);
914 }
915
916 levels
917 }
918
919 fn visit_agent(&self, n: &AgentDefinition) -> IRAgent {
922 IRAgent {
923 node_type: "agent",
924 source_line: n.loc.line,
925 source_column: n.loc.column,
926 name: n.name.clone(),
927 goal: n.goal.clone(),
928 tools: n.tools.clone(),
929 memory_ref: n.memory_ref.clone(),
930 strategy: n.strategy.clone(),
931 on_stuck: n.on_stuck.clone(),
932 shield_ref: n.shield_ref.clone(),
933 max_iterations: n.max_iterations,
934 max_tokens: n.max_tokens,
935 max_time: n.max_time.clone(),
936 max_cost: n.max_cost,
937 }
938 }
939
940 fn visit_shield(&self, n: &ShieldDefinition) -> IRShield {
941 let strategy = if n.strategy.is_empty() {
943 "pattern".to_string()
944 } else {
945 n.strategy.clone()
946 };
947 IRShield {
948 node_type: "shield",
949 source_line: n.loc.line,
950 source_column: n.loc.column,
951 name: n.name.clone(),
952 scan: n.scan.clone(),
953 strategy,
954 on_breach: n.on_breach.clone(),
955 severity: n.severity.clone(),
956 quarantine: n.quarantine.clone(),
957 max_retries: n.max_retries.unwrap_or(0),
958 confidence_threshold: n.confidence_threshold.unwrap_or(0.0),
959 allow_tools: n.allow_tools.clone(),
960 deny_tools: n.deny_tools.clone(),
961 sandbox: n.sandbox.unwrap_or(false),
962 redact: n.redact.clone(),
963 log: n.log.clone(),
964 deflect_message: n.deflect_message.clone(),
965 taint: n.taint.clone(),
966 compliance: n.compliance.clone(),
967 }
968 }
969
970 fn visit_pix(&self, n: &PixDefinition) -> IRPix {
971 IRPix {
972 node_type: "pix",
973 source_line: n.loc.line,
974 source_column: n.loc.column,
975 name: n.name.clone(),
976 source: n.source.clone(),
977 depth: n.depth,
978 branching: n.branching,
979 model: n.model.clone(),
980 }
981 }
982
983 fn visit_psyche(&self, n: &PsycheDefinition) -> IRPsyche {
984 IRPsyche {
985 node_type: "psyche",
986 source_line: n.loc.line,
987 source_column: n.loc.column,
988 name: n.name.clone(),
989 dimensions: n.dimensions.clone(),
990 manifold_noise: n.manifold_noise,
991 manifold_momentum: n.manifold_momentum,
992 safety_constraints: n.safety_constraints.clone(),
993 quantum_enabled: n.quantum_enabled,
994 inference_mode: n.inference_mode.clone(),
995 }
996 }
997
998 fn visit_corpus(&self, n: &CorpusDefinition) -> IRCorpus {
999 IRCorpus {
1000 node_type: "corpus",
1001 source_line: n.loc.line,
1002 source_column: n.loc.column,
1003 name: n.name.clone(),
1004 documents: n.documents.clone(),
1005 mcp_server: n.mcp_server.clone(),
1006 mcp_resource_uri: n.mcp_resource_uri.clone(),
1007 }
1008 }
1009
1010 fn visit_dataspace(&self, n: &DataspaceDefinition) -> IRDataspace {
1011 IRDataspace {
1012 node_type: "dataspace",
1013 source_line: n.loc.line,
1014 source_column: n.loc.column,
1015 name: n.name.clone(),
1016 }
1017 }
1018
1019 fn visit_ots(&self, n: &OtsDefinition) -> IROts {
1020 IROts {
1021 node_type: "ots",
1022 source_line: n.loc.line,
1023 source_column: n.loc.column,
1024 name: n.name.clone(),
1025 teleology: n.teleology.clone(),
1026 homotopy_search: n.homotopy_search.clone(),
1027 loss_function: n.loss_function.clone(),
1028 }
1029 }
1030
1031 fn visit_mandate(&self, n: &MandateDefinition) -> IRMandate {
1032 IRMandate {
1033 node_type: "mandate",
1034 source_line: n.loc.line,
1035 source_column: n.loc.column,
1036 name: n.name.clone(),
1037 constraint: n.constraint.clone(),
1038 kp: n.kp,
1039 ki: n.ki,
1040 kd: n.kd,
1041 tolerance: n.tolerance,
1042 max_steps: n.max_steps,
1043 on_violation: n.on_violation.clone(),
1044 }
1045 }
1046
1047 fn visit_compute(&self, n: &ComputeDefinition) -> IRCompute {
1048 IRCompute {
1049 node_type: "compute",
1050 source_line: n.loc.line,
1051 source_column: n.loc.column,
1052 name: n.name.clone(),
1053 shield_ref: n.shield_ref.clone(),
1054 }
1055 }
1056
1057 fn visit_daemon(&self, n: &DaemonDefinition) -> IRDaemon {
1058 IRDaemon {
1059 node_type: "daemon",
1060 source_line: n.loc.line,
1061 source_column: n.loc.column,
1062 name: n.name.clone(),
1063 goal: n.goal.clone(),
1064 tools: n.tools.clone(),
1065 memory_ref: n.memory_ref.clone(),
1066 strategy: n.strategy.clone(),
1067 on_stuck: n.on_stuck.clone(),
1068 shield_ref: n.shield_ref.clone(),
1069 max_tokens: n.max_tokens,
1070 max_time: n.max_time.clone(),
1071 max_cost: n.max_cost,
1072 }
1073 }
1074
1075 fn visit_axonstore(&self, n: &AxonStoreDefinition) -> IRAxonStore {
1076 IRAxonStore {
1077 node_type: "axonstore",
1078 source_line: n.loc.line,
1079 source_column: n.loc.column,
1080 name: n.name.clone(),
1081 backend: n.backend.clone(),
1082 connection: n.connection.clone(),
1083 confidence_floor: n.confidence_floor,
1084 isolation: n.isolation.clone(),
1085 on_breach: n.on_breach.clone(),
1086 capability: n.capability.clone(),
1087 column_schema: n.column_schema.as_ref().map(lower_column_schema),
1092 }
1093 }
1094
1095 fn visit_extension(&self, n: &crate::ast::ExtensionDefinition) -> crate::ir_nodes::IRExtension {
1100 crate::ir_nodes::IRExtension {
1101 node_type: "extension",
1102 source_line: n.loc.line,
1103 source_column: n.loc.column,
1104 name: n.name.clone(),
1105 category: n.category.clone(),
1106 members: n
1107 .members
1108 .iter()
1109 .map(|m| crate::ir_nodes::IRExtensionMember {
1110 name: m.name.clone(),
1111 semantics: m.semantics.clone(),
1112 default_confidence: m.default_confidence,
1113 })
1114 .collect(),
1115 }
1116 }
1117
1118 fn visit_axonendpoint(&self, n: &AxonEndpointDefinition) -> IRAxonEndpoint {
1119 IRAxonEndpoint {
1121 node_type: "endpoint",
1122 source_line: n.loc.line,
1123 source_column: n.loc.column,
1124 name: n.name.clone(),
1125 method: n.method.clone(),
1126 path: n.path.clone(),
1127 body_type: n.body_type.clone(),
1128 execute_flow: n.execute_flow.clone(),
1129 output_type: n.output_type.clone(),
1130 shield_ref: n.shield_ref.clone(),
1131 retries: n.retries.unwrap_or(0),
1132 timeout: n.timeout.clone(),
1133 compliance: n.compliance.clone(),
1134 path_params: n.path_params.clone(),
1138 query_params: n
1142 .query_params
1143 .iter()
1144 .map(|f| crate::ir_nodes::IRTypeField {
1145 node_type: "type_field",
1146 source_line: f.loc.line,
1147 source_column: f.loc.column,
1148 name: f.name.clone(),
1149 type_name: f.type_expr.name.clone(),
1150 generic_param: f.type_expr.generic_param.clone(),
1151 optional: f.type_expr.optional,
1152 })
1153 .collect(),
1154 requires_capabilities: n.requires_capabilities.clone(),
1159 }
1160 }
1161
1162 fn visit_resource(&self, n: &ResourceDefinition) -> IRResource {
1164 IRResource {
1165 node_type: "resource",
1166 source_line: n.loc.line,
1167 source_column: n.loc.column,
1168 name: n.name.clone(),
1169 kind: n.kind.clone(),
1170 endpoint: n.endpoint.clone(),
1171 capacity: n.capacity,
1172 lifetime: n.lifetime.clone(),
1173 certainty_floor: n.certainty_floor,
1174 shield_ref: n.shield_ref.clone(),
1175 }
1176 }
1177
1178 fn visit_fabric(&self, n: &FabricDefinition) -> IRFabric {
1180 IRFabric {
1181 node_type: "fabric",
1182 source_line: n.loc.line,
1183 source_column: n.loc.column,
1184 name: n.name.clone(),
1185 provider: n.provider.clone(),
1186 region: n.region.clone(),
1187 zones: n.zones,
1188 ephemeral: n.ephemeral,
1189 shield_ref: n.shield_ref.clone(),
1190 }
1191 }
1192
1193 fn visit_manifest(&self, n: &ManifestDefinition) -> IRManifest {
1195 IRManifest {
1196 node_type: "manifest",
1197 source_line: n.loc.line,
1198 source_column: n.loc.column,
1199 name: n.name.clone(),
1200 resources: n.resources.clone(),
1201 fabric_ref: n.fabric_ref.clone(),
1202 region: n.region.clone(),
1203 zones: n.zones,
1204 compliance: n.compliance.clone(),
1205 }
1206 }
1207
1208 fn visit_observe(&self, n: &ObserveDefinition) -> IRObserve {
1210 IRObserve {
1211 node_type: "observe",
1212 source_line: n.loc.line,
1213 source_column: n.loc.column,
1214 name: n.name.clone(),
1215 target: n.target.clone(),
1216 sources: n.sources.clone(),
1217 quorum: n.quorum,
1218 timeout: n.timeout.clone(),
1219 on_partition: if n.on_partition.is_empty() {
1220 "fail".to_string()
1221 } else {
1222 n.on_partition.clone()
1223 },
1224 certainty_floor: n.certainty_floor,
1225 }
1226 }
1227
1228 fn visit_reconcile(&self, n: &ReconcileDefinition) -> IRReconcile {
1230 IRReconcile {
1231 node_type: "reconcile",
1232 source_line: n.loc.line,
1233 source_column: n.loc.column,
1234 name: n.name.clone(),
1235 observe_ref: n.observe_ref.clone(),
1236 threshold: n.threshold,
1237 tolerance: n.tolerance,
1238 on_drift: if n.on_drift.is_empty() {
1239 "provision".to_string()
1240 } else {
1241 n.on_drift.clone()
1242 },
1243 shield_ref: n.shield_ref.clone(),
1244 mandate_ref: n.mandate_ref.clone(),
1245 max_retries: n.max_retries,
1246 }
1247 }
1248
1249 fn visit_lease(&self, n: &LeaseDefinition) -> IRLease {
1251 IRLease {
1252 node_type: "lease",
1253 source_line: n.loc.line,
1254 source_column: n.loc.column,
1255 name: n.name.clone(),
1256 resource_ref: n.resource_ref.clone(),
1257 duration: n.duration.clone(),
1258 acquire: if n.acquire.is_empty() {
1259 "on_start".to_string()
1260 } else {
1261 n.acquire.clone()
1262 },
1263 on_expire: if n.on_expire.is_empty() {
1264 "anchor_breach".to_string()
1265 } else {
1266 n.on_expire.clone()
1267 },
1268 }
1269 }
1270
1271 fn visit_immune(&self, n: &ImmuneDefinition) -> IRImmune {
1273 IRImmune {
1274 node_type: "immune",
1275 source_line: n.loc.line,
1276 source_column: n.loc.column,
1277 name: n.name.clone(),
1278 watch: n.watch.clone(),
1279 sensitivity: n.sensitivity,
1280 baseline: if n.baseline.is_empty() {
1281 "learned".to_string()
1282 } else {
1283 n.baseline.clone()
1284 },
1285 window: n.window,
1286 scope: n.scope.clone(),
1287 tau: n.tau.clone(),
1288 decay: if n.decay.is_empty() {
1289 "exponential".to_string()
1290 } else {
1291 n.decay.clone()
1292 },
1293 }
1294 }
1295
1296 fn visit_reflex(&self, n: &ReflexDefinition) -> IRReflex {
1298 IRReflex {
1299 node_type: "reflex",
1300 source_line: n.loc.line,
1301 source_column: n.loc.column,
1302 name: n.name.clone(),
1303 trigger: n.trigger.clone(),
1304 on_level: if n.on_level.is_empty() {
1305 "doubt".to_string()
1306 } else {
1307 n.on_level.clone()
1308 },
1309 action: n.action.clone(),
1310 scope: n.scope.clone(),
1311 sla: n.sla.clone(),
1312 }
1313 }
1314
1315 fn visit_heal(&self, n: &HealDefinition) -> IRHeal {
1317 IRHeal {
1318 node_type: "heal",
1319 source_line: n.loc.line,
1320 source_column: n.loc.column,
1321 name: n.name.clone(),
1322 source: n.source.clone(),
1323 on_level: if n.on_level.is_empty() {
1324 "doubt".to_string()
1325 } else {
1326 n.on_level.clone()
1327 },
1328 mode: if n.mode.is_empty() {
1329 "human_in_loop".to_string()
1330 } else {
1331 n.mode.clone()
1332 },
1333 scope: n.scope.clone(),
1334 review_sla: n.review_sla.clone(),
1335 shield_ref: n.shield_ref.clone(),
1336 max_patches: n.max_patches,
1337 }
1338 }
1339
1340 fn visit_component(&self, n: &ComponentDefinition) -> IRComponent {
1342 IRComponent {
1343 node_type: "component",
1344 source_line: n.loc.line,
1345 source_column: n.loc.column,
1346 name: n.name.clone(),
1347 renders: n.renders.clone(),
1348 via_shield: n.via_shield.clone(),
1349 on_interact: n.on_interact.clone(),
1350 render_hint: if n.render_hint.is_empty() {
1351 "custom".to_string()
1352 } else {
1353 n.render_hint.clone()
1354 },
1355 }
1356 }
1357
1358 fn visit_view(&self, n: &ViewDefinition) -> IRView {
1360 IRView {
1361 node_type: "view",
1362 source_line: n.loc.line,
1363 source_column: n.loc.column,
1364 name: n.name.clone(),
1365 title: n.title.clone(),
1366 components: n.components.clone(),
1367 route: n.route.clone(),
1368 }
1369 }
1370
1371 fn visit_session(&self, n: &SessionDefinition) -> IRSession {
1373 let roles = n
1374 .roles
1375 .iter()
1376 .map(|r| IRSessionRole {
1377 node_type: "session_role",
1378 source_line: r.loc.line,
1379 source_column: r.loc.column,
1380 name: r.name.clone(),
1381 steps: r
1382 .steps
1383 .iter()
1384 .map(|s| self.lower_session_step_ir(s))
1385 .collect(),
1386 })
1387 .collect();
1388 IRSession {
1389 node_type: "session",
1390 source_line: n.loc.line,
1391 source_column: n.loc.column,
1392 name: n.name.clone(),
1393 roles,
1394 }
1395 }
1396
1397 fn lower_session_step_ir(&self, s: &SessionStep) -> IRSessionStep {
1401 IRSessionStep {
1402 node_type: "session_step",
1403 source_line: s.loc.line,
1404 source_column: s.loc.column,
1405 op: s.op.clone(),
1406 message_type: s.message_type.clone(),
1407 branches: s
1408 .branches
1409 .iter()
1410 .map(|b| IRSessionBranch {
1411 node_type: "session_branch",
1412 label: b.label.clone(),
1413 steps: b
1414 .steps
1415 .iter()
1416 .map(|st| self.lower_session_step_ir(st))
1417 .collect(),
1418 })
1419 .collect(),
1420 }
1421 }
1422
1423 fn visit_topology(&self, n: &TopologyDefinition) -> IRTopology {
1425 IRTopology {
1426 node_type: "topology",
1427 source_line: n.loc.line,
1428 source_column: n.loc.column,
1429 name: n.name.clone(),
1430 nodes: n.nodes.clone(),
1431 edges: n
1432 .edges
1433 .iter()
1434 .map(|e| IRTopologyEdge {
1435 node_type: "topology_edge",
1436 source_line: e.loc.line,
1437 source_column: e.loc.column,
1438 source: e.source.clone(),
1439 target: e.target.clone(),
1440 session_ref: e.session_ref.clone(),
1441 })
1442 .collect(),
1443 }
1444 }
1445
1446 fn visit_ensemble(&self, n: &EnsembleDefinition) -> IREnsemble {
1448 IREnsemble {
1449 node_type: "ensemble",
1450 source_line: n.loc.line,
1451 source_column: n.loc.column,
1452 name: n.name.clone(),
1453 observations: n.observations.clone(),
1454 quorum: n.quorum,
1455 aggregation: if n.aggregation.is_empty() {
1456 "majority".to_string()
1457 } else {
1458 n.aggregation.clone()
1459 },
1460 certainty_mode: if n.certainty_mode.is_empty() {
1461 "min".to_string()
1462 } else {
1463 n.certainty_mode.clone()
1464 },
1465 }
1466 }
1467
1468 fn visit_lambda_data(&self, n: &LambdaDataDefinition) -> IRLambdaData {
1469 IRLambdaData {
1470 node_type: "lambda_data",
1471 source_line: n.loc.line,
1472 source_column: n.loc.column,
1473 name: n.name.clone(),
1474 ontology: n.ontology.clone(),
1475 certainty: n.certainty,
1476 temporal_frame_start: n.temporal_frame_start.clone(),
1477 temporal_frame_end: n.temporal_frame_end.clone(),
1478 provenance: n.provenance.clone(),
1479 derivation: n.derivation.clone(),
1480 }
1481 }
1482
1483 fn visit_run(&self, n: &RunStatement) -> IRRun {
1484 IRRun {
1485 node_type: "run",
1486 source_line: n.loc.line,
1487 source_column: n.loc.column,
1488 flow_name: n.flow_name.clone(),
1489 arguments: n.arguments.clone(),
1490 persona_name: n.persona.clone(),
1491 context_name: n.context.clone(),
1492 anchor_names: n.anchors.clone(),
1493 on_failure: n.on_failure.clone(),
1494 on_failure_params: n
1495 .on_failure_params
1496 .iter()
1497 .map(|(k, v)| vec![k.clone(), v.clone()])
1498 .collect(),
1499 output_to: n.output_to.clone(),
1500 effort: n.effort.clone(),
1501 resolved_flow: None,
1502 resolved_persona: None,
1503 resolved_context: None,
1504 resolved_anchors: Vec::new(),
1505 }
1506 }
1507
1508 fn visit_channel(&self, n: &ChannelDefinition) -> IRChannel {
1515 IRChannel {
1516 node_type: "channel",
1517 source_line: n.loc.line,
1518 source_column: n.loc.column,
1519 name: n.name.clone(),
1520 message: n.message.clone(),
1521 qos: n.qos.clone(),
1522 lifetime: n.lifetime.clone(),
1523 persistence: n.persistence.clone(),
1524 shield_ref: n.shield_ref.clone(),
1525 }
1526 }
1527
1528 fn visit_socket(&self, n: &SocketDefinition) -> IRSocket {
1531 IRSocket {
1532 node_type: "socket",
1533 source_line: n.loc.line,
1534 source_column: n.loc.column,
1535 name: n.name.clone(),
1536 protocol: n.protocol.clone(),
1537 backpressure_credit: n.backpressure_credit,
1538 reconnect: n.reconnect,
1539 legal_basis: n.legal_basis.clone(),
1540 }
1541 }
1542}
1543
1544#[cfg(test)]
1547mod fase13_ir_tests {
1548 use super::*;
1549 use crate::lexer::Lexer;
1550 use crate::parser::Parser;
1551
1552 fn compile(src: &str) -> IRProgram {
1553 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
1554 let prog = Parser::new(tokens).parse().expect("parse");
1555 IRGenerator::new().generate(&prog)
1556 }
1557
1558 #[test]
1559 fn channel_lowered_with_all_fields() {
1560 let src = r#"
1561 type Order { id: String }
1562 shield Gate { scan: [pii_leak] }
1563 channel C { message: Order qos: at_least_once lifetime: affine persistence: ephemeral shield: Gate }
1564 "#;
1565 let ir = compile(src);
1566 assert_eq!(ir.channels.len(), 1);
1567 let c = &ir.channels[0];
1568 assert_eq!(c.name, "C");
1569 assert_eq!(c.message, "Order");
1570 assert_eq!(c.qos, "at_least_once");
1571 assert_eq!(c.lifetime, "affine");
1572 assert_eq!(c.persistence, "ephemeral");
1573 assert_eq!(c.shield_ref, "Gate");
1574 }
1575
1576 #[test]
1577 fn channel_second_order_message_preserved() {
1578 let ir = compile(
1579 r#"
1580 type Order { id: String }
1581 channel C1 { message: Order }
1582 channel C2 { message: Channel<Order> }
1583 channel C3 { message: Channel<Channel<Order>> }
1584 "#,
1585 );
1586 let names_to_msgs: std::collections::HashMap<_, _> = ir
1587 .channels
1588 .iter()
1589 .map(|c| (c.name.clone(), c.message.clone()))
1590 .collect();
1591 assert_eq!(names_to_msgs.get("C1"), Some(&"Order".to_string()));
1592 assert_eq!(names_to_msgs.get("C2"), Some(&"Channel<Order>".to_string()));
1593 assert_eq!(
1594 names_to_msgs.get("C3"),
1595 Some(&"Channel<Channel<Order>>".to_string())
1596 );
1597 }
1598
1599 #[test]
1600 fn emit_value_is_channel_resolves_at_lowering() {
1601 let ir = compile(
1602 r#"
1603 type Order { id: String }
1604 channel Inner { message: Order }
1605 channel Outer { message: Channel<Order> }
1606 flow f() -> O { emit Outer(Inner) }
1607 "#,
1608 );
1609 let flow = &ir.flows[0];
1610 match &flow.steps[0] {
1611 IRFlowNode::Emit(e) => {
1612 assert_eq!(e.channel_ref, "Outer");
1613 assert_eq!(e.value_ref, "Inner");
1614 assert!(e.value_is_channel, "Inner is a registered channel");
1615 }
1616 other => panic!("expected Emit, got {:?}", other),
1617 }
1618 }
1619
1620 #[test]
1621 fn emit_scalar_payload_value_is_channel_false() {
1622 let ir = compile(
1623 r#"
1624 type Order { id: String }
1625 channel Out { message: Order }
1626 flow f() -> O { emit Out(payload) }
1627 "#,
1628 );
1629 let flow = &ir.flows[0];
1630 match &flow.steps[0] {
1631 IRFlowNode::Emit(e) => {
1632 assert!(!e.value_is_channel, "scalar payload");
1633 }
1634 other => panic!("expected Emit, got {:?}", other),
1635 }
1636 }
1637
1638 #[test]
1639 fn publish_lowered_with_shield_ref() {
1640 let ir = compile(
1641 r#"
1642 type Order { id: String }
1643 shield Gate { scan: [pii_leak] }
1644 channel C { message: Order shield: Gate }
1645 flow f() -> Cap { publish C within Gate }
1646 "#,
1647 );
1648 match &ir.flows[0].steps[0] {
1649 IRFlowNode::Publish(p) => {
1650 assert_eq!(p.channel_ref, "C");
1651 assert_eq!(p.shield_ref, "Gate");
1652 }
1653 other => panic!("expected Publish, got {:?}", other),
1654 }
1655 }
1656
1657 #[test]
1658 fn discover_lowered_with_alias() {
1659 let ir = compile(
1660 r#"
1661 type Order { id: String }
1662 shield Gate { scan: [pii_leak] }
1663 channel C { message: Order shield: Gate }
1664 flow f() -> O { discover C as ch }
1665 "#,
1666 );
1667 match &ir.flows[0].steps[0] {
1668 IRFlowNode::Discover(d) => {
1669 assert_eq!(d.capability_ref, "C");
1670 assert_eq!(d.alias, "ch");
1671 }
1672 other => panic!("expected Discover, got {:?}", other),
1673 }
1674 }
1675
1676 #[test]
1677 fn json_serialization_works() {
1678 let ir = compile(
1679 r#"
1680 type Order { id: String }
1681 channel C { message: Order }
1682 flow f() -> O { emit C(payload) }
1683 "#,
1684 );
1685 let json = serde_json::to_string(&ir).expect("serialize");
1686 assert!(json.contains(r#""node_type":"channel""#));
1687 assert!(json.contains(r#""node_type":"emit""#));
1688 assert!(json.contains(r#""value_is_channel":false"#));
1689 }
1690}
1691
1692#[cfg(test)]
1693mod fase19_ir_tests {
1694 use super::*;
1702 use crate::ir_nodes::IRFlowNode;
1703 use crate::lexer::Lexer;
1704 use crate::parser::Parser;
1705
1706 fn for_body_ir(body_src: &str) -> Vec<IRFlowNode> {
1709 let src = format!(
1710 "flow Probe() -> Out {{ for x in items.list {{ {body_src} }} }}"
1711 );
1712 let tokens = Lexer::new(&src, "<test>").tokenize().expect("lex");
1713 let prog = Parser::new(tokens).parse().expect("parse");
1714 let ir = IRGenerator::new().generate(&prog);
1715 let flow = ir
1716 .flows
1717 .iter()
1718 .find(|f| f.name == "Probe")
1719 .expect("flow Probe in IR");
1720 match flow.steps.first().expect("flow has at least one step") {
1721 IRFlowNode::ForIn(inner) => inner.body.clone(),
1722 other => panic!("expected ForIn, got {other:?}"),
1723 }
1724 }
1725
1726 #[test]
1727 fn break_keyword_lowers_to_ir_break() {
1728 let body = for_body_ir("break");
1729 assert_eq!(body.len(), 1);
1730 match &body[0] {
1731 IRFlowNode::Break(b) => assert_eq!(b.node_type, "break"),
1732 other => panic!("expected IRFlowNode::Break, got {other:?}"),
1733 }
1734 }
1735
1736 #[test]
1737 fn continue_keyword_lowers_to_ir_continue() {
1738 let body = for_body_ir("continue");
1739 assert_eq!(body.len(), 1);
1740 match &body[0] {
1741 IRFlowNode::Continue(c) => assert_eq!(c.node_type, "continue"),
1742 other => panic!("expected IRFlowNode::Continue, got {other:?}"),
1743 }
1744 }
1745
1746 #[test]
1747 fn break_outside_loop_rejected_by_parser() {
1748 let src = "flow F() -> Out { break }";
1751 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
1752 let result = Parser::new(tokens).parse();
1753 assert!(result.is_err(), "parser must reject break outside loop");
1754 }
1755
1756 #[test]
1757 fn continue_outside_loop_rejected_by_parser() {
1758 let src = "flow F() -> Out { continue }";
1759 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
1760 let result = Parser::new(tokens).parse();
1761 assert!(result.is_err(), "parser must reject continue outside loop");
1762 }
1763
1764 #[test]
1765 fn break_continue_serialize_with_node_type_field() {
1766 let body = for_body_ir("break\ncontinue");
1767 let json = serde_json::to_string(&body).expect("serialize");
1768 assert!(json.contains(r#""node_type":"break""#));
1769 assert!(json.contains(r#""node_type":"continue""#));
1770 }
1771}
1772
1773#[cfg(test)]
1779mod fase37y_ir_mirror_tests {
1780 use super::*;
1781 use crate::lexer::Lexer;
1782 use crate::parser::Parser;
1783
1784 fn lower_endpoint(src: &str) -> crate::ir_nodes::IRAxonEndpoint {
1785 let tokens = Lexer::new(src, "<test>").tokenize().expect("lex");
1786 let prog = Parser::new(tokens).parse().expect("parse");
1787 let ir = IRGenerator::new().generate(&prog);
1788 ir.endpoints
1789 .into_iter()
1790 .next()
1791 .expect("at least one endpoint in IR")
1792 }
1793
1794 #[test]
1795 fn ir_carries_path_params_from_ast() {
1796 let src = r#"
1797 axonendpoint write {
1798 method: POST
1799 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1800 body: SecretWriteRequest
1801 execute: Write
1802 }
1803 "#;
1804 let ep = lower_endpoint(src);
1805 assert_eq!(
1806 ep.path_params,
1807 vec!["tenant_id".to_string(), "secret_name".to_string()],
1808 "IR.path_params mirrors AST.path_params 1:1"
1809 );
1810 }
1811
1812 #[test]
1813 fn ir_carries_query_params_with_type_field_shape() {
1814 let src = r#"
1815 axonendpoint list {
1816 method: GET
1817 path: "/api/users"
1818 query: { status: Text, limit: Int?, after: Uuid? }
1819 execute: ListUsers
1820 }
1821 "#;
1822 let ep = lower_endpoint(src);
1823 assert_eq!(ep.query_params.len(), 3);
1824 assert_eq!(ep.query_params[0].name, "status");
1825 assert_eq!(ep.query_params[0].type_name, "Text");
1826 assert!(!ep.query_params[0].optional);
1827 assert_eq!(ep.query_params[1].name, "limit");
1828 assert_eq!(ep.query_params[1].type_name, "Int");
1829 assert!(ep.query_params[1].optional);
1830 assert_eq!(ep.query_params[2].name, "after");
1831 assert_eq!(ep.query_params[2].type_name, "Uuid");
1832 assert!(ep.query_params[2].optional);
1833 assert_eq!(ep.query_params[0].node_type, "type_field");
1835 }
1836
1837 #[test]
1838 fn d5_byte_identity_when_no_path_or_query() {
1839 let src = r#"
1845 axonendpoint hello {
1846 method: GET
1847 path: "/api/hello"
1848 body: HelloRequest
1849 execute: Hello
1850 }
1851 "#;
1852 let ep = lower_endpoint(src);
1853 let json = serde_json::to_string(&ep).expect("serialize");
1854 assert!(
1855 !json.contains("path_params"),
1856 "D5: absent `path_params` key when empty. Got: {json}"
1857 );
1858 assert!(
1859 !json.contains("query_params"),
1860 "D5: absent `query_params` key when empty. Got: {json}"
1861 );
1862 }
1863
1864 #[test]
1865 fn ir_json_emits_path_params_when_present() {
1866 let src = r#"
1867 axonendpoint x {
1868 method: GET
1869 path: "/api/users/{id}"
1870 execute: X
1871 }
1872 "#;
1873 let ep = lower_endpoint(src);
1874 let json = serde_json::to_string(&ep).expect("serialize");
1875 assert!(
1876 json.contains(r#""path_params":["id"]"#),
1877 "path_params present in JSON. Got: {json}"
1878 );
1879 }
1880
1881 #[test]
1882 fn ir_json_emits_query_params_as_type_field_array() {
1883 let src = r#"
1884 axonendpoint x {
1885 method: GET
1886 path: "/api/x"
1887 query: { status: Text? }
1888 execute: X
1889 }
1890 "#;
1891 let ep = lower_endpoint(src);
1892 let json = serde_json::to_string(&ep).expect("serialize");
1893 assert!(json.contains("query_params"), "key present: {json}");
1894 assert!(json.contains(r#""name":"status""#), "field name: {json}");
1895 assert!(json.contains(r#""type_name":"Text""#), "type_name: {json}");
1896 assert!(json.contains(r#""optional":true"#), "optional: {json}");
1897 }
1898
1899 #[test]
1900 fn ir_round_trips_kivi_corpus() {
1901 let src = r#"
1904 axonendpoint write_secret {
1905 method: POST
1906 path: "/api/tenants/{tenant_id}/secrets/{secret_name}"
1907 query: { dry_run: Bool?, overwrite: Bool? }
1908 body: SecretWriteRequest
1909 execute: WriteSecret
1910 }
1911 "#;
1912 let ep = lower_endpoint(src);
1913 assert_eq!(ep.path_params, vec!["tenant_id", "secret_name"]);
1914 assert_eq!(ep.query_params.len(), 2);
1915 assert_eq!(ep.query_params[0].name, "dry_run");
1916 assert!(ep.query_params[0].optional);
1917 assert_eq!(ep.body_type, "SecretWriteRequest");
1918 assert_eq!(ep.method, "POST");
1919 }
1920}