aimx/aim/context.rs
1//! Evaluation context for AIMX expression evaluation within workflows.
2//!
3//! Provides [`ContextLike`] and [`Context`] for resolving references, invoking
4//! registered functions/methods, managing closure parameters, performing
5//! inference, and running imperative workflow evaluation.
6//! Context instances are not thread-safe and should be used per evaluation.
7//!
8use crate::{
9 get_config,
10 aim::{Row, Rule, Typedef, Workflow, WorkflowLike, WorkflowStatus, Workspace, Writer, LockManager},
11 expressions::{Expression, ExpressionLike, Reference},
12 functions::FunctionRegistry,
13 inference::{Prompt, parse_response, send_request, validate_responses},
14 values::{Errata, Value},
15};
16use anyhow::{Result, anyhow};
17use std::{
18 mem::replace,
19 sync::Arc,
20};
21
22/// Evaluation context interface used by the AIMX engine.
23///
24/// Implementors provide:
25/// - Reference lookup (`get_value` / `set_value`).
26/// - Function and method dispatch via [`FunctionRegistry`].
27/// - Closure parameter stack management.
28/// - Inference calls into workflows or closures.
29/// - Imperative workflow execution via [`ContextLike::run_evaluation`].
30pub trait ContextLike {
31 /// Start a new closure and save the current stack.
32 fn start_closure(&mut self) -> [(Arc<str>, Arc<Value>); 2];
33
34 /// Set the identifier of a closure parameter slot (0 or 1).
35 fn set_key(&mut self, index: usize, identifier: Arc<str>);
36
37 /// Set the value of the index mapped parameter used by element-wise functions like `map`.
38 ///
39 /// # Arguments
40 ///
41 /// * `index` - The index value to set (0 or 1)
42 /// * `value` - The value to set
43 fn set_parameter(&mut self, index: usize, value: Arc<Value>);
44
45 /// End the closure and restore the previous stack.
46 fn end_closure(&mut self, stack: [(Arc<str>, Arc<Value>); 2]);
47
48 /// Get the value of a referenced rule or parameter variable.
49 ///
50 /// # Arguments
51 ///
52 /// * `reference` - The reference to resolve
53 ///
54 /// # Returns
55 ///
56 /// Returns the value of the referenced variable or an error if the reference
57 /// cannot be resolved.
58 fn get_value(&mut self, reference: &Reference) -> Result<Arc<Value>>;
59
60 /// Set the value of a rule.
61 ///
62 /// # Arguments
63 ///
64 /// * `reference` - The reference to the rule to set
65 /// * `value` - The value to assign
66 ///
67 /// # Returns
68 ///
69 /// Returns `Ok(())` on success or an error if the referenced rule cannot be set.
70 fn set_value(&mut self, identifier: &str, value: Arc<Value>) -> Result<()>;
71
72 /// Call a standalone function.
73 ///
74 /// # Arguments
75 /// * `name` - The name of the function to call
76 /// * `arg` - The argument(s) to pass to the function (Value can be Empty, Literal, or Array)
77 ///
78 /// # Returns
79 ///
80 /// Returns the result of the function call or an error if the function is not found
81 /// or if there's an error during execution.
82 fn function_call(&mut self, name: Arc<str>, arg: Arc<Value>) -> Result<Arc<Value>>;
83
84 /// Call a method on a value.
85 ///
86 /// # Arguments
87 /// * `name` - The name of the method to call
88 /// * `value` - The value on which to call the method
89 /// * `arg` - The argument(s) to pass to the method (Value can be Empty, Literal, or Array)
90 ///
91 /// # Returns
92 ///
93 /// Returns the result of the method call or an error if the method is not found
94 /// or if there's an error during execution.
95 fn method_call(&mut self, name: Arc<str>, value: Arc<Value>, arg: Arc<Value>) -> Result<Arc<Value>>;
96
97 /// Run inference on a referenced workflow.
98 ///
99 /// # Arguments
100 /// * `reference` - The workflow reference
101 /// * `arg` - The argument(s) to pass to the inference call (Value can be Empty, Literal, or Array)
102 ///
103 /// # Returns
104 ///
105 /// Returns the result of the inference call or an error if the workflow is not found
106 /// or if there's an error during execution.
107 fn inference_call(&mut self, reference: Arc<Reference>, arg: Arc<Value>) -> Result<Arc<Value>>;
108
109 fn run_evaluation(&mut self, start_state: Option<Arc<str>>) -> WorkflowStatus;
110}
111
112pub struct StackFrame {
113 prev_source: Arc<Workflow>,
114 prev_target: Workflow,
115}
116
117impl StackFrame {
118 fn new(prev_source: Arc<Workflow>, prev_target: Workflow,) -> Self {
119 Self {
120 prev_source,
121 prev_target,
122 }
123 }
124}
125
126/// Default AIMX evaluation context implementing [`ContextLike`].
127///
128/// Holds the current locator, active workflow (read-only and optional mutable clone),
129/// closure parameters, and an optional workflow lock. Supports reference resolution,
130/// rule updates, inference into nested workflows, and imperative workflow execution.
131/// Not thread-safe; use per evaluation.
132#[derive(Debug)]
133pub struct Context {
134 source: Arc<Workflow>,
135 target: Workflow,
136 parameters: [(Arc<str>, Arc<Value>); 2],
137}
138
139impl Context {
140 /// Acquire context with enhanced error handling
141 pub fn open_with_error_handling(source_locator: Arc<Reference>, target_locator: Arc<Reference>) -> Result<Self> {
142 let source_node = Workspace::locate_node(&source_locator)?;
143 let source = source_node.get_workflow()?;
144 let target_node = Workspace::locate_node(&target_locator)?;
145 let target = target_node.get_workflow_mut()?;
146 Ok(Self {
147 source,
148 target,
149 parameters: [(Value::empty_str(), Value::empty()), (Value::empty_str(), Value::empty())],
150 })
151 }
152}
153
154impl Context {
155 // -----------------------------------------------------------------------------
156 // Test Methods
157 // -----------------------------------------------------------------------------
158
159 // A helper function to create a context for integration tests
160 pub fn for_testing() -> Result<Self> {
161 let locator = Reference::workspace();
162 let source = Arc::new(Workflow::for_testing(locator.clone())?);
163 let target = Workflow::for_testing(locator)?;
164 Ok(Self {
165 source,
166 target,
167 parameters: [(Value::empty_str(), Value::empty()), (Value::empty_str(), Value::empty())],
168 })
169 }
170
171 /// Test Helper function to add a predefined value to the context for testing purposes.
172 ///
173 /// This method creates a rule with the given identifier and value and adds it
174 /// to the workspace. This is primarily used for testing expressions that reference
175 /// predefined variables.
176 ///
177 /// # Arguments
178 ///
179 /// * `identifier` - The identifier name for the value
180 /// * `value` - The value to associate with the identifier
181 ///
182 /// # Returns
183 ///
184 /// Returns the modified context for method chaining.
185 pub fn with_value(mut self, identifier: &str, value: Value) -> Self {
186 let typedef = value.get_type().unwrap_or(Typedef::Any);
187 let rule = Rule::new(
188 Arc::from(identifier),
189 typedef,
190 Expression::Empty,
191 Arc::new(value)
192 );
193 self.target.append_or_update(Row::Rule(Arc::new(rule)));
194 self
195 }
196
197 /// Test Helper function to add a predefined rule to the context for testing purposes.
198 pub fn with_rule(
199 mut self,
200 identifier: &str,
201 typedef: Typedef,
202 expression: Expression,
203 value: Value,
204 ) -> Self {
205 let rule = Rule::new(
206 Arc::from(identifier),
207 typedef,
208 expression,
209 Arc::new(value)
210 );
211 self.target.append_or_update(Row::Rule(Arc::new(rule)));
212 self
213 }
214
215 /// Test Helper function to commit the predefined rules to the source workflow for testing.
216 pub fn with_commit(mut self) -> Self {
217 self.source = Arc::new(self.target.clone());
218 self
219 }
220
221 /// Test Helper function to lookup the predefined rule values in the workflow for testing.
222 pub fn get_local(&mut self, identifier: &str) -> Result<Arc<Value>> {
223 let local_reference = Reference::new(Arc::from(identifier));
224 self.get_value(&local_reference)
225 }
226
227 // -----------------------------------------------------------------------------
228 // Api Methods
229 // -----------------------------------------------------------------------------
230
231 // Exclusively called by Instance::locate() via Instance::get_context_lock()
232 pub fn open(source_locator: Arc<Reference>, target_locator: Arc<Reference>) -> Result<Self> {
233 Self::open_with_error_handling(source_locator, target_locator)
234 }
235
236 pub fn save(&mut self) -> Result<()> {
237 let locator = self.source.locator();
238 // Save the target workflow
239 self.target.save()?;
240 // If the source and target are the same
241 if self.target.locator() == locator {
242 let node = Workspace::locate_node(&locator)?;
243 // Atomically copy the target to update the source
244 node.set_workflow(self.target.clone());
245 }
246 Ok(())
247 }
248
249 pub fn save_all(&mut self) -> Result<()> {
250 let locator = self.source.locator();
251 // Save the target workflow
252 self.target.save_all()?;
253 // If the source and target are the same
254 if self.target.locator() == locator {
255 let node = Workspace::locate_node(&locator)?;
256 // Atomically copy the target to update the source
257 node.set_workflow(self.target.clone());
258 }
259 Ok(())
260 }
261
262 /// Gets read-only access to the current workflow.
263 ///
264 /// Returns a clone of the Arc reference to the current workflow, allowing
265 /// read-only access to workflow data without affecting the context's ownership.
266 pub fn source(&self) -> Arc<Workflow> {
267 self.source.clone()
268 }
269
270 /// Gets mutable access to the workflow if available.
271 ///
272 /// Returns a mutable reference to the workflow if one is currently being
273 /// modified, or None if only read-only access is available.
274 pub fn target(&mut self) -> &mut Workflow {
275 &mut self.target
276 }
277
278 fn enter_scope(&mut self, source: Arc<Workflow>, target_locator: Arc<Reference>) -> Result<StackFrame> {
279 let target = Workflow::for_testing(target_locator)?;
280 let prev_source = replace(&mut self.source, source);
281 let prev_target = replace(&mut self.target, target);
282 Ok(StackFrame::new(prev_source, prev_target))
283 }
284
285 fn exit_scope(&mut self, stack: StackFrame) -> Workflow {
286 self.source = stack.prev_source;
287 replace(&mut self.target, stack.prev_target)
288 }
289
290 fn locate_value(&self, reference: &Reference) -> Result<Arc<Value>> {
291 match reference {
292 Reference::One(one) => {
293 // Try target workflow
294 if let Some(rule) = self.target.get_rule(one) {
295 return Ok(rule.value().clone());
296 }
297 // Try source workflow
298 if let Some(rule) = self.source.get_rule(one) {
299 return Ok(rule.value().clone());
300 }
301 }
302 Reference::Two(one, two) => {
303 // Try target workflow
304 if let Some(rule) = self.target.get_rule(one) {
305 if let Some(node) = rule.get_node() {
306 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
307 return Ok(rule.value().clone());
308 }
309 }
310 }
311 // Try source workflow
312 if let Some(rule) = self.source.get_rule(one) {
313 if let Some(node) = rule.get_node() {
314 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
315 return Ok(rule.value().clone());
316 }
317 }
318 }
319 }
320 Reference::Three(one, two, three) => {
321 // Try target workflow
322 if let Some(rule) = self.target.get_rule(one) {
323 if let Some(node) = rule.get_node() {
324 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
325 if let Some(node) = rule.get_node() {
326 if let Some(rule) = &node.get_workflow()?.get_rule(three) {
327 return Ok(rule.value().clone());
328 }
329 }
330 }
331 }
332 }
333 // Try source workflow
334 if let Some(rule) = self.source.get_rule(one) {
335 if let Some(node) = rule.get_node() {
336 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
337 if let Some(node) = rule.get_node() {
338 if let Some(rule) = &node.get_workflow()?.get_rule(three) {
339 return Ok(rule.value().clone());
340 }
341 }
342 }
343 }
344 }
345 }
346 _ => {}
347 }
348 if let Some(rule) = Workspace::locate_rule(reference)? {
349 return Ok(rule.value().clone());
350 }
351 Err(anyhow!("Undefined locator ~{}", reference))
352 }
353
354 fn locate_typedef(&mut self, identifier: &str) -> Result<Typedef> {
355 // Try target workflow
356 if let Some(rule) = self.target.get_rule(identifier) {
357 return Ok(rule.typedef().clone());
358 }
359 // Try source workflow
360 if let Some(rule) = self.source.get_rule(identifier) {
361 return Ok(rule.typedef().clone());
362 }
363 Err(anyhow!("Undefined locator ~{}", identifier))
364 }
365
366 // Dereference the Value and return a normalized reference to it.
367 fn locate_inference(&self, reference: &Reference) -> Result<(Reference, Arc<str>, Arc<Value>)> {
368 match reference {
369 Reference::One(one) => {
370 // Try writable workflow
371 if let Some(rule) = self.target.get_rule(one) {
372 let normalized = self.target.locator().combine_and_normalize(reference)?;
373 return Ok((normalized, one.clone(), rule.value().clone()));
374 }
375 // Try read-only workflow
376 if let Some(rule) = self.source.get_rule(one) {
377 let normalized = self.source.locator().combine_and_normalize(reference)?;
378 return Ok((normalized, one.clone(), rule.value().clone()));
379 }
380 }
381 Reference::Two(one, two) => {
382 // Try writable workflow
383 if let Some(rule) = self.target.get_rule(one) {
384 if let Some(node) = rule.get_node() {
385 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
386 let normalized = self.target.locator().combine_and_normalize(reference)?;
387 return Ok((normalized, two.clone(), rule.value().clone()));
388 }
389 }
390 }
391 // Try read-only workflow
392 if let Some(rule) = self.source.get_rule(one) {
393 if let Some(node) = rule.get_node() {
394 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
395 let normalized = self.source.locator().combine_and_normalize(reference)?;
396 return Ok((normalized, two.clone(), rule.value().clone()));
397 }
398 }
399 }
400 // Try workspace
401 if let Ok(node) = Workspace::get_node(one) {
402 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
403 return Ok((reference.clone(), two.clone(), rule.value().clone()));
404 }
405 }
406 }
407 Reference::Three(one, two, three) => {
408 // Try workspace
409 if let Ok(node) = Workspace::get_node(one) {
410 if let Some(rule) = &node.get_workflow()?.get_rule(two) {
411 if let Some(node) = rule.get_node() {
412 if let Some(rule) = &node.get_workflow()?.get_rule(three) {
413 return Ok((reference.clone(), three.clone(), rule.value().clone()));
414 }
415 }
416 }
417 }
418 }
419 _ => {}
420 }
421 Err(anyhow!(
422 "Undefined locator {} within {} scope~{}",
423 reference,
424 &self.source.locator(),
425 reference
426 ))
427 }
428
429 pub fn print(&self, writer: &mut Writer) {
430 self.source.locator().print(writer);
431 writer.write_str(", ");
432 self.target.locator().print(writer);
433 }
434}
435
436impl ContextLike for Context {
437 /// Start a new closure and save the current parameter stack.
438 fn start_closure(&mut self) -> [(Arc<str>, Arc<Value>); 2] {
439 replace(
440 &mut self.parameters,
441 [(Value::empty_str(), Value::empty()), (Value::empty_str(), Value::empty())],
442 )
443 }
444
445 /// Set the key of the indexed mapped parameter used by element-wise functions like `map`.
446 ///
447 /// # Arguments
448 ///
449 /// * `index` - The index key to set (0 or 1)
450 /// * `identifier` - The key to set
451 fn set_key(&mut self, index: usize, identifier: Arc<str>) {
452 if self.parameters[index].0 != identifier {
453 self.parameters[index].0 = identifier.clone();
454 }
455 }
456
457 /// Set the value of the index mapped parameter used by element-wise functions like `map`.
458 ///
459 /// # Arguments
460 ///
461 /// * `index` - The index value to set (0 or 1)
462 /// * `value` - The value to set
463 fn set_parameter(&mut self, index: usize, value: Arc<Value>) {
464 self.parameters[index].1 = value.clone();
465 }
466
467 /// End the closure and restore the previous parameter stack.
468 fn end_closure(&mut self, stack: [(Arc<str>, Arc<Value>); 2]) {
469 self.parameters = stack;
470 }
471 /// Get the value of a referenced rule or parameter variable.
472 ///
473 /// This implementation only supports single-part references (default identifiers).
474 /// Multi-part references (e.g., `object.property`) are not supported in this
475 /// simplified context.
476 ///
477 /// # Arguments
478 ///
479 /// * `reference` - The reference to resolve
480 ///
481 /// # Returns
482 ///
483 /// Returns a `Result<Value>` containing the referenced value or an error
484 /// if the reference cannot be resolved.
485 fn get_value(&mut self, reference: &Reference) -> Result<Arc<Value>> {
486 // Check closure parameters first
487 if *reference == *self.parameters[0].0 {
488 Ok(self.parameters[0].1.clone())
489 } else if *reference == *self.parameters[1].0 {
490 Ok(self.parameters[1].1.clone())
491 } else {
492 self.locate_value(reference)
493 }
494 }
495
496 /// Get the value of a rule or parameter.
497 ///
498 /// # Arguments
499 ///
500 /// * `reference` - The reference to set
501 /// * `literal` - The value to assign
502 ///
503 /// # Returns
504 ///
505 /// Returns `Ok(())` if the referenced value was set or an error if the
506 /// reference cannot be resolved.
507 fn set_value(&mut self, identifier: &str, value: Arc<Value>) -> Result<()> {
508 // Check closure parameters first
509 if *identifier == *self.parameters[0].0 {
510 self.parameters[0].1 = value;
511 Ok(())
512 } else if *identifier == *self.parameters[1].0 {
513 self.parameters[1].1 = value;
514 Ok(())
515 } else {
516 // Find the referenced rule's typedef
517 match self.locate_typedef(identifier) {
518 Ok(typedef) => {
519 // Promote type
520 let value = match value.to_type(&typedef) {
521 Ok(v) => v,
522 Err(e) => return Err(anyhow!("{}~{}", e, identifier)),
523 };
524 // Try mutable workflow first
525 if let Some(rule) = self.target.get_rule(identifier) {
526 let mut new_rule = (*rule).clone();
527 new_rule.set_value(value)?;
528 let _ = self.target.update_rule(Arc::new(new_rule));
529 Ok(())
530 } else {
531 Err(anyhow!("Undefined locator ~{}", identifier))
532 }
533 }
534 Err(e) => Err(e),
535 }
536 }
537 }
538
539 /// Call a standalone function.
540 ///
541 /// Delegates function calls to the singleton function registry.
542 ///
543 /// # Arguments
544 ///
545 /// * `name` - The name of the function to call
546 /// * `arg` - The argument(s) to pass to the function
547 ///
548 /// # Returns
549 ///
550 /// Returns a `Result<Value>` containing the function result or an error
551 /// if the function is not found or execution fails.
552 fn function_call(&mut self, name: Arc<str>, arg: Arc<Value>) -> Result<Arc<Value>> {
553 let registry = FunctionRegistry::read_lock()?;
554 registry.function_call(self, name, arg)
555 }
556
557 /// Call a method on a value.
558 ///
559 /// Delegates method calls to the singleton function registry.
560 ///
561 /// # Arguments
562 ///
563 /// * `name` - The name of the method to call
564 /// * `value` - The value on which to call the method
565 /// * `arg` - The argument(s) to pass to the method
566 ///
567 /// # Returns
568 ///
569 /// Returns a `Result<Value>` containing the method result or an error
570 /// if the method is not found or execution fails.
571 fn method_call(&mut self, name: Arc<str>, value: Arc<Value>, arg: Arc<Value>) -> Result<Arc<Value>> {
572 let registry = FunctionRegistry::read_lock()?;
573 registry.method_call(self, name, value, arg)
574 }
575
576 /// Run inference on a workflow node or call a closure.
577 ///
578 /// * `Node` - Delegates inference to a workflow node
579 /// * `Closure` - Delegates closure calls to a first class function
580 ///
581 /// # Arguments
582 ///
583 /// * `reference` - A reference to the rule containing the node or closure
584 /// * `arg` - The argument(s) to pass to the inference/call
585 ///
586 /// # Returns
587 ///
588 /// Returns a `Result<Value>` containing the inference/call result or an error
589 /// if the rule is not found or the inference/call fails.
590 fn inference_call(&mut self, reference: Arc<Reference>, arg: Arc<Value>) -> Result<Arc<Value>> {
591
592 let (source_locator, identifier, value) = self.locate_inference(&reference)?;
593
594 if *self.source.locator() == source_locator {
595 return Err(anyhow!("Circular inference detected ~{}", source_locator));
596 }
597
598 let node = match &*value {
599 Value::Node(node) => node,
600 // Call the first class function
601 Value::Closure(closure) => {
602 return closure.call(self, arg);
603 }
604 _ => {
605 return Err(anyhow!(
606 "Expected Node or Closure found {}~{}",
607 value.type_as_string(),
608 source_locator
609 ));
610 }
611 };
612
613 let target_locator = Arc::new(self.target.locator().add_child(identifier)?);
614
615 // 1️⃣ Acquire file-specific write lock ONLY when this is a descendant workflow
616 let mutex = LockManager::get_mutex(target_locator.clone());
617 let _lock_guard = mutex.lock()
618 .expect("Lock poisoned in context inference.");
619
620 // 2️⃣ Push current state onto stack and switch to the new node workflow
621 let source = node.get_workflow()?.clone();
622 let stack = self.enter_scope(source, target_locator)?;
623
624 let inference = node.inference().clone();
625 let model = inference.model();
626 //let pattern = self.source.pattern();
627 // Get the inference provider
628 let provider = get_config().unwrap().get_provider(model).clone();
629
630 // 3️⃣ Assign arguments
631 // TODO add argument list to node?
632
633 // 4️⃣ Generate a prompt for the workflow pattern
634 let prompt = Prompt::generate(self.source.clone(), self, &provider.capability);
635
636 // 5️⃣ Send provider inference prompt
637 match send_request(&provider, &prompt) {
638 Ok(response) => {
639 // 6️⃣ Parse inference response
640 if let Some(responses) = parse_response(&response.text()) {
641 // 7️⃣ Validate and assign responses
642 validate_responses(self, responses);
643 }
644 //let input = inference.finish();
645 }
646 _ => {}
647 }
648
649 // 8️⃣ Evaluate workflow expressions
650 let status = self.run_evaluation(None);
651 let _ = status;
652
653 // 9️⃣ Pop previous state from stack
654 let mut target = self.exit_scope(stack);
655
656 // 🔟 Save changes and update node state
657 target.save()?;
658 // Atomically replace the old node workflow with the new workflow.
659 node.set_workflow(target);
660
661 // Return results
662 Ok(Value::empty())
663 // _lock_guard is dropped here, releasing the lock if it was acquired.
664
665 }
666
667 /// Evaluate this workflow as an imperative rule program.
668 ///
669 /// This interpreter walks rules by row index and respects control-flow
670 /// values produced by rule evaluation.
671 ///
672 /// Branch and Retry rules are treated as workflow-level control
673 /// constructs; their `Value::Branch` results are not written back
674 /// via `set_value`.
675 ///
676 /// To guard against accidental cycles (e.g. infinite branch loops),
677 /// a fixed maximum step count is enforced. Exceeding this limit
678 /// terminates evaluation with a `WorkflowStatus::Failed` result.
679 ///
680 /// # Parameters
681 ///
682 /// * `context` - The evaluation context providing variable access
683 /// * `start_state` - Optional rule identifier to start from when
684 /// resuming a suspended workflow.
685 fn run_evaluation(&mut self, start_state: Option<Arc<str>>) -> WorkflowStatus {
686 const MAX_STEPS: usize = 10_000;
687
688 let mut steps: usize = 0;
689 let mut index: usize = match start_state {
690 Some(ref identifier) => self.source.get_index(identifier).unwrap_or(0),
691 None => 0,
692 };
693 let row_len = self.source.row_count();
694
695 // Index-based interpreter loop
696 while index < row_len {
697 if steps >= MAX_STEPS {
698 // Protect against cycles; caller can inspect Errata if emitted
699 let state_id = match self.source.get_row(index).rule() {
700 Some(rule) => Some(rule.identifier()),
701 None => None,
702 };
703
704 let errata = Some(Arc::new(Errata::Two {
705 reason: Arc::from("MAX_STEPS exceeded in workflow evaluation"),
706 formula: Arc::from(format!("Workflow: {}", self.source.locator())),
707 }));
708 return WorkflowStatus::Failed { state_id, errata };
709 }
710 steps += 1;
711
712 let rule = match self.source.get_row(index).rule() {
713 Some(rule) => rule,
714 None => {
715 index += 1;
716 continue;
717 }
718 };
719
720 let current_id = rule.identifier();
721
722 // Evaluate the rule; Rule::evaluate already maps non-critical
723 // errors into Value::Errata.
724 match rule.evaluate(self) {
725 Ok(value) => {
726 match &*value {
727 Value::Branch(target) => {
728 // Control-flow branch: jump to target rule if it exists.
729 if let Some(next_index) = self.source.get_index(target) {
730 index = next_index;
731 continue;
732 } else {
733 // Unknown branch target: signal failure at this rule.
734 let errata = Some(Arc::new(Errata::Three {
735 reason: Arc::from(format!("Unknown branch target '{}'", target)),
736 formula: current_id.clone(),
737 location: target.clone(),
738 }));
739 return WorkflowStatus::Failed {
740 state_id: Some(current_id),
741 errata,
742 };
743 }
744 }
745 // Normal values (including Errata) are left for
746 // Rule::evaluate / Context to handle; proceed.
747 _ => {
748 index += 1;
749 }
750 }
751 }
752 Err(err) => {
753 // Critical errors: capture details into Errata and surface
754 // them via the workflow status so callers can propagate.
755 let errata = Some(Arc::new(Errata::Two {
756 reason: Arc::from(format!("Workflow evaluation error at '{}': {}", current_id, err)),
757 formula: current_id.clone(),
758 }));
759 return WorkflowStatus::Failed {
760 state_id: Some(current_id),
761 errata,
762 };
763 }
764 }
765 }
766
767 WorkflowStatus::Completed
768 }
769}