telltale_runtime/effects/
contract.rs1use thiserror::Error;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum HandlerContractTier {
18 FullProtocol,
20 ObservationalHarness,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum DeliveryModel {
27 InMemoryChannels,
28 ScriptedHarness,
29 SessionBoundary,
30 NoTransport,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum RetryPolicy {
36 None,
37 ExternalMiddleware,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum TimeoutPolicy {
43 EnforcingRoleOnly,
44 PassThrough,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ExtensionDispatchMode {
50 Unsupported,
51 RegisteredOnlyTypeExact,
52}
53
54#[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#[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#[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#[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#[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
113pub trait DocumentedHandlerContract {
115 fn contract_profile() -> HandlerContractProfile
116 where
117 Self: Sized;
118}
119
120pub 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
164pub 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}