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.
5///
6/// Host-contract rules:
7/// - Methods on this trait are synchronous. Async I/O, transport polling,
8///   storage flushes, and background retries must happen outside callback
9///   execution and feed their results back through canonical ingress.
10/// - Implementations must treat the provided `state` as session-local scratch
11///   for the current callback only. They must not rely on unrelated session
12///   state or mutate VM session metadata through side channels.
13/// - Host-managed session-local mutation should flow through an explicit
14///   ownership capability such as `OwnedSession`, not through ad hoc access to
15///   the session store while callbacks are executing.
16pub trait EffectHandler: Send + Sync {
17    /// Stable identifier for effect-trace attribution.
18    fn handler_identity(&self) -> String {
19        crate::session::DEFAULT_HANDLER_ID.to_string()
20    }
21
22    /// Compute the payload for a send instruction.
23    ///
24    /// Helper hook used by the default `send_decision` implementation and by
25    /// custom runners that want direct payload computation.
26    ///
27    /// # Arguments
28    /// * `role` - The sending role
29    /// * `partner` - The receiving role
30    /// * `label` - The message label
31    /// * `state` - The coroutine's register file (for reading state)
32    ///
33    /// Returns a typed outcome for the callback.
34    fn handle_send(
35        &self,
36        role: &str,
37        partner: &str,
38        label: &str,
39        state: &[Value],
40    ) -> EffectResult<Value>;
41
42    /// Optional fast-path hook for send decision dispatch.
43    ///
44    /// Returning `Some(result)` bypasses `send_decision`.
45    /// Returning `None` keeps canonical behavior unchanged.
46    fn send_decision_fast_path(
47        &self,
48        _fast_path: SendDecisionFastPathInput<'_>,
49        _state: &[Value],
50        _payload: Option<&Value>,
51    ) -> Option<EffectResult<SendDecision>> {
52        None
53    }
54
55    /// Decide how to handle a send, optionally with a precomputed payload.
56    ///
57    /// Middleware can override this to model loss/delay/corruption. The default
58    /// behavior computes a payload via `handle_send` unless one is provided.
59    ///
60    /// Returns a typed outcome for the callback.
61    fn send_decision(&self, input: SendDecisionInput<'_>) -> EffectResult<SendDecision> {
62        if let Some(payload) = input.payload {
63            EffectResult::success(SendDecision::Deliver(payload))
64        } else {
65            self.handle_send(input.role, input.partner, input.label, input.state)
66                .map_success(SendDecision::Deliver)
67        }
68    }
69
70    /// Process a received value.
71    ///
72    /// # Arguments
73    /// * `role` - The receiving role
74    /// * `partner` - The sending role
75    /// * `label` - The message label
76    /// * `state` - The coroutine's register file (mutable for state updates)
77    /// * `payload` - The received value
78    ///
79    /// Returns a typed outcome for the callback.
80    fn handle_recv(
81        &self,
82        role: &str,
83        partner: &str,
84        label: &str,
85        state: &mut Vec<Value>,
86        payload: &Value,
87    ) -> EffectResult<()>;
88
89    /// Choose which branch to take for internal choice (select).
90    ///
91    /// Branch-selection helper for custom runners.
92    ///
93    /// The canonical VM resolves branch labels from received payloads and does
94    /// not call this method in default dispatch paths.
95    ///
96    /// # Arguments
97    /// * `role` - The choosing role
98    /// * `partner` - The partner role
99    /// * `labels` - The available branch labels
100    /// * `state` - The coroutine's register file (for reading state)
101    ///
102    /// Returns a typed outcome for the callback.
103    fn handle_choose(
104        &self,
105        role: &str,
106        partner: &str,
107        labels: &[String],
108        state: &[Value],
109    ) -> EffectResult<String>;
110
111    /// Perform an integration step after a protocol round.
112    ///
113    /// Called after all sends/receives for a tick are complete.
114    ///
115    /// Returns a typed outcome for the callback.
116    fn step(&self, role: &str, state: &mut Vec<Value>) -> EffectResult<()>;
117
118    /// Attempt to acquire a guard layer.
119    ///
120    /// Returning `EffectResult::Blocked` causes the coroutine to block.
121    /// `Success(evidence)` grants the acquire and binds the evidence value.
122    fn handle_acquire(
123        &self,
124        _sid: SessionId,
125        _role: &str,
126        _layer: &str,
127        _state: &[Value],
128    ) -> EffectResult<Value> {
129        EffectResult::success(Value::Unit)
130    }
131
132    /// Release a guard layer using previously acquired evidence.
133    fn handle_release(
134        &self,
135        _sid: SessionId,
136        _role: &str,
137        _layer: &str,
138        _evidence: &Value,
139        _state: &[Value],
140    ) -> EffectResult<()> {
141        EffectResult::success(())
142    }
143
144    /// Topology perturbations injected by the environment for this scheduler tick.
145    ///
146    /// The VM ingests these before selecting coroutines for the round. This is
147    /// a canonical ingress surface for external events; implementations should
148    /// stage async discoveries before this method is called rather than doing
149    /// async work from inside the callback.
150    ///
151    /// Returns a typed outcome for the callback.
152    fn topology_events(&self, _tick: u64) -> EffectResult<Vec<TopologyPerturbation>> {
153        EffectResult::success(Vec::new())
154    }
155
156    /// Optional output-condition metadata for commit gating.
157    ///
158    /// The VM calls this only when a step emits observable events. Returning `None`
159    /// delegates to VM-default metadata.
160    fn output_condition_hint(
161        &self,
162        _sid: SessionId,
163        _role: &str,
164        _state: &[Value],
165    ) -> Option<OutputConditionHint> {
166        None
167    }
168}
169
170impl<T: EffectHandler + ?Sized> EffectHandler for &T {
171    fn handler_identity(&self) -> String {
172        (**self).handler_identity()
173    }
174
175    fn handle_send(
176        &self,
177        role: &str,
178        partner: &str,
179        label: &str,
180        state: &[Value],
181    ) -> EffectResult<Value> {
182        (**self).handle_send(role, partner, label, state)
183    }
184
185    fn send_decision(&self, input: SendDecisionInput<'_>) -> EffectResult<SendDecision> {
186        (**self).send_decision(input)
187    }
188
189    fn send_decision_fast_path(
190        &self,
191        fast_path: SendDecisionFastPathInput<'_>,
192        state: &[Value],
193        payload: Option<&Value>,
194    ) -> Option<EffectResult<SendDecision>> {
195        (**self).send_decision_fast_path(fast_path, state, payload)
196    }
197
198    fn handle_recv(
199        &self,
200        role: &str,
201        partner: &str,
202        label: &str,
203        state: &mut Vec<Value>,
204        payload: &Value,
205    ) -> EffectResult<()> {
206        (**self).handle_recv(role, partner, label, state, payload)
207    }
208
209    fn handle_choose(
210        &self,
211        role: &str,
212        partner: &str,
213        labels: &[String],
214        state: &[Value],
215    ) -> EffectResult<String> {
216        (**self).handle_choose(role, partner, labels, state)
217    }
218
219    fn step(&self, role: &str, state: &mut Vec<Value>) -> EffectResult<()> {
220        (**self).step(role, state)
221    }
222
223    fn handle_acquire(
224        &self,
225        sid: SessionId,
226        role: &str,
227        layer: &str,
228        state: &[Value],
229    ) -> EffectResult<Value> {
230        (**self).handle_acquire(sid, role, layer, state)
231    }
232
233    fn handle_release(
234        &self,
235        sid: SessionId,
236        role: &str,
237        layer: &str,
238        evidence: &Value,
239        state: &[Value],
240    ) -> EffectResult<()> {
241        (**self).handle_release(sid, role, layer, evidence, state)
242    }
243
244    fn topology_events(&self, tick: u64) -> EffectResult<Vec<TopologyPerturbation>> {
245        (**self).topology_events(tick)
246    }
247
248    fn output_condition_hint(
249        &self,
250        sid: SessionId,
251        role: &str,
252        state: &[Value],
253    ) -> Option<OutputConditionHint> {
254        (**self).output_condition_hint(sid, role, state)
255    }
256}