ferrum_interfaces/vnext/operation/
checkpoint.rs1use 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#[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#[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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum CheckpointPartitionNumerics {
169 CapturedExecutionContinuation,
177 SamePartitionOnly,
179 BitwiseEquivalent,
181 OperationOracle,
188}
189
190#[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#[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;