Skip to main content

parallax_ues/
state.rs

1//! Universal Execution State document.
2
3use crate::deterministic::DeterministicContext;
4use crate::frame::UniversalFrame;
5use crate::version::{check_ues_format, UES_FORMAT_VERSION};
6use indexmap::IndexMap;
7use parallax_core::{ExceptionInfo, ExecutionId, RuntimeKind};
8use parallax_pcir::PcirProgram;
9use serde::{Deserialize, Serialize};
10
11/// Control-position summary at capture time.
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
13pub struct ControlState {
14    /// Explicit safepoint label when paused at `parallax.checkpoint`.
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub safepoint_label: Option<String>,
17    /// Kind of safepoint (`explicit_checkpoint`, `await`, …).
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub safepoint_kind: Option<String>,
20    /// Opaque instruction / IP string when known.
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub instruction_position: Option<String>,
23    /// True when execution is suspended (not completed).
24    #[serde(default)]
25    pub suspended: bool,
26    /// Source offset after which resume should continue (byte index into source).
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub resume_byte_offset: Option<usize>,
29}
30
31/// Heap / object-graph payload (PIR document JSON).
32#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
33pub struct HeapState {
34    /// PIR-tagged bindings object (same shape as worker capture).
35    #[serde(default)]
36    pub bindings: serde_json::Value,
37    /// Optional full PIR document JSON.
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub pir_document: Option<serde_json::Value>,
40}
41
42/// Module table entry.
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct ModuleState {
45    /// Module name / path.
46    pub name: String,
47    /// Origin runtime (mixed-origin stacks).
48    pub runtime: RuntimeKind,
49    /// Optional source fingerprint.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub fingerprint: Option<String>,
52}
53
54/// Async / await bookkeeping (mostly Unsupported today).
55#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
56pub struct AsyncState {
57    /// Status: `none`, `awaiting`, `unsupported`.
58    pub status: String,
59    /// Detail when unsupported or partial.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub detail: Option<String>,
62    /// Opaque tasks payload.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub tasks: Option<serde_json::Value>,
65}
66
67/// Capability tokens / granted rights at capture.
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
69pub struct CapabilityState {
70    /// Granted capability names.
71    #[serde(default)]
72    pub granted: Vec<String>,
73    /// Notes about denied / unsupported capabilities.
74    #[serde(default)]
75    pub notes: Vec<String>,
76}
77
78/// External resource that cannot migrate (files, sockets, …).
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80pub struct ExternalResource {
81    /// Resource kind.
82    pub kind: String,
83    /// Descriptor / path / id.
84    pub descriptor: String,
85    /// Whether migration must fail if this is required.
86    #[serde(default)]
87    pub required: bool,
88}
89
90/// Migration bookkeeping attached to a UES.
91#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
92pub struct MigrationMetadata {
93    /// Capture mode (`explicit_checkpoint`, …).
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub capture_mode: Option<String>,
96    /// Continuum status: `experimental`, `unsupported`, `verified`.
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub continuum_status: Option<String>,
99    /// Human notes.
100    #[serde(default)]
101    pub notes: Vec<String>,
102    /// Extra fields reserved for later contract versions.
103    #[serde(default)]
104    pub extra: IndexMap<String, serde_json::Value>,
105}
106
107/// Universal Execution State — suspended computation (not value-only PIR).
108#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
109pub struct UniversalExecutionState {
110    /// Format version (independent of PIR / PCIR).
111    pub format_version: u32,
112    /// Execution identity.
113    pub execution_id: ExecutionId,
114    /// Source runtime that produced this UES.
115    pub source_runtime: RuntimeKind,
116    /// Source program path / label.
117    pub source_program: Option<String>,
118    /// Control / safepoint state.
119    pub control_state: ControlState,
120    /// Call stack (mixed-origin frames allowed in the model).
121    pub call_stack: Vec<UniversalFrame>,
122    /// Heap / bindings.
123    pub heap: HeapState,
124    /// Global bindings (PIR-tagged JSON object).
125    #[serde(default)]
126    pub globals: IndexMap<String, serde_json::Value>,
127    /// Modules.
128    #[serde(default)]
129    pub modules: Vec<ModuleState>,
130    /// Pending exception.
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub exception_state: Option<ExceptionInfo>,
133    /// Async state.
134    #[serde(default)]
135    pub async_state: AsyncState,
136    /// Capability state.
137    #[serde(default)]
138    pub capability_state: CapabilityState,
139    /// Non-migratable external resources.
140    #[serde(default)]
141    pub external_resources: Vec<ExternalResource>,
142    /// Deterministic replay context / hooks.
143    #[serde(default)]
144    pub deterministic_context: DeterministicContext,
145    /// Migration metadata.
146    #[serde(default)]
147    pub migration_metadata: MigrationMetadata,
148    /// Optional PCIR for the supported region around the safepoint.
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub pcir: Option<PcirProgram>,
151    /// Opaque resume payload for same-runtime restore (e.g. post-checkpoint source).
152    #[serde(default, skip_serializing_if = "Option::is_none")]
153    pub resume_source: Option<String>,
154    /// Free-form extension bag (forward-compatible).
155    #[serde(default)]
156    pub extensions: IndexMap<String, serde_json::Value>,
157}
158
159impl UniversalExecutionState {
160    /// Empty suspended shell.
161    pub fn empty(runtime: RuntimeKind) -> Self {
162        Self {
163            format_version: UES_FORMAT_VERSION,
164            execution_id: ExecutionId::new(),
165            source_runtime: runtime,
166            source_program: None,
167            control_state: ControlState {
168                suspended: true,
169                ..ControlState::default()
170            },
171            call_stack: Vec::new(),
172            heap: HeapState::default(),
173            globals: IndexMap::new(),
174            modules: Vec::new(),
175            exception_state: None,
176            async_state: AsyncState {
177                status: "none".into(),
178                detail: None,
179                tasks: None,
180            },
181            capability_state: CapabilityState::default(),
182            external_resources: Vec::new(),
183            deterministic_context: DeterministicContext::unsupported_engine(),
184            migration_metadata: MigrationMetadata {
185                continuum_status: Some("experimental".into()),
186                ..MigrationMetadata::default()
187            },
188            pcir: None,
189            resume_source: None,
190            extensions: IndexMap::new(),
191        }
192    }
193
194    /// Build a UES for an explicit checkpoint safepoint (supported Continuum subset).
195    pub fn checkpoint_shell(
196        runtime: RuntimeKind,
197        program: impl Into<String>,
198        label: impl Into<String>,
199    ) -> Self {
200        let program = program.into();
201        let label = label.into();
202        let mut ues = Self::empty(runtime.clone());
203        ues.source_program = Some(program.clone());
204        ues.control_state = ControlState {
205            safepoint_label: Some(label.clone()),
206            safepoint_kind: Some("explicit_checkpoint".into()),
207            instruction_position: Some(format!("safepoint:{label}")),
208            suspended: true,
209            resume_byte_offset: None,
210        };
211        ues.call_stack.push(UniversalFrame::checkpoint(
212            runtime.clone(),
213            "__parallax_main",
214            program.clone(),
215            label.clone(),
216            IndexMap::new(),
217        ));
218        ues.modules.push(ModuleState {
219            name: program,
220            runtime,
221            fingerprint: None,
222        });
223        ues.pcir = Some(PcirProgram::checkpoint_stub(&label));
224        ues.migration_metadata = MigrationMetadata {
225            capture_mode: Some("explicit_checkpoint".into()),
226            continuum_status: Some("experimental".into()),
227            notes: vec![
228                "Captured at explicit parallax.checkpoint safepoint".into(),
229                "Arbitrary live stack migration is NOT claimed".into(),
230            ],
231            extra: IndexMap::new(),
232        };
233        ues
234    }
235
236    /// Validate format version and nested PCIR when present.
237    pub fn validate(&self) -> parallax_core::Result<()> {
238        check_ues_format(self.format_version)?;
239        if let Some(pcir) = &self.pcir {
240            pcir.validate()?;
241        }
242        Ok(())
243    }
244}