Skip to main content

telltale_machine/engine/runtime_state/
policy.rs

1/// Information-flow policy used by epistemic `check`.
2pub enum FlowPolicy {
3    /// Permit all facts to all roles.
4    AllowAll,
5    /// Deny all flows.
6    DenyAll,
7    /// Permit only listed roles.
8    AllowRoles(BTreeSet<String>),
9    /// Deny listed roles.
10    DenyRoles(BTreeSet<String>),
11    /// Runtime closure policy:
12    /// `Predicate(Box<dyn Fn(&Knowledge, &Role) -> bool>)`.
13    Predicate(Box<dyn FlowPolicyFn>),
14    /// Serializable knowledge-dependent predicate policy.
15    PredicateExpr(FlowPredicate),
16}
17
18/// Cloneable dynamic predicate for runtime flow checks.
19pub trait FlowPolicyFn: Send + Sync {
20    /// Evaluate whether a fact may flow to a target role.
21    fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool;
22    /// Clone trait-object predicate.
23    fn clone_box(&self) -> Box<dyn FlowPolicyFn>;
24}
25
26impl<F> FlowPolicyFn for F
27where
28    F: Fn(&KnowledgeFact, &str) -> bool + Clone + Send + Sync + 'static,
29{
30    fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
31        self(knowledge, target_role)
32    }
33
34    fn clone_box(&self) -> Box<dyn FlowPolicyFn> {
35        Box::new(self.clone())
36    }
37}
38
39impl Clone for Box<dyn FlowPolicyFn> {
40    fn clone(&self) -> Self {
41        self.clone_box()
42    }
43}
44
45#[allow(clippy::derivable_impls)]
46impl Default for FlowPolicy {
47    fn default() -> Self {
48        Self::AllowAll
49    }
50}
51
52impl Clone for FlowPolicy {
53    fn clone(&self) -> Self {
54        match self {
55            Self::AllowAll => Self::AllowAll,
56            Self::DenyAll => Self::DenyAll,
57            Self::AllowRoles(roles) => Self::AllowRoles(roles.clone()),
58            Self::DenyRoles(roles) => Self::DenyRoles(roles.clone()),
59            Self::Predicate(predicate) => Self::Predicate(predicate.clone()),
60            Self::PredicateExpr(predicate) => Self::PredicateExpr(predicate.clone()),
61        }
62    }
63}
64
65impl fmt::Debug for FlowPolicy {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::AllowAll => f.write_str("AllowAll"),
69            Self::DenyAll => f.write_str("DenyAll"),
70            Self::AllowRoles(roles) => f.debug_tuple("AllowRoles").field(roles).finish(),
71            Self::DenyRoles(roles) => f.debug_tuple("DenyRoles").field(roles).finish(),
72            Self::Predicate(_) => f.write_str("Predicate(<dynamic>)"),
73            Self::PredicateExpr(predicate) => {
74                f.debug_tuple("PredicateExpr").field(predicate).finish()
75            }
76        }
77    }
78}
79
80impl PartialEq for FlowPolicy {
81    fn eq(&self, other: &Self) -> bool {
82        match (self, other) {
83            (Self::AllowAll, Self::AllowAll) => true,
84            (Self::DenyAll, Self::DenyAll) => true,
85            (Self::AllowRoles(lhs), Self::AllowRoles(rhs)) => lhs == rhs,
86            (Self::DenyRoles(lhs), Self::DenyRoles(rhs)) => lhs == rhs,
87            (Self::Predicate(lhs), Self::Predicate(rhs)) => {
88                // Dynamic closure policies cannot be value-compared.
89                // Equality is identity-based: true only when both variants
90                // point to the exact same trait object instance.
91                std::ptr::eq::<dyn FlowPolicyFn>(&**lhs, &**rhs)
92            }
93            (Self::PredicateExpr(lhs), Self::PredicateExpr(rhs)) => lhs == rhs,
94            _ => false,
95        }
96    }
97}
98
99impl Eq for FlowPolicy {}
100
101#[derive(Serialize, Deserialize)]
102enum FlowPolicyRepr {
103    AllowAll,
104    DenyAll,
105    AllowRoles(BTreeSet<String>),
106    DenyRoles(BTreeSet<String>),
107    PredicateExpr(FlowPredicate),
108}
109
110impl Serialize for FlowPolicy {
111    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
112    where
113        S: Serializer,
114    {
115        let repr = match self {
116            Self::AllowAll => FlowPolicyRepr::AllowAll,
117            Self::DenyAll => FlowPolicyRepr::DenyAll,
118            Self::AllowRoles(roles) => FlowPolicyRepr::AllowRoles(roles.clone()),
119            Self::DenyRoles(roles) => FlowPolicyRepr::DenyRoles(roles.clone()),
120            Self::PredicateExpr(predicate) => FlowPolicyRepr::PredicateExpr(predicate.clone()),
121            Self::Predicate(_) => {
122                return Err(serde::ser::Error::custom(
123                    "runtime closure predicate is not serializable",
124                ))
125            }
126        };
127        repr.serialize(serializer)
128    }
129}
130
131impl<'de> Deserialize<'de> for FlowPolicy {
132    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133    where
134        D: Deserializer<'de>,
135    {
136        let repr = FlowPolicyRepr::deserialize(deserializer)?;
137        let policy = match repr {
138            FlowPolicyRepr::AllowAll => Self::AllowAll,
139            FlowPolicyRepr::DenyAll => Self::DenyAll,
140            FlowPolicyRepr::AllowRoles(roles) => Self::AllowRoles(roles),
141            FlowPolicyRepr::DenyRoles(roles) => Self::DenyRoles(roles),
142            FlowPolicyRepr::PredicateExpr(predicate) => Self::PredicateExpr(predicate),
143        };
144        Ok(policy)
145    }
146}
147
148impl FlowPolicy {
149    /// Build a runtime closure-based flow predicate policy.
150    #[must_use]
151    pub fn predicate<F>(predicate: F) -> Self
152    where
153        F: Fn(&KnowledgeFact, &str) -> bool + Clone + Send + Sync + 'static,
154    {
155        Self::Predicate(Box::new(predicate))
156    }
157}
158
159/// Serializable flow-policy predicate over known fact + destination.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub enum FlowPredicate {
162    /// Allow when destination role starts with prefix.
163    TargetRolePrefix(String),
164    /// Allow when fact contains substring.
165    FactContains(String),
166    /// Allow when the fact endpoint role equals destination role.
167    EndpointRoleMatchesTarget,
168    /// Conjunction.
169    All(Vec<FlowPredicate>),
170    /// Disjunction.
171    Any(Vec<FlowPredicate>),
172}
173
174impl FlowPolicy {
175    /// Check whether knowledge flow to `target_role` is permitted.
176    #[must_use]
177    pub fn allows(&self, target_role: &str) -> bool {
178        match self {
179            Self::AllowAll => true,
180            Self::DenyAll => false,
181            Self::AllowRoles(roles) => roles.contains(target_role),
182            Self::DenyRoles(roles) => !roles.contains(target_role),
183            Self::Predicate(_) | Self::PredicateExpr(_) => true,
184        }
185    }
186
187    /// Check whether a concrete knowledge fact may flow to a target role.
188    #[must_use]
189    pub fn allows_knowledge(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
190        match self {
191            Self::Predicate(predicate) => predicate.eval(knowledge, target_role),
192            Self::PredicateExpr(predicate) => predicate.eval(knowledge, target_role),
193            other => other.allows(target_role),
194        }
195    }
196}
197
198impl FlowPredicate {
199    /// Evaluate this serialized predicate against one fact and target role.
200    #[must_use]
201    pub fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
202        match self {
203            Self::TargetRolePrefix(prefix) => target_role.starts_with(prefix),
204            Self::FactContains(fragment) => knowledge.fact.contains(fragment),
205            Self::EndpointRoleMatchesTarget => knowledge.endpoint.role == target_role,
206            Self::All(predicates) => predicates
207                .iter()
208                .all(|predicate| predicate.eval(knowledge, target_role)),
209            Self::Any(predicates) => predicates
210                .iter()
211                .any(|predicate| predicate.eval(knowledge, target_role)),
212        }
213    }
214}
215
216/// Typed runtime tuning profile for benchmark/runtime configuration harmonization.
217#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
218#[serde(rename_all = "snake_case")]
219pub enum RuntimeTuningProfile {
220    /// Default production-like tuning.
221    #[default]
222    Standard,
223    /// Reference profile approximating early M1 stress behavior.
224    M1StressReference,
225}
226
227/// Threaded scheduler round semantics mode.
228#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
229#[serde(rename_all = "snake_case")]
230pub enum ThreadedRoundSemantics {
231    /// Canonical one-step semantics aligned with Lean runner rounds.
232    #[default]
233    CanonicalOneStep,
234    /// Performance extension: multi-pick waves within one round.
235    WaveParallelExtension,
236}
237
238/// Effect-trace capture mode for runtime overhead control.
239#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
240#[serde(rename_all = "snake_case")]
241pub enum EffectTraceCaptureMode {
242    /// Record all canonical effect kinds.
243    #[default]
244    Full,
245    /// Record only topology ingress events.
246    TopologyOnly,
247    /// Disable effect-trace recording.
248    Disabled,
249}
250
251/// Retention policy for stored observability artifacts.
252#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
253#[serde(rename_all = "snake_case")]
254pub enum ObservabilityRetentionMode {
255    /// Retain all recorded artifacts.
256    #[default]
257    Full,
258    /// Retain only the most recent `capacity` artifacts per stream.
259    Capped,
260    /// Disable retention entirely while preserving runtime behavior.
261    Disabled,
262}
263
264/// Retention configuration for observability artifacts.
265#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
266pub struct ObservabilityRetentionConfig {
267    /// Retention policy for observability artifacts.
268    #[serde(default)]
269    pub mode: ObservabilityRetentionMode,
270    /// Maximum retained items per stream when `mode = capped`.
271    #[serde(default = "default_observability_retention_capacity")]
272    pub capacity: usize,
273}
274
275const fn default_observability_retention_capacity() -> usize {
276    4_096
277}
278
279impl Default for ObservabilityRetentionConfig {
280    fn default() -> Self {
281        Self {
282            mode: ObservabilityRetentionMode::Full,
283            capacity: default_observability_retention_capacity(),
284        }
285    }
286}
287
288/// Payload validation mode for runtime message hardening.
289#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
290#[serde(rename_all = "snake_case")]
291pub enum PayloadValidationMode {
292    /// Disable ProtocolMachine-side payload validation checks.
293    Off,
294    /// Validate payload size and annotated `ValType` compatibility.
295    #[default]
296    Structural,
297    /// Structural checks plus strict annotation requirement for `Send` and `Receive`.
298    StrictSchema,
299}