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