Skip to main content

telltale_vm/
commit_common.rs

1//! Shared commit-phase helpers used by cooperative and threaded backends.
2
3use serde_json::json;
4
5use crate::coroutine::Fault;
6use crate::effect::EffectTraceEntry;
7use crate::output_condition::{
8    verify_output_condition, OutputConditionCheck, OutputConditionHint, OutputConditionMeta,
9    OutputConditionPolicy,
10};
11use crate::vm::ObsEvent;
12
13/// Apply output-condition verification for a commit and record diagnostics.
14pub(crate) fn apply_output_condition_gate(
15    policy: &OutputConditionPolicy,
16    checks: &mut Vec<OutputConditionCheck>,
17    trace: &mut Vec<ObsEvent>,
18    tick: u64,
19    output_hint: Option<OutputConditionHint>,
20) -> Result<(), Fault> {
21    let digest = "vm.output_digest.unspecified".to_string();
22    let meta = match output_hint {
23        Some(h) => OutputConditionMeta::from_hint(h, digest),
24        None => OutputConditionMeta::default_observable(digest),
25    };
26    let passed = verify_output_condition(policy, &meta);
27    checks.push(OutputConditionCheck {
28        meta: meta.clone(),
29        passed,
30    });
31    trace.push(ObsEvent::OutputConditionChecked {
32        tick,
33        predicate_ref: meta.predicate_ref.clone(),
34        witness_ref: meta.witness_ref.clone(),
35        output_digest: meta.output_digest.clone(),
36        passed,
37    });
38    if passed {
39        Ok(())
40    } else {
41        Err(Fault::OutputCondition {
42            predicate_ref: meta.predicate_ref,
43        })
44    }
45}
46
47/// Build canonical effect-trace entry from an observable event, if applicable.
48pub(crate) fn effect_trace_entry_for_event(
49    ev: &ObsEvent,
50    effect_id: u64,
51    handler_identity: &str,
52    ordering_key: u64,
53) -> Option<EffectTraceEntry> {
54    match ev {
55        ObsEvent::Sent {
56            session,
57            from,
58            to,
59            label,
60            ..
61        } => Some(EffectTraceEntry {
62            effect_id,
63            effect_kind: "send_decision".to_string(),
64            inputs: json!({
65                "session": session,
66                "from": from,
67                "to": to,
68                "label": label,
69            }),
70            outputs: json!({"committed": true}),
71            handler_identity: handler_identity.to_string(),
72            ordering_key,
73            topology: None,
74        }),
75        ObsEvent::Received {
76            session,
77            from,
78            to,
79            label,
80            ..
81        } => Some(EffectTraceEntry {
82            effect_id,
83            effect_kind: "handle_recv".to_string(),
84            inputs: json!({
85                "session": session,
86                "from": from,
87                "to": to,
88                "label": label,
89            }),
90            outputs: json!({"committed": true}),
91            handler_identity: handler_identity.to_string(),
92            ordering_key,
93            topology: None,
94        }),
95        ObsEvent::Invoked { coro_id, role, .. } => Some(EffectTraceEntry {
96            effect_id,
97            effect_kind: "invoke_step".to_string(),
98            inputs: json!({
99                "coro_id": coro_id,
100                "role": role,
101            }),
102            outputs: json!({"ok": true}),
103            handler_identity: handler_identity.to_string(),
104            ordering_key,
105            topology: None,
106        }),
107        ObsEvent::Acquired {
108            session,
109            role,
110            layer,
111            ..
112        } => Some(EffectTraceEntry {
113            effect_id,
114            effect_kind: "handle_acquire".to_string(),
115            inputs: json!({
116                "session": session,
117                "role": role,
118                "layer": layer,
119            }),
120            outputs: json!({"granted": true}),
121            handler_identity: handler_identity.to_string(),
122            ordering_key,
123            topology: None,
124        }),
125        ObsEvent::Released {
126            session,
127            role,
128            layer,
129            ..
130        } => Some(EffectTraceEntry {
131            effect_id,
132            effect_kind: "handle_release".to_string(),
133            inputs: json!({
134                "session": session,
135                "role": role,
136                "layer": layer,
137            }),
138            outputs: json!({"ok": true}),
139            handler_identity: handler_identity.to_string(),
140            ordering_key,
141            topology: None,
142        }),
143        _ => None,
144    }
145}