Skip to main content

choir_queue/
landing.rs

1//! How a landing is written into the log (D68).
2//!
3//! The queue decides *what* landed and in *what order*; it does not
4//! know what a landing means to whoever is running it. For a forge
5//! bridge it means nothing outside the round -- upstream is canonical
6//! (D21) and the ordering is the queue's own bookkeeping. For a node
7//! it means the branch moved, which is the whole point, and the op that
8//! says so has to be one the daemon's policy will accept.
9//!
10//! Hence a seam rather than a hardcoded op, for the same reason
11//! [`crate::speculate::Speculator`] is one: the two callers disagree
12//! about the answer, and the disagreement is not a parameter.
13//!
14//! **A refusal is a real outcome, not an error path.** The unsigned
15//! [`choir_sequencer::SequencerHandle::submit`] panics when a policy
16//! rejects, so a queue that landed by calling it would take the process
17//! down the first time it met a daemon that demands signatures. Every
18//! implementation here answers with a `Result` and the queue stops the
19//! round on `Err`, because a landing the log refused means the base the
20//! rest of the train was speculating on is not the base the log has.
21
22use choir_hash::ContentHash;
23use choir_sequencer::SequencerHandle;
24
25use crate::Change;
26
27/// How the queue records that a change landed.
28pub trait Landing: Send {
29    /// The implementation's name, for reports and journals.
30    fn name(&self) -> &'static str;
31
32    /// Records that `change` landed, with the merged state named by
33    /// `commit`.
34    ///
35    /// Called once per landing, in landing order, so an implementation
36    /// that needs the previous value for a compare-and-swap can keep it
37    /// between calls.
38    ///
39    /// # Errors
40    ///
41    /// The sequencer refused the op: an unsigned op under a policy that
42    /// demands a signature, a failed CAS because something else moved
43    /// the target first, or a quota. Every one of them voids the rest
44    /// of the round rather than the one change, because the changes
45    /// behind it were merged onto a state the log did not accept.
46    fn record(
47        &mut self,
48        handle: &SequencerHandle,
49        change: &Change,
50        commit: ContentHash,
51    ) -> Result<(), String>;
52}
53
54/// The in-memory landing: the change's workspace now holds this state.
55///
56/// The default, and what a caller whose repository is somebody else's
57/// wants. `prev` is `None` because a change lands at most once -- the
58/// already-landed identity check refuses a resubmission, so the head
59/// this names does not exist yet -- and because the ordering it records
60/// is the queue's own, which nothing outside the round reads back.
61#[derive(Debug, Default, Clone, Copy)]
62pub struct WorkspaceLanding;
63
64impl Landing for WorkspaceLanding {
65    fn name(&self) -> &'static str {
66        "workspace"
67    }
68
69    fn record(
70        &mut self,
71        handle: &SequencerHandle,
72        change: &Change,
73        commit: ContentHash,
74    ) -> Result<(), String> {
75        let op = choir_view::ViewOp::new(choir_view::OpKind::SetWorkspaceHead {
76            workspace: change.workspace.clone(),
77            commit,
78            prev: None,
79        });
80        let payload = serde_json::to_vec(&op).expect("a ViewOp serializes");
81        handle
82            .try_submit(&change.workspace, payload, None)
83            .map(|_| ())
84    }
85}