Skip to main content

ferrum_interfaces/vnext/operation/
checkpoint.rs

1//! Checkpoint continuation capabilities of a chosen operation implementation.
2//! Reusable device execution (`ProviderReplayEquivalence`) is independent.
3
4use std::num::NonZeroU64;
5
6use serde::{Deserialize, Deserializer, Serialize};
7
8use super::super::{CheckpointInputDependency, ContractVersion, VNextError};
9use super::foundation::invalid_operation;
10
11mod state_port;
12pub use state_port::{ProviderCheckpointStateLayout, ProviderCheckpointStatePort};
13
14pub const PROVIDER_CHECKPOINT_CONTRACT_VERSION: ContractVersion = ContractVersion::new(1, 0);
15
16/// A positive token span accepted by an implementation. Both the minimum and
17/// alignment are explicit; construction rejects an unreachable rounded minimum.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
19pub struct CheckpointTokenSpanConstraint {
20    minimum_tokens: NonZeroU64,
21    alignment: NonZeroU64,
22}
23
24#[derive(Deserialize)]
25#[serde(deny_unknown_fields)]
26struct CheckpointTokenSpanConstraintWire {
27    minimum_tokens: NonZeroU64,
28    alignment: NonZeroU64,
29}
30
31impl<'de> Deserialize<'de> for CheckpointTokenSpanConstraint {
32    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
33    where
34        D: Deserializer<'de>,
35    {
36        let wire = CheckpointTokenSpanConstraintWire::deserialize(deserializer)?;
37        Self::new(wire.minimum_tokens, wire.alignment).map_err(serde::de::Error::custom)
38    }
39}
40
41impl CheckpointTokenSpanConstraint {
42    pub fn new(minimum_tokens: NonZeroU64, alignment: NonZeroU64) -> Result<Self, VNextError> {
43        let span = Self {
44            minimum_tokens,
45            alignment,
46        };
47        if span.first_legal_tokens().is_none() {
48            return Err(invalid_operation(
49                "checkpoint token span has no representable aligned length",
50            ));
51        }
52        Ok(span)
53    }
54
55    pub const fn any_positive() -> Self {
56        Self {
57            minimum_tokens: NonZeroU64::MIN,
58            alignment: NonZeroU64::MIN,
59        }
60    }
61
62    pub const fn minimum_tokens(&self) -> NonZeroU64 {
63        self.minimum_tokens
64    }
65
66    pub const fn alignment(&self) -> NonZeroU64 {
67        self.alignment
68    }
69
70    pub fn permits(&self, tokens: u64) -> bool {
71        tokens >= self.minimum_tokens.get() && tokens.is_multiple_of(self.alignment.get())
72    }
73
74    fn first_legal_tokens(&self) -> Option<u64> {
75        let multiple = (self.minimum_tokens.get() - 1) / self.alignment.get() + 1;
76        multiple.checked_mul(self.alignment.get())
77    }
78}
79
80/// Legality of computing a span from M to a boundary N and continuing with a
81/// nonempty suffix. These constrain span lengths, not absolute token positions.
82/// An implementation that additionally requires absolute-position alignment
83/// cannot use this contract without a separate, versioned boundary declaration.
84/// Plans intersect all declarations; validating only the first span is insufficient.
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
86pub struct CheckpointBoundaryConstraint {
87    prefix: CheckpointTokenSpanConstraint,
88    suffix: CheckpointTokenSpanConstraint,
89}
90
91#[derive(Deserialize)]
92#[serde(deny_unknown_fields)]
93struct CheckpointBoundaryConstraintWire {
94    prefix: CheckpointTokenSpanConstraint,
95    suffix: CheckpointTokenSpanConstraint,
96}
97
98impl<'de> Deserialize<'de> for CheckpointBoundaryConstraint {
99    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
100    where
101        D: Deserializer<'de>,
102    {
103        let wire = CheckpointBoundaryConstraintWire::deserialize(deserializer)?;
104        Self::new(wire.prefix, wire.suffix).map_err(serde::de::Error::custom)
105    }
106}
107
108impl CheckpointBoundaryConstraint {
109    pub fn new(
110        prefix: CheckpointTokenSpanConstraint,
111        suffix: CheckpointTokenSpanConstraint,
112    ) -> Result<Self, VNextError> {
113        if prefix
114            .first_legal_tokens()
115            .zip(suffix.first_legal_tokens())
116            .and_then(|(prefix, suffix)| prefix.checked_add(suffix))
117            .is_none()
118        {
119            return Err(invalid_operation(
120                "checkpoint boundary has no representable prefix and nonempty suffix",
121            ));
122        }
123        Ok(Self { prefix, suffix })
124    }
125
126    pub const fn any_positive() -> Self {
127        Self {
128            prefix: CheckpointTokenSpanConstraint::any_positive(),
129            suffix: CheckpointTokenSpanConstraint::any_positive(),
130        }
131    }
132
133    pub const fn prefix(&self) -> CheckpointTokenSpanConstraint {
134        self.prefix
135    }
136
137    pub const fn suffix(&self) -> CheckpointTokenSpanConstraint {
138        self.suffix
139    }
140
141    pub fn permits(&self, prefix_tokens: u64, prompt_tokens: u64) -> bool {
142        self.permits_from(0, prefix_tokens, prompt_tokens)
143    }
144
145    /// Validates both actual execution spans, including after restoration to M.
146    /// Neither a zero-length capture frame nor an empty suffix is permitted.
147    pub fn permits_from(
148        &self,
149        processed_tokens: u64,
150        boundary_tokens: u64,
151        prompt_tokens: u64,
152    ) -> bool {
153        boundary_tokens
154            .checked_sub(processed_tokens)
155            .zip(prompt_tokens.checked_sub(boundary_tokens))
156            .is_some_and(|(prefix, suffix)| {
157                self.prefix.permits(prefix) && self.suffix.permits(suffix)
158            })
159    }
160}
161
162/// Numerical reference for checkpoint continuation and any stronger promise
163/// about repartitioning. Every supported contract requires bitwise continuation
164/// from identical complete state with identical suffix inputs, execution
165/// partitions, implementation choices, and numerical/runtime identity.
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum CheckpointPartitionNumerics {
169    /// Adopt the complete boundary state of an authenticated successful source
170    /// execution. Restoring all of that state preserves subsequent outputs and
171    /// state effects under the identical-suffix conditions above. No uncaptured
172    /// execution history may affect continuation. This does not promise that
173    /// recomputing the prefix with other partitions produces identical state.
174    /// The actual capture and native restore identities bind the adopted state;
175    /// a caller-supplied expected history is neither needed nor sufficient.
176    CapturedExecutionContinuation,
177    /// Reuse needs identical execution partitions, also in the matching identity.
178    SamePartitionOnly,
179    /// Legal repartitioning preserves all outputs and persistent state effects.
180    BitwiseEquivalent,
181    /// Legal repartitioning obeys the *owning provider descriptor's exact
182    /// operation_fingerprint* oracle against the same unpartitioned execution,
183    /// including every persistent state effect. This is not a new tolerance.
184    /// Plan validation must reject this declaration if that operation's oracle
185    /// does not actually cover the state effects. It does not weaken the
186    /// identical-partition bitwise continuation requirement.
187    OperationOracle,
188}
189
190/// Whether a successful frame may be captured at the end of its complete input.
191/// This is separate from the existing partial-input boundary declaration: an
192/// older provider has not promised that its final frame leaves resumable state.
193/// Even when supported, restoration still requires a legal nonempty suffix and
194/// all of the provider's input-dependency and numerical conditions.
195#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "snake_case")]
197pub enum CheckpointCompletedInputCapture {
198    #[default]
199    Unsupported,
200    Supported,
201}
202
203impl CheckpointCompletedInputCapture {
204    pub const fn is_unsupported(&self) -> bool {
205        matches!(self, Self::Unsupported)
206    }
207}
208
209/// An implementation promises all of its persistent state effects are complete
210/// at the permitted successful frame boundaries, and legal suffix execution can
211/// resume from those values without hidden execution history. Physical export
212/// mappings, operation-oracle coverage, and whole-program closure are separate
213/// plan checks; this declaration alone does not authorize restoration.
214#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
215pub struct ProviderCheckpointContract {
216    contract_version: ContractVersion,
217    input_dependency: CheckpointInputDependency,
218    boundaries: CheckpointBoundaryConstraint,
219    partition_numerics: CheckpointPartitionNumerics,
220    #[serde(skip_serializing_if = "CheckpointCompletedInputCapture::is_unsupported")]
221    completed_input_capture: CheckpointCompletedInputCapture,
222    #[serde(skip_serializing_if = "Vec::is_empty")]
223    state_ports: Vec<ProviderCheckpointStatePort>,
224}
225
226#[derive(Deserialize)]
227#[serde(deny_unknown_fields)]
228struct ProviderCheckpointContractWire {
229    contract_version: ContractVersion,
230    input_dependency: CheckpointInputDependency,
231    boundaries: CheckpointBoundaryConstraint,
232    partition_numerics: CheckpointPartitionNumerics,
233    #[serde(default)]
234    completed_input_capture: CheckpointCompletedInputCapture,
235    #[serde(default)]
236    state_ports: Vec<ProviderCheckpointStatePort>,
237}
238
239impl<'de> Deserialize<'de> for ProviderCheckpointContract {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: Deserializer<'de>,
243    {
244        let wire = ProviderCheckpointContractWire::deserialize(deserializer)?;
245        if wire.contract_version != PROVIDER_CHECKPOINT_CONTRACT_VERSION {
246            return Err(serde::de::Error::custom(format!(
247                "provider checkpoint contract version {} is unsupported",
248                wire.contract_version
249            )));
250        }
251        let original_ports = wire.state_ports.clone();
252        let contract = Self::new(
253            wire.input_dependency,
254            wire.boundaries,
255            wire.partition_numerics,
256        )
257        .with_completed_input_capture(wire.completed_input_capture)
258        .with_state_ports(wire.state_ports)
259        .map_err(serde::de::Error::custom)?;
260        if contract.state_ports != original_ports {
261            return Err(serde::de::Error::custom(
262                "checkpoint state ports are not canonical",
263            ));
264        }
265        Ok(contract)
266    }
267}
268
269impl ProviderCheckpointContract {
270    pub const fn new(
271        input_dependency: CheckpointInputDependency,
272        boundaries: CheckpointBoundaryConstraint,
273        partition_numerics: CheckpointPartitionNumerics,
274    ) -> Self {
275        Self {
276            contract_version: PROVIDER_CHECKPOINT_CONTRACT_VERSION,
277            input_dependency,
278            boundaries,
279            partition_numerics,
280            completed_input_capture: CheckpointCompletedInputCapture::Unsupported,
281            state_ports: Vec::new(),
282        }
283    }
284
285    pub const fn with_completed_input_capture(
286        mut self,
287        capability: CheckpointCompletedInputCapture,
288    ) -> Self {
289        self.completed_input_capture = capability;
290        self
291    }
292
293    pub const fn completed_input_capture(&self) -> CheckpointCompletedInputCapture {
294        self.completed_input_capture
295    }
296
297    pub fn with_state_ports(
298        mut self,
299        mut ports: Vec<ProviderCheckpointStatePort>,
300    ) -> Result<Self, VNextError> {
301        ports.sort_by_key(ProviderCheckpointStatePort::key);
302        if ports.windows(2).any(|pair| pair[0].key() == pair[1].key()) {
303            return Err(invalid_operation(
304                "duplicate checkpoint state port/storage ABI",
305            ));
306        }
307        self.state_ports = ports;
308        Ok(self)
309    }
310
311    pub fn state_ports(&self) -> &[ProviderCheckpointStatePort] {
312        &self.state_ports
313    }
314
315    pub const fn contract_version(&self) -> ContractVersion {
316        self.contract_version
317    }
318
319    pub const fn input_dependency(&self) -> CheckpointInputDependency {
320        self.input_dependency
321    }
322
323    pub const fn boundaries(&self) -> CheckpointBoundaryConstraint {
324        self.boundaries
325    }
326
327    pub const fn partition_numerics(&self) -> CheckpointPartitionNumerics {
328        self.partition_numerics
329    }
330}
331
332#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
333#[serde(rename_all = "snake_case", deny_unknown_fields)]
334pub enum ProviderCheckpointCapability {
335    #[default]
336    Unsupported,
337    CompletedBoundary(ProviderCheckpointContract),
338}
339
340impl ProviderCheckpointCapability {
341    pub const fn is_unsupported(&self) -> bool {
342        matches!(self, Self::Unsupported)
343    }
344}
345
346#[cfg(test)]
347mod tests;