Skip to main content

telltale_vm/vm/
runtime_value_and_resource_state.rs

1fn default_instruction_cost() -> usize {
2    1
3}
4
5fn default_initial_cost_budget() -> usize {
6    usize::MAX
7}
8
9fn default_config_schema_version() -> u32 {
10    1
11}
12
13fn default_max_payload_bytes() -> usize {
14    64 * 1024
15}
16
17/// Lean-aligned scope identifier placeholder.
18pub type ScopeId = usize;
19
20/// Lean-aligned program representation.
21pub type Program = Vec<Instr>;
22
23/// Branch list type used in local types.
24type BranchList = Vec<(
25    telltale_types::Label,
26    Option<telltale_types::ValType>,
27    LocalTypeR,
28)>;
29
30// RECURSION_SAFE: structural recursion over a finite runtime value tree.
31pub(crate) fn runtime_value_val_type(value: &Value) -> ValType {
32    match value {
33        Value::Unit => ValType::Unit,
34        Value::Nat(_) => ValType::Nat,
35        Value::Bool(_) => ValType::Bool,
36        Value::Str(_) => ValType::String,
37        Value::Prod(left, right) => ValType::Prod(
38            Box::new(runtime_value_val_type(left)),
39            Box::new(runtime_value_val_type(right)),
40        ),
41        Value::Endpoint(endpoint) => ValType::Chan {
42            sid: endpoint.sid,
43            role: endpoint.role.clone(),
44        },
45    }
46}
47
48// RECURSION_SAFE: structural recursion over a finite runtime value tree.
49pub(crate) fn runtime_value_wire_size_bytes(value: &Value) -> usize {
50    match value {
51        Value::Unit => 1,
52        Value::Nat(_) => 8,
53        Value::Bool(_) => 1,
54        Value::Str(text) => 8_usize.saturating_add(text.len()),
55        Value::Prod(left, right) => 1_usize
56            .saturating_add(runtime_value_wire_size_bytes(left))
57            .saturating_add(runtime_value_wire_size_bytes(right)),
58        Value::Endpoint(endpoint) => 8_usize
59            .saturating_add(8_usize)
60            .saturating_add(endpoint.role.len()),
61    }
62}
63
64// RECURSION_SAFE: structural recursion over finite value/type trees.
65pub(crate) fn runtime_value_matches_val_type(value: &Value, expected: &ValType) -> bool {
66    match (value, expected) {
67        (Value::Unit, ValType::Unit) => true,
68        (Value::Nat(_), ValType::Nat) => true,
69        (Value::Bool(_), ValType::Bool) => true,
70        (Value::Str(_), ValType::String) => true,
71        (Value::Prod(left, right), ValType::Prod(expected_left, expected_right)) => {
72            runtime_value_matches_val_type(left, expected_left)
73                && runtime_value_matches_val_type(right, expected_right)
74        }
75        (Value::Endpoint(endpoint), ValType::Chan { sid, role }) => {
76            endpoint.sid == *sid && endpoint.role == *role
77        }
78        _ => false,
79    }
80}
81
82/// Lean-aligned resource state with commitments and nullifiers.
83#[derive(Debug, Clone, Serialize, Deserialize, Default)]
84pub struct ResourceState {
85    commitments: BTreeSet<crate::verification::Commitment>,
86    nullifiers: BTreeSet<crate::verification::Nullifier>,
87}
88
89impl ResourceState {
90    /// Record a commitment for a value and return the commitment digest.
91    #[must_use]
92    pub fn commit(&mut self, value: &Value) -> crate::verification::Commitment {
93        let commitment = crate::verification::DefaultVerificationModel::commitment(value);
94        self.commitments.insert(commitment);
95        commitment
96    }
97
98    /// Consume a value by inserting its nullifier.
99    ///
100    /// # Errors
101    ///
102    /// Returns an error when the value has already been consumed.
103    pub fn consume(&mut self, value: &Value) -> Result<crate::verification::Nullifier, String> {
104        let nullifier = crate::verification::DefaultVerificationModel::nullifier(value);
105        if self.nullifiers.contains(&nullifier) {
106            return Err("resource already consumed".to_string());
107        }
108        self.nullifiers.insert(nullifier);
109        Ok(nullifier)
110    }
111
112    /// Check whether a value has not yet been consumed.
113    #[must_use]
114    pub fn verify_uncommitted(&self, value: &Value) -> bool {
115        let nullifier = crate::verification::DefaultVerificationModel::nullifier(value);
116        !self.nullifiers.contains(&nullifier)
117    }
118}
119
120/// Runtime arena with slot reuse.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct Arena {
123    slots: Vec<Option<Value>>,
124    next_free: usize,
125    capacity: usize,
126}
127
128impl Default for Arena {
129    fn default() -> Self {
130        Self::new(128)
131    }
132}
133
134impl Arena {
135    /// Construct an arena with the given slot capacity.
136    #[must_use]
137    pub fn new(capacity: usize) -> Self {
138        let cap = capacity.max(1);
139        Self {
140            slots: vec![None; cap],
141            next_free: 0,
142            capacity: cap,
143        }
144    }
145
146    /// Allocate one slot and return its index.
147    ///
148    /// # Errors
149    ///
150    /// Returns an error when no free slot is available.
151    pub fn alloc(&mut self, value: Value) -> Result<usize, String> {
152        for offset in 0..self.capacity {
153            let idx = (self.next_free + offset) % self.capacity;
154            if self.slots[idx].is_none() {
155                self.slots[idx] = Some(value);
156                self.next_free = (idx + 1) % self.capacity;
157                debug_assert!(self.check_invariants());
158                return Ok(idx);
159            }
160        }
161        Err("arena full".to_string())
162    }
163
164    /// Free one occupied slot and return its value.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error if the index is invalid or the slot is already free.
169    pub fn free(&mut self, idx: usize) -> Result<Value, String> {
170        if idx >= self.capacity {
171            return Err("arena index out of bounds".to_string());
172        }
173        let value = self.slots[idx]
174            .take()
175            .ok_or_else(|| "arena slot already free".to_string())?;
176        if idx < self.next_free {
177            self.next_free = idx;
178        }
179        debug_assert!(self.check_invariants());
180        Ok(value)
181    }
182
183    /// Borrow a value in a slot by index.
184    #[must_use]
185    pub fn get(&self, idx: usize) -> Option<&Value> {
186        self.slots.get(idx).and_then(Option::as_ref)
187    }
188
189    /// Mutably borrow a value in a slot by index.
190    pub fn get_mut(&mut self, idx: usize) -> Option<&mut Value> {
191        self.slots.get_mut(idx).and_then(Option::as_mut)
192    }
193
194    /// Validate arena structural invariants.
195    #[must_use]
196    pub fn check_invariants(&self) -> bool {
197        self.slots.len() == self.capacity && self.next_free < self.capacity
198    }
199}
200
201/// Session kind monitored at runtime.
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203pub enum SessionKind {
204    /// Endpoint is acting as a client.
205    Client,
206    /// Endpoint is acting as a server.
207    Server,
208    /// Endpoint is acting as a peer.
209    Peer,
210}
211
212/// Runtime judgment for one monitor check.
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct WellTypedInstr {
215    /// Endpoint checked by the monitor.
216    pub endpoint: Endpoint,
217    /// Instruction tag emitted for this check.
218    pub instr_tag: String,
219    /// Tick at which the monitor check occurred.
220    pub tick: u64,
221}
222
223/// Runtime monitor state for session checks.
224#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225pub struct SessionMonitor {
226    session_kinds: BTreeMap<SessionId, SessionKind>,
227    last_judgment: Option<WellTypedInstr>,
228}
229
230impl SessionMonitor {
231    /// Set the session kind for one session id.
232    pub fn set_kind(&mut self, sid: SessionId, kind: SessionKind) {
233        self.session_kinds.insert(sid, kind);
234    }
235
236    /// Remove tracked kind metadata for a session id.
237    pub fn remove_kind(&mut self, sid: SessionId) {
238        self.session_kinds.remove(&sid);
239    }
240
241    /// Record the most recent monitor judgment.
242    pub fn record(&mut self, endpoint: &Endpoint, instr_tag: &str, tick: u64) {
243        self.last_judgment = Some(WellTypedInstr {
244            endpoint: endpoint.clone(),
245            instr_tag: instr_tag.to_string(),
246            tick,
247        });
248    }
249}
250
251/// Lean-aligned site identifier for failure topology state.
252pub type SiteId = String;
253
254/// Active corruption policy for one directed edge.
255#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
256pub struct CorruptedEdge {
257    edge: Edge,
258    corruption: CorruptionType,
259}
260
261/// Active timeout window for one site.
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
263pub struct SiteTimeout {
264    site: SiteId,
265    until_tick: u64,
266}
267
268/// Guard layer configuration.
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct GuardLayerConfig {
271    /// Guard layer identifier.
272    pub id: String,
273    /// Whether the layer is active.
274    pub active: bool,
275}
276
277/// Instruction monitor mode for pre-dispatch checks.
278#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
279pub enum MonitorMode {
280    /// Disable monitor precheck at dispatch.
281    Off,
282    /// Perform session-type-shape monitor precheck before stepping.
283    #[default]
284    SessionTypePrecheck,
285}
286
287/// Information-flow policy used by epistemic `check`.
288pub enum FlowPolicy {
289    /// Permit all facts to all roles.
290    AllowAll,
291    /// Deny all flows.
292    DenyAll,
293    /// Permit only listed roles.
294    AllowRoles(BTreeSet<String>),
295    /// Deny listed roles.
296    DenyRoles(BTreeSet<String>),
297    /// Runtime closure policy:
298    /// `Predicate(Box<dyn Fn(&Knowledge, &Role) -> bool>)`.
299    Predicate(Box<dyn FlowPolicyFn>),
300    /// Serializable knowledge-dependent predicate policy.
301    PredicateExpr(FlowPredicate),
302}
303
304/// Cloneable dynamic predicate for runtime flow checks.
305pub trait FlowPolicyFn: Send + Sync {
306    /// Evaluate whether a fact may flow to a target role.
307    fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool;
308    /// Clone trait-object predicate.
309    fn clone_box(&self) -> Box<dyn FlowPolicyFn>;
310}
311
312impl<F> FlowPolicyFn for F
313where
314    F: Fn(&KnowledgeFact, &str) -> bool + Clone + Send + Sync + 'static,
315{
316    fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
317        self(knowledge, target_role)
318    }
319
320    fn clone_box(&self) -> Box<dyn FlowPolicyFn> {
321        Box::new(self.clone())
322    }
323}
324
325impl Clone for Box<dyn FlowPolicyFn> {
326    fn clone(&self) -> Self {
327        self.clone_box()
328    }
329}
330
331#[allow(clippy::derivable_impls)]
332impl Default for FlowPolicy {
333    fn default() -> Self {
334        Self::AllowAll
335    }
336}
337
338impl Clone for FlowPolicy {
339    fn clone(&self) -> Self {
340        match self {
341            Self::AllowAll => Self::AllowAll,
342            Self::DenyAll => Self::DenyAll,
343            Self::AllowRoles(roles) => Self::AllowRoles(roles.clone()),
344            Self::DenyRoles(roles) => Self::DenyRoles(roles.clone()),
345            Self::Predicate(predicate) => Self::Predicate(predicate.clone()),
346            Self::PredicateExpr(predicate) => Self::PredicateExpr(predicate.clone()),
347        }
348    }
349}
350
351impl fmt::Debug for FlowPolicy {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        match self {
354            Self::AllowAll => f.write_str("AllowAll"),
355            Self::DenyAll => f.write_str("DenyAll"),
356            Self::AllowRoles(roles) => f.debug_tuple("AllowRoles").field(roles).finish(),
357            Self::DenyRoles(roles) => f.debug_tuple("DenyRoles").field(roles).finish(),
358            Self::Predicate(_) => f.write_str("Predicate(<dynamic>)"),
359            Self::PredicateExpr(predicate) => {
360                f.debug_tuple("PredicateExpr").field(predicate).finish()
361            }
362        }
363    }
364}
365
366impl PartialEq for FlowPolicy {
367    fn eq(&self, other: &Self) -> bool {
368        match (self, other) {
369            (Self::AllowAll, Self::AllowAll) => true,
370            (Self::DenyAll, Self::DenyAll) => true,
371            (Self::AllowRoles(lhs), Self::AllowRoles(rhs)) => lhs == rhs,
372            (Self::DenyRoles(lhs), Self::DenyRoles(rhs)) => lhs == rhs,
373            (Self::Predicate(lhs), Self::Predicate(rhs)) => {
374                // Dynamic closure policies cannot be value-compared.
375                // Equality is identity-based: true only when both variants
376                // point to the exact same trait object instance.
377                std::ptr::eq::<dyn FlowPolicyFn>(&**lhs, &**rhs)
378            }
379            (Self::PredicateExpr(lhs), Self::PredicateExpr(rhs)) => lhs == rhs,
380            _ => false,
381        }
382    }
383}
384
385impl Eq for FlowPolicy {}
386
387#[derive(Serialize, Deserialize)]
388enum FlowPolicyRepr {
389    AllowAll,
390    DenyAll,
391    AllowRoles(BTreeSet<String>),
392    DenyRoles(BTreeSet<String>),
393    PredicateExpr(FlowPredicate),
394}
395
396impl Serialize for FlowPolicy {
397    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
398    where
399        S: Serializer,
400    {
401        let repr = match self {
402            Self::AllowAll => FlowPolicyRepr::AllowAll,
403            Self::DenyAll => FlowPolicyRepr::DenyAll,
404            Self::AllowRoles(roles) => FlowPolicyRepr::AllowRoles(roles.clone()),
405            Self::DenyRoles(roles) => FlowPolicyRepr::DenyRoles(roles.clone()),
406            Self::PredicateExpr(predicate) => FlowPolicyRepr::PredicateExpr(predicate.clone()),
407            Self::Predicate(_) => {
408                return Err(serde::ser::Error::custom(
409                    "runtime closure predicate is not serializable",
410                ))
411            }
412        };
413        repr.serialize(serializer)
414    }
415}
416
417impl<'de> Deserialize<'de> for FlowPolicy {
418    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
419    where
420        D: Deserializer<'de>,
421    {
422        let repr = FlowPolicyRepr::deserialize(deserializer)?;
423        let policy = match repr {
424            FlowPolicyRepr::AllowAll => Self::AllowAll,
425            FlowPolicyRepr::DenyAll => Self::DenyAll,
426            FlowPolicyRepr::AllowRoles(roles) => Self::AllowRoles(roles),
427            FlowPolicyRepr::DenyRoles(roles) => Self::DenyRoles(roles),
428            FlowPolicyRepr::PredicateExpr(predicate) => Self::PredicateExpr(predicate),
429        };
430        Ok(policy)
431    }
432}
433
434impl FlowPolicy {
435    /// Build a runtime closure-based flow predicate policy.
436    #[must_use]
437    pub fn predicate<F>(predicate: F) -> Self
438    where
439        F: Fn(&KnowledgeFact, &str) -> bool + Clone + Send + Sync + 'static,
440    {
441        Self::Predicate(Box::new(predicate))
442    }
443}
444
445/// Serializable flow-policy predicate over known fact + destination.
446#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
447pub enum FlowPredicate {
448    /// Allow when destination role starts with prefix.
449    TargetRolePrefix(String),
450    /// Allow when fact contains substring.
451    FactContains(String),
452    /// Allow when the fact endpoint role equals destination role.
453    EndpointRoleMatchesTarget,
454    /// Conjunction.
455    All(Vec<FlowPredicate>),
456    /// Disjunction.
457    Any(Vec<FlowPredicate>),
458}
459
460impl FlowPolicy {
461    /// Check whether knowledge flow to `target_role` is permitted.
462    #[must_use]
463    pub fn allows(&self, target_role: &str) -> bool {
464        match self {
465            Self::AllowAll => true,
466            Self::DenyAll => false,
467            Self::AllowRoles(roles) => roles.contains(target_role),
468            Self::DenyRoles(roles) => !roles.contains(target_role),
469            Self::Predicate(_) | Self::PredicateExpr(_) => true,
470        }
471    }
472
473    /// Check whether a concrete knowledge fact may flow to a target role.
474    #[must_use]
475    pub fn allows_knowledge(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
476        match self {
477            Self::Predicate(predicate) => predicate.eval(knowledge, target_role),
478            Self::PredicateExpr(predicate) => predicate.eval(knowledge, target_role),
479            other => other.allows(target_role),
480        }
481    }
482}
483
484impl FlowPredicate {
485    /// Evaluate this serialized predicate against one fact and target role.
486    #[must_use]
487    pub fn eval(&self, knowledge: &KnowledgeFact, target_role: &str) -> bool {
488        match self {
489            Self::TargetRolePrefix(prefix) => target_role.starts_with(prefix),
490            Self::FactContains(fragment) => knowledge.fact.contains(fragment),
491            Self::EndpointRoleMatchesTarget => knowledge.endpoint.role == target_role,
492            Self::All(predicates) => predicates
493                .iter()
494                .all(|predicate| predicate.eval(knowledge, target_role)),
495            Self::Any(predicates) => predicates
496                .iter()
497                .any(|predicate| predicate.eval(knowledge, target_role)),
498        }
499    }
500}
501
502/// Typed runtime tuning profile for benchmark/runtime configuration harmonization.
503#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
504#[serde(rename_all = "snake_case")]
505pub enum RuntimeTuningProfile {
506    /// Default production-like tuning.
507    #[default]
508    Standard,
509    /// Reference profile approximating early M1 stress behavior.
510    M1StressReference,
511}
512
513/// Threaded scheduler round semantics mode.
514#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
515#[serde(rename_all = "snake_case")]
516pub enum ThreadedRoundSemantics {
517    /// Canonical one-step semantics aligned with Lean runner rounds.
518    #[default]
519    CanonicalOneStep,
520    /// Performance extension: multi-pick waves within one round.
521    WaveParallelExtension,
522}
523
524/// Effect-trace capture mode for runtime overhead control.
525#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
526#[serde(rename_all = "snake_case")]
527pub enum EffectTraceCaptureMode {
528    /// Record all canonical effect kinds.
529    #[default]
530    Full,
531    /// Record only topology ingress events.
532    TopologyOnly,
533    /// Disable effect-trace recording.
534    Disabled,
535}
536
537/// Payload validation mode for runtime message hardening.
538#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
539#[serde(rename_all = "snake_case")]
540pub enum PayloadValidationMode {
541    /// Disable VM-side payload validation checks.
542    Off,
543    /// Validate payload size and annotated `ValType` compatibility.
544    #[default]
545    Structural,
546    /// Structural checks plus strict annotation requirement for `Send` and `Receive`.
547    StrictSchema,
548}
549