Skip to main content

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}