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}