Skip to main content

eredu_runtime/
draft.rs

1//! Transactional ownership for embedded draft mutable state.
2
3/// Failure while executing one architecture-identified target or draft group.
4#[derive(Debug, thiserror::Error)]
5#[non_exhaustive]
6pub enum DraftGroupExecutionError<E> {
7    /// The selected architecture graph has no such group.
8    #[error("execution graph has no target or draft group {0:?}")]
9    UnknownGroup(String),
10    /// The architecture-owned group executor failed.
11    #[error("target or draft group execution failed")]
12    Execution(#[source] E),
13}
14
15/// Executes one exact graph identity against the state owned by a target or
16/// draft transaction.
17pub fn execute_draft_group<S, I, O, E>(
18    graph: &crate::ExecutionGraph,
19    group: &str,
20    input: I,
21    state: &mut S,
22    execute: impl FnOnce(usize, &str, I, &mut S) -> Result<O, E>,
23) -> Result<O, DraftGroupExecutionError<E>> {
24    let index = graph
25        .group_index(group)
26        .ok_or_else(|| DraftGroupExecutionError::UnknownGroup(group.to_owned()))?;
27    execute(index, graph.groups()[index].id(), input, state)
28        .map_err(DraftGroupExecutionError::Execution)
29}
30
31/// An exact speculative fork retaining both the pre-verification checkpoint
32/// and an independently advanceable draft state.
33///
34/// Model families supply ordinary cloneable runtime state; proposal,
35/// verification, commit, cancellation, and rejection all use this one neutral
36/// ownership boundary.
37#[derive(Debug, Clone)]
38pub struct DraftStateTransaction<S: Clone> {
39    checkpoint: S,
40    draft: S,
41}
42
43impl<S: Clone> DraftStateTransaction<S> {
44    /// Forks draft state and preserves an exact rollback checkpoint.
45    pub fn fork(state: &S) -> Self {
46        Self {
47            checkpoint: state.clone(),
48            draft: state.clone(),
49        }
50    }
51
52    /// Borrows the independently advanceable proposal state.
53    pub const fn draft(&self) -> &S {
54        &self.draft
55    }
56
57    /// Mutably borrows the independently advanceable proposal state.
58    pub fn draft_mut(&mut self) -> &mut S {
59        &mut self.draft
60    }
61
62    /// Borrows the exact state from before proposal and verification.
63    pub const fn checkpoint(&self) -> &S {
64        &self.checkpoint
65    }
66
67    /// Commits the advanced draft fork into canonical state.
68    pub fn commit_draft(self, canonical: &mut S) {
69        canonical.clone_from(&self.draft);
70    }
71
72    /// Restores canonical state after rejection, cancellation, or failed
73    /// verification.
74    pub fn rollback(self, canonical: &mut S) {
75        canonical.clone_from(&self.checkpoint);
76    }
77
78    /// Keeps target state already advanced by successful verification while
79    /// consuming the unused fork and checkpoint.
80    pub fn commit_verified(self) {}
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn one_transaction_owns_fork_commit_and_rollback() {
89        let mut canonical = vec![1, 2];
90        let mut commit = DraftStateTransaction::fork(&canonical);
91        commit.draft_mut().push(3);
92        commit.commit_draft(&mut canonical);
93        assert_eq!(canonical, [1, 2, 3]);
94
95        let mut rollback = DraftStateTransaction::fork(&canonical);
96        rollback.draft_mut().push(4);
97        canonical.push(9); // target verification advanced canonical state
98        rollback.rollback(&mut canonical);
99        assert_eq!(canonical, [1, 2, 3]);
100
101        let verified = DraftStateTransaction::fork(&canonical);
102        canonical.push(5);
103        verified.commit_verified();
104        assert_eq!(canonical, [1, 2, 3, 5]);
105    }
106
107    #[test]
108    fn prediction_and_external_draft_groups_drive_transactional_acceptance() {
109        let graph = crate::ExecutionGraph::new(
110            vec![
111                crate::ExecutionGroupSpec::root("target"),
112                crate::ExecutionGroupSpec::root("external-drafter"),
113                crate::ExecutionGroupSpec::with_dependencies(
114                    "prediction.0",
115                    ["target", "external-drafter"],
116                ),
117            ],
118            "prediction.0",
119        )
120        .unwrap();
121        let mut trace = Vec::new();
122        let execute = |group: &str, input: u32, state: &mut Vec<u32>, trace: &mut Vec<String>| {
123            execute_draft_group(
124                &graph,
125                group,
126                input,
127                state,
128                |group_index, id, input, state| {
129                    let output = match group_index {
130                        0 => input + 1,
131                        1 => input + 3,
132                        2 => input + 2,
133                        _ => unreachable!("fixture graph has exactly three groups"),
134                    };
135                    state.push(output);
136                    trace.push(id.to_owned());
137                    Ok::<_, std::convert::Infallible>(output)
138                },
139            )
140            .unwrap()
141        };
142
143        let mut canonical = vec![10];
144        let target_output = execute("target", 0, &mut canonical, &mut trace);
145        assert_eq!(target_output, 1);
146
147        let mut embedded = DraftStateTransaction::fork(&canonical);
148        let embedded_output = execute("prediction.0", 9, embedded.draft_mut(), &mut trace);
149        assert_eq!(embedded_output, 11);
150        embedded.commit_draft(&mut canonical);
151
152        let mut accepted_external = DraftStateTransaction::fork(&canonical);
153        let accepted = execute(
154            "external-drafter",
155            9,
156            accepted_external.draft_mut(),
157            &mut trace,
158        );
159        assert_eq!(accepted, 12);
160        accepted_external.commit_draft(&mut canonical);
161
162        let mut rejected_external = DraftStateTransaction::fork(&canonical);
163        let rejected = execute(
164            "external-drafter",
165            10,
166            rejected_external.draft_mut(),
167            &mut trace,
168        );
169        assert_eq!(rejected, 13);
170        rejected_external.rollback(&mut canonical);
171
172        assert_eq!(canonical, [10, 1, 11, 12]);
173        assert!(!canonical.contains(&rejected));
174        assert_eq!(
175            trace,
176            [
177                "target",
178                "prediction.0",
179                "external-drafter",
180                "external-drafter"
181            ]
182        );
183    }
184}