Skip to main content

lsp_max/pipeline/
phase.rs

1//! Phase-shift model: conformance state as a thermodynamic phase transition.
2//!
3//! Water expands roughly 1,700x in volume when it crosses the boiling point into
4//! steam. This module borrows that picture for the law-state runtime: the
5//! **boiling point is the admission threshold**, and the **1,700x expansion is
6//! the autonomic-mesh fan-out** applied to an observation once it is admitted —
7//! a single admitted observation propagates as many bounded intents across the
8//! layer-5 mesh. The phase is the bounded admission state of that observation.
9//!
10//! This is a *read-only* projection: it reports a phase and an amplification
11//! factor; it mutates nothing. It is distinct from the runtime's `LspPhase`,
12//! which tracks the LSP protocol lifecycle (`Uninitialized -> ... -> Exited`).
13//!
14//! Bounded statuses only, three-state law preserved. The precedence matches
15//! [`crate::pipeline`]'s admission semantics: an active ANDON (`Frozen`/BLOCKED)
16//! dominates; an explicit refusal (`Decomposed`/REFUSED) is next; an
17//! undetermined measurement (`Unsettled`/UNKNOWN) is next and is **never**
18//! coerced into `Liquid`/PARTIAL or `Vapor`/ADMITTED; only a settled measurement
19//! at or above the boiling point reaches `Vapor`/ADMITTED.
20
21use serde::{Deserialize, Serialize};
22
23/// Volumetric expansion of water into steam at standard pressure (~1,700x).
24/// Used as the autonomic-mesh amplification factor for an admitted observation.
25pub const STEAM_EXPANSION_FACTOR: u32 = 1700;
26
27/// The bounded conformance phase of an observation, named for matter states.
28///
29/// Each variant maps to exactly one of the project's bounded admission statuses;
30/// the mapping is total and the three-state law is preserved (`Unsettled` is its
31/// own phase, never folded into `Liquid` or `Vapor`).
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
33pub enum ConformancePhase {
34    /// Solid / ice — an ANDON signal is active; no flow until it clears (BLOCKED).
35    Frozen,
36    /// Liquid / water — flowing below the admission boiling point (PARTIAL).
37    Liquid,
38    /// Gas / steam — crossed the boiling point; the mesh expands it (ADMITTED).
39    Vapor,
40    /// Phase indeterminate — the measurement is undetermined (UNKNOWN).
41    Unsettled,
42    /// Decomposed — an explicit refusal; no longer the same substance (REFUSED).
43    Decomposed,
44}
45
46impl ConformancePhase {
47    /// The bounded admission status string this phase maps to.
48    pub fn as_status(self) -> &'static str {
49        match self {
50            Self::Frozen => "BLOCKED",
51            Self::Liquid => "PARTIAL",
52            Self::Vapor => "ADMITTED",
53            Self::Unsettled => "UNKNOWN",
54            Self::Decomposed => "REFUSED",
55        }
56    }
57
58    /// The autonomic-mesh amplification factor for an observation in this phase.
59    ///
60    /// `Vapor` expands by [`STEAM_EXPANSION_FACTOR`] (the admitted observation
61    /// fans out across the mesh); `Liquid` carries unit volume (it flows but does
62    /// not expand); every non-flowing phase is `0` (nothing propagates).
63    pub fn expansion_factor(self) -> u32 {
64        match self {
65            Self::Vapor => STEAM_EXPANSION_FACTOR,
66            Self::Liquid => 1,
67            Self::Frozen | Self::Unsettled | Self::Decomposed => 0,
68        }
69    }
70}
71
72impl std::fmt::Display for ConformancePhase {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        f.write_str(self.as_status())
75    }
76}
77
78/// A hypothetical world-state to resolve into a [`ConformancePhase`].
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct PhaseInput {
81    /// Whether an ANDON signal is active (the highest-precedence floor).
82    pub andon_active: bool,
83    /// Whether the observation is explicitly refused by law.
84    pub refused: bool,
85    /// Whether the measurement is undetermined (forces UNKNOWN).
86    pub unknown: bool,
87    /// Conformance measurement in `[0.0, 1.0]` (the "temperature").
88    pub conformance: f64,
89    /// The admission threshold in `[0.0, 1.0]` (the "boiling point").
90    pub boiling_point: f64,
91}
92
93/// Resolve a world-state into its bounded [`ConformancePhase`].
94///
95/// Precedence, three-state law preserved:
96/// 1. `andon_active` -> `Frozen` (BLOCKED). A hard floor.
97/// 2. `refused` -> `Decomposed` (REFUSED). Refusal dominates remaining polarity.
98/// 3. `unknown` -> `Unsettled` (UNKNOWN). Never coerced to `Liquid` or `Vapor`.
99/// 4. `conformance >= boiling_point` -> `Vapor` (ADMITTED), else `Liquid`
100///    (PARTIAL).
101pub fn phase_for(input: &PhaseInput) -> ConformancePhase {
102    if input.andon_active {
103        return ConformancePhase::Frozen;
104    }
105    if input.refused {
106        return ConformancePhase::Decomposed;
107    }
108    if input.unknown {
109        return ConformancePhase::Unsettled;
110    }
111    if input.conformance >= input.boiling_point {
112        ConformancePhase::Vapor
113    } else {
114        ConformancePhase::Liquid
115    }
116}
117
118/// A read-only report of a resolved phase shift.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct PhaseShiftReport {
121    /// The bounded admission status of the resolved phase.
122    pub phase: String,
123    /// The autonomic-mesh amplification factor for the resolved phase.
124    pub expansion_factor: u32,
125    /// The conformance measurement that drove the resolution.
126    pub conformance: f64,
127    /// The admission threshold (boiling point) compared against.
128    pub boiling_point: f64,
129    /// Whether the measurement crossed the boiling point into `Vapor`.
130    pub crossed_boiling_point: bool,
131    /// One-line bounded summary (no victory language).
132    pub summary: String,
133}
134
135/// Build a read-only [`PhaseShiftReport`] for a world-state.
136pub fn phase_shift_report(input: &PhaseInput) -> PhaseShiftReport {
137    let phase = phase_for(input);
138    let crossed = phase == ConformancePhase::Vapor;
139    let summary = match phase {
140        ConformancePhase::Frozen => {
141            "ANDON active; phase Frozen — status BLOCKED, no observation propagates".to_string()
142        }
143        ConformancePhase::Decomposed => {
144            "explicit refusal; phase Decomposed — status REFUSED, no observation propagates"
145                .to_string()
146        }
147        ConformancePhase::Unsettled => {
148            "measurement undetermined; phase Unsettled — status UNKNOWN, never coerced to PARTIAL \
149             or ADMITTED"
150                .to_string()
151        }
152        ConformancePhase::Liquid => format!(
153            "conformance {:.3} below boiling point {:.3}; phase Liquid — status PARTIAL",
154            input.conformance, input.boiling_point
155        ),
156        ConformancePhase::Vapor => format!(
157            "conformance {:.3} at or above boiling point {:.3}; phase Vapor — status ADMITTED, mesh \
158             expansion {}x",
159            input.conformance, input.boiling_point, STEAM_EXPANSION_FACTOR
160        ),
161    };
162    PhaseShiftReport {
163        phase: phase.as_status().to_string(),
164        expansion_factor: phase.expansion_factor(),
165        conformance: input.conformance,
166        boiling_point: input.boiling_point,
167        crossed_boiling_point: crossed,
168        summary,
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    fn input(andon: bool, refused: bool, unknown: bool, c: f64, bp: f64) -> PhaseInput {
177        PhaseInput {
178            andon_active: andon,
179            refused,
180            unknown,
181            conformance: c,
182            boiling_point: bp,
183        }
184    }
185
186    #[test]
187    fn precedence_blocked_over_refused_over_unknown() {
188        // ANDON dominates even when refused and unknown are also set.
189        assert_eq!(
190            phase_for(&input(true, true, true, 0.9, 0.5)),
191            ConformancePhase::Frozen
192        );
193        // Refusal dominates unknown and a high measurement.
194        assert_eq!(
195            phase_for(&input(false, true, true, 0.9, 0.5)),
196            ConformancePhase::Decomposed
197        );
198        // Unknown dominates a high measurement.
199        assert_eq!(
200            phase_for(&input(false, false, true, 0.9, 0.5)),
201            ConformancePhase::Unsettled
202        );
203    }
204
205    #[test]
206    fn unknown_never_collapses_into_liquid_or_vapor() {
207        let p = phase_for(&input(false, false, true, 1.0, 0.0));
208        assert_eq!(p, ConformancePhase::Unsettled);
209        assert_ne!(p, ConformancePhase::Liquid);
210        assert_ne!(p, ConformancePhase::Vapor);
211        assert_eq!(p.as_status(), "UNKNOWN");
212    }
213
214    #[test]
215    fn boiling_point_boundary_is_admission() {
216        // At the boiling point exactly -> Vapor; just below -> Liquid.
217        assert_eq!(
218            phase_for(&input(false, false, false, 0.7, 0.7)),
219            ConformancePhase::Vapor
220        );
221        assert_eq!(
222            phase_for(&input(false, false, false, 0.699, 0.7)),
223            ConformancePhase::Liquid
224        );
225    }
226
227    #[test]
228    fn vapor_expands_seventeen_hundred_x() {
229        assert_eq!(ConformancePhase::Vapor.expansion_factor(), 1700);
230        assert_eq!(ConformancePhase::Liquid.expansion_factor(), 1);
231        assert_eq!(ConformancePhase::Frozen.expansion_factor(), 0);
232        assert_eq!(ConformancePhase::Unsettled.expansion_factor(), 0);
233        assert_eq!(ConformancePhase::Decomposed.expansion_factor(), 0);
234    }
235
236    #[test]
237    fn report_marks_crossing_only_for_vapor() {
238        let admitted = phase_shift_report(&input(false, false, false, 0.8, 0.7));
239        assert!(admitted.crossed_boiling_point);
240        assert_eq!(admitted.phase, "ADMITTED");
241        assert_eq!(admitted.expansion_factor, 1700);
242
243        let partial = phase_shift_report(&input(false, false, false, 0.5, 0.7));
244        assert!(!partial.crossed_boiling_point);
245        assert_eq!(partial.phase, "PARTIAL");
246    }
247
248    #[test]
249    fn all_phases_map_to_bounded_statuses() {
250        for p in [
251            ConformancePhase::Frozen,
252            ConformancePhase::Liquid,
253            ConformancePhase::Vapor,
254            ConformancePhase::Unsettled,
255            ConformancePhase::Decomposed,
256        ] {
257            assert!(
258                ["BLOCKED", "PARTIAL", "ADMITTED", "UNKNOWN", "REFUSED"].contains(&p.as_status()),
259                "{p:?} mapped to a non-bounded status"
260            );
261        }
262    }
263}