Skip to main content

txtx_core/eval/
mod.rs

1use crate::runbook::embedded_runbook::ExecutableEmbeddedRunbookInstance;
2use crate::runbook::{
3    get_source_context_for_diagnostic, RunbookExecutionMode, RunbookWorkspaceContext,
4    RuntimeContext,
5};
6use crate::types::{ConstructType, RunbookExecutionContext, RunbookSources};
7use kit::constants::{RE_EXECUTE_COMMAND, THIRD_PARTY_SIGNATURE_STATUS};
8use kit::types::commands::{
9    ConstructInstance, PostConditionEvaluationResult, PreConditionEvaluationResult,
10};
11use kit::types::types::ObjectDefinition;
12use std::collections::{BTreeMap, HashMap, HashSet};
13use std::fmt::Display;
14use txtx_addon_kit::constants::{
15    SIGNATURE_APPROVED, SIGNATURE_SKIPPABLE, SIGNED_MESSAGE_BYTES, SIGNED_TRANSACTION_BYTES,
16    TX_HASH,
17};
18use txtx_addon_kit::hcl::structure::Block as HclBlock;
19use txtx_addon_kit::helpers::hcl::visit_optional_untyped_attribute;
20use txtx_addon_kit::indexmap::IndexMap;
21use txtx_addon_kit::types::commands::{
22    add_ctx_to_diag, add_ctx_to_embedded_runbook_diag, CommandExecutionFuture,
23    DependencyExecutionResultCache, UnevaluatedInputsMap,
24};
25use txtx_addon_kit::types::embedded_runbooks::EmbeddedRunbookStatefulExecutionContext;
26use txtx_addon_kit::types::frontend::{
27    ActionItemRequestUpdate, ActionItemResponse, ActionItemResponseType, Actions, Block,
28    BlockEvent, ErrorPanelData, Panel,
29};
30use txtx_addon_kit::types::signers::SignersState;
31use txtx_addon_kit::types::stores::AddonDefaults;
32use txtx_addon_kit::types::types::{ObjectProperty, RunbookSupervisionContext, Type};
33use txtx_addon_kit::types::{ConstructId, PackageId};
34use txtx_addon_kit::types::{EvaluatableInput, WithEvaluatableInputs};
35use txtx_addon_kit::{
36    hcl::{
37        expr::{BinaryOperator, Expression, UnaryOperator},
38        template::Element,
39    },
40    types::{
41        commands::{CommandExecutionResult, CommandInputsEvaluationResult},
42        diagnostics::Diagnostic,
43        frontend::{ActionItemRequest, ActionItemStatus},
44        signers::SignerInstance,
45        types::Value,
46        ConstructDid,
47    },
48    uuid::Uuid,
49};
50
51// The flow for signer evaluation should be drastically different
52// Instead of activating all the signers detected in a graph, we should instead traverse the graph and collecting the signers
53// being used.
54pub async fn run_signers_evaluation(
55    runbook_workspace_context: &RunbookWorkspaceContext,
56    runbook_execution_context: &mut RunbookExecutionContext,
57    runtime_context: &RuntimeContext,
58    supervision_context: &RunbookSupervisionContext,
59    action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
60    action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
61    progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
62) -> EvaluationPassResult {
63    let mut pass_result = EvaluationPassResult::new(&Uuid::new_v4());
64
65    let signers_instances = &runbook_execution_context.signers_instances;
66    let instantiated_signers = runbook_execution_context.order_for_signers_initialization.clone();
67
68    for construct_did in instantiated_signers.into_iter() {
69        let Some(signer_instance) = runbook_execution_context.signers_instances.get(&construct_did)
70        else {
71            continue;
72        };
73        let package_id = signer_instance.package_id.clone();
74
75        let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
76
77        let instantiated = runbook_execution_context.is_signer_instantiated(&construct_did);
78
79        let add_ctx_to_diag = add_ctx_to_diag(
80            ConstructType::Signer.to_string(),
81            signer_instance.specification.matcher.clone(),
82            signer_instance.name.clone(),
83            signer_instance.namespace.clone(),
84        );
85
86        let mut cached_dependency_execution_results = DependencyExecutionResultCache::new();
87
88        let references_expressions =
89            signer_instance.get_expressions_referencing_commands_from_inputs();
90
91        for (_input, expr) in references_expressions.into_iter() {
92            if let Some((dependency, _, _)) = runbook_workspace_context
93                .try_resolve_construct_reference_in_expression(&package_id, &expr)
94                .unwrap()
95            {
96                if let Some(evaluation_result) =
97                    runbook_execution_context.commands_execution_results.get(&dependency)
98                {
99                    match cached_dependency_execution_results.merge(&dependency, &evaluation_result)
100                    {
101                        Ok(_) => {}
102                        Err(diag) => {
103                            pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
104                            continue;
105                        }
106                    }
107                }
108            }
109        }
110
111        let input_evaluation_results = runbook_execution_context
112            .commands_inputs_evaluation_results
113            .get(&construct_did.clone());
114
115        let addon_context_key = (package_id.did(), signer_instance.namespace.clone());
116        let addon_defaults = runbook_workspace_context.get_addon_defaults(&addon_context_key);
117
118        let evaluated_inputs_res = perform_signer_inputs_evaluation(
119            &signer_instance,
120            &cached_dependency_execution_results,
121            &input_evaluation_results,
122            addon_defaults,
123            &package_id,
124            &runbook_workspace_context,
125            &runbook_execution_context,
126            runtime_context,
127        );
128
129        let evaluated_inputs = match evaluated_inputs_res {
130            Ok(result) => match result {
131                CommandInputEvaluationStatus::Complete(result) => result,
132                CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
133                    continue;
134                }
135                CommandInputEvaluationStatus::Aborted(_, diags) => {
136                    pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
137                    continue;
138                }
139            },
140            Err(diags) => {
141                pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
142                return pass_result;
143            }
144        };
145
146        let signer = runbook_execution_context.signers_instances.get(&construct_did).unwrap();
147
148        let mut signers_state = runbook_execution_context.signers_state.take().unwrap();
149        signers_state.create_new_signer(&construct_did, &signer.name);
150
151        let res = signer
152            .check_activability(
153                &construct_did,
154                &evaluated_inputs,
155                signers_state,
156                signers_instances,
157                &action_item_requests.get(&construct_did),
158                &action_item_responses.get(&construct_did),
159                supervision_context,
160                &runtime_context.authorization_context,
161                instantiated,
162                instantiated,
163            )
164            .await;
165        let signers_state = match res {
166            Ok((signers_state, mut new_actions)) => {
167                if new_actions.has_pending_actions() {
168                    runbook_execution_context.signers_state = Some(signers_state);
169                    // a signer could be dependent on a reference to a signer. if this another signer is depending
170                    // on this one that has actions, it still is safe to check for actions on the depending signer.
171                    // but for the depending signer to get this far in the execution, it needs to be able to reference
172                    // the did of _this_ signer, so we insert empty execution results.
173                    runbook_execution_context
174                        .commands_execution_results
175                        .insert(construct_did, CommandExecutionResult::new());
176                    pass_result.actions.append(&mut new_actions);
177                    continue;
178                }
179                pass_result.actions.append(&mut new_actions);
180                signers_state
181            }
182            Err((signers_state, diag)) => {
183                runbook_execution_context.signers_state = Some(signers_state);
184                if let Some(requests) = action_item_requests.get_mut(&construct_did) {
185                    for item in requests.iter_mut() {
186                        // This should be improved / become more granular
187                        let update = ActionItemRequestUpdate::from_id(&item.id)
188                            .set_status(ActionItemStatus::Error(diag.clone()));
189                        pass_result.actions.push_action_item_update(update);
190                    }
191                }
192
193                pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
194
195                return pass_result;
196            }
197        };
198
199        runbook_execution_context
200            .commands_inputs_evaluation_results
201            .insert(construct_did.clone(), evaluated_inputs.clone());
202
203        let res = signer
204            .perform_activation(
205                &construct_did,
206                &evaluated_inputs,
207                signers_state,
208                signers_instances,
209                progress_tx,
210            )
211            .await;
212
213        let (mut result, signers_state) = match res {
214            Ok((signers_state, result)) => (Some(result), Some(signers_state)),
215            Err((signers_state, diag)) => {
216                runbook_execution_context.signers_state = Some(signers_state);
217                pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
218                return pass_result;
219            }
220        };
221        runbook_execution_context.signers_state = signers_state;
222        let Some(result) = result.take() else {
223            continue;
224        };
225
226        runbook_execution_context.commands_execution_results.insert(construct_did.clone(), result);
227    }
228
229    pass_result
230}
231
232pub struct EvaluationPassResult {
233    pub actions: Actions,
234    diagnostics: Vec<Diagnostic>,
235    pub pending_background_tasks_futures: Vec<CommandExecutionFuture>,
236    pub pending_background_tasks_constructs_uuids: Vec<(ConstructDid, ConstructDid)>,
237    pub background_tasks_uuid: Uuid,
238    pub nodes_to_re_execute: Vec<ConstructDid>,
239}
240
241impl EvaluationPassResult {
242    pub fn new(background_tasks_uuid: &Uuid) -> Self {
243        Self {
244            actions: Actions::none(),
245            diagnostics: vec![],
246            pending_background_tasks_futures: vec![],
247            pending_background_tasks_constructs_uuids: vec![],
248            background_tasks_uuid: background_tasks_uuid.clone(),
249            nodes_to_re_execute: vec![],
250        }
251    }
252
253    pub fn merge(&mut self, mut other: EvaluationPassResult) {
254        self.actions.append(&mut other.actions);
255        self.diagnostics.append(&mut other.diagnostics);
256        self.pending_background_tasks_futures.append(&mut other.pending_background_tasks_futures);
257        self.pending_background_tasks_constructs_uuids
258            .append(&mut other.pending_background_tasks_constructs_uuids);
259    }
260
261    pub fn compile_diagnostics_to_block(&self) -> Option<Block> {
262        if self.diagnostics.is_empty() {
263            return None;
264        };
265        Some(Block {
266            uuid: Uuid::new_v4(),
267            visible: true,
268            panel: Panel::ErrorPanel(ErrorPanelData::from_diagnostics(&self.diagnostics)),
269        })
270    }
271
272    pub fn diagnostics(&self) -> Vec<Diagnostic> {
273        self.diagnostics.clone()
274    }
275    pub fn has_diagnostics(&self) -> bool {
276        !self.diagnostics.is_empty()
277    }
278
279    pub fn push_diagnostic(
280        &mut self,
281        diag: &Diagnostic,
282        construct_id: &ConstructId,
283        ctx_adder: &impl Fn(&Diagnostic) -> Diagnostic,
284    ) {
285        self.diagnostics.push(ctx_adder(diag).location(&construct_id.construct_location))
286    }
287
288    pub fn append_diagnostics(
289        &mut self,
290        diags: Vec<Diagnostic>,
291        construct_id: &ConstructId,
292        ctx_adder: &impl Fn(&Diagnostic) -> Diagnostic,
293    ) {
294        diags.iter().for_each(|diag| self.push_diagnostic(diag, construct_id, &ctx_adder))
295    }
296
297    pub fn fill_diagnostic_span(&mut self, runbook_sources: &RunbookSources) {
298        for diag in self.diagnostics.iter_mut() {
299            diag.span = get_source_context_for_diagnostic(diag, runbook_sources);
300        }
301    }
302
303    pub fn with_spans_filled(mut self, runbook_sources: &RunbookSources) -> Vec<Diagnostic> {
304        self.fill_diagnostic_span(runbook_sources);
305        self.diagnostics()
306    }
307}
308
309impl Display for EvaluationPassResult {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        writeln!(f, "EvaluationPassResult {} {{", self.background_tasks_uuid)?;
312        writeln!(f, "  actions: {:?}", self.actions)?;
313        writeln!(f, "  diagnostics: {:?}", self.diagnostics)?;
314        writeln!(
315            f,
316            "  pending_background_tasks: {:?}",
317            self.pending_background_tasks_constructs_uuids
318        )?;
319        writeln!(f, "}}")
320    }
321}
322
323fn should_skip_construct_evaluation(execution_result: &CommandExecutionResult) -> bool {
324    // Check if the execution result indicates that the construct should be skipped
325    let has_re_execute_command =
326        execution_result.outputs.get(RE_EXECUTE_COMMAND).and_then(|v| v.as_bool()).unwrap_or(false);
327
328    let is_third_party_signed_construct_not_yet_signed_by_third_party = {
329        let third_party_val = execution_result
330            .outputs
331            .get(THIRD_PARTY_SIGNATURE_STATUS)
332            .map(|v| v.expect_third_party_signature());
333        let is_action_signed_by_third_party = third_party_val.is_some();
334        let is_third_party_sign_complete =
335            third_party_val.map(|v| v.is_approved()).unwrap_or(false);
336
337        is_action_signed_by_third_party && !is_third_party_sign_complete
338    };
339
340    // If we have an output dictating to re execute command, we should not skip the construct evaluation
341    // If we have an output indicating this construct is signed by a third party, but not yet signed by the third party,
342    // we should not skip the construct evaluation
343    !has_re_execute_command && !is_third_party_signed_construct_not_yet_signed_by_third_party
344}
345
346fn should_retry_construct_evaluation(execution_result: &CommandExecutionResult) -> bool {
347    let is_third_party_signed_construct_not_yet_signed_by_third_party = {
348        let third_party_val = execution_result
349            .outputs
350            .get(THIRD_PARTY_SIGNATURE_STATUS)
351            .map(|v| v.expect_third_party_signature());
352        let is_action_signed_by_third_party = third_party_val.is_some();
353        let is_third_party_sign_check_requested =
354            third_party_val.as_ref().map(|v| v.is_check_requested()).unwrap_or(false);
355
356        is_action_signed_by_third_party && is_third_party_sign_check_requested
357    };
358
359    is_third_party_signed_construct_not_yet_signed_by_third_party
360}
361
362// When the graph is being traversed, we are evaluating constructs one after the other.
363// After ensuring their executability, we execute them.
364// Unexecutable nodes are tainted.
365// Before evaluating the executability, we first check if they depend on a tainted node.
366pub async fn run_constructs_evaluation(
367    background_tasks_uuid: &Uuid,
368    runbook_workspace_context: &RunbookWorkspaceContext,
369    runbook_execution_context: &mut RunbookExecutionContext,
370    runtime_context: &RuntimeContext,
371    supervision_context: &RunbookSupervisionContext,
372    action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
373    action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
374    progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
375) -> EvaluationPassResult {
376    let mut pass_result = EvaluationPassResult::new(background_tasks_uuid);
377
378    let mut unexecutable_nodes: HashSet<ConstructDid> = HashSet::new();
379
380    let top_level_inputs = runbook_workspace_context.top_level_inputs_values.clone();
381    for (input_uuid, value) in top_level_inputs.into_iter() {
382        let mut res = CommandExecutionResult::new();
383        res.outputs.insert("value".into(), value);
384        runbook_execution_context.commands_execution_results.insert(input_uuid, res);
385    }
386
387    for signer_states in runbook_execution_context.signers_state.iter_mut() {
388        for (_, signer) in signer_states.store.iter_mut() {
389            signer.clear_autoincrementable_nonce();
390        }
391    }
392
393    let mut genesis_dependency_execution_results = DependencyExecutionResultCache::new();
394
395    let mut signers_results = HashMap::new();
396    for (signer_construct_did, _) in runbook_execution_context.signers_instances.iter() {
397        let mut result = CommandExecutionResult::new();
398        result
399            .outputs
400            .insert("value".into(), Value::string(signer_construct_did.value().to_string()));
401        signers_results.insert(signer_construct_did.clone(), result);
402    }
403
404    for (signer_construct_did, _) in runbook_execution_context.signers_instances.iter() {
405        let results = signers_results.get(signer_construct_did).unwrap();
406        genesis_dependency_execution_results
407            .insert(signer_construct_did.clone(), Ok(results.clone()));
408    }
409
410    let ordered_constructs = runbook_execution_context.order_for_commands_execution.clone();
411
412    for construct_did in ordered_constructs.into_iter() {
413        if let Some(execution_results) =
414            runbook_execution_context.commands_execution_results.get(&construct_did)
415        {
416            if should_skip_construct_evaluation(execution_results) {
417                continue;
418            }
419        }
420
421        if let Some(_) = unexecutable_nodes.get(&construct_did) {
422            if let Some(deps) = runbook_execution_context.commands_dependencies.get(&construct_did)
423            {
424                for dep in deps.iter() {
425                    unexecutable_nodes.insert(dep.clone());
426                }
427            }
428            continue;
429        }
430
431        if let Some(_) = runbook_execution_context.commands_instances.get(&construct_did) {
432            match evaluate_command_instance(
433                &construct_did,
434                &mut pass_result,
435                &mut unexecutable_nodes,
436                &mut genesis_dependency_execution_results,
437                runbook_workspace_context,
438                runbook_execution_context,
439                runtime_context,
440                supervision_context,
441                action_item_requests,
442                action_item_responses,
443                progress_tx,
444            )
445            .await
446            {
447                LoopEvaluationResult::Continue => continue,
448                LoopEvaluationResult::Bail => {
449                    return pass_result;
450                }
451            }
452        }
453
454        if let Some(_) = runbook_execution_context.embedded_runbooks.get(&construct_did) {
455            match evaluate_embedded_runbook_instance(
456                background_tasks_uuid,
457                &construct_did,
458                &mut pass_result,
459                &mut unexecutable_nodes,
460                &mut genesis_dependency_execution_results,
461                runbook_workspace_context,
462                runbook_execution_context,
463                runtime_context,
464                supervision_context,
465                action_item_requests,
466                action_item_responses,
467                progress_tx,
468            )
469            .await
470            {
471                LoopEvaluationResult::Continue => continue,
472                LoopEvaluationResult::Bail => {
473                    return pass_result;
474                }
475            }
476        }
477    }
478    pass_result
479}
480
481pub enum LoopEvaluationResult {
482    Continue,
483    Bail,
484}
485
486pub async fn evaluate_command_instance(
487    construct_did: &ConstructDid,
488    pass_result: &mut EvaluationPassResult,
489    unexecutable_nodes: &mut HashSet<ConstructDid>,
490    genesis_dependency_execution_results: &mut DependencyExecutionResultCache,
491    runbook_workspace_context: &RunbookWorkspaceContext,
492    runbook_execution_context: &mut RunbookExecutionContext,
493    runtime_context: &RuntimeContext,
494    supervision_context: &RunbookSupervisionContext,
495    action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
496    action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
497    progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
498) -> LoopEvaluationResult {
499    let Some(command_instance) = runbook_execution_context.commands_instances.get(&construct_did)
500    else {
501        // runtime_context.addons.index_command_instance(namespace, package_did, block)
502        return LoopEvaluationResult::Continue;
503    };
504
505    let add_ctx_to_diag = add_ctx_to_diag(
506        "command".to_string(),
507        command_instance.specification.matcher.clone(),
508        command_instance.name.clone(),
509        command_instance.namespace.clone(),
510    );
511
512    let package_id = command_instance.package_id.clone();
513    let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
514
515    let addon_context_key = (package_id.did(), command_instance.namespace.clone());
516    let addon_defaults = runbook_workspace_context.get_addon_defaults(&addon_context_key);
517
518    let input_evaluation_results = runbook_execution_context
519        .commands_inputs_evaluation_results
520        .get(&construct_did.clone())
521        .cloned();
522
523    let mut cached_dependency_execution_results = genesis_dependency_execution_results.clone();
524
525    // Retrieve the construct_did of the inputs
526    // Collect the outputs
527    let references_expressions =
528        command_instance.get_expressions_referencing_commands_from_inputs();
529
530    for (_input, expr) in references_expressions.into_iter() {
531        if let Some((dependency, _, _)) = runbook_workspace_context
532            .try_resolve_construct_reference_in_expression(&package_id, &expr)
533            .unwrap()
534        {
535            if let Some(evaluation_result) =
536                runbook_execution_context.commands_execution_results.get(&dependency)
537            {
538                match cached_dependency_execution_results.merge(&dependency, evaluation_result) {
539                    Ok(_) => {}
540                    Err(_) => continue,
541                }
542            }
543        }
544    }
545
546    let evaluated_inputs_res = perform_inputs_evaluation(
547        command_instance,
548        &cached_dependency_execution_results,
549        &input_evaluation_results.as_ref(),
550        addon_defaults,
551        &action_item_responses.get(&construct_did),
552        &package_id,
553        runbook_workspace_context,
554        runbook_execution_context,
555        runtime_context,
556        false,
557        false,
558    );
559
560    let mut evaluated_inputs = match evaluated_inputs_res {
561        Ok(result) => match result {
562            CommandInputEvaluationStatus::Complete(result) => result,
563            CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
564                return LoopEvaluationResult::Continue;
565            }
566            CommandInputEvaluationStatus::Aborted(_, diags) => {
567                pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
568                return LoopEvaluationResult::Bail;
569            }
570        },
571        Err(diags) => {
572            pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
573            return LoopEvaluationResult::Bail;
574        }
575    };
576
577    let command_execution_result = {
578        let Some(command_instance) =
579            runbook_execution_context.commands_instances.get_mut(&construct_did)
580        else {
581            // runtime_context.addons.index_command_instance(namespace, package_did, block)
582            return LoopEvaluationResult::Continue;
583        };
584
585        match command_instance.evaluate_pre_conditions(
586            construct_did,
587            &evaluated_inputs,
588            progress_tx,
589            &pass_result.background_tasks_uuid,
590        ) {
591            Ok(result) => match result {
592                PreConditionEvaluationResult::Noop => {}
593                PreConditionEvaluationResult::Halt(diags) => {
594                    pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
595                    return LoopEvaluationResult::Bail;
596                }
597                PreConditionEvaluationResult::SkipDownstream => {
598                    if let Some(deps) =
599                        runbook_execution_context.commands_dependencies.get(&construct_did)
600                    {
601                        for dep in deps.iter() {
602                            unexecutable_nodes.insert(dep.clone());
603                        }
604                    }
605                    return LoopEvaluationResult::Continue;
606                }
607            },
608            Err(diag) => {
609                pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
610                return LoopEvaluationResult::Bail;
611            }
612        };
613
614        let executions_for_action = if command_instance.specification.implements_signing_capability
615        {
616            let signers = runbook_execution_context.signers_state.take().unwrap();
617            let signers = update_signer_instances_from_action_response(
618                signers,
619                &construct_did,
620                &action_item_responses.get(&construct_did),
621            );
622            match command_instance
623                .prepare_signed_nested_execution(
624                    &construct_did,
625                    &evaluated_inputs,
626                    signers,
627                    &runbook_execution_context.signers_instances,
628                )
629                .await
630            {
631                Ok((updated_signers, executions)) => {
632                    runbook_execution_context.signers_state = Some(updated_signers);
633                    executions
634                }
635                Err((updated_signers, diag)) => {
636                    pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
637                    runbook_execution_context.signers_state = Some(updated_signers);
638                    return LoopEvaluationResult::Bail;
639                }
640            }
641        } else {
642            match command_instance.prepare_nested_execution(&construct_did, &evaluated_inputs) {
643                Ok(executions) => executions,
644                Err(diag) => {
645                    pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
646                    return LoopEvaluationResult::Bail;
647                }
648            }
649        };
650        let signed_by = runbook_execution_context
651            .signers_downstream_dependencies
652            .iter()
653            .filter_map(
654                |(signer, deps)| if deps.contains(construct_did) { Some(signer) } else { None },
655            )
656            .filter_map(|signer_did| {
657                runbook_execution_context
658                    .signers_instances
659                    .get(signer_did)
660                    .map(|si| (signer_did.clone(), si.clone()))
661            })
662            .collect::<Vec<_>>();
663
664        let force_sequential_signing = signed_by
665            .iter()
666            .any(|(_, signer_instance)| signer_instance.specification.force_sequential_signing);
667
668        for (nested_construct_did, nested_evaluation_values) in executions_for_action.iter() {
669            if let Some(execution_results) =
670                runbook_execution_context.commands_execution_results.get(&nested_construct_did)
671            {
672                if should_skip_construct_evaluation(execution_results) {
673                    continue;
674                }
675            };
676
677            let execution_result = if command_instance.specification.implements_signing_capability {
678                let signers = runbook_execution_context.signers_state.take().unwrap();
679                let signers = update_signer_instances_from_action_response(
680                    signers,
681                    &nested_construct_did,
682                    &action_item_responses.get(&nested_construct_did),
683                );
684
685                let res = command_instance
686                    .check_signed_executability(
687                        &construct_did,
688                        nested_evaluation_values,
689                        &evaluated_inputs,
690                        signers,
691                        &mut runbook_execution_context.signers_instances,
692                        &action_item_responses.get(&nested_construct_did),
693                        &action_item_requests.get(&construct_did),
694                        supervision_context,
695                        &runtime_context.authorization_context,
696                    )
697                    .await;
698
699                let signers = match res {
700                    Ok((updated_signers, mut new_actions)) => {
701                        if new_actions.has_pending_actions() {
702                            pass_result.actions.append(&mut new_actions);
703                            runbook_execution_context.signers_state = Some(updated_signers);
704                            if let Some(deps) =
705                                runbook_execution_context.commands_dependencies.get(&construct_did)
706                            {
707                                for dep in deps.iter() {
708                                    unexecutable_nodes.insert(dep.clone());
709                                }
710                            }
711                            if force_sequential_signing {
712                                return LoopEvaluationResult::Bail;
713                            } else {
714                                return LoopEvaluationResult::Continue;
715                            }
716                        }
717                        pass_result.actions.append(&mut new_actions);
718                        updated_signers
719                    }
720                    Err((updated_signers, diag)) => {
721                        pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
722                        runbook_execution_context.signers_state = Some(updated_signers);
723                        return LoopEvaluationResult::Bail;
724                    }
725                };
726
727                runbook_execution_context
728                    .commands_inputs_evaluation_results
729                    .insert(construct_did.clone(), evaluated_inputs.clone());
730
731                let mut empty_vec = vec![];
732                let action_items_requests =
733                    action_item_requests.get_mut(&construct_did).unwrap_or(&mut empty_vec);
734                let action_items_response = action_item_responses.get(&nested_construct_did);
735
736                let execution_result = command_instance
737                    .perform_signed_execution(
738                        &construct_did,
739                        nested_evaluation_values,
740                        &evaluated_inputs,
741                        signers,
742                        &runbook_execution_context.signers_instances,
743                        action_items_requests,
744                        &action_items_response,
745                        progress_tx,
746                        &runtime_context.authorization_context,
747                    )
748                    .await;
749                let execution_result = match execution_result {
750                    Ok((updated_signers, result)) => {
751                        runbook_execution_context.signers_state = Some(updated_signers);
752                        if should_retry_construct_evaluation(&result) {
753                            return LoopEvaluationResult::Continue;
754                        }
755                        Ok(result)
756                    }
757                    Err((updated_signers, diag)) => {
758                        runbook_execution_context.signers_state = Some(updated_signers);
759                        if let Some(deps) =
760                            runbook_execution_context.commands_dependencies.get(&construct_did)
761                        {
762                            for dep in deps.iter() {
763                                unexecutable_nodes.insert(dep.clone());
764                            }
765                        }
766                        Err(diag)
767                    }
768                };
769
770                execution_result
771            } else {
772                match command_instance.check_executability(
773                    &construct_did,
774                    nested_evaluation_values,
775                    &mut evaluated_inputs,
776                    &mut runbook_execution_context.signers_instances,
777                    &action_item_responses.get(&nested_construct_did),
778                    supervision_context,
779                    &runtime_context.authorization_context,
780                ) {
781                    Ok(mut new_actions) => {
782                        if new_actions.has_pending_actions() {
783                            pass_result.actions.append(&mut new_actions);
784                            if let Some(deps) =
785                                runbook_execution_context.commands_dependencies.get(&construct_did)
786                            {
787                                for dep in deps.iter() {
788                                    unexecutable_nodes.insert(dep.clone());
789                                }
790                            }
791                            return LoopEvaluationResult::Continue;
792                        }
793                        pass_result.actions.append(&mut new_actions);
794                    }
795                    Err(diag) => {
796                        pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
797                        return LoopEvaluationResult::Bail;
798                    }
799                }
800
801                runbook_execution_context
802                    .commands_inputs_evaluation_results
803                    .insert(construct_did.clone(), evaluated_inputs.clone());
804
805                let mut empty_vec = vec![];
806                let action_items_requests =
807                    action_item_requests.get_mut(&construct_did).unwrap_or(&mut empty_vec);
808                let action_items_response = action_item_responses.get(&nested_construct_did);
809
810                let execution_result = {
811                    command_instance
812                        .perform_execution(
813                            &construct_did,
814                            nested_evaluation_values,
815                            &evaluated_inputs,
816                            action_items_requests,
817                            &action_items_response,
818                            progress_tx,
819                            &runtime_context.authorization_context,
820                        )
821                        .await
822                };
823
824                let execution_result = match execution_result {
825                    Ok(result) => Ok(result),
826                    Err(e) => {
827                        if let Some(deps) =
828                            runbook_execution_context.commands_dependencies.get(&construct_did)
829                        {
830                            for dep in deps.iter() {
831                                unexecutable_nodes.insert(dep.clone());
832                            }
833                        }
834                        Err(e)
835                    }
836                };
837                execution_result
838            };
839
840            let mut execution_result = match execution_result {
841                Ok(res) => res,
842                Err(diag) => {
843                    pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
844                    return LoopEvaluationResult::Continue;
845                }
846            };
847
848            if let RunbookExecutionMode::Partial(ref mut executed_constructs) =
849                runbook_execution_context.execution_mode
850            {
851                executed_constructs.push(nested_construct_did.clone());
852            }
853
854            if command_instance.specification.implements_background_task_capability {
855                let future_res = command_instance.build_background_task(
856                    &construct_did,
857                    nested_evaluation_values,
858                    &evaluated_inputs,
859                    &execution_result,
860                    progress_tx,
861                    &pass_result.background_tasks_uuid,
862                    supervision_context,
863                    &runtime_context.cloud_service_context,
864                );
865                let future = match future_res {
866                    Ok(future) => future,
867                    Err(diag) => {
868                        pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
869                        return LoopEvaluationResult::Bail;
870                    }
871                };
872                if let Some(deps) =
873                    runbook_execution_context.commands_dependencies.get(&construct_did)
874                {
875                    for dep in deps.iter() {
876                        unexecutable_nodes.insert(dep.clone());
877                    }
878                }
879                // if this construct has no execution results stored, and the construct is re-evaluated
880                // before this bg future we're pushing is awaited, we'll end up pushing this future twice.
881                // so we need to push some execution results, which will cue this loop to fix future evaluations
882                // of this construct.
883                runbook_execution_context.append_commands_execution_result(
884                    &nested_construct_did,
885                    &CommandExecutionResult::from([(
886                        "background_task_uuid",
887                        Value::string(pass_result.background_tasks_uuid.to_string()),
888                    )]),
889                );
890                pass_result.pending_background_tasks_futures.push(future);
891                pass_result
892                    .pending_background_tasks_constructs_uuids
893                    .push((nested_construct_did.clone(), construct_did.clone()));
894
895                // we need to be sure that each background task is completed before continuing the execution.
896                // so we will return a Continue result to ensure that the next nested evaluation is not executed.
897                // once the background task is completed, it will mark _this_ nested construct as completed and move to the next one.
898                if force_sequential_signing {
899                    return LoopEvaluationResult::Bail;
900                } else {
901                    return LoopEvaluationResult::Continue;
902                }
903            } else {
904                runbook_execution_context
905                    .commands_execution_results
906                    .entry(nested_construct_did.clone())
907                    .or_insert_with(CommandExecutionResult::new)
908                    .append(&mut execution_result);
909            }
910        }
911
912        let res = command_instance.aggregate_nested_execution_results(
913            &construct_did,
914            &executions_for_action,
915            &runbook_execution_context.commands_execution_results,
916        );
917        match res {
918            Ok(result) => result,
919            Err(diag) => {
920                pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
921                return LoopEvaluationResult::Continue;
922            }
923        }
924    };
925
926    let command_instance =
927        runbook_execution_context.commands_instances.get(&construct_did).unwrap();
928
929    cached_dependency_execution_results.merge(construct_did, &command_execution_result).unwrap();
930
931    let self_referencing_inputs = perform_inputs_evaluation(
932        command_instance,
933        &cached_dependency_execution_results,
934        &input_evaluation_results.as_ref(),
935        addon_defaults,
936        &action_item_responses.get(&construct_did),
937        &package_id,
938        runbook_workspace_context,
939        runbook_execution_context,
940        runtime_context,
941        false,
942        true,
943    );
944
945    let mut self_referencing_inputs = match self_referencing_inputs {
946        Ok(result) => match result {
947            CommandInputEvaluationStatus::Complete(result) => result,
948            CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
949                return LoopEvaluationResult::Continue;
950            }
951            CommandInputEvaluationStatus::Aborted(_, diags) => {
952                pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
953                return LoopEvaluationResult::Bail;
954            }
955        },
956        Err(diags) => {
957            pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
958            return LoopEvaluationResult::Bail;
959        }
960    };
961
962    self_referencing_inputs.inputs =
963        self_referencing_inputs.inputs.append_inputs(&evaluated_inputs.inputs.inputs);
964
965    match command_instance.evaluate_post_conditions(
966        construct_did,
967        &self_referencing_inputs,
968        runbook_execution_context
969            .commands_execution_results
970            .entry(construct_did.clone())
971            .or_insert(CommandExecutionResult::new()),
972        progress_tx,
973        &pass_result.background_tasks_uuid,
974    ) {
975        Ok(result) => {
976            match result {
977                PostConditionEvaluationResult::Noop => {}
978                PostConditionEvaluationResult::Halt(diags) => {
979                    pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
980                    return LoopEvaluationResult::Bail;
981                }
982                PostConditionEvaluationResult::SkipDownstream => {
983                    if let Some(deps) =
984                        runbook_execution_context.commands_dependencies.get(&construct_did)
985                    {
986                        for dep in deps.iter() {
987                            unexecutable_nodes.insert(dep.clone());
988                        }
989                    }
990                    return LoopEvaluationResult::Continue;
991                }
992                PostConditionEvaluationResult::Retry(_) => {
993                    // If the post condition requires a retry, we will not continue the execution of this command.
994                    // We will return a Continue result to ensure that the next nested evaluation is not executed.
995                    // Once the retry is completed, it will mark _this_ nested construct as completed and move to the next one.
996                    if let Some(deps) =
997                        runbook_execution_context.commands_dependencies.get(&construct_did)
998                    {
999                        for dep in deps.iter() {
1000                            unexecutable_nodes.insert(dep.clone());
1001                        }
1002                    }
1003                    pass_result.nodes_to_re_execute.push(construct_did.clone());
1004                    return LoopEvaluationResult::Continue;
1005                }
1006            }
1007        }
1008        Err(diag) => {
1009            pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
1010            return LoopEvaluationResult::Bail;
1011        }
1012    };
1013
1014    runbook_execution_context
1015        .commands_execution_results
1016        .insert(construct_did.clone(), command_execution_result);
1017
1018    if let RunbookExecutionMode::Partial(ref mut executed_constructs) =
1019        runbook_execution_context.execution_mode
1020    {
1021        executed_constructs.push(construct_did.clone());
1022    }
1023
1024    LoopEvaluationResult::Continue
1025}
1026
1027pub async fn evaluate_embedded_runbook_instance(
1028    background_tasks_uuid: &Uuid,
1029    construct_did: &ConstructDid,
1030    pass_result: &mut EvaluationPassResult,
1031    unexecutable_nodes: &mut HashSet<ConstructDid>,
1032    genesis_dependency_execution_results: &mut DependencyExecutionResultCache,
1033    runbook_workspace_context: &RunbookWorkspaceContext,
1034    runbook_execution_context: &mut RunbookExecutionContext,
1035    runtime_context: &RuntimeContext,
1036    supervision_context: &RunbookSupervisionContext,
1037    action_item_requests: &mut BTreeMap<ConstructDid, Vec<&mut ActionItemRequest>>,
1038    action_item_responses: &BTreeMap<ConstructDid, Vec<ActionItemResponse>>,
1039    progress_tx: &txtx_addon_kit::channel::Sender<BlockEvent>,
1040) -> LoopEvaluationResult {
1041    let Some(embedded_runbook) = runbook_execution_context.embedded_runbooks.get(&construct_did)
1042    else {
1043        return LoopEvaluationResult::Continue;
1044    };
1045
1046    let add_ctx_to_diag = add_ctx_to_embedded_runbook_diag(embedded_runbook.name.clone());
1047
1048    let package_id = embedded_runbook.package_id.clone();
1049    let construct_id = &runbook_workspace_context.expect_construct_id(&construct_did);
1050
1051    let input_evaluation_results =
1052        runbook_execution_context.commands_inputs_evaluation_results.get(&construct_did.clone());
1053
1054    let mut cached_dependency_execution_results = genesis_dependency_execution_results.clone();
1055
1056    // Retrieve the construct_did of the inputs
1057    // Collect the outputs
1058    let references_expressions =
1059        embedded_runbook.get_expressions_referencing_commands_from_inputs();
1060
1061    for (_input, expr) in references_expressions.into_iter() {
1062        if let Some((dependency, _, _)) = runbook_workspace_context
1063            .try_resolve_construct_reference_in_expression(&package_id, &expr)
1064            .unwrap()
1065        {
1066            if let Some(evaluation_result) =
1067                runbook_execution_context.commands_execution_results.get(&dependency)
1068            {
1069                match cached_dependency_execution_results.merge(&dependency, evaluation_result) {
1070                    Ok(_) => {}
1071                    Err(_) => continue,
1072                }
1073            }
1074        }
1075    }
1076
1077    let evaluated_inputs_res = perform_inputs_evaluation(
1078        embedded_runbook,
1079        &cached_dependency_execution_results,
1080        &input_evaluation_results,
1081        &AddonDefaults::new("empty"),
1082        &action_item_responses.get(&construct_did),
1083        &package_id,
1084        runbook_workspace_context,
1085        runbook_execution_context,
1086        runtime_context,
1087        false,
1088        false,
1089    );
1090    let evaluated_inputs = match evaluated_inputs_res {
1091        Ok(result) => match result {
1092            CommandInputEvaluationStatus::Complete(result) => result,
1093            CommandInputEvaluationStatus::NeedsUserInteraction(_) => {
1094                return LoopEvaluationResult::Continue
1095            }
1096            CommandInputEvaluationStatus::Aborted(_, diags) => {
1097                pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
1098                return LoopEvaluationResult::Bail;
1099            }
1100        },
1101        Err(diags) => {
1102            pass_result.append_diagnostics(diags, construct_id, &add_ctx_to_diag);
1103            return LoopEvaluationResult::Bail;
1104        }
1105    };
1106
1107    // todo: assert that the evaluated inputs are sufficient to execute the embedded runbook (we have all required inputs)
1108
1109    let mut executable_embedded_runbook = match ExecutableEmbeddedRunbookInstance::new(
1110        embedded_runbook.clone(),
1111        EmbeddedRunbookStatefulExecutionContext::new(
1112            &runbook_execution_context.signers_instances,
1113            &runbook_execution_context.signers_state,
1114            &runbook_workspace_context
1115                .constructs
1116                .iter()
1117                .filter_map(|(did, id)| {
1118                    if runbook_execution_context.signers_instances.contains_key(did) {
1119                        Some((did.clone(), id.clone()))
1120                    } else {
1121                        None
1122                    }
1123                })
1124                .collect(),
1125        ),
1126        &evaluated_inputs.inputs,
1127        runtime_context,
1128    ) {
1129        Ok(res) => res,
1130        Err(diag) => {
1131            pass_result.push_diagnostic(&diag, construct_id, &add_ctx_to_diag);
1132            return LoopEvaluationResult::Bail;
1133        }
1134    };
1135
1136    let result = Box::pin(run_constructs_evaluation(
1137        background_tasks_uuid,
1138        &executable_embedded_runbook.context.workspace_context,
1139        &mut executable_embedded_runbook.context.execution_context,
1140        runtime_context,
1141        supervision_context,
1142        action_item_requests,
1143        action_item_responses,
1144        progress_tx,
1145    ))
1146    .await;
1147
1148    runbook_execution_context
1149        .commands_inputs_evaluation_results
1150        .insert(construct_did.clone(), evaluated_inputs.clone());
1151
1152    // update the runbook's context with the results of the embedded runbook
1153    runbook_execution_context.append_command_inputs_evaluation_results_no_override(
1154        &executable_embedded_runbook.context.execution_context.commands_inputs_evaluation_results,
1155    );
1156
1157    runbook_execution_context.signers_state =
1158        executable_embedded_runbook.context.execution_context.signers_state;
1159
1160    pass_result.merge(result);
1161
1162    let has_diags = !pass_result.diagnostics.is_empty();
1163    let has_pending_actions = pass_result.actions.has_pending_actions();
1164    let has_pending_background_tasks = !pass_result.pending_background_tasks_futures.is_empty();
1165
1166    if has_diags || has_pending_actions || has_pending_background_tasks {
1167        if let Some(deps) = runbook_execution_context.commands_dependencies.get(&construct_did) {
1168            for dep in deps.iter() {
1169                unexecutable_nodes.insert(dep.clone());
1170            }
1171        }
1172        return LoopEvaluationResult::Continue;
1173    } else {
1174        // loop over all of the results of executing this embedded runbook and merge them into the current runbook's context
1175        runbook_execution_context.append_commands_execution_results(
1176            &executable_embedded_runbook.context.execution_context.commands_execution_results,
1177        );
1178    }
1179
1180    LoopEvaluationResult::Continue
1181}
1182
1183// When the graph is being traversed, we are evaluating constructs one after the other.
1184// After ensuring their executability, we execute them.
1185// Unexecutable nodes are tainted.
1186// Before evaluating the executability, we first check if they depend on a tainted node.
1187
1188#[derive(Debug)]
1189pub enum ExpressionEvaluationStatus {
1190    CompleteOk(Value),
1191    CompleteErr(Diagnostic),
1192    DependencyNotComputed,
1193}
1194
1195pub fn eval_expression(
1196    expr: &Expression,
1197    dependencies_execution_results: &DependencyExecutionResultCache,
1198    package_id: &PackageId,
1199    runbook_workspace_context: &RunbookWorkspaceContext,
1200    runbook_execution_context: &RunbookExecutionContext,
1201    runtime_context: &RuntimeContext,
1202) -> Result<ExpressionEvaluationStatus, Diagnostic> {
1203    let value = match expr {
1204        // Represents a null value.
1205        Expression::Null(_decorated_null) => Value::null(),
1206        // Represents a boolean.
1207        Expression::Bool(decorated_bool) => Value::bool(*decorated_bool.value()),
1208        // Represents a number, either integer or float.
1209        Expression::Number(formatted_number) => {
1210            match (
1211                formatted_number.value().as_u64(),
1212                formatted_number.value().as_i64(),
1213                formatted_number.value().as_f64(),
1214            ) {
1215                (Some(value), _, _) => Value::integer(value.into()),
1216                (_, Some(value), _) => Value::integer(value.into()),
1217                (_, _, Some(value)) => Value::float(value),
1218                (None, None, None) => unreachable!(), // todo(lgalabru): return Diagnostic
1219            }
1220        }
1221        // Represents a string that does not contain any template interpolations or template directives.
1222        Expression::String(decorated_string) => Value::string(decorated_string.to_string()),
1223        // Represents an HCL array.
1224        Expression::Array(entries) => {
1225            let mut res = vec![];
1226            for entry_expr in entries {
1227                let value = match eval_expression(
1228                    entry_expr,
1229                    dependencies_execution_results,
1230                    package_id,
1231                    runbook_workspace_context,
1232                    runbook_execution_context,
1233                    runtime_context,
1234                )? {
1235                    ExpressionEvaluationStatus::CompleteOk(result) => result,
1236                    ExpressionEvaluationStatus::CompleteErr(e) => {
1237                        return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1238                    }
1239                    ExpressionEvaluationStatus::DependencyNotComputed => {
1240                        return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1241                    }
1242                };
1243                res.push(value);
1244            }
1245            Value::array(res)
1246        }
1247        // Represents an HCL object.
1248        Expression::Object(object) => {
1249            let mut map = IndexMap::new();
1250            for (k, v) in object.into_iter() {
1251                let key = match k {
1252                    txtx_addon_kit::hcl::expr::ObjectKey::Expression(k_expr) => {
1253                        match eval_expression(
1254                            k_expr,
1255                            dependencies_execution_results,
1256                            package_id,
1257                            runbook_workspace_context,
1258                            runbook_execution_context,
1259                            runtime_context,
1260                        )? {
1261                            ExpressionEvaluationStatus::CompleteOk(result) => match result {
1262                                Value::String(result) => result,
1263                                _ => {
1264                                    return Ok(ExpressionEvaluationStatus::CompleteErr(
1265                                        Diagnostic::error_from_string(
1266                                            "object key must evaluate to a string".to_string(),
1267                                        ),
1268                                    ))
1269                                }
1270                            },
1271                            ExpressionEvaluationStatus::CompleteErr(e) => {
1272                                return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1273                            }
1274                            ExpressionEvaluationStatus::DependencyNotComputed => {
1275                                return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1276                            }
1277                        }
1278                    }
1279                    txtx_addon_kit::hcl::expr::ObjectKey::Ident(k_ident) => k_ident.to_string(),
1280                };
1281                let value = match eval_expression(
1282                    v.expr(),
1283                    dependencies_execution_results,
1284                    package_id,
1285                    runbook_workspace_context,
1286                    runbook_execution_context,
1287                    runtime_context,
1288                )? {
1289                    ExpressionEvaluationStatus::CompleteOk(result) => result,
1290                    ExpressionEvaluationStatus::CompleteErr(e) => {
1291                        return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1292                    }
1293                    ExpressionEvaluationStatus::DependencyNotComputed => {
1294                        return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1295                    }
1296                };
1297                map.insert(key, value);
1298            }
1299            Value::Object(map)
1300        }
1301        // Represents a string containing template interpolations and template directives.
1302        Expression::StringTemplate(string_template) => {
1303            let mut res = String::new();
1304            for element in string_template.into_iter() {
1305                match element {
1306                    Element::Literal(literal) => {
1307                        res.push_str(literal.value());
1308                    }
1309                    Element::Interpolation(interpolation) => {
1310                        let value = match eval_expression(
1311                            &interpolation.expr,
1312                            dependencies_execution_results,
1313                            package_id,
1314                            runbook_workspace_context,
1315                            runbook_execution_context,
1316                            runtime_context,
1317                        )? {
1318                            ExpressionEvaluationStatus::CompleteOk(result) => result.to_string(),
1319                            ExpressionEvaluationStatus::CompleteErr(e) => {
1320                                return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1321                            }
1322                            ExpressionEvaluationStatus::DependencyNotComputed => {
1323                                return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1324                            }
1325                        };
1326                        res.push_str(&value);
1327                    }
1328                    Element::Directive(_) => {
1329                        unimplemented!("string templates with directives not yet supported")
1330                    }
1331                };
1332            }
1333            Value::string(res)
1334        }
1335        // Represents an HCL heredoc template.
1336        Expression::HeredocTemplate(_heredoc_template) => {
1337            unimplemented!()
1338        }
1339        // Represents a sub-expression wrapped in parenthesis.
1340        Expression::Parenthesis(_sub_expr) => {
1341            unimplemented!()
1342        }
1343        // Represents a variable identifier.
1344        Expression::Variable(_decorated_var) => {
1345            return Err(diagnosed_error!(
1346                "Directly referencing a variable is not supported. Did you mean `variable.{}`?",
1347                _decorated_var.as_str()
1348            )
1349            .into());
1350        }
1351        // Represents conditional operator which selects one of two expressions based on the outcome of a boolean expression.
1352        Expression::Conditional(_conditional) => {
1353            unimplemented!()
1354        }
1355        // Represents a function call.
1356        Expression::FuncCall(function_call) => {
1357            let func_namespace = function_call.name.namespace.first().map(|n| n.to_string());
1358            let func_name = function_call.name.name.to_string();
1359            let mut args = vec![];
1360            for expr in function_call.args.iter() {
1361                let value = match eval_expression(
1362                    expr,
1363                    dependencies_execution_results,
1364                    package_id,
1365                    runbook_workspace_context,
1366                    runbook_execution_context,
1367                    runtime_context,
1368                )? {
1369                    ExpressionEvaluationStatus::CompleteOk(result) => result,
1370                    ExpressionEvaluationStatus::CompleteErr(e) => {
1371                        return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1372                    }
1373                    ExpressionEvaluationStatus::DependencyNotComputed => {
1374                        return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1375                    }
1376                };
1377                args.push(value);
1378            }
1379            runtime_context
1380                .execute_function(
1381                    package_id.did(),
1382                    func_namespace,
1383                    &func_name,
1384                    &args,
1385                    &runtime_context.authorization_context,
1386                )
1387                .map_err(|e| e)?
1388        }
1389        // Represents an attribute or element traversal.
1390        Expression::Traversal(_) => {
1391            let (dependency, mut components, _subpath) = match runbook_workspace_context
1392                .try_resolve_construct_reference_in_expression(package_id, expr)
1393            {
1394                Ok(Some(res)) => res,
1395                Ok(None) => {
1396                    return Err(diagnosed_error!(
1397                        "unable to resolve expression '{}'",
1398                        expr.to_string().trim()
1399                    ));
1400                }
1401                Err(e) => {
1402                    return Err(diagnosed_error!(
1403                        "unable to resolve expression '{}': {}",
1404                        expr.to_string().trim(),
1405                        e
1406                    ))
1407                }
1408            };
1409
1410            let res = match dependencies_execution_results.get(&dependency) {
1411                Some(res) => match res.clone() {
1412                    Ok(res) => res,
1413                    Err(e) => return Ok(ExpressionEvaluationStatus::CompleteErr(e.clone())),
1414                },
1415                None => match runbook_execution_context.commands_execution_results.get(&dependency)
1416                {
1417                    Some(res) => res.clone(),
1418                    None => return Ok(ExpressionEvaluationStatus::DependencyNotComputed),
1419                },
1420            };
1421
1422            let some_attribute = components.pop_front();
1423            let no_attribute = some_attribute.is_none();
1424            let attribute = some_attribute.unwrap_or("value".into());
1425
1426            match res.outputs.get(&attribute) {
1427                Some(output) => {
1428                    if let Some(_) = output.as_object() {
1429                        output.get_keys_from_object(components)?
1430                    } else {
1431                        output.clone()
1432                    }
1433                }
1434                // this is a bit hacky. in some cases, our outputs are nested in a "value" key, but we don't want the user
1435                // to have to provide that key. if that's the case, the above line consumed an attribute we want to use and
1436                // didn't actually use the default "value" key. so if fetching the provided attribute key yields no
1437                // results, fetch "value", and add our attribute back to the list of components
1438                None => match res.outputs.get("value") {
1439                    Some(output) => {
1440                        if let Some(_) = output.as_object() {
1441                            components.push_front(attribute);
1442                            output.get_keys_from_object(components)?
1443                        } else {
1444                            output.clone()
1445                        }
1446                    }
1447                    None => {
1448                        // if the user didn't provide any attributes from the traversal to access, they possibly
1449                        // are referencing the dependency itself, i.e. `signer.deployer` just wants the deployer dep
1450                        if no_attribute {
1451                            return Ok(ExpressionEvaluationStatus::CompleteOk(Value::string(
1452                                dependency.to_string(),
1453                            )));
1454                        } else {
1455                            return Ok(ExpressionEvaluationStatus::DependencyNotComputed);
1456                        }
1457                    }
1458                },
1459            }
1460        }
1461        // Represents an operation which applies a unary operator to an expression.
1462        Expression::UnaryOp(unary_op) => {
1463            let _expr = eval_expression(
1464                &unary_op.expr,
1465                dependencies_execution_results,
1466                package_id,
1467                runbook_workspace_context,
1468                runbook_execution_context,
1469                runtime_context,
1470            )?;
1471            match &unary_op.operator.value() {
1472                UnaryOperator::Neg => {}
1473                UnaryOperator::Not => {}
1474            }
1475            unimplemented!()
1476        }
1477        // Represents an operation which applies a binary operator to two expressions.
1478        Expression::BinaryOp(binary_op) => {
1479            let lhs = match eval_expression(
1480                &binary_op.lhs_expr,
1481                dependencies_execution_results,
1482                package_id,
1483                runbook_workspace_context,
1484                runbook_execution_context,
1485                runtime_context,
1486            )? {
1487                ExpressionEvaluationStatus::CompleteOk(result) => result,
1488                ExpressionEvaluationStatus::CompleteErr(e) => {
1489                    return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1490                }
1491                ExpressionEvaluationStatus::DependencyNotComputed => {
1492                    return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1493                }
1494            };
1495            let rhs = match eval_expression(
1496                &binary_op.rhs_expr,
1497                dependencies_execution_results,
1498                package_id,
1499                runbook_workspace_context,
1500                runbook_execution_context,
1501                runtime_context,
1502            )? {
1503                ExpressionEvaluationStatus::CompleteOk(result) => result,
1504                ExpressionEvaluationStatus::CompleteErr(e) => {
1505                    return Ok(ExpressionEvaluationStatus::CompleteErr(e))
1506                }
1507                ExpressionEvaluationStatus::DependencyNotComputed => {
1508                    return Ok(ExpressionEvaluationStatus::DependencyNotComputed)
1509                }
1510            };
1511
1512            let func = match &binary_op.operator.value() {
1513                BinaryOperator::And => "and_bool",
1514                BinaryOperator::Div => "div",
1515                BinaryOperator::Eq => "eq",
1516                BinaryOperator::Greater => "gt",
1517                BinaryOperator::GreaterEq => "gte",
1518                BinaryOperator::Less => "lt",
1519                BinaryOperator::LessEq => "lte",
1520                BinaryOperator::Minus => "minus",
1521                BinaryOperator::Mod => "modulo",
1522                BinaryOperator::Mul => "multiply",
1523                BinaryOperator::Plus => "add",
1524                BinaryOperator::NotEq => "neq",
1525                BinaryOperator::Or => "or_bool",
1526            };
1527            runtime_context.execute_function(
1528                package_id.did(),
1529                None,
1530                func,
1531                &vec![lhs, rhs],
1532                &runtime_context.authorization_context,
1533            )?
1534        }
1535        // Represents a construct for constructing a collection by projecting the items from another collection.
1536        Expression::ForExpr(_for_expr) => {
1537            unimplemented!()
1538        }
1539    };
1540
1541    Ok(ExpressionEvaluationStatus::CompleteOk(value))
1542}
1543
1544// pub struct EvaluatedExpression {
1545//     value: Value,
1546// }
1547
1548pub fn update_signer_instances_from_action_response(
1549    mut signers: SignersState,
1550    construct_did: &ConstructDid,
1551    action_item_response: &Option<&Vec<ActionItemResponse>>,
1552) -> SignersState {
1553    match action_item_response {
1554        Some(responses) => {
1555            responses.into_iter().for_each(|ActionItemResponse { action_item_id: _, payload }| {
1556                match payload {
1557                    ActionItemResponseType::ProvideSignedTransaction(response) => {
1558                        if let Some(mut signer_state) =
1559                            signers.pop_signer_state(&response.signer_uuid)
1560                        {
1561                            let did = &construct_did.to_string();
1562                            match &response.signed_transaction_bytes {
1563                                Some(bytes) => {
1564                                    signer_state.insert_scoped_value(
1565                                        &did,
1566                                        SIGNED_TRANSACTION_BYTES,
1567                                        Value::string(bytes.clone()),
1568                                    );
1569                                }
1570                                None => match response.signature_approved {
1571                                    Some(true) => {
1572                                        signer_state.insert_scoped_value(
1573                                            &did,
1574                                            SIGNATURE_APPROVED,
1575                                            Value::bool(true),
1576                                        );
1577                                    }
1578                                    Some(false) => {}
1579                                    None => {
1580                                        let skippable = signer_state
1581                                            .get_scoped_value(&did, SIGNATURE_SKIPPABLE)
1582                                            .and_then(|v| v.as_bool())
1583                                            .unwrap_or(false);
1584                                        if skippable {
1585                                            signer_state.insert_scoped_value(
1586                                                &did,
1587                                                SIGNED_TRANSACTION_BYTES,
1588                                                Value::null(),
1589                                            );
1590                                        }
1591                                    }
1592                                },
1593                            }
1594                            signers.push_signer_state(signer_state.clone());
1595                        }
1596                    }
1597                    ActionItemResponseType::SendTransaction(response) => {
1598                        if let Some(mut signer_state) =
1599                            signers.pop_signer_state(&response.signer_uuid)
1600                        {
1601                            let did = &construct_did.to_string();
1602                            signer_state.insert_scoped_value(
1603                                &did,
1604                                TX_HASH,
1605                                Value::string(response.transaction_hash.clone()),
1606                            );
1607
1608                            signers.push_signer_state(signer_state.clone());
1609                        }
1610                    }
1611                    ActionItemResponseType::ProvideSignedMessage(response) => {
1612                        if let Some(mut signer_state) =
1613                            signers.pop_signer_state(&response.signer_uuid)
1614                        {
1615                            signer_state.insert_scoped_value(
1616                                &construct_did.value().to_string(),
1617                                SIGNED_MESSAGE_BYTES,
1618                                Value::string(response.signed_message_bytes.clone()),
1619                            );
1620                            signers.push_signer_state(signer_state.clone());
1621                        }
1622                    }
1623                    ActionItemResponseType::VerifyThirdPartySignature(response) => {
1624                        if let Some(mut signer_state) =
1625                            signers.pop_signer_state(&response.signer_uuid)
1626                        {
1627                            // If the user has requested to check if the third party signature has been completed
1628                            // insert into the appropriate signer state that the check has been requested, so it
1629                            // can handle accordingly
1630                            signer_state.insert_scoped_value(
1631                                &construct_did.value().to_string(),
1632                                THIRD_PARTY_SIGNATURE_STATUS,
1633                                Value::third_party_signature_check_requested(),
1634                            );
1635                            signers.push_signer_state(signer_state.clone());
1636                        }
1637                    }
1638                    _ => {}
1639                }
1640            });
1641        }
1642        None => {}
1643    }
1644
1645    signers
1646}
1647
1648#[derive(Debug)]
1649pub enum CommandInputEvaluationStatus {
1650    Complete(CommandInputsEvaluationResult),
1651    NeedsUserInteraction(CommandInputsEvaluationResult),
1652    Aborted(CommandInputsEvaluationResult, Vec<Diagnostic>),
1653}
1654
1655pub fn perform_inputs_evaluation(
1656    with_evaluatable_inputs: &impl WithEvaluatableInputs,
1657    dependencies_execution_results: &DependencyExecutionResultCache,
1658    input_evaluation_results: &Option<&CommandInputsEvaluationResult>,
1659    addon_defaults: &AddonDefaults,
1660    action_item_response: &Option<&Vec<ActionItemResponse>>,
1661    package_id: &PackageId,
1662    runbook_workspace_context: &RunbookWorkspaceContext,
1663    runbook_execution_context: &RunbookExecutionContext,
1664    runtime_context: &RuntimeContext,
1665    simulation: bool,
1666    self_referencing_inputs: bool,
1667) -> Result<CommandInputEvaluationStatus, Vec<Diagnostic>> {
1668    let mut has_existing_evaluation_results = true;
1669    let mut results = match *input_evaluation_results {
1670        Some(evaluated_inputs) => {
1671            let mut inputs = evaluated_inputs.clone();
1672            inputs.inputs = inputs.inputs.with_defaults(&addon_defaults.store);
1673            inputs
1674        }
1675        None => {
1676            has_existing_evaluation_results = false;
1677            CommandInputsEvaluationResult::new(
1678                &with_evaluatable_inputs.name(),
1679                &addon_defaults.store,
1680            )
1681        }
1682    };
1683    let mut require_user_interaction = false;
1684    let mut diags = vec![];
1685    let inputs = if self_referencing_inputs {
1686        has_existing_evaluation_results = false;
1687        with_evaluatable_inputs.self_referencing_inputs()
1688    } else {
1689        with_evaluatable_inputs.spec_inputs()
1690    };
1691
1692    let mut fatal_error = false;
1693
1694    match action_item_response {
1695        Some(responses) => {
1696            responses.into_iter().for_each(|ActionItemResponse { action_item_id: _, payload }| {
1697                match payload {
1698                    ActionItemResponseType::ReviewInput(_update) => {}
1699                    ActionItemResponseType::ProvideInput(update) => {
1700                        results.inputs.insert(&update.input_name, update.updated_value.clone());
1701                    }
1702                    _ => {}
1703                }
1704            })
1705        }
1706        None => {}
1707    }
1708
1709    for input in inputs.into_iter() {
1710        let input_name = input.name();
1711        let input_typing = input.typing();
1712
1713        if simulation {
1714            // Hard coding "signer" here is a shortcut - to be improved, we should retrieve a pointer instead that is defined on the spec
1715            if input_name.eq("signer") {
1716                results.unevaluated_inputs.insert("signer".into(), None);
1717                continue;
1718            }
1719            if input_name.eq("signers") {
1720                results.unevaluated_inputs.insert("signers".into(), None);
1721                continue;
1722            }
1723        } else if has_existing_evaluation_results {
1724            if !results.unevaluated_inputs.contains_key(&input_name) {
1725                continue;
1726            }
1727        }
1728        if let Some(object_def) = input.as_object() {
1729            // get this object expression to check if it's a traversal. if the expected
1730            // object type is a traversal, we should parse it as a regular field rather than
1731            // looking at each property of the object
1732            let Some(expr) =
1733                with_evaluatable_inputs.get_expression_from_object(&input_name, &input_typing)?
1734            else {
1735                continue;
1736            };
1737            if let Expression::Traversal(traversal) = &expr {
1738                let value = match eval_expression(
1739                    &Expression::Traversal(traversal.clone()),
1740                    dependencies_execution_results,
1741                    package_id,
1742                    runbook_workspace_context,
1743                    runbook_execution_context,
1744                    runtime_context,
1745                ) {
1746                    Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1747                    Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1748                        if e.is_error() {
1749                            fatal_error = true;
1750                        }
1751                        results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1752                        diags.push(e);
1753                        continue;
1754                    }
1755                    Err(e) => {
1756                        if e.is_error() {
1757                            fatal_error = true;
1758                        }
1759                        results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1760                        diags.push(e);
1761                        continue;
1762                    }
1763                    Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1764                        require_user_interaction = true;
1765                        results.unevaluated_inputs.insert(input_name.clone(), None);
1766                        continue;
1767                    }
1768                };
1769                results.insert(&input_name, value);
1770                continue;
1771            }
1772
1773            if let Expression::FuncCall(ref function_call) = expr {
1774                let value = match eval_expression(
1775                    &Expression::FuncCall(function_call.clone()),
1776                    dependencies_execution_results,
1777                    package_id,
1778                    runbook_workspace_context,
1779                    runbook_execution_context,
1780                    runtime_context,
1781                ) {
1782                    Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1783                    Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1784                        if e.is_error() {
1785                            fatal_error = true;
1786                        }
1787                        results.unevaluated_inputs.insert(input_name.to_string(), Some(e.clone()));
1788                        diags.push(e);
1789                        continue;
1790                    }
1791                    Err(e) => {
1792                        if e.is_error() {
1793                            fatal_error = true;
1794                        }
1795                        results.unevaluated_inputs.insert(input_name.to_string(), Some(e.clone()));
1796                        diags.push(e);
1797                        continue;
1798                    }
1799                    Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1800                        require_user_interaction = true;
1801                        results.unevaluated_inputs.insert(input_name.to_string(), None);
1802                        continue;
1803                    }
1804                };
1805                results.insert(&input_name, value);
1806                continue;
1807            }
1808            let mut object_values = IndexMap::new();
1809            match object_def {
1810                ObjectDefinition::Strict(props) => {
1811                    for prop in props.iter() {
1812                        let Some(expr) = with_evaluatable_inputs
1813                            .get_expression_from_object_property(&input_name, &prop)
1814                        else {
1815                            continue;
1816                        };
1817
1818                        let value = match eval_expression(
1819                            &expr,
1820                            dependencies_execution_results,
1821                            package_id,
1822                            runbook_workspace_context,
1823                            runbook_execution_context,
1824                            runtime_context,
1825                        ) {
1826                            Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1827                            Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1828                                if e.is_error() {
1829                                    fatal_error = true;
1830                                }
1831                                results
1832                                    .unevaluated_inputs
1833                                    .insert(input_name.clone(), Some(e.clone()));
1834                                diags.push(e);
1835                                continue;
1836                            }
1837                            Err(e) => {
1838                                if e.is_error() {
1839                                    fatal_error = true;
1840                                }
1841                                results
1842                                    .unevaluated_inputs
1843                                    .insert(input_name.clone(), Some(e.clone()));
1844                                diags.push(e);
1845                                continue;
1846                            }
1847                            Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1848                                require_user_interaction = true;
1849                                results.unevaluated_inputs.insert(input_name.clone(), None);
1850                                continue;
1851                            }
1852                        };
1853
1854                        // todo: this seems wrong. when we evaluate an object and one of its results is an object,
1855                        // this looks like we're flattening it?
1856                        match value.clone() {
1857                            Value::Object(obj) => {
1858                                for (k, v) in obj.into_iter() {
1859                                    object_values.insert(k, v);
1860                                }
1861                            }
1862                            v => {
1863                                object_values.insert(prop.name.to_string(), v);
1864                            }
1865                        };
1866                    }
1867                }
1868                ObjectDefinition::Arbitrary(_) => {
1869                    let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name)
1870                    else {
1871                        continue;
1872                    };
1873                    let Some(object_block) = expr.as_object() else {
1874                        continue;
1875                    };
1876                    for (object_key, object_value) in object_block.iter() {
1877                        let object_key = match object_key {
1878                            kit::hcl::expr::ObjectKey::Ident(ident) => ident.to_string(),
1879                            kit::hcl::expr::ObjectKey::Expression(expr) => match expr.as_str() {
1880                                Some(s) => s.to_string(),
1881                                None => {
1882                                    let diag = diagnosed_error!("object key must be a string");
1883                                    results
1884                                        .unevaluated_inputs
1885                                        .insert(input_name.clone(), Some(diag.clone()));
1886                                    diags.push(diag);
1887                                    fatal_error = true;
1888                                    continue;
1889                                }
1890                            },
1891                        };
1892                        let expr = object_value.expr();
1893
1894                        let value = match eval_expression(
1895                            &expr,
1896                            dependencies_execution_results,
1897                            package_id,
1898                            runbook_workspace_context,
1899                            runbook_execution_context,
1900                            runtime_context,
1901                        ) {
1902                            Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
1903                            Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1904                                if e.is_error() {
1905                                    fatal_error = true;
1906                                }
1907                                results
1908                                    .unevaluated_inputs
1909                                    .insert(input_name.clone(), Some(e.clone()));
1910                                diags.push(e);
1911                                continue;
1912                            }
1913                            Err(e) => {
1914                                if e.is_error() {
1915                                    fatal_error = true;
1916                                }
1917                                results
1918                                    .unevaluated_inputs
1919                                    .insert(input_name.clone(), Some(e.clone()));
1920                                diags.push(e);
1921                                continue;
1922                            }
1923                            Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1924                                require_user_interaction = true;
1925                                results.unevaluated_inputs.insert(input_name.clone(), None);
1926                                continue;
1927                            }
1928                        };
1929                        object_values.insert(object_key, value);
1930                    }
1931                }
1932                ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
1933                    unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
1934                }
1935            }
1936
1937            if !object_values.is_empty() {
1938                results.insert(&input_name, Value::object(object_values));
1939            }
1940        } else if let Some(_) = input.as_array() {
1941            let mut array_values = vec![];
1942            let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name) else {
1943                continue;
1944            };
1945            let value = match eval_expression(
1946                &expr,
1947                dependencies_execution_results,
1948                package_id,
1949                runbook_workspace_context,
1950                runbook_execution_context,
1951                runtime_context,
1952            ) {
1953                Ok(ExpressionEvaluationStatus::CompleteOk(result)) => match result {
1954                    Value::Addon(_) => unreachable!(),
1955                    Value::Object(_) => unreachable!(),
1956                    Value::Array(entries) => {
1957                        for (i, entry) in entries.into_iter().enumerate() {
1958                            array_values.insert(i, entry); // todo: is it okay that we possibly overwrite array values from previous input evals?
1959                        }
1960                        Value::array(array_values)
1961                    }
1962                    _ => result,
1963                },
1964                Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
1965                    if e.is_error() {
1966                        fatal_error = true;
1967                    }
1968                    results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1969                    diags.push(e);
1970                    continue;
1971                }
1972                Err(e) => {
1973                    if e.is_error() {
1974                        fatal_error = true;
1975                    }
1976                    results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
1977                    diags.push(e);
1978                    continue;
1979                }
1980                Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
1981                    require_user_interaction = true;
1982                    results.unevaluated_inputs.insert(input_name.clone(), None);
1983                    continue;
1984                }
1985            };
1986
1987            results.insert(&input_name, value);
1988        } else if let Some(_) = input.as_map() {
1989            match evaluate_map_input(
1990                results.clone(),
1991                &input,
1992                with_evaluatable_inputs,
1993                dependencies_execution_results,
1994                package_id,
1995                runbook_workspace_context,
1996                runbook_execution_context,
1997                runtime_context,
1998            ) {
1999                Ok(Some(res)) => {
2000                    if res.fatal_error {
2001                        fatal_error = true;
2002                    }
2003                    if res.require_user_interaction {
2004                        require_user_interaction = true;
2005                    }
2006                    results = res.result;
2007                    diags.extend(res.diags);
2008                }
2009                Ok(None) => continue,
2010                Err(e) => return Err(e),
2011            };
2012        } else {
2013            let Some(expr) = with_evaluatable_inputs.get_expression_from_input(&input_name) else {
2014                continue;
2015            };
2016
2017            let value = match eval_expression(
2018                &expr,
2019                dependencies_execution_results,
2020                package_id,
2021                runbook_workspace_context,
2022                runbook_execution_context,
2023                runtime_context,
2024            ) {
2025                Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2026                Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2027                    if e.is_error() {
2028                        fatal_error = true;
2029                    }
2030                    results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
2031                    diags.push(e);
2032                    continue;
2033                }
2034                Err(e) => {
2035                    if e.is_error() {
2036                        fatal_error = true;
2037                    }
2038                    results.unevaluated_inputs.insert(input_name.clone(), Some(e.clone()));
2039                    diags.push(e);
2040                    continue;
2041                }
2042                Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2043                    require_user_interaction = true;
2044                    results.unevaluated_inputs.insert(input_name.clone(), None);
2045                    continue;
2046                }
2047            };
2048            results.insert(&input_name, value);
2049        }
2050    }
2051
2052    if fatal_error {
2053        return Ok(CommandInputEvaluationStatus::Aborted(results, diags));
2054    }
2055
2056    let status = match (fatal_error, require_user_interaction) {
2057        (false, false) => CommandInputEvaluationStatus::Complete(results),
2058        (_, _) => CommandInputEvaluationStatus::NeedsUserInteraction(results),
2059    };
2060    Ok(status)
2061}
2062
2063pub fn perform_signer_inputs_evaluation(
2064    signer_instance: &SignerInstance,
2065    dependencies_execution_results: &DependencyExecutionResultCache,
2066    input_evaluation_results: &Option<&CommandInputsEvaluationResult>,
2067    addon_defaults: &AddonDefaults,
2068    package_id: &PackageId,
2069    runbook_workspace_context: &RunbookWorkspaceContext,
2070    runbook_execution_context: &RunbookExecutionContext,
2071    runtime_context: &RuntimeContext,
2072) -> Result<CommandInputEvaluationStatus, Vec<Diagnostic>> {
2073    let mut results =
2074        CommandInputsEvaluationResult::new(&signer_instance.name, &addon_defaults.store);
2075    let mut require_user_interaction = false;
2076    let mut diags = vec![];
2077    let inputs = signer_instance.inputs();
2078    let mut fatal_error = false;
2079
2080    for input in inputs.into_iter() {
2081        let input_name = input.name();
2082
2083        // todo(micaiah): this value still needs to be for inputs that are objects
2084        let previously_evaluated_input = match input_evaluation_results {
2085            Some(input_evaluation_results) => {
2086                input_evaluation_results.inputs.get_value(&input_name)
2087            }
2088            None => None,
2089        };
2090        if let Some(object_def) = input.as_object() {
2091            // todo(micaiah) - figure out how user-input values work for this branch
2092            let mut object_values = IndexMap::new();
2093            match object_def {
2094                ObjectDefinition::Strict(props) => {
2095                    for prop in props.iter() {
2096                        if let Some(value) = previously_evaluated_input {
2097                            match value.clone() {
2098                                Value::Object(obj) => {
2099                                    for (k, v) in obj.into_iter() {
2100                                        object_values.insert(k, v);
2101                                    }
2102                                }
2103                                v => {
2104                                    object_values.insert(prop.name.to_string(), v);
2105                                }
2106                            };
2107                        }
2108
2109                        let Some(expr) =
2110                            signer_instance.get_expression_from_object_property(&input_name, &prop)
2111                        else {
2112                            continue;
2113                        };
2114                        let value = match eval_expression(
2115                            &expr,
2116                            dependencies_execution_results,
2117                            package_id,
2118                            runbook_workspace_context,
2119                            runbook_execution_context,
2120                            runtime_context,
2121                        ) {
2122                            Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2123                            Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2124                                if e.is_error() {
2125                                    fatal_error = true;
2126                                }
2127                                diags.push(e);
2128                                continue;
2129                            }
2130                            Err(e) => {
2131                                if e.is_error() {
2132                                    fatal_error = true;
2133                                }
2134                                diags.push(e);
2135                                continue;
2136                            }
2137                            Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2138                                require_user_interaction = true;
2139                                continue;
2140                            }
2141                        };
2142
2143                        match value.clone() {
2144                            Value::Object(obj) => {
2145                                for (k, v) in obj.into_iter() {
2146                                    object_values.insert(k, v);
2147                                }
2148                            }
2149                            v => {
2150                                object_values.insert(prop.name.to_string(), v);
2151                            }
2152                        };
2153                    }
2154                }
2155                ObjectDefinition::Arbitrary(_) => {
2156                    println!(
2157                        "Warning: arbitrary object definition is not supported for signer inputs"
2158                    );
2159                }
2160                ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2161                    unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2162                }
2163            }
2164
2165            if !object_values.is_empty() {
2166                results.insert(&input_name, Value::Object(object_values));
2167            }
2168        } else if let Some(_) = input.as_array() {
2169            let mut array_values = vec![];
2170            if let Some(value) = previously_evaluated_input {
2171                match value.clone() {
2172                    Value::Array(entries) => {
2173                        array_values.extend::<Vec<Value>>(entries.into_iter().collect());
2174                    }
2175                    _ => {
2176                        unreachable!()
2177                    }
2178                }
2179            }
2180
2181            let Some(expr) = signer_instance.get_expression_from_input(&input_name) else {
2182                continue;
2183            };
2184            let value = match eval_expression(
2185                &expr,
2186                dependencies_execution_results,
2187                package_id,
2188                runbook_workspace_context,
2189                runbook_execution_context,
2190                runtime_context,
2191            ) {
2192                Ok(ExpressionEvaluationStatus::CompleteOk(result)) => match result {
2193                    Value::Array(entries) => {
2194                        for (i, entry) in entries.into_iter().enumerate() {
2195                            array_values.insert(i, entry); // todo: is it okay that we possibly overwrite array values from previous input evals?
2196                        }
2197                        Value::array(array_values)
2198                    }
2199                    _ => unreachable!(),
2200                },
2201                Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2202                    if e.is_error() {
2203                        fatal_error = true;
2204                    }
2205                    diags.push(e);
2206                    continue;
2207                }
2208                Err(e) => {
2209                    if e.is_error() {
2210                        fatal_error = true;
2211                    }
2212                    diags.push(e);
2213                    continue;
2214                }
2215                Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2216                    // todo
2217                    let Expression::Array(exprs) = expr else { panic!() };
2218                    let mut references = vec![];
2219                    for expr in exprs.iter() {
2220                        let result = runbook_workspace_context
2221                            .try_resolve_construct_reference_in_expression(package_id, &expr);
2222                        if let Ok(Some((construct_did, _, _))) = result {
2223                            references.push(Value::string(construct_did.value().to_string()));
2224                        }
2225                    }
2226                    results.inputs.insert(&input_name, Value::array(references));
2227                    continue;
2228                }
2229            };
2230            results.insert(&input_name, value);
2231        } else {
2232            let value = if let Some(value) = previously_evaluated_input {
2233                value.clone()
2234            } else {
2235                let Some(expr) = signer_instance.get_expression_from_input(&input_name) else {
2236                    continue;
2237                };
2238                match eval_expression(
2239                    &expr,
2240                    dependencies_execution_results,
2241                    package_id,
2242                    runbook_workspace_context,
2243                    runbook_execution_context,
2244                    runtime_context,
2245                ) {
2246                    Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2247                    Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2248                        if e.is_error() {
2249                            fatal_error = true;
2250                        }
2251                        diags.push(e);
2252                        continue;
2253                    }
2254                    Err(e) => {
2255                        if e.is_error() {
2256                            fatal_error = true;
2257                        }
2258                        diags.push(e);
2259                        continue;
2260                    }
2261                    Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2262                        require_user_interaction = true;
2263                        continue;
2264                    }
2265                }
2266            };
2267
2268            results.insert(&input_name, value);
2269        }
2270    }
2271
2272    let status = match (fatal_error, require_user_interaction) {
2273        (false, false) => CommandInputEvaluationStatus::Complete(results),
2274        (true, _) => CommandInputEvaluationStatus::Aborted(results, diags),
2275        (false, _) => CommandInputEvaluationStatus::NeedsUserInteraction(results),
2276    };
2277    Ok(status)
2278}
2279
2280#[derive(Clone, Debug)]
2281struct EvaluateMapInputResult {
2282    result: CommandInputsEvaluationResult,
2283    require_user_interaction: bool,
2284    diags: Vec<Diagnostic>,
2285    fatal_error: bool,
2286}
2287/// Evaluating a map input is different from other inputs because it can contain nested blocks.
2288/// For other types, once we have an input block and an identifier in the block, we can expect that identifier to point to an "attribute".
2289/// For a map, it could point to another block, so we need to recursively look inside maps.
2290fn evaluate_map_input(
2291    mut result: CommandInputsEvaluationResult,
2292    input_spec: &Box<dyn EvaluatableInput>,
2293    with_evaluatable_inputs: &impl WithEvaluatableInputs,
2294    dependencies_execution_results: &DependencyExecutionResultCache,
2295    package_id: &PackageId,
2296    runbook_workspace_context: &RunbookWorkspaceContext,
2297    runbook_execution_context: &RunbookExecutionContext,
2298    runtime_context: &RuntimeContext,
2299) -> Result<Option<EvaluateMapInputResult>, Vec<Diagnostic>> {
2300    let input_name = input_spec.name();
2301    let input_typing = input_spec.typing();
2302    let input_optional = input_spec.optional();
2303
2304    let spec_object_def = input_spec.as_map().expect("expected input to be a map");
2305
2306    let Some(blocks) =
2307        with_evaluatable_inputs.get_blocks_for_map(&input_name, &input_typing, input_optional)?
2308    else {
2309        return Ok(None);
2310    };
2311
2312    let res = match spec_object_def {
2313        ObjectDefinition::Strict(props) => evaluate_map_object_prop(
2314            &input_name,
2315            EvaluateMapObjectPropResult::new(),
2316            blocks,
2317            &props,
2318            dependencies_execution_results,
2319            package_id,
2320            runbook_workspace_context,
2321            runbook_execution_context,
2322            runtime_context,
2323        )
2324        .map_err(|diag| vec![diag])?,
2325        ObjectDefinition::Arbitrary(_) => evaluate_arbitrary_inputs_map(
2326            &input_name,
2327            EvaluateMapObjectPropResult::new(),
2328            blocks,
2329            dependencies_execution_results,
2330            package_id,
2331            runbook_workspace_context,
2332            runbook_execution_context,
2333            runtime_context,
2334        )
2335        .map_err(|diag| vec![diag])?,
2336        ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2337            unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2338        }
2339    };
2340
2341    result.insert(&input_name, Value::array(res.entries));
2342    result.unevaluated_inputs.merge(&res.unevaluated_inputs);
2343    Ok(Some(EvaluateMapInputResult {
2344        result,
2345        require_user_interaction: res.require_user_interaction,
2346        diags: res.diags,
2347        fatal_error: res.fatal_error,
2348    }))
2349}
2350
2351fn evaluate_arbitrary_inputs_map(
2352    spec_input_name: &str,
2353    mut parent_result: EvaluateMapObjectPropResult,
2354    blocks: Vec<HclBlock>,
2355    dependencies_execution_results: &DependencyExecutionResultCache,
2356    package_id: &PackageId,
2357    runbook_workspace_context: &RunbookWorkspaceContext,
2358    runbook_execution_context: &RunbookExecutionContext,
2359    runtime_context: &RuntimeContext,
2360) -> Result<EvaluateMapObjectPropResult, Diagnostic> {
2361    for block in blocks {
2362        let mut object_values = IndexMap::new();
2363        for attr in block.body.attributes() {
2364            let expr = attr.value.clone();
2365            let ident = attr.key.to_string();
2366
2367            let value = match eval_expression(
2368                &expr,
2369                dependencies_execution_results,
2370                package_id,
2371                runbook_workspace_context,
2372                runbook_execution_context,
2373                runtime_context,
2374            ) {
2375                Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2376                Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2377                    if e.is_error() {
2378                        parent_result.fatal_error = true;
2379                    }
2380                    parent_result
2381                        .unevaluated_inputs
2382                        .insert(spec_input_name.to_string(), Some(e.clone()));
2383                    parent_result.diags.push(e);
2384                    continue;
2385                }
2386                Err(e) => {
2387                    if e.is_error() {
2388                        parent_result.fatal_error = true;
2389                    }
2390                    parent_result
2391                        .unevaluated_inputs
2392                        .insert(spec_input_name.to_string(), Some(e.clone()));
2393                    parent_result.diags.push(e);
2394                    continue;
2395                }
2396                Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2397                    parent_result.require_user_interaction = true;
2398                    parent_result.unevaluated_inputs.insert(spec_input_name.to_string(), None);
2399                    continue;
2400                }
2401            };
2402            match value.clone() {
2403                Value::Object(obj) => {
2404                    for (k, v) in obj.into_iter() {
2405                        object_values.insert(k, v);
2406                    }
2407                }
2408                v => {
2409                    object_values.insert(ident, v);
2410                }
2411            };
2412        }
2413
2414        let child_blocks = block.body.blocks().cloned().collect::<Vec<_>>();
2415        if !child_blocks.is_empty() {
2416            let mut ident_grouped_child_blocks = IndexMap::new();
2417            child_blocks.iter().for_each(|child_block| {
2418                ident_grouped_child_blocks
2419                    .entry(child_block.ident.to_string())
2420                    .or_insert_with(Vec::new)
2421                    .push(child_block.clone())
2422            });
2423            for (ident, child_blocks) in ident_grouped_child_blocks {
2424                let child_block_result = evaluate_arbitrary_inputs_map(
2425                    spec_input_name,
2426                    EvaluateMapObjectPropResult::new(),
2427                    child_blocks,
2428                    dependencies_execution_results,
2429                    package_id,
2430                    runbook_workspace_context,
2431                    runbook_execution_context,
2432                    runtime_context,
2433                )?;
2434                parent_result.unevaluated_inputs = child_block_result.unevaluated_inputs;
2435                let mut diags = parent_result.diags.clone();
2436                diags.extend(child_block_result.diags);
2437                parent_result.diags = diags;
2438                if child_block_result.fatal_error {
2439                    parent_result.fatal_error = true;
2440                    continue;
2441                }
2442                if child_block_result.require_user_interaction {
2443                    parent_result.require_user_interaction = true;
2444                    continue;
2445                }
2446
2447                object_values.insert(ident, Value::array(child_block_result.entries));
2448            }
2449        }
2450        parent_result.entries.push(Value::object(object_values));
2451    }
2452    Ok(parent_result)
2453}
2454
2455#[derive(Clone, Debug)]
2456struct EvaluateMapObjectPropResult {
2457    entries: Vec<Value>,
2458    unevaluated_inputs: UnevaluatedInputsMap,
2459    require_user_interaction: bool,
2460    diags: Vec<Diagnostic>,
2461    fatal_error: bool,
2462}
2463impl EvaluateMapObjectPropResult {
2464    fn new() -> Self {
2465        Self {
2466            entries: vec![],
2467            unevaluated_inputs: UnevaluatedInputsMap::new(),
2468            require_user_interaction: false,
2469            diags: vec![],
2470            fatal_error: false,
2471        }
2472    }
2473}
2474
2475fn evaluate_map_object_prop(
2476    spec_input_name: &str,
2477    mut parent_result: EvaluateMapObjectPropResult,
2478    blocks: Vec<HclBlock>,
2479    spec_object_props: &Vec<ObjectProperty>,
2480    dependencies_execution_results: &DependencyExecutionResultCache,
2481    package_id: &PackageId,
2482    runbook_workspace_context: &RunbookWorkspaceContext,
2483    runbook_execution_context: &RunbookExecutionContext,
2484    runtime_context: &RuntimeContext,
2485) -> Result<EvaluateMapObjectPropResult, Diagnostic> {
2486    for block in blocks.iter() {
2487        let mut object_values = IndexMap::new();
2488        for spec_object_prop in spec_object_props.iter() {
2489            let value = if let Some(expr) =
2490                visit_optional_untyped_attribute(&spec_object_prop.name, &block)
2491            {
2492                let value = match eval_expression(
2493                    &expr,
2494                    dependencies_execution_results,
2495                    package_id,
2496                    runbook_workspace_context,
2497                    runbook_execution_context,
2498                    runtime_context,
2499                ) {
2500                    Ok(ExpressionEvaluationStatus::CompleteOk(result)) => result,
2501                    Ok(ExpressionEvaluationStatus::CompleteErr(e)) => {
2502                        if e.is_error() {
2503                            parent_result.fatal_error = true;
2504                        }
2505                        parent_result
2506                            .unevaluated_inputs
2507                            .insert(spec_input_name.to_string(), Some(e.clone()));
2508                        parent_result.diags.push(e);
2509                        continue;
2510                    }
2511                    Err(e) => {
2512                        if e.is_error() {
2513                            parent_result.fatal_error = true;
2514                        }
2515                        parent_result
2516                            .unevaluated_inputs
2517                            .insert(spec_input_name.to_string(), Some(e.clone()));
2518                        parent_result.diags.push(e);
2519                        continue;
2520                    }
2521                    Ok(ExpressionEvaluationStatus::DependencyNotComputed) => {
2522                        parent_result.require_user_interaction = true;
2523                        parent_result.unevaluated_inputs.insert(spec_input_name.to_string(), None);
2524                        continue;
2525                    }
2526                };
2527                value
2528            } else {
2529                let child_map_blocks = block
2530                    .body
2531                    .get_blocks(&spec_object_prop.name)
2532                    .into_iter()
2533                    .map(|b| b.clone())
2534                    .collect::<Vec<_>>();
2535                if child_map_blocks.is_empty() {
2536                    continue;
2537                }
2538                let Type::Map(ref child_map_spec_object_def) = spec_object_prop.typing else {
2539                    return Err(diagnosed_error!(
2540                        "expected type {} for property {}, found map",
2541                        spec_object_prop.typing.to_string(),
2542                        spec_object_prop.name
2543                    ));
2544                };
2545
2546                let res = match child_map_spec_object_def {
2547                    ObjectDefinition::Strict(props) => evaluate_map_object_prop(
2548                        spec_input_name,
2549                        EvaluateMapObjectPropResult::new(),
2550                        child_map_blocks,
2551                        &props,
2552                        dependencies_execution_results,
2553                        package_id,
2554                        runbook_workspace_context,
2555                        runbook_execution_context,
2556                        runtime_context,
2557                    )?,
2558                    ObjectDefinition::Arbitrary(_) => evaluate_arbitrary_inputs_map(
2559                        spec_input_name,
2560                        EvaluateMapObjectPropResult::new(),
2561                        child_map_blocks,
2562                        dependencies_execution_results,
2563                        package_id,
2564                        runbook_workspace_context,
2565                        runbook_execution_context,
2566                        runtime_context,
2567                    )?,
2568                    ObjectDefinition::Tuple(_) | ObjectDefinition::Enum(_) => {
2569                        unimplemented!("ObjectDefinition::Tuple and ObjectDefinition::Enum are not supported for runbook types");
2570                    }
2571                };
2572
2573                parent_result.unevaluated_inputs = res.unevaluated_inputs;
2574                let mut diags = parent_result.diags.clone();
2575                diags.extend(res.diags);
2576                parent_result.diags = diags;
2577                if res.fatal_error {
2578                    parent_result.fatal_error = true;
2579                    continue;
2580                }
2581                if res.require_user_interaction {
2582                    parent_result.require_user_interaction = true;
2583                    continue;
2584                }
2585                Value::array(res.entries)
2586            };
2587
2588            match value.clone() {
2589                Value::Object(obj) => {
2590                    for (k, v) in obj.into_iter() {
2591                        object_values.insert(k, v);
2592                    }
2593                }
2594                v => {
2595                    object_values.insert(spec_object_prop.name.to_string(), v);
2596                }
2597            };
2598        }
2599        parent_result.entries.push(Value::object(object_values));
2600    }
2601
2602    Ok(parent_result)
2603}
2604
2605#[cfg(test)]
2606mod map_eval_tests;