Skip to main content

ferrum_interfaces/vnext/model/
checkpoint.rs

1//! Semantic declarations only: these do not authorize copying state or prove
2//! that a selected provider/storage layout implements checkpoint restoration.
3
4use serde::{Deserialize, Deserializer, Serialize};
5
6use super::{ContractVersion, StateLifetime, VNextError};
7
8mod inputs;
9pub use inputs::{ProgramCheckpointInputs, PROGRAM_CHECKPOINT_INPUTS_VERSION};
10
11pub const STATE_CHECKPOINT_CONTRACT_VERSION: ContractVersion = ContractVersion::new(1, 0);
12
13/// Token input that must remain identical when a completed state is reused.
14/// Both variants additionally require the same plan, numerical/position
15/// semantics, and every non-token conditioning input in the matching identity.
16/// An uncovered dependency makes the plan unsupported.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum CheckpointInputDependency {
20    /// The state at N is independent of tokens after N. A different legal
21    /// suffix may follow the exact token prefix [0, N).
22    ExactTokenPrefix,
23    /// The complete token input, including its length and suffix, affects the
24    /// state at N. Matching only [0, N) is insufficient.
25    EntireTokenInput,
26}
27
28/// Logical contents valid at one completed boundary N. Physical regions,
29/// padding, aliases, and initialization coverage must be resolved separately.
30/// This is not inferred from a capacity formula or tensor/state name.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum StateCheckpointContents {
34    /// The ordered logical positions [0, N), with one semantic tensor value
35    /// per position. Unwritten capacity after N is not part of the checkpoint.
36    PrefixPositions,
37    /// The entire semantic tensor value at exactly N, with no earlier values.
38    /// It cannot be shortened to represent an earlier boundary.
39    BoundaryValue,
40}
41
42/// Complete continuation state at a successful frame boundary. The program
43/// still has to account for *all* persistent effects and conditioning inputs;
44/// declaring one state does not establish that closure.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
46pub struct StateCheckpointContract {
47    contract_version: ContractVersion,
48    contents: StateCheckpointContents,
49    input_dependency: CheckpointInputDependency,
50}
51
52#[derive(Deserialize)]
53#[serde(deny_unknown_fields)]
54struct StateCheckpointContractWire {
55    contract_version: ContractVersion,
56    contents: StateCheckpointContents,
57    input_dependency: CheckpointInputDependency,
58}
59
60impl<'de> Deserialize<'de> for StateCheckpointContract {
61    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62    where
63        D: Deserializer<'de>,
64    {
65        let wire = StateCheckpointContractWire::deserialize(deserializer)?;
66        if wire.contract_version != STATE_CHECKPOINT_CONTRACT_VERSION {
67            return Err(serde::de::Error::custom(format!(
68                "state checkpoint contract version {} is unsupported",
69                wire.contract_version
70            )));
71        }
72        Ok(Self::new(wire.contents, wire.input_dependency))
73    }
74}
75
76impl StateCheckpointContract {
77    pub const fn new(
78        contents: StateCheckpointContents,
79        input_dependency: CheckpointInputDependency,
80    ) -> Self {
81        Self {
82            contract_version: STATE_CHECKPOINT_CONTRACT_VERSION,
83            contents,
84            input_dependency,
85        }
86    }
87
88    pub const fn contract_version(&self) -> ContractVersion {
89        self.contract_version
90    }
91
92    pub const fn contents(&self) -> StateCheckpointContents {
93        self.contents
94    }
95
96    pub const fn input_dependency(&self) -> CheckpointInputDependency {
97        self.input_dependency
98    }
99}
100
101/// Missing declarations remain unsupported, including on old wire payloads.
102/// Unsupported is omitted from StateSpec serialization so ordinary-compute
103/// identities remain unchanged; an explicit contract changes that identity.
104#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105#[serde(rename_all = "snake_case", deny_unknown_fields)]
106pub enum StateCheckpointCapability {
107    #[default]
108    Unsupported,
109    CompletedBoundary(StateCheckpointContract),
110}
111
112impl StateCheckpointCapability {
113    pub const fn is_unsupported(&self) -> bool {
114        matches!(self, Self::Unsupported)
115    }
116
117    pub(super) fn validate_lifetime(self, lifetime: StateLifetime) -> Result<(), VNextError> {
118        if !self.is_unsupported() && lifetime != StateLifetime::Sequence {
119            return Err(VNextError::InvalidExecutionPlan {
120                reason: "checkpoint contracts currently support only Sequence state; Request/Step state requires a separate closure contract".to_owned(),
121            });
122        }
123        Ok(())
124    }
125}
126
127#[cfg(test)]
128mod tests;