Skip to main content

origin/
lib.rs

1//! # Origin
2//!
3//! Every value is origin, at a boundary, or contents.
4//! The compiler knows which. One pattern. Every domain.
5//!
6//! Three sorts — matching the formal foundation in `Val α`:
7//! - `Origin(B)` — pure absorption. The system hit its absolute boundary.
8//!   No last value. The reason is all that exists.
9//! - `Boundary { reason, last }` — crossed an edge. The container preserves
10//!   the last known value and the reason.
11//! - `Contents(T)` — actual value. Arithmetic lives here.
12
13use std::fmt;
14
15// Re-export proc-macros so users write `use origin::BoundaryKind;`
16pub use origin_macros::BoundaryKind;
17pub use origin_macros::boundary_check;
18
19/// A boundary reason. Every domain defines its own.
20///
21/// The `'static` bound means boundary kinds must be owned —
22/// they cannot borrow from the computation that produced them.
23/// Use owned `String` instead of `&str`, clone data you need
24/// to carry. This keeps boundary reasons self-contained and
25/// safe to propagate across thread boundaries.
26pub trait BoundaryKind: fmt::Debug + Send + Sync + 'static {}
27
28/// The core type. Origin, Boundary, or Contents. Three sorts. Every domain.
29///
30/// Formally verified in Lean 4 as `Val α`:
31/// - `origin` — absorbs everything (I1, I2, I3)
32/// - `container` — structural, preserves last known value
33/// - `contents` — actual arithmetic, the field interior
34///
35/// 508 theorems. Zero errors. Zero sorries.
36#[derive(Debug)]
37#[must_use = "this Value may be at a boundary that must be handled"]
38pub enum Value<T, B: BoundaryKind = GenericBoundary> {
39    /// Origin: the computation hit the absolute boundary.
40    /// There is no last value. The reason is all that exists.
41    /// The ocean absorbed the fish.
42    Origin(B),
43    /// Boundary: the computation crossed an edge.
44    /// Carries the reason and the last known value.
45    /// The container preserves what was there.
46    Boundary {
47        reason: B,
48        last: T,
49    },
50    /// Contents: the value is in safe territory. Arithmetic lives here.
51    Contents(T),
52}
53
54impl<T, B: BoundaryKind> Value<T, B> {
55    /// Construct an origin value — pure boundary, no last value.
56    pub fn origin(reason: B) -> Self {
57        Value::Origin(reason)
58    }
59
60    /// Construct a boundary value with a reason and last known value.
61    pub fn boundary(reason: B, last: T) -> Self {
62        Value::Boundary { reason, last }
63    }
64
65    /// Construct a contents value.
66    pub fn contents(value: T) -> Self {
67        Value::Contents(value)
68    }
69
70    pub fn is_contents(&self) -> bool {
71        matches!(self, Value::Contents(_))
72    }
73
74    pub fn is_origin(&self) -> bool {
75        matches!(self, Value::Origin(_))
76    }
77
78    pub fn is_boundary(&self) -> bool {
79        matches!(self, Value::Boundary { .. })
80    }
81
82    /// Unwrap the contents value, or return a fallback.
83    ///
84    /// Both Origin and Boundary return the fallback — if you need to
85    /// distinguish them, use `match` or `or_else` instead.
86    pub fn or(self, fallback: T) -> T {
87        match self {
88            Value::Contents(v) => v,
89            Value::Boundary { .. } => fallback,
90            Value::Origin(_) => fallback,
91        }
92    }
93
94    /// Handle Origin and Boundary distinctly when extracting a value.
95    ///
96    /// Unlike `or`, this preserves the distinction between the three sorts:
97    /// - Contents: returns the value directly
98    /// - Boundary: calls `on_boundary` with the reason AND last value
99    /// - Origin: calls `on_origin` with only the reason (no last value)
100    pub fn or_else(
101        self,
102        on_boundary: impl FnOnce(B, T) -> T,
103        on_origin: impl FnOnce(B) -> T,
104    ) -> T {
105        match self {
106            Value::Contents(v) => v,
107            Value::Boundary { reason, last } => on_boundary(reason, last),
108            Value::Origin(reason) => on_origin(reason),
109        }
110    }
111
112    /// Unwrap the contents value, or panic.
113    pub fn unwrap(self) -> T {
114        match self {
115            Value::Contents(v) => v,
116            Value::Boundary { reason, .. } => {
117                panic!("called unwrap on a Boundary: {reason:?}")
118            }
119            Value::Origin(reason) => {
120                panic!("called unwrap on an Origin: {reason:?}")
121            }
122        }
123    }
124
125    /// Map over the contents value. Origin passes through unchanged.
126    /// Boundary maps the transform over `last`.
127    ///
128    /// Note: if you chain multiple `map` calls, `last` reflects the
129    /// transformed value at each step, not the original value at the
130    /// boundary. If you need the original `last`, use `match` explicitly.
131    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Value<U, B> {
132        match self {
133            Value::Contents(v) => Value::Contents(f(v)),
134            Value::Boundary { reason, last } => Value::Boundary {
135                reason,
136                last: f(last),
137            },
138            Value::Origin(reason) => Value::Origin(reason),
139        }
140    }
141
142    /// Extract the contents value, or return the boundary reason as `Err`
143    /// for `?` propagation.
144    ///
145    /// Both Boundary and Origin become `Err(reason)`. The `last` value
146    /// in Boundary is dropped. If you need `last`, use `match` or
147    /// `propagate_with_last` instead.
148    pub fn propagate(self) -> Result<T, B> {
149        match self {
150            Value::Contents(v) => Ok(v),
151            Value::Boundary { reason, .. } => Err(reason),
152            Value::Origin(reason) => Err(reason),
153        }
154    }
155
156    /// Propagate, preserving the last value if at a Boundary.
157    ///
158    /// - Contents → `Ok(value)`
159    /// - Boundary → `Err((reason, Some(last)))`
160    /// - Origin → `Err((reason, None))`
161    pub fn propagate_with_last(self) -> Result<T, (B, Option<T>)> {
162        match self {
163            Value::Contents(v) => Ok(v),
164            Value::Boundary { reason, last } => Err((reason, Some(last))),
165            Value::Origin(reason) => Err((reason, None)),
166        }
167    }
168
169    /// Extract the contents value while recording the step in a trace.
170    pub fn trace(self, chain: &mut Chain, label: &'static str) -> Result<T, B>
171    where
172        T: std::fmt::Debug,
173    {
174        match self {
175            Value::Contents(v) => {
176                chain.record(label, &v);
177                Ok(v)
178            }
179            Value::Boundary { reason, last } => {
180                chain.record_boundary(label, &last);
181                Err(reason)
182            }
183            Value::Origin(reason) => {
184                chain.record_origin(label);
185                Err(reason)
186            }
187        }
188    }
189
190    /// Chain operations that may themselves hit a boundary.
191    ///
192    /// If already at Origin, propagates as Origin.
193    /// If at Boundary, `last` is dropped and propagates as Origin —
194    /// because the type has changed and the old `last` has no meaning
195    /// in the new type. If you need `last`, use `match` before chaining.
196    ///
197    /// `and_then` is for Contents pipelines. For Boundary-aware chaining,
198    /// use `match` explicitly. No `Default` bound needed — Origin carries
199    /// no value.
200    pub fn and_then<U>(self, f: impl FnOnce(T) -> Value<U, B>) -> Value<U, B> {
201        match self {
202            Value::Contents(v) => f(v),
203            Value::Boundary { reason, .. } => Value::Origin(reason),
204            Value::Origin(reason) => Value::Origin(reason),
205        }
206    }
207}
208
209// -- Residual chain --
210
211/// A step in the residual chain: what was computed at each layer.
212#[derive(Debug, Clone)]
213pub struct ChainEntry {
214    /// Which layer produced this value.
215    pub label: &'static str,
216    /// Debug representation of the value at this layer.
217    pub value: String,
218    /// Whether this was the layer that hit the boundary.
219    pub is_boundary: bool,
220}
221
222/// The residual chain: every step of how you got there.
223///
224/// When a pipeline propagates through `?`, each layer records what it
225/// computed. If a boundary is reached, the chain shows the full path —
226/// not just where you stopped, but every step on the way.
227#[derive(Debug, Clone, Default)]
228pub struct Chain {
229    entries: Vec<ChainEntry>,
230}
231
232impl Chain {
233    pub fn new() -> Self {
234        Chain { entries: Vec::new() }
235    }
236
237    /// Record a successful contents step.
238    pub fn record(&mut self, label: &'static str, value: &dyn fmt::Debug) {
239        self.entries.push(ChainEntry {
240            label,
241            value: format!("{value:?}"),
242            is_boundary: false,
243        });
244    }
245
246    /// Record the residual at the boundary — the last step before propagation.
247    pub fn record_boundary(&mut self, label: &'static str, value: &dyn fmt::Debug) {
248        self.entries.push(ChainEntry {
249            label,
250            value: format!("{value:?}"),
251            is_boundary: true,
252        });
253    }
254
255    /// Record an origin hit — pure boundary, no value.
256    pub fn record_origin(&mut self, label: &'static str) {
257        self.entries.push(ChainEntry {
258            label,
259            value: "⊘ origin".to_string(),
260            is_boundary: true,
261        });
262    }
263
264    /// The entries in order, from first layer to the boundary.
265    pub fn entries(&self) -> &[ChainEntry] {
266        &self.entries
267    }
268
269    /// How many layers completed before the boundary.
270    pub fn depth(&self) -> usize {
271        self.entries.iter().filter(|e| !e.is_boundary).count()
272    }
273}
274
275impl fmt::Display for Chain {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        for (i, entry) in self.entries.iter().enumerate() {
278            let marker = if entry.is_boundary { "✗" } else { "✓" };
279            write!(f, "  {marker} [{}] {}: {}", i + 1, entry.label, entry.value)?;
280            if i < self.entries.len() - 1 {
281                writeln!(f)?;
282            }
283        }
284        Ok(())
285    }
286}
287
288// -- Built-in boundary kinds --
289
290#[derive(Debug, Clone)]
291pub struct GenericBoundary;
292impl BoundaryKind for GenericBoundary {}
293
294// Math
295#[derive(Debug, Clone)]
296pub struct DivisionByZero;
297impl BoundaryKind for DivisionByZero {}
298
299#[derive(Debug, Clone)]
300pub struct Overflow;
301impl BoundaryKind for Overflow {}
302
303#[derive(Debug, Clone)]
304pub struct Underflow;
305impl BoundaryKind for Underflow {}
306
307#[derive(Debug, Clone)]
308pub struct Imaginary { pub real_part: f64 }
309impl BoundaryKind for Imaginary {}
310
311// AI inference
312#[derive(Debug, Clone)]
313pub struct ModelUncertain { pub confidence: f64 }
314impl BoundaryKind for ModelUncertain {}
315
316#[derive(Debug, Clone)]
317pub struct Hallucinated { pub confidence: f64, pub grounding: Option<String> }
318impl BoundaryKind for Hallucinated {}
319
320#[derive(Debug, Clone)]
321pub struct Contradicted { pub sources: Vec<String>, pub claim: String }
322impl BoundaryKind for Contradicted {}
323
324// -- Demonstration functions --
325//
326// These show the shape of Value<T, B>. They demonstrate that contents
327// divided by contents is always contents — the sort is determined by
328// the type, not by the value.
329//
330// IEEE 754 already handles the value-level edge cases: 0/0 = NaN,
331// x/0 = ±Inf, sqrt(-1) = NaN. These are contents values — they're
332// what the arithmetic produces. The sort system doesn't override
333// IEEE 754. It names what IEEE 754 leaves unnamed: origin is the
334// boundary of the system, not a float value.
335
336/// Division. Contents divided by contents is always contents.
337/// IEEE 754 handles the value: 10/0 = Inf, 0/0 = NaN.
338/// The sort is determined. The value is arithmetic's problem.
339pub fn divide(a: f64, b: f64) -> f64 {
340    a / b
341}
342
343/// Square root. Contents in, contents out.
344/// IEEE 754 handles negative input: sqrt(-1) = NaN.
345/// The sort is determined. The value is arithmetic's problem.
346pub fn sqrt(a: f64) -> f64 {
347    a.sqrt()
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn division_is_always_contents() {
356        // Contents divided by contents is contents. Always.
357        assert_eq!(divide(10.0, 2.0), 5.0);
358    }
359
360    #[test]
361    fn division_by_zero_is_contents() {
362        // 0.0 is contents(0), not origin. IEEE 754 gives infinity.
363        // The SORT is determined. The VALUE is arithmetic's problem.
364        assert!(divide(10.0, 0.0).is_infinite());
365    }
366
367    #[test]
368    fn zero_div_zero_is_contents() {
369        // 0/0 = NaN. NaN is a contents value — what IEEE 754 produces.
370        // Not origin. Not boundary. Contents.
371        assert!(divide(0.0, 0.0).is_nan());
372    }
373
374    #[test]
375    fn sqrt_is_always_contents() {
376        assert_eq!(sqrt(9.0), 3.0);
377    }
378
379    #[test]
380    fn sqrt_negative_is_contents() {
381        // sqrt(-1) = NaN. A contents value. Not a boundary.
382        assert!(sqrt(-1.0).is_nan());
383    }
384
385    #[test]
386    fn map_propagates_boundary() {
387        let v: Value<f64, DivisionByZero> = Value::boundary(DivisionByZero, 10.0);
388        let result = v.map(|v| v * 2.0);
389        assert!(result.is_boundary());
390    }
391
392    #[test]
393    fn map_transforms_contents() {
394        let v: Value<f64, DivisionByZero> = Value::contents(10.0);
395        let result = v.map(|v| v * 2.0);
396        assert_eq!(result.unwrap(), 20.0);
397    }
398
399    #[test]
400    fn map_propagates_origin() {
401        let v: Value<f64, DivisionByZero> = Value::origin(DivisionByZero);
402        let result = v.map(|x| x * 2.0);
403        assert!(result.is_origin());
404    }
405
406    #[test]
407    fn model_uncertain() {
408        let result: Value<String, ModelUncertain> = Value::Boundary {
409            reason: ModelUncertain { confidence: 0.3 },
410            last: "Paris is the capital of France".to_string(),
411        };
412        assert!(result.is_boundary());
413    }
414
415    #[test]
416    fn hallucinated() {
417        let result: Value<String, Hallucinated> = Value::Boundary {
418            reason: Hallucinated { confidence: 0.1, grounding: None },
419            last: "The moon is made of cheese".to_string(),
420        };
421        match result {
422            Value::Boundary { reason, .. } => assert!(reason.grounding.is_none()),
423            _ => panic!("expected boundary"),
424        }
425    }
426
427    #[test]
428    fn and_then_contents() {
429        let v: Value<f64, DivisionByZero> = Value::contents(10.0);
430        let result = v.and_then(|x| Value::contents(x / 2.0));
431        assert!(result.is_contents());
432        assert_eq!(result.unwrap(), 5.0);
433    }
434
435    #[test]
436    fn and_then_propagates_boundary_as_origin() {
437        // Boundary through and_then becomes Origin (type changed, last meaningless)
438        let v: Value<f64, DivisionByZero> = Value::boundary(DivisionByZero, 10.0);
439        let result = v.and_then(|x| Value::contents(x / 2.0));
440        assert!(result.is_origin());
441    }
442
443    #[test]
444    fn origin_propagates_through_and_then() {
445        let v: Value<f64, DivisionByZero> = Value::origin(DivisionByZero);
446        let result = v.and_then(|x| Value::contents(x / 2.0));
447        assert!(result.is_origin());
448    }
449
450    #[test]
451    fn origin_or_fallback() {
452        let v: Value<f64, DivisionByZero> = Value::origin(DivisionByZero);
453        assert_eq!(v.or(42.0), 42.0);
454    }
455
456    // -- boundary_check macro tests --
457
458    fn make_value(a: f64, b: f64) -> Value<f64, DivisionByZero> {
459        // Domain logic decides what's a boundary — not the arithmetic.
460        // This is how a real application would use Value:
461        // the application defines its own boundary conditions.
462        if b.abs() < 1e-10 {
463            Value::boundary(DivisionByZero, a)
464        } else {
465            Value::contents(a / b)
466        }
467    }
468
469    #[boundary_check]
470    fn safe_divide(a: f64, b: f64) -> f64 {
471        make_value(a, b).or(0.0)
472    }
473
474    #[test]
475    fn boundary_check_allows_or() {
476        assert_eq!(safe_divide(10.0, 2.0), 5.0);
477        assert_eq!(safe_divide(10.0, 0.0), 0.0);
478    }
479
480    #[boundary_check]
481    fn safe_match(a: f64, b: f64) -> f64 {
482        match make_value(a, b) {
483            Value::Contents(v) => v,
484            Value::Boundary { reason: DivisionByZero, last } => last,
485            Value::Origin(DivisionByZero) => 0.0,
486        }
487    }
488
489    #[test]
490    fn boundary_check_allows_match() {
491        assert_eq!(safe_match(10.0, 2.0), 5.0);
492        assert_eq!(safe_match(10.0, 0.0), 10.0);
493    }
494
495    // -- derive(BoundaryKind) test --
496
497    #[derive(Debug, Clone, BoundaryKind)]
498    #[boundary_kind(crate_path = "crate")]
499    struct InsufficientFunds {
500        balance: f64,
501        required: f64,
502    }
503
504    #[test]
505    fn user_defined_boundary() {
506        let result: Value<f64, InsufficientFunds> = Value::Boundary {
507            reason: InsufficientFunds { balance: 50.0, required: 100.0 },
508            last: 50.0,
509        };
510        assert!(result.is_boundary());
511        match result {
512            Value::Boundary { reason, .. } => {
513                assert_eq!(reason.balance, 50.0);
514                assert_eq!(reason.required, 100.0);
515            }
516            _ => panic!("expected boundary"),
517        }
518    }
519}