telltale_machine/engine/protocol_machine_config.rs
1/// Runtime host-contract assertion mode.
2///
3/// Production configurations should use [`HostContractMode::Enforced`]. The
4/// relaxed mode exists for tests that intentionally violate handler identity,
5/// topology ordering, or transfer-audit contracts.
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum HostContractMode {
9 /// Enforce host integration contracts at runtime.
10 #[default]
11 Enforced,
12 /// Disable host integration assertions for targeted tests only.
13 RelaxedTestOnly,
14}
15
16impl HostContractMode {
17 /// Whether runtime host-contract assertions are enabled.
18 #[must_use]
19 pub const fn is_enforced(self) -> bool {
20 matches!(self, Self::Enforced)
21 }
22}
23
24fn deserialize_host_contract_mode<'de, D>(deserializer: D) -> Result<HostContractMode, D::Error>
25where
26 D: Deserializer<'de>,
27{
28 #[derive(Deserialize)]
29 #[serde(untagged)]
30 enum Compat {
31 Bool(bool),
32 Mode(HostContractMode),
33 }
34
35 Ok(match Compat::deserialize(deserializer)? {
36 Compat::Bool(true) => HostContractMode::Enforced,
37 Compat::Bool(false) => HostContractMode::RelaxedTestOnly,
38 Compat::Mode(mode) => mode,
39 })
40}
41
42/// ProtocolMachine configuration.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ProtocolMachineConfig {
45 /// Migration-safe config schema version.
46 #[serde(default = "default_config_schema_version")]
47 pub config_schema_version: u32,
48 /// Scheduling policy.
49 pub sched_policy: SchedPolicy,
50 /// Default buffer configuration for new sessions.
51 pub buffer_config: BufferConfig,
52 /// Maximum number of concurrent sessions.
53 pub max_sessions: usize,
54 /// Maximum number of concurrent coroutines.
55 pub max_coroutines: usize,
56 /// Number of registers per coroutine.
57 pub num_registers: u16,
58 /// Simulated time per scheduler round.
59 pub tick_duration: Duration,
60 /// Guard layers configured for the ProtocolMachine.
61 pub guard_layers: Vec<GuardLayerConfig>,
62 /// Whether speculative execution is enabled.
63 pub speculation_enabled: bool,
64 /// Determinism profile for replay/equivalence behavior.
65 pub determinism_mode: DeterminismMode,
66 /// Effect determinism tier used by admission and envelope artifacts.
67 #[serde(default)]
68 pub effect_determinism_tier: EffectDeterminismTier,
69 /// Output-condition policy for commit eligibility of observable outputs.
70 pub output_condition_policy: OutputConditionPolicy,
71 /// Monitor mode for pre-dispatch type checks.
72 #[serde(default)]
73 pub monitor_mode: MonitorMode,
74 /// Flow policy for epistemic knowledge checks.
75 #[serde(default)]
76 pub flow_policy: FlowPolicy,
77 /// Deterministic cost charged for each instruction dispatch.
78 #[serde(default = "default_instruction_cost")]
79 pub instruction_cost: usize,
80 /// Initial cost budget assigned to each coroutine.
81 #[serde(default = "default_initial_cost_budget")]
82 pub initial_cost_budget: usize,
83 /// Whether threaded scheduler may admit same-session picks when footprint-disjoint.
84 #[serde(default)]
85 pub footprint_guided_wave_widening: bool,
86 /// Runtime tuning profile used by instrumentation/benchmark harnesses.
87 #[serde(default)]
88 pub runtime_tuning_profile: RuntimeTuningProfile,
89 /// Round semantics mode used by threaded scheduler.
90 #[serde(default)]
91 pub threaded_round_semantics: ThreadedRoundSemantics,
92 /// Effect-trace capture mode for integration/perf tuning.
93 #[serde(default)]
94 pub effect_trace_capture_mode: EffectTraceCaptureMode,
95 /// Retention policy for observable and diagnostic artifacts.
96 #[serde(default)]
97 pub observability_retention: ObservabilityRetentionConfig,
98 /// Runtime payload hardening mode for inbound/outbound messages.
99 #[serde(default)]
100 pub payload_validation_mode: PayloadValidationMode,
101 /// Communication replay-consumption mode.
102 #[serde(default)]
103 pub communication_replay_mode: CommunicationReplayMode,
104 /// Upper bound for ProtocolMachine payload values in estimated wire bytes.
105 #[serde(default = "default_max_payload_bytes")]
106 pub max_payload_bytes: usize,
107 /// Runtime host-contract assertion mode with deterministic diagnostics.
108 #[serde(
109 default = "default_host_contract_assertions",
110 deserialize_with = "deserialize_host_contract_mode"
111 )]
112 pub host_contract_assertions: HostContractMode,
113}
114
115fn default_host_contract_assertions() -> HostContractMode {
116 HostContractMode::Enforced
117}
118
119impl Default for ProtocolMachineConfig {
120 fn default() -> Self {
121 Self {
122 config_schema_version: default_config_schema_version(),
123 sched_policy: SchedPolicy::Cooperative,
124 buffer_config: BufferConfig::default(),
125 max_sessions: 256,
126 max_coroutines: 1024,
127 num_registers: 16,
128 tick_duration: Duration::from_millis(1),
129 guard_layers: Vec::new(),
130 speculation_enabled: false,
131 determinism_mode: DeterminismMode::Full,
132 effect_determinism_tier: EffectDeterminismTier::StrictDeterministic,
133 output_condition_policy: OutputConditionPolicy::AllowAll,
134 monitor_mode: MonitorMode::SessionTypePrecheck,
135 flow_policy: FlowPolicy::AllowAll,
136 instruction_cost: 1,
137 initial_cost_budget: usize::MAX,
138 footprint_guided_wave_widening: false,
139 runtime_tuning_profile: RuntimeTuningProfile::Standard,
140 threaded_round_semantics: ThreadedRoundSemantics::CanonicalOneStep,
141 effect_trace_capture_mode: EffectTraceCaptureMode::Full,
142 observability_retention: ObservabilityRetentionConfig::default(),
143 payload_validation_mode: PayloadValidationMode::Structural,
144 communication_replay_mode: CommunicationReplayMode::Off,
145 max_payload_bytes: default_max_payload_bytes(),
146 host_contract_assertions: default_host_contract_assertions(),
147 }
148 }
149}
150
151impl ProtocolMachineConfig {
152 /// Validate ProtocolMachine configuration invariants required for safe state initialization.
153 ///
154 /// # Errors
155 ///
156 /// Returns a reason string if a required invariant is violated.
157 pub fn validate_invariants(&self) -> Result<(), String> {
158 if self.config_schema_version < 1 {
159 return Err("config_schema_version must be >= 1".to_string());
160 }
161 if self.max_sessions == 0 {
162 return Err("max_sessions must be > 0".to_string());
163 }
164 if self.max_coroutines == 0 {
165 return Err("max_coroutines must be > 0".to_string());
166 }
167 if self.num_registers == 0 {
168 return Err("num_registers must be > 0".to_string());
169 }
170 if self.instruction_cost == 0 {
171 return Err("instruction_cost must be > 0".to_string());
172 }
173 if self.max_payload_bytes == 0 {
174 return Err("max_payload_bytes must be > 0".to_string());
175 }
176 if self.observability_retention.mode == ObservabilityRetentionMode::Capped
177 && self.observability_retention.capacity == 0
178 {
179 return Err("observability_retention.capacity must be > 0 in capped mode".to_string());
180 }
181 if self.determinism_mode == DeterminismMode::Full
182 && self.host_contract_assertions == HostContractMode::RelaxedTestOnly
183 {
184 return Err(
185 "host_contract_assertions=relaxed_test_only is not valid with DeterminismMode::Full"
186 .to_string(),
187 );
188 }
189 Ok(())
190 }
191
192 /// Assert ProtocolMachine configuration invariants required for safe state initialization.
193 ///
194 /// # Panics
195 ///
196 /// Panics when a required invariant is violated.
197 pub fn assert_invariants(&self) {
198 if let Err(reason) = self.validate_invariants() {
199 panic!("{reason}");
200 }
201 }
202
203 /// Deterministic baseline profile with minimal retained instrumentation.
204 #[must_use]
205 pub fn strict_minimal() -> Self {
206 Self {
207 determinism_mode: DeterminismMode::Full,
208 threaded_round_semantics: ThreadedRoundSemantics::CanonicalOneStep,
209 effect_trace_capture_mode: EffectTraceCaptureMode::Disabled,
210 payload_validation_mode: PayloadValidationMode::Structural,
211 communication_replay_mode: CommunicationReplayMode::Off,
212 observability_retention: ObservabilityRetentionConfig {
213 mode: ObservabilityRetentionMode::Capped,
214 capacity: 1_024,
215 },
216 ..Self::default()
217 }
218 }
219
220 /// Deterministic profile with full observable/effect tracing enabled.
221 #[must_use]
222 pub fn strict_observable() -> Self {
223 Self {
224 effect_trace_capture_mode: EffectTraceCaptureMode::Full,
225 observability_retention: ObservabilityRetentionConfig::default(),
226 ..Self::strict_minimal()
227 }
228 }
229
230 /// Deterministic profile with strict validation and replay tracking enabled.
231 #[must_use]
232 pub fn strict_verified() -> Self {
233 Self {
234 effect_trace_capture_mode: EffectTraceCaptureMode::Full,
235 payload_validation_mode: PayloadValidationMode::StrictSchema,
236 communication_replay_mode: CommunicationReplayMode::Nullifier,
237 observability_retention: ObservabilityRetentionConfig::default(),
238 ..Self::strict_minimal()
239 }
240 }
241
242 /// Deterministic churn profile for repeated short-lived sessions.
243 #[must_use]
244 pub fn strict_churn() -> Self {
245 Self {
246 observability_retention: ObservabilityRetentionConfig {
247 mode: ObservabilityRetentionMode::Capped,
248 capacity: 256,
249 },
250 ..Self::strict_minimal()
251 }
252 }
253
254 /// Deterministic buffer-pressure profile for allocator and queue stress.
255 #[must_use]
256 pub fn strict_buffer_pressure() -> Self {
257 Self {
258 buffer_config: BufferConfig {
259 mode: crate::buffer::BufferMode::Fifo,
260 initial_capacity: 1,
261 policy: crate::buffer::BackpressurePolicy::Resize { max_capacity: 8 },
262 },
263 ..Self::strict_minimal()
264 }
265 }
266
267 /// Deterministic large-fanout profile for scheduler and metadata scaling tests.
268 #[must_use]
269 pub fn strict_large_fanout() -> Self {
270 Self {
271 observability_retention: ObservabilityRetentionConfig {
272 mode: ObservabilityRetentionMode::Capped,
273 capacity: 4_096,
274 },
275 ..Self::strict_minimal()
276 }
277 }
278}
279
280/// Observable event emitted by the ProtocolMachine.
281#[allow(dead_code)]
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub(crate) struct TickedObsEvent {
284 /// Scheduler tick when the wrapped event occurred.
285 pub tick: u64,
286 /// Underlying observable event payload.
287 pub event: ObsEvent,
288}
289
290/// Observable event emitted by the ProtocolMachine.
291#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
292pub enum SessionTerminalReason {
293 /// Session closed normally.
294 Closed {
295 /// Deterministic terminal explanation recorded in the trace.
296 reason: String,
297 },
298 /// Session cancelled through an explicit cancellation path.
299 Cancelled {
300 /// Deterministic terminal explanation recorded in the trace.
301 reason: String,
302 },
303 /// Session aborted through an explicit abort path.
304 Aborted {
305 /// Deterministic terminal explanation recorded in the trace.
306 reason: String,
307 },
308 /// Session faulted with an explicit terminal reason.
309 Faulted {
310 /// Deterministic terminal explanation recorded in the trace.
311 reason: String,
312 },
313}
314
315/// Observable event emitted by the ProtocolMachine.
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317pub enum ObsEvent {
318 /// Value sent on an edge.
319 Sent {
320 /// Scheduler tick when the event occurred.
321 tick: u64,
322 /// Session-scoped edge for this send.
323 edge: Edge,
324 /// Session ID.
325 session: SessionId,
326 /// Sender role.
327 from: String,
328 /// Receiver role.
329 to: String,
330 /// Message label.
331 label: String,
332 },
333 /// Value received on an edge.
334 Received {
335 /// Scheduler tick when the event occurred.
336 tick: u64,
337 /// Session-scoped edge for this receive.
338 edge: Edge,
339 /// Session ID.
340 session: SessionId,
341 /// Sender role.
342 from: String,
343 /// Receiver role.
344 to: String,
345 /// Message label.
346 label: String,
347 },
348 /// Label offered on an edge.
349 Offered {
350 /// Scheduler tick when the event occurred.
351 tick: u64,
352 /// Session-scoped edge for this offer.
353 edge: Edge,
354 /// Label offered.
355 label: String,
356 },
357 /// Label chosen on an edge.
358 Chose {
359 /// Scheduler tick when the event occurred.
360 tick: u64,
361 /// Session-scoped edge for this choice.
362 edge: Edge,
363 /// Label chosen.
364 label: String,
365 },
366 /// Session opened.
367 Opened {
368 /// Scheduler tick when the event occurred.
369 tick: u64,
370 /// Session ID.
371 session: SessionId,
372 /// Participating roles.
373 roles: Vec<String>,
374 },
375 /// Session closed.
376 Closed {
377 /// Scheduler tick when the event occurred.
378 tick: u64,
379 /// Session ID.
380 session: SessionId,
381 },
382 /// Explicit terminal transition for one session.
383 SessionTerminal {
384 /// Scheduler tick when the event occurred.
385 tick: u64,
386 /// Session ID.
387 session: SessionId,
388 /// Explicit terminal reason.
389 reason: SessionTerminalReason,
390 },
391 /// Session epoch advanced.
392 EpochAdvanced {
393 /// Scheduler tick when the event occurred.
394 tick: u64,
395 /// Session ID.
396 sid: SessionId,
397 /// New epoch number.
398 epoch: usize,
399 },
400 /// Coroutine halted.
401 Halted {
402 /// Scheduler tick when the event occurred.
403 tick: u64,
404 /// Coroutine ID.
405 coro_id: usize,
406 },
407 /// Effect handler invoked.
408 Invoked {
409 /// Scheduler tick when the event occurred.
410 tick: u64,
411 /// Coroutine ID.
412 coro_id: usize,
413 /// Role name.
414 role: String,
415 },
416 /// Guard layer acquired.
417 Acquired {
418 /// Scheduler tick when the event occurred.
419 tick: u64,
420 /// Session ID.
421 session: SessionId,
422 /// Role name.
423 role: String,
424 /// Guard layer identifier.
425 layer: String,
426 },
427 /// Guard layer released.
428 Released {
429 /// Scheduler tick when the event occurred.
430 tick: u64,
431 /// Session ID.
432 session: SessionId,
433 /// Role name.
434 role: String,
435 /// Guard layer identifier.
436 layer: String,
437 },
438 /// Endpoint transferred between coroutines.
439 Transferred {
440 /// Scheduler tick when the event occurred.
441 tick: u64,
442 /// Session ID.
443 session: SessionId,
444 /// Role name.
445 role: String,
446 /// Source coroutine.
447 from: usize,
448 /// Target coroutine.
449 to: usize,
450 },
451 /// Speculation forked for a ghost session.
452 Forked {
453 /// Scheduler tick when the event occurred.
454 tick: u64,
455 /// Session ID.
456 session: SessionId,
457 /// Ghost session id.
458 ghost: usize,
459 },
460 /// Speculation joined.
461 Joined {
462 /// Scheduler tick when the event occurred.
463 tick: u64,
464 /// Session ID.
465 session: SessionId,
466 },
467 /// Speculation aborted.
468 Aborted {
469 /// Scheduler tick when the event occurred.
470 tick: u64,
471 /// Session ID.
472 session: SessionId,
473 },
474 /// Knowledge fact tagged.
475 Tagged {
476 /// Scheduler tick when the event occurred.
477 tick: u64,
478 /// Session ID.
479 session: SessionId,
480 /// Role name.
481 role: String,
482 /// Fact payload.
483 fact: String,
484 },
485 /// Knowledge fact checked.
486 Checked {
487 /// Scheduler tick when the event occurred.
488 tick: u64,
489 /// Session ID.
490 session: SessionId,
491 /// Role name.
492 role: String,
493 /// Target role.
494 target: String,
495 /// Whether the flow policy permitted the fact.
496 permitted: bool,
497 },
498 /// Coroutine faulted.
499 Faulted {
500 /// Scheduler tick when the event occurred.
501 tick: u64,
502 /// Coroutine ID.
503 coro_id: usize,
504 /// The fault.
505 fault: Fault,
506 },
507 /// Typed failure branch entry became visible before terminal fault handling.
508 FailureBranchEntered {
509 /// Scheduler tick when the event occurred.
510 tick: u64,
511 /// Session ID.
512 session: SessionId,
513 /// Coroutine ID.
514 coro_id: usize,
515 /// Failure that entered the branch.
516 fault: Fault,
517 },
518 /// Explicit timeout occurrence became active for one site.
519 TimeoutIssued {
520 /// Scheduler tick when the event occurred.
521 tick: u64,
522 /// Site that timed out.
523 site: String,
524 /// Tick until which the timeout remains active.
525 until_tick: u64,
526 /// Timeout witness issued for the occurrence.
527 witness_id: AuthorityWitnessId,
528 },
529 /// Explicit cancellation path was requested.
530 CancellationRequested {
531 /// Scheduler tick when the event occurred.
532 tick: u64,
533 /// Session ID.
534 session: SessionId,
535 /// Cancellation witness issued for the request.
536 witness_id: AuthorityWitnessId,
537 /// Owner whose lifecycle triggered the cancellation.
538 owner_id: FragmentOwnerId,
539 /// Ownership reason for the cancellation request.
540 reason: OwnershipTerminalReason,
541 },
542 /// Explicit cancellation path completed.
543 Cancelled {
544 /// Scheduler tick when the event occurred.
545 tick: u64,
546 /// Session ID.
547 session: SessionId,
548 /// Cancellation witness used for the completion.
549 witness_id: AuthorityWitnessId,
550 /// Ownership reason for the completed cancellation.
551 reason: OwnershipTerminalReason,
552 },
553 /// Output-condition verification was evaluated at commit time.
554 OutputConditionChecked {
555 /// Scheduler tick when the event occurred.
556 tick: u64,
557 /// Predicate reference that was checked.
558 predicate_ref: String,
559 /// Optional witness reference used by the check.
560 witness_ref: Option<String>,
561 /// Opaque output digest checked by the verifier.
562 output_digest: String,
563 /// Verification outcome.
564 passed: bool,
565 },
566}
567
568/// The ProtocolMachine execution result for a single step.
569#[derive(Debug)]
570pub enum StepResult {
571 /// A coroutine executed an instruction and may continue.
572 Continue,
573 /// No coroutines are ready (all blocked or done).
574 Stuck,
575 /// All coroutines have completed.
576 AllDone,
577}
578
579/// Terminal status returned by bounded ProtocolMachine run APIs.
580#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
581pub enum RunStatus {
582 /// All coroutines reached terminal states.
583 AllDone,
584 /// No runnable coroutines remain (blocked/stuck).
585 Stuck,
586 /// `max_rounds`/`max_steps` budget was exhausted before termination.
587 MaxRoundsExceeded,
588}
589
590/// Debug metadata for the most recent scheduler-dispatched step.
591#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
592pub enum SchedExecStatus {
593 /// Instruction continued execution.
594 Continue,
595 /// Instruction yielded cooperative control.
596 Yielded,
597 /// Instruction blocked.
598 Blocked,
599 /// Coroutine halted normally.
600 Halted,
601 /// Coroutine faulted.
602 Faulted,
603}
604
605/// Debug metadata for the most recent scheduler-dispatched step.
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
607pub struct SchedStepDebug {
608 /// Selected coroutine id.
609 pub selected_coro: usize,
610 /// Instruction-step execution status.
611 pub exec_status: SchedExecStatus,
612}