1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Immutable candidate checkpoint data, not authoritative HEAD, placement or dispatch.
use crate::{
boundary::Exit,
format::{CodecError, DaslCid},
graph::RestoredAgent,
preparation::InputBinding,
state::Continuation,
};
use alloc::vec::Vec;
/// Complete validated candidate closure plus source/event binding and exact block delta.
/// Creation does not accept the transition or authorize effects. The host must bind
/// authorized execution, place the required data, resolve CAS/event consumption and
/// accept this exact root before dispatching its embedded Command.
#[derive(Debug, Eq, PartialEq)]
pub struct CommitCandidate {
binding: InputBinding,
snapshot: RestoredAgent,
new_cids: Vec<DaslCid>,
}
impl CommitCandidate {
pub(crate) fn from_validated(
binding: InputBinding,
snapshot: RestoredAgent,
source: &RestoredAgent,
) -> Result<Self, CodecError> {
let mut new_cids = Vec::new();
for (cid, _) in snapshot.blocks() {
if !source.blocks().any(|(old, _)| old == cid) {
new_cids
.try_reserve(1)
.map_err(|_| CodecError::AllocationFailure)?;
new_cids.push(cid);
}
}
new_cids.sort_unstable();
Ok(Self {
binding,
snapshot,
new_cids,
})
}
pub fn binding(&self) -> InputBinding {
self.binding
}
pub fn new_root(&self) -> DaslCid {
self.snapshot.root()
}
pub fn disposition(&self) -> Continuation {
self.snapshot.state().continuation()
}
/// Validated DATA for inspection/recovery, never evidence of host acceptance.
pub fn snapshot(&self) -> &RestoredAgent {
&self.snapshot
}
/// The whole exact required closure, not just blocks newly authored by this run.
pub fn blocks(&self) -> impl Iterator<Item = (DaslCid, &[u8])> {
self.snapshot.blocks()
}
/// Exact unique delta relative to the source closure, ordered by binary CID.
/// Unchanged or previously retained Memory blocks are reused, not emitted twice.
pub fn new_blocks(&self) -> impl ExactSizeIterator<Item = (DaslCid, &[u8])> {
self.new_cids.iter().map(|cid| {
(
*cid,
self.snapshot
.blocks()
.find(|(key, _)| key == cid)
.expect("validated candidate delta")
.1,
)
})
}
/// Same bytes embedded in the final Boundary, not another mutable dispatch payload.
pub fn command_bytes(&self) -> Option<&[u8]> {
let episode = self.snapshot.current_episode()?;
let last = episode
.boundaries()
.last()
.expect("validated nonempty Episode");
match self
.snapshot
.boundary(last)
.expect("validated Boundary")
.exit()
{
Exit::AwaitAction(bytes) => Some(bytes),
Exit::AwaitActor => None,
}
}
/// Transfer checkpoint data only. The host must resolve acceptance before using
/// this as canonical continuation; a Rust snapshot does not establish HEAD.
pub fn into_snapshot(self) -> RestoredAgent {
self.snapshot
}
}