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