Skip to main content

telltale_machine/
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::{infer_effect_interface_and_operation, EffectTraceEntry};
7use crate::engine::ObsEvent;
8use crate::output_condition::{
9    verify_output_condition, OutputConditionCheck, OutputConditionHint, OutputConditionMeta,
10    OutputConditionPolicy,
11};
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 = "machine.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.
52#[allow(clippy::too_many_lines)]
53pub(crate) fn effect_trace_entry_for_event(
54    ev: &ObsEvent,
55    effect_id: u64,
56    handler_identity: &str,
57    ordering_key: u64,
58) -> Option<EffectTraceEntry> {
59    match ev {
60        ObsEvent::Sent {
61            session,
62            from,
63            to,
64            label,
65            ..
66        } => {
67            let effect_kind = "send_decision".to_string();
68            let (effect_interface, effect_operation) =
69                infer_effect_interface_and_operation(&effect_kind);
70            Some(EffectTraceEntry {
71                effect_id,
72                effect_kind,
73                inputs: json!({
74                    "session": session,
75                    "from": from,
76                    "to": to,
77                    "label": label,
78                }),
79                outputs: json!({"committed": true}),
80                handler_identity: handler_identity.to_string(),
81                effect_interface,
82                effect_operation,
83                ordering_key,
84                topology: None,
85            })
86        }
87        ObsEvent::Received {
88            session,
89            from,
90            to,
91            label,
92            ..
93        } => {
94            let effect_kind = "handle_recv".to_string();
95            let (effect_interface, effect_operation) =
96                infer_effect_interface_and_operation(&effect_kind);
97            Some(EffectTraceEntry {
98                effect_id,
99                effect_kind,
100                inputs: json!({
101                    "session": session,
102                    "from": from,
103                    "to": to,
104                    "label": label,
105                }),
106                outputs: json!({"committed": true}),
107                handler_identity: handler_identity.to_string(),
108                effect_interface,
109                effect_operation,
110                ordering_key,
111                topology: None,
112            })
113        }
114        ObsEvent::Invoked { coro_id, role, .. } => {
115            let effect_kind = "invoke_step".to_string();
116            let (effect_interface, effect_operation) =
117                infer_effect_interface_and_operation(&effect_kind);
118            Some(EffectTraceEntry {
119                effect_id,
120                effect_kind,
121                inputs: json!({
122                    "coro_id": coro_id,
123                    "role": role,
124                }),
125                outputs: json!({"ok": true}),
126                handler_identity: handler_identity.to_string(),
127                effect_interface,
128                effect_operation,
129                ordering_key,
130                topology: None,
131            })
132        }
133        ObsEvent::Acquired {
134            session,
135            role,
136            layer,
137            ..
138        } => {
139            let effect_kind = "handle_acquire".to_string();
140            let (effect_interface, effect_operation) =
141                infer_effect_interface_and_operation(&effect_kind);
142            Some(EffectTraceEntry {
143                effect_id,
144                effect_kind,
145                inputs: json!({
146                    "session": session,
147                    "role": role,
148                    "layer": layer,
149                }),
150                outputs: json!({"granted": true}),
151                handler_identity: handler_identity.to_string(),
152                effect_interface,
153                effect_operation,
154                ordering_key,
155                topology: None,
156            })
157        }
158        ObsEvent::Released {
159            session,
160            role,
161            layer,
162            ..
163        } => {
164            let effect_kind = "handle_release".to_string();
165            let (effect_interface, effect_operation) =
166                infer_effect_interface_and_operation(&effect_kind);
167            Some(EffectTraceEntry {
168                effect_id,
169                effect_kind,
170                inputs: json!({
171                    "session": session,
172                    "role": role,
173                    "layer": layer,
174                }),
175                outputs: json!({"ok": true}),
176                handler_identity: handler_identity.to_string(),
177                effect_interface,
178                effect_operation,
179                ordering_key,
180                topology: None,
181            })
182        }
183        _ => None,
184    }
185}