Skip to main content

telltale_runtime/effects/
contract.rs

1//! Machine-checkable handler contract ledger for the algebraic-effects boundary.
2//!
3//! This module is the contract document for `ChoreoHandler` and runtime extension
4//! dispatch. It separates:
5//!
6//! - protocol semantics that every conforming handler must preserve
7//! - transport policy choices that handlers are still free to choose
8//!
9//! The contract is intentionally narrow. It does not try to prove arbitrary
10//! handlers correct. Instead, it gives every important runtime handler a
11//! structured profile that tests and CI can validate mechanically.
12
13use thiserror::Error;
14
15/// Broad classification of what kind of behavioral surface a handler exposes.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum HandlerContractTier {
18    /// A handler that can execute the full typed protocol surface.
19    FullProtocol,
20    /// A harness that is intentionally observational or partial.
21    ObservationalHarness,
22}
23
24/// Delivery substrate chosen by a handler implementation.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum DeliveryModel {
27    InMemoryChannels,
28    ScriptedHarness,
29    SessionBoundary,
30    NoTransport,
31}
32
33/// How retries are introduced.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum RetryPolicy {
36    None,
37    ExternalMiddleware,
38}
39
40/// How timeouts are enforced.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum TimeoutPolicy {
43    EnforcingRoleOnly,
44    PassThrough,
45}
46
47/// Extension dispatch support model.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ExtensionDispatchMode {
50    Unsupported,
51    RegisteredOnlyTypeExact,
52}
53
54/// Semantic obligations of a `ChoreoHandler`.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ProtocolSemanticContract {
57    pub typed_send_recv_roundtrip: bool,
58    pub exact_choice_label_preservation: bool,
59    pub fail_closed_transport_errors: bool,
60    pub timeouts_scoped_to_enforcing_role: bool,
61    pub deterministic_for_regression: bool,
62    pub can_materialize_values: bool,
63}
64
65/// Transport-policy choices a handler is allowed to vary.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct TransportPolicyContract {
68    pub delivery_model: DeliveryModel,
69    pub retry_policy: RetryPolicy,
70    pub timeout_policy: TimeoutPolicy,
71}
72
73/// Contract for runtime extension dispatch.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ExtensionDispatchContract {
76    pub mode: ExtensionDispatchMode,
77    pub fail_closed_when_unregistered: bool,
78    pub type_exact_before_side_effects: bool,
79}
80
81/// Complete machine-checkable profile for a handler family.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct HandlerContractProfile {
84    pub handler_name: &'static str,
85    pub tier: HandlerContractTier,
86    pub semantics: ProtocolSemanticContract,
87    pub transport: TransportPolicyContract,
88    pub extension_dispatch: ExtensionDispatchContract,
89    pub notes: Vec<&'static str>,
90}
91
92/// Contract profile must be internally consistent before tests can trust it.
93#[derive(Debug, Error, PartialEq, Eq)]
94pub enum HandlerContractViolation {
95    #[error("full-protocol handlers must preserve typed send/recv round trips")]
96    MissingTypedRoundtrip,
97    #[error("full-protocol handlers must preserve exact choice labels")]
98    MissingChoicePreservation,
99    #[error("full-protocol handlers must fail closed on transport/dispatch errors")]
100    MissingFailClosedErrors,
101    #[error("full-protocol handlers must be able to materialize values")]
102    MissingValueMaterialization,
103    #[error("timeout policy says enforcing-role only, but semantics do not")]
104    TimeoutPolicyMismatch,
105    #[error("observational harnesses must be deterministic for regression use")]
106    NonDeterministicHarness,
107    #[error("registered extension dispatch must fail closed when unregistered")]
108    NonFailClosedExtensionDispatch,
109    #[error("registered extension dispatch must validate type before side effects")]
110    NonExactExtensionDispatch,
111}
112
113/// Trait implemented by handlers that document their runtime contract.
114pub trait DocumentedHandlerContract {
115    fn contract_profile() -> HandlerContractProfile
116    where
117        Self: Sized;
118}
119
120/// Validate that a documented profile is self-consistent.
121pub fn validate_handler_contract_profile(
122    profile: &HandlerContractProfile,
123) -> Result<(), HandlerContractViolation> {
124    if profile.transport.timeout_policy == TimeoutPolicy::EnforcingRoleOnly
125        && !profile.semantics.timeouts_scoped_to_enforcing_role
126    {
127        return Err(HandlerContractViolation::TimeoutPolicyMismatch);
128    }
129
130    match profile.tier {
131        HandlerContractTier::FullProtocol => {
132            if !profile.semantics.typed_send_recv_roundtrip {
133                return Err(HandlerContractViolation::MissingTypedRoundtrip);
134            }
135            if !profile.semantics.exact_choice_label_preservation {
136                return Err(HandlerContractViolation::MissingChoicePreservation);
137            }
138            if !profile.semantics.fail_closed_transport_errors {
139                return Err(HandlerContractViolation::MissingFailClosedErrors);
140            }
141            if !profile.semantics.can_materialize_values {
142                return Err(HandlerContractViolation::MissingValueMaterialization);
143            }
144        }
145        HandlerContractTier::ObservationalHarness => {
146            if !profile.semantics.deterministic_for_regression {
147                return Err(HandlerContractViolation::NonDeterministicHarness);
148            }
149        }
150    }
151
152    if profile.extension_dispatch.mode == ExtensionDispatchMode::RegisteredOnlyTypeExact {
153        if !profile.extension_dispatch.fail_closed_when_unregistered {
154            return Err(HandlerContractViolation::NonFailClosedExtensionDispatch);
155        }
156        if !profile.extension_dispatch.type_exact_before_side_effects {
157            return Err(HandlerContractViolation::NonExactExtensionDispatch);
158        }
159    }
160
161    Ok(())
162}
163
164/// Convenience helper for compile-time discoverable handler profile checks.
165pub fn validated_contract_profile<H>() -> Result<HandlerContractProfile, HandlerContractViolation>
166where
167    H: DocumentedHandlerContract,
168{
169    let profile = H::contract_profile();
170    validate_handler_contract_profile(&profile)?;
171    Ok(profile)
172}