Skip to main content

telltale_vm/effect/
handler_trait.rs

1/// VM-level effect handler.
2///
3/// This is the interface between the VM and the host application. Each
4/// choreography can bind a different handler at session open time.
5pub trait EffectHandler: Send + Sync {
6    /// Stable identifier for effect-trace attribution.
7    fn handler_identity(&self) -> String {
8        crate::session::DEFAULT_HANDLER_ID.to_string()
9    }
10
11    /// Compute the payload for a send instruction.
12    ///
13    /// Compatibility hook:
14    /// Canonical VM send paths pass an explicit payload into `send_decision`.
15    /// This method remains for adapters and custom runners.
16    ///
17    /// # Arguments
18    /// * `role` - The sending role
19    /// * `partner` - The receiving role
20    /// * `label` - The message label
21    /// * `state` - The coroutine's register file (for reading state)
22    ///
23    /// # Errors
24    /// Returns an error string if the handler fails.
25    fn handle_send(
26        &self,
27        role: &str,
28        partner: &str,
29        label: &str,
30        state: &[Value],
31    ) -> Result<Value, String>;
32
33    /// Optional fast-path hook for send decision dispatch.
34    ///
35    /// Returning `Some(result)` bypasses `send_decision`.
36    /// Returning `None` keeps canonical behavior unchanged.
37    fn send_decision_fast_path(
38        &self,
39        _fast_path: SendDecisionFastPathInput<'_>,
40        _state: &[Value],
41        _payload: Option<&Value>,
42    ) -> Option<Result<SendDecision, String>> {
43        None
44    }
45
46    /// Decide how to handle a send, optionally with a precomputed payload.
47    ///
48    /// Middleware can override this to model loss/delay/corruption. The default
49    /// behavior computes a payload via `handle_send` unless one is provided.
50    ///
51    /// # Errors
52    ///
53    /// Returns an error string if the handler fails.
54    fn send_decision(&self, input: SendDecisionInput<'_>) -> Result<SendDecision, String> {
55        if let Some(payload) = input.payload {
56            Ok(SendDecision::Deliver(payload))
57        } else {
58            self.handle_send(input.role, input.partner, input.label, input.state)
59                .map(SendDecision::Deliver)
60        }
61    }
62
63    /// Process a received value.
64    ///
65    /// # Arguments
66    /// * `role` - The receiving role
67    /// * `partner` - The sending role
68    /// * `label` - The message label
69    /// * `state` - The coroutine's register file (mutable for state updates)
70    /// * `payload` - The received value
71    ///
72    /// # Errors
73    /// Returns an error string if the handler fails.
74    fn handle_recv(
75        &self,
76        role: &str,
77        partner: &str,
78        label: &str,
79        state: &mut Vec<Value>,
80        payload: &Value,
81    ) -> Result<(), String>;
82
83    /// Choose which branch to take for internal choice (select).
84    ///
85    /// Compatibility hook:
86    /// The canonical VM currently resolves branch labels from received payloads and
87    /// does not call this method in default dispatch paths.
88    ///
89    /// Custom runners may still use this as an explicit branch-selection hook.
90    ///
91    /// # Arguments
92    /// * `role` - The choosing role
93    /// * `partner` - The partner role
94    /// * `labels` - The available branch labels
95    /// * `state` - The coroutine's register file (for reading state)
96    ///
97    /// # Errors
98    /// Returns an error string if the handler fails.
99    fn handle_choose(
100        &self,
101        role: &str,
102        partner: &str,
103        labels: &[String],
104        state: &[Value],
105    ) -> Result<String, String>;
106
107    /// Perform an integration step after a protocol round.
108    ///
109    /// Called after all sends/receives for a tick are complete.
110    ///
111    /// # Errors
112    /// Returns an error string if the handler fails.
113    fn step(&self, role: &str, state: &mut Vec<Value>) -> Result<(), String>;
114
115    /// Attempt to acquire a guard layer.
116    ///
117    /// Returning `AcquireDecision::Block` causes the coroutine to block.
118    ///
119    /// # Errors
120    ///
121    /// Returns an error string if acquisition fails.
122    fn handle_acquire(
123        &self,
124        _sid: SessionId,
125        _role: &str,
126        _layer: &str,
127        _state: &[Value],
128    ) -> Result<AcquireDecision, String> {
129        Ok(AcquireDecision::Grant(Value::Unit))
130    }
131
132    /// Release a guard layer using previously acquired evidence.
133    ///
134    /// # Errors
135    ///
136    /// Returns an error string if release fails.
137    fn handle_release(
138        &self,
139        _sid: SessionId,
140        _role: &str,
141        _layer: &str,
142        _evidence: &Value,
143        _state: &[Value],
144    ) -> Result<(), String> {
145        Ok(())
146    }
147
148    /// Topology perturbations injected by the environment for this scheduler tick.
149    ///
150    /// The VM ingests these before selecting coroutines for the round.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error string if topology retrieval fails.
155    fn topology_events(&self, _tick: u64) -> Result<Vec<TopologyPerturbation>, String> {
156        Ok(Vec::new())
157    }
158
159    /// Optional output-condition metadata for commit gating.
160    ///
161    /// The VM calls this only when a step emits observable events. Returning `None`
162    /// delegates to VM-default metadata.
163    fn output_condition_hint(
164        &self,
165        _sid: SessionId,
166        _role: &str,
167        _state: &[Value],
168    ) -> Option<OutputConditionHint> {
169        None
170    }
171}
172
173impl<T: EffectHandler + ?Sized> EffectHandler for &T {
174    fn handler_identity(&self) -> String {
175        (**self).handler_identity()
176    }
177
178    fn handle_send(
179        &self,
180        role: &str,
181        partner: &str,
182        label: &str,
183        state: &[Value],
184    ) -> Result<Value, String> {
185        (**self).handle_send(role, partner, label, state)
186    }
187
188    fn send_decision(&self, input: SendDecisionInput<'_>) -> Result<SendDecision, String> {
189        (**self).send_decision(input)
190    }
191
192    fn send_decision_fast_path(
193        &self,
194        fast_path: SendDecisionFastPathInput<'_>,
195        state: &[Value],
196        payload: Option<&Value>,
197    ) -> Option<Result<SendDecision, String>> {
198        (**self).send_decision_fast_path(fast_path, state, payload)
199    }
200
201    fn handle_recv(
202        &self,
203        role: &str,
204        partner: &str,
205        label: &str,
206        state: &mut Vec<Value>,
207        payload: &Value,
208    ) -> Result<(), String> {
209        (**self).handle_recv(role, partner, label, state, payload)
210    }
211
212    fn handle_choose(
213        &self,
214        role: &str,
215        partner: &str,
216        labels: &[String],
217        state: &[Value],
218    ) -> Result<String, String> {
219        (**self).handle_choose(role, partner, labels, state)
220    }
221
222    fn step(&self, role: &str, state: &mut Vec<Value>) -> Result<(), String> {
223        (**self).step(role, state)
224    }
225
226    fn handle_acquire(
227        &self,
228        sid: SessionId,
229        role: &str,
230        layer: &str,
231        state: &[Value],
232    ) -> Result<AcquireDecision, String> {
233        (**self).handle_acquire(sid, role, layer, state)
234    }
235
236    fn handle_release(
237        &self,
238        sid: SessionId,
239        role: &str,
240        layer: &str,
241        evidence: &Value,
242        state: &[Value],
243    ) -> Result<(), String> {
244        (**self).handle_release(sid, role, layer, evidence, state)
245    }
246
247    fn topology_events(&self, tick: u64) -> Result<Vec<TopologyPerturbation>, String> {
248        (**self).topology_events(tick)
249    }
250
251    fn output_condition_hint(
252        &self,
253        sid: SessionId,
254        role: &str,
255        state: &[Value],
256    ) -> Option<OutputConditionHint> {
257        (**self).output_condition_hint(sid, role, state)
258    }
259}