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}