Skip to main content

parallax_core/
version.rs

1//! Version constants for independently evolving Parallax interfaces.
2//!
3//! Each constant can be bumped without implying a product release bump.
4//! Compatibility helpers fail clearly when a peer speaks a newer major.
5
6use crate::error::{ErrorCode, ParallaxError, Remediation, Result};
7use serde::{Deserialize, Serialize};
8
9/// Parallax product version (`CARGO_PKG_VERSION` / workspace).
10pub const PARALLAX_VERSION: &str = env!("CARGO_PKG_VERSION");
11
12/// PIR schema version. Bump when the IR wire format changes incompatibly.
13pub const PIR_SCHEMA_VERSION: u32 = 1;
14
15/// Worker protocol version.
16pub const PROTOCOL_VERSION: u32 = 1;
17
18/// Snapshot (`.plx`) format version.
19pub const SNAPSHOT_FORMAT_VERSION: u32 = 1;
20
21/// Adapter interface version.
22pub const ADAPTER_INTERFACE_VERSION: u32 = 1;
23
24/// Universal Execution State (UES) format version.
25/// Independent of PIR — UES models suspended execution, PIR models values.
26pub const UES_FORMAT_VERSION: u32 = 1;
27
28/// Parallax Continuation IR (PCIR) schema version.
29/// Independent of PIR and UES.
30pub const PCIR_SCHEMA_VERSION: u32 = 1;
31
32/// Parallax Universal Program IR (PUIR) schema version.
33/// Independent of PIR / UES / PCIR — PUIR models program semantics for Transmute.
34pub const PUIR_SCHEMA_VERSION: u32 = 1;
35
36/// Mirror link metadata format version (`.parallax-link/`).
37pub const MIRROR_LINK_FORMAT_VERSION: u32 = 1;
38
39/// Aggregate of independently bumpable component versions.
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct ComponentVersions {
42    /// Product / workspace version string.
43    pub parallax: String,
44    /// PIR schema major.
45    pub pir_schema: u32,
46    /// Worker NDJSON protocol major.
47    pub protocol: u32,
48    /// Snapshot (`.plx`) format major.
49    pub snapshot: u32,
50    /// Adapter trait / capability surface major.
51    pub adapter_interface: u32,
52    /// Universal Execution State format major.
53    pub ues_format: u32,
54    /// Parallax Continuation IR schema major.
55    pub pcir_schema: u32,
56    /// Parallax Universal Program IR schema major.
57    pub puir_schema: u32,
58    /// Mirror link metadata format major.
59    pub mirror_link_format: u32,
60}
61
62impl ComponentVersions {
63    /// Versions compiled into this binary.
64    pub fn current() -> Self {
65        Self {
66            parallax: PARALLAX_VERSION.to_string(),
67            pir_schema: PIR_SCHEMA_VERSION,
68            protocol: PROTOCOL_VERSION,
69            snapshot: SNAPSHOT_FORMAT_VERSION,
70            adapter_interface: ADAPTER_INTERFACE_VERSION,
71            ues_format: UES_FORMAT_VERSION,
72            pcir_schema: PCIR_SCHEMA_VERSION,
73            puir_schema: PUIR_SCHEMA_VERSION,
74            mirror_link_format: MIRROR_LINK_FORMAT_VERSION,
75        }
76    }
77
78    /// Human-readable multi-line summary.
79    pub fn format_human(&self) -> String {
80        format!(
81            "parallax {parallax}\n\
82             pir_schema {pir}\n\
83             protocol {proto}\n\
84             snapshot {snap}\n\
85             adapter_interface {iface}\n\
86             ues_format {ues}\n\
87             pcir_schema {pcir}\n\
88             puir_schema {puir}\n\
89             mirror_link_format {mirror}\n",
90            parallax = self.parallax,
91            pir = self.pir_schema,
92            proto = self.protocol,
93            snap = self.snapshot,
94            iface = self.adapter_interface,
95            ues = self.ues_format,
96            pcir = self.pcir_schema,
97            puir = self.puir_schema,
98            mirror = self.mirror_link_format,
99        )
100    }
101}
102
103/// Reject unknown / future PIR schema majors.
104pub fn check_pir_schema(version: u32) -> Result<()> {
105    if version == 0 || version > PIR_SCHEMA_VERSION {
106        return Err(ParallaxError::new(
107            ErrorCode::InvalidSnapshot,
108            format!(
109                "unsupported PIR schema version {version} (supported: 1..={PIR_SCHEMA_VERSION})"
110            ),
111        )
112        .with_source("parallax-core")
113        .with_operation("check_pir_schema")
114        .context("got", version.to_string())
115        .context("expected_max", PIR_SCHEMA_VERSION.to_string())
116        .remediate(Remediation::new(
117            "Upgrade Parallax, or re-export the document with a supported PIR schema",
118        )));
119    }
120    Ok(())
121}
122
123/// Reject unknown / future snapshot format majors.
124pub fn check_snapshot_format(version: u32) -> Result<()> {
125    if version != SNAPSHOT_FORMAT_VERSION {
126        return Err(ParallaxError::new(
127            ErrorCode::InvalidSnapshot,
128            format!(
129                "unsupported snapshot format version {version} (expected {SNAPSHOT_FORMAT_VERSION})"
130            ),
131        )
132        .with_source("parallax-core")
133        .with_operation("check_snapshot_format")
134        .context("got", version.to_string())
135        .context("expected", SNAPSHOT_FORMAT_VERSION.to_string())
136        .remediate(Remediation::new(
137            "Upgrade Parallax to read this snapshot, or recreate it with the current format",
138        )));
139    }
140    Ok(())
141}
142
143/// Reject worker protocol version mismatches (exact match required).
144pub fn check_protocol(version: u32) -> Result<()> {
145    if version != PROTOCOL_VERSION {
146        return Err(ParallaxError::new(
147            ErrorCode::ProtocolViolation,
148            format!("protocol version mismatch: got {version}, expected {PROTOCOL_VERSION}"),
149        )
150        .with_source("parallax-core")
151        .with_operation("check_protocol")
152        .context("got", version.to_string())
153        .context("expected", PROTOCOL_VERSION.to_string())
154        .remediate(Remediation::new(
155            "Ensure host and worker were built from the same Parallax release",
156        )));
157    }
158    Ok(())
159}
160
161/// Reject adapters targeting a newer interface than this host understands.
162pub fn check_adapter_interface(version: u32) -> Result<()> {
163    if version == 0 || version > ADAPTER_INTERFACE_VERSION {
164        return Err(ParallaxError::new(
165            ErrorCode::CapabilityViolation,
166            format!(
167                "unsupported adapter interface version {version} (supported: 1..={ADAPTER_INTERFACE_VERSION})"
168            ),
169        )
170        .with_source("parallax-core")
171        .with_operation("check_adapter_interface")
172        .context("got", version.to_string())
173        .context("expected_max", ADAPTER_INTERFACE_VERSION.to_string())
174        .remediate(Remediation::new(
175            "Upgrade the Parallax host or use an adapter built for this interface version",
176        )));
177    }
178    Ok(())
179}
180
181/// Reject unknown / future UES format majors.
182pub fn check_ues_format(version: u32) -> Result<()> {
183    if version == 0 || version > UES_FORMAT_VERSION {
184        return Err(ParallaxError::new(
185            ErrorCode::InvalidSnapshot,
186            format!(
187                "unsupported UES format version {version} (supported: 1..={UES_FORMAT_VERSION})"
188            ),
189        )
190        .with_source("parallax-core")
191        .with_operation("check_ues_format")
192        .context("got", version.to_string())
193        .context("expected_max", UES_FORMAT_VERSION.to_string())
194        .remediate(Remediation::new(
195            "Upgrade Parallax, or recapture the continuation with a supported UES format",
196        )));
197    }
198    Ok(())
199}
200
201/// Reject unknown / future PCIR schema majors.
202pub fn check_pcir_schema(version: u32) -> Result<()> {
203    if version == 0 || version > PCIR_SCHEMA_VERSION {
204        return Err(ParallaxError::new(
205            ErrorCode::InvalidSnapshot,
206            format!(
207                "unsupported PCIR schema version {version} (supported: 1..={PCIR_SCHEMA_VERSION})"
208            ),
209        )
210        .with_source("parallax-core")
211        .with_operation("check_pcir_schema")
212        .context("got", version.to_string())
213        .context("expected_max", PCIR_SCHEMA_VERSION.to_string())
214        .remediate(Remediation::new(
215            "Upgrade Parallax, or re-export the continuation with a supported PCIR schema",
216        )));
217    }
218    Ok(())
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    #[test]
226    fn current_versions_are_positive() {
227        let v = ComponentVersions::current();
228        assert_eq!(v.parallax, PARALLAX_VERSION);
229        assert!(v.pir_schema >= 1);
230        assert!(v.protocol >= 1);
231        assert!(v.snapshot >= 1);
232        assert!(v.adapter_interface >= 1);
233        assert!(v.ues_format >= 1);
234        assert!(v.pcir_schema >= 1);
235        assert!(v.puir_schema >= 1);
236        assert!(v.mirror_link_format >= 1);
237    }
238
239    #[test]
240    fn rejects_future_ues_and_pcir() {
241        assert_eq!(
242            check_ues_format(UES_FORMAT_VERSION + 1).unwrap_err().code,
243            ErrorCode::InvalidSnapshot
244        );
245        assert_eq!(
246            check_pcir_schema(PCIR_SCHEMA_VERSION + 1).unwrap_err().code,
247            ErrorCode::InvalidSnapshot
248        );
249        check_ues_format(UES_FORMAT_VERSION).unwrap();
250        check_pcir_schema(PCIR_SCHEMA_VERSION).unwrap();
251    }
252
253    #[test]
254    fn rejects_future_pir_schema() {
255        let err = check_pir_schema(PIR_SCHEMA_VERSION + 1).unwrap_err();
256        assert_eq!(err.code, ErrorCode::InvalidSnapshot);
257        assert!(err.message.contains("unsupported PIR schema"));
258    }
259
260    #[test]
261    fn rejects_zero_pir_schema() {
262        assert!(check_pir_schema(0).is_err());
263    }
264
265    #[test]
266    fn accepts_current_pir_schema() {
267        check_pir_schema(PIR_SCHEMA_VERSION).unwrap();
268    }
269
270    #[test]
271    fn rejects_future_snapshot_format() {
272        let err = check_snapshot_format(SNAPSHOT_FORMAT_VERSION + 99).unwrap_err();
273        assert_eq!(err.code, ErrorCode::InvalidSnapshot);
274    }
275
276    #[test]
277    fn rejects_protocol_mismatch() {
278        let err = check_protocol(PROTOCOL_VERSION + 1).unwrap_err();
279        assert_eq!(err.code, ErrorCode::ProtocolViolation);
280    }
281
282    #[test]
283    fn rejects_future_adapter_interface() {
284        let err = check_adapter_interface(ADAPTER_INTERFACE_VERSION + 1).unwrap_err();
285        assert_eq!(err.code, ErrorCode::CapabilityViolation);
286    }
287
288    #[test]
289    fn component_versions_json_round_trip() {
290        let v = ComponentVersions::current();
291        let s = serde_json::to_string(&v).unwrap();
292        let back: ComponentVersions = serde_json::from_str(&s).unwrap();
293        assert_eq!(v, back);
294        assert!(s.contains("adapter_interface"));
295        assert!(s.contains("pir_schema"));
296        assert!(s.contains("ues_format"));
297        assert!(s.contains("pcir_schema"));
298        assert!(s.contains("puir_schema"));
299    }
300}