Skip to main content

telltale_machine/
faults.rs

1//! Stable fault taxonomy and machine-readable mapping helpers.
2
3use crate::coroutine::Fault;
4use serde::{Deserialize, Serialize};
5
6/// Stable fault taxonomy used by snapshot tests and cross-target diagnostics.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum FaultClass {
9    /// Session-type or value-shape mismatch.
10    Type,
11    /// Unknown branch/choice label.
12    Label,
13    /// Channel ownership/lifecycle violation.
14    Channel,
15    /// Signature/proof verification failure.
16    Verification,
17    /// Host effect invocation failure.
18    Invoke,
19    /// Guard/evidence acquisition failure.
20    Acquire,
21    /// Endpoint transfer/delegation failure.
22    Transfer,
23    /// Speculation/fork-join failure.
24    Speculation,
25    /// Session close lifecycle failure.
26    Close,
27    /// Information-flow policy violation.
28    Flow,
29    /// Progress-token discipline failure.
30    Progress,
31    /// Output-condition gating rejection.
32    OutputCondition,
33    /// Register-addressing failure.
34    Register,
35    /// Program-counter addressing failure.
36    ProgramCounter,
37    /// Buffer saturation/backpressure failure.
38    Buffer,
39    /// Deterministic credit budget exhaustion.
40    Credits,
41}
42
43/// Classify a concrete runtime fault into the stable taxonomy.
44#[must_use]
45pub fn classify_fault(fault: &Fault) -> FaultClass {
46    match fault {
47        Fault::TypeViolation { .. } => FaultClass::Type,
48        Fault::UnknownLabel { .. } => FaultClass::Label,
49        Fault::ChannelClosed { .. } => FaultClass::Channel,
50        Fault::InvalidSignature { .. } | Fault::VerificationFailed { .. } => {
51            FaultClass::Verification
52        }
53        Fault::Invoke { .. } => FaultClass::Invoke,
54        Fault::Acquire { .. } => FaultClass::Acquire,
55        Fault::Transfer { .. } => FaultClass::Transfer,
56        Fault::Speculation { .. } => FaultClass::Speculation,
57        Fault::Close { .. } => FaultClass::Close,
58        Fault::FlowViolation { .. } => FaultClass::Flow,
59        Fault::NoProgressToken { .. } => FaultClass::Progress,
60        Fault::OutputCondition { .. } => FaultClass::OutputCondition,
61        Fault::OutOfRegisters => FaultClass::Register,
62        Fault::PcOutOfBounds => FaultClass::ProgramCounter,
63        Fault::BufferFull { .. } => FaultClass::Buffer,
64        Fault::OutOfCredits => FaultClass::Credits,
65    }
66}
67
68/// Stable machine-readable code for each fault class.
69#[must_use]
70pub fn fault_code(class: FaultClass) -> &'static str {
71    match class {
72        FaultClass::Type => "machine.fault.type",
73        FaultClass::Label => "machine.fault.label",
74        FaultClass::Channel => "machine.fault.channel",
75        FaultClass::Verification => "machine.fault.verification",
76        FaultClass::Invoke => "machine.fault.invoke",
77        FaultClass::Acquire => "machine.fault.acquire",
78        FaultClass::Transfer => "machine.fault.transfer",
79        FaultClass::Speculation => "machine.fault.speculation",
80        FaultClass::Close => "machine.fault.close",
81        FaultClass::Flow => "machine.fault.flow",
82        FaultClass::Progress => "machine.fault.progress",
83        FaultClass::OutputCondition => "machine.fault.output_condition",
84        FaultClass::Register => "machine.fault.register",
85        FaultClass::ProgramCounter => "machine.fault.pc",
86        FaultClass::Buffer => "machine.fault.buffer",
87        FaultClass::Credits => "machine.fault.credits",
88    }
89}
90
91/// Stable machine-readable code for a concrete runtime fault.
92#[must_use]
93pub fn fault_code_of(fault: &Fault) -> &'static str {
94    fault_code(classify_fault(fault))
95}
96
97/// Build a stable transfer fault for a non-endpoint transfer source register.
98#[must_use]
99pub fn transfer_fault_expect_endpoint_register(role: &str) -> Fault {
100    Fault::Transfer {
101        message: format!("{role}: transfer expects endpoint register"),
102    }
103}
104
105/// Build a stable transfer fault for a non-nat transfer target register.
106#[must_use]
107pub fn transfer_fault_expect_nat_target(role: &str) -> Fault {
108    Fault::Transfer {
109        message: format!("{role}: transfer expects nat target coroutine id"),
110    }
111}
112
113/// Build a stable transfer fault when transfer target id is out of range.
114#[must_use]
115pub fn transfer_fault_target_id_out_of_range(role: &str) -> Fault {
116    Fault::Transfer {
117        message: format!("{role}: target id out of range"),
118    }
119}
120
121/// Build a stable transfer fault when endpoint ownership is missing.
122#[must_use]
123pub fn transfer_fault_endpoint_not_owned() -> Fault {
124    Fault::Transfer {
125        message: "endpoint not owned".to_string(),
126    }
127}
128
129/// Build a stable transfer fault for delegation-owner guard violations.
130#[must_use]
131pub fn transfer_fault_delegation_guard_violation(phase: &str) -> Fault {
132    Fault::Transfer {
133        message: format!("delegation guard violation {phase} transfer"),
134    }
135}
136
137/// Build a stable speculation fault when speculation is disabled.
138#[must_use]
139pub fn speculation_fault_disabled() -> Fault {
140    Fault::Speculation {
141        message: "speculation disabled".to_string(),
142    }
143}
144
145/// Build a stable speculation fault when join is attempted without active state.
146#[must_use]
147pub fn speculation_fault_join_requires_active() -> Fault {
148    Fault::Speculation {
149        message: "join requires active speculation".to_string(),
150    }
151}
152
153/// Build a stable speculation fault when abort is attempted without active state.
154#[must_use]
155pub fn speculation_fault_abort_requires_active() -> Fault {
156    Fault::Speculation {
157        message: "abort requires active speculation".to_string(),
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use crate::instr::Endpoint;
165    use crate::session::Edge;
166    use telltale_types::ValType;
167
168    #[test]
169    fn fault_codes_are_stable_for_representative_variants() {
170        let samples = [
171            Fault::TypeViolation {
172                expected: ValType::Unit,
173                actual: ValType::Nat,
174                message: "m".to_string(),
175            },
176            Fault::UnknownLabel {
177                label: "x".to_string(),
178            },
179            Fault::ChannelClosed {
180                endpoint: Endpoint {
181                    sid: 1,
182                    role: "A".to_string(),
183                },
184            },
185            Fault::VerificationFailed {
186                edge: Edge::new(1, "A", "B"),
187                message: "bad sig".to_string(),
188            },
189            Fault::OutOfCredits,
190        ];
191
192        let codes: Vec<&str> = samples.iter().map(fault_code_of).collect();
193        assert_eq!(
194            codes,
195            vec![
196                "machine.fault.type",
197                "machine.fault.label",
198                "machine.fault.channel",
199                "machine.fault.verification",
200                "machine.fault.credits",
201            ]
202        );
203    }
204}