Skip to main content

clawft_kernel/
context_graft_state.rs

1//! Causal-model node lifecycle ([`NodeState`]) and cross-substrate mirroring
2//! for the session graft layer (ADR-058 / ADR-062 P0.2).
3//!
4//! Split out of `context_graft.rs` to keep that file under the 500-line cap.
5
6use super::SessionView;
7
8/// Node lifecycle state on the causal model (ADR-058 / ADR-056).
9///
10/// A grafted branch that coheres **collapses (commits)** to the trunk; one that
11/// does not is held off `main_line` and pruned — the same branch/merge/prune
12/// semantics as git ("causal collapse modeled on the git tree").
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum NodeState {
15    /// A candidate branch not yet grafted into the working set.
16    Speculative,
17    /// Grafted and live in the working window this run.
18    Frontier,
19    /// Promoted (collapsed) onto the durable trunk (L3).
20    Committed,
21    /// Aged out of the live window; origin retained on the chain, re-graftable.
22    Stale,
23    /// Contradicted/revised; dropped off `main_line`.
24    Pruned,
25}
26
27impl NodeState {
28    /// Short stable tag for metadata/serialization.
29    pub fn as_str(self) -> &'static str {
30        match self {
31            NodeState::Speculative => "speculative",
32            NodeState::Frontier => "frontier",
33            NodeState::Committed => "committed",
34            NodeState::Stale => "stale",
35            NodeState::Pruned => "pruned",
36        }
37    }
38
39    /// Whether `self → next` is a legal per-turn lifecycle transition
40    /// (ADR-062 D2). The spine is `Speculative → Frontier → Committed`;
41    /// soft/hard rebases drop to `Stale`/`Pruned`. `Pruned` is terminal.
42    ///
43    /// `Committed → Stale` models aging a committed node out of the live window
44    /// (it stays re-graftable on the chain); `Stale → Frontier` is the re-graft.
45    pub fn can_transition_to(self, next: NodeState) -> bool {
46        use NodeState::*;
47        matches!(
48            (self, next),
49            (Speculative, Frontier)
50                | (Speculative, Stale)
51                | (Speculative, Pruned)
52                | (Frontier, Committed)
53                | (Frontier, Stale)
54                | (Frontier, Pruned)
55                | (Committed, Stale)
56                | (Stale, Frontier)
57                | (Stale, Pruned)
58        )
59    }
60}
61
62/// Mirror a per-turn node-state transition across **both** substrates
63/// (ADR-062 P0.2): the authoritative L2 [`SessionView`] chunk and the durable
64/// [`CausalNode`](crate::causal::CausalNode) `metadata.state`.
65///
66/// The session view is the source of truth and gates the transition
67/// ([`SessionView::transition`]); only if that legal step lands is the state
68/// mirrored onto the causal node. Returns `true` only when **both** sides were
69/// updated; if the causal node is unknown the view has still advanced (the
70/// boolean reports the mirror was incomplete, so callers can re-sync).
71pub fn mirror_state(
72    view: &SessionView,
73    chain_seq: u64,
74    graph: &crate::causal::CausalGraph,
75    causal_node_id: crate::causal::NodeId,
76    next: NodeState,
77) -> bool {
78    if !view.transition(chain_seq, next) {
79        return false;
80    }
81    graph.set_node_state(causal_node_id, next.as_str())
82}