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}