crates/kernel/ents-receive/src/proposal.rs
proposal.rshistorycomment on this file
| 1 | //! The fourth argument's shape (`receive.proposal-shape`): the ref |
| 2 | //! transitions a caller proposes, the new objects that accompany them, and |
| 3 | //! any transport-auth evidence the frontend collected. |
| 4 | |
| 5 | use gix::refs::FullName; |
| 6 | use gix_hash::ObjectId; |
| 7 | |
| 8 | /// One proposed ref transition: `(refname, old-oid, new-oid)`, exactly the |
| 9 | /// triple `receive.proposal-shape` names. |
| 10 | /// |
| 11 | /// `old` is the frontier the *proposal* claims — what a `git push` command |
| 12 | /// line reports as its own base, or what a local UI last read. [`crate::receive`] |
| 13 | /// never trusts it for admission — [`ents_gate::verify`] re-reads the actual |
| 14 | /// current tip itself (the same snapshot every other check uses) — nor does |
| 15 | /// it enforce it: unlike git's `receive-pack`, which refuses a push whose |
| 16 | /// old-oid is stale, a stale `old` whose `new` still descends from the real |
| 17 | /// tip applies cleanly, and one that does not is refused by the gate's own |
| 18 | /// fast-forward check against the re-read tip, never by comparing this field. |
| 19 | /// |
| 20 | /// # Examples |
| 21 | /// |
| 22 | /// ``` |
| 23 | /// use ents_receive::RefTransition; |
| 24 | /// |
| 25 | /// let transition = RefTransition { |
| 26 | /// name: "refs/meta/issues/1".try_into().expect("valid"), |
| 27 | /// old: None, |
| 28 | /// new: Some(gix_hash::ObjectId::null(gix_hash::Kind::Sha1)), |
| 29 | /// }; |
| 30 | /// assert!(transition.old.is_none()); |
| 31 | /// ``` |
| 32 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 33 | pub struct RefTransition { |
| 34 | /// The ref being updated. |
| 35 | pub name: FullName, |
| 36 | /// The tip the proposal claims as its base, or `None` for creation. |
| 37 | pub old: Option<ObjectId>, |
| 38 | /// The proposed new tip, or `None` to delete the ref. |
| 39 | pub new: Option<ObjectId>, |
| 40 | } |
| 41 | |
| 42 | /// Transport-level authentication evidence a frontend collected: a |
| 43 | /// signed-push credential, a smart-HTTP session, or nothing for a frontend |
| 44 | /// whose transport carries no separate authentication (`receive.proposal-shape`). |
| 45 | /// |
| 46 | /// This is a connection-level ACL input for `refs/heads/*` only |
| 47 | /// (`gate.principled-split`) — no such ACL policy is defined yet anywhere in |
| 48 | /// the spec, so [`crate::receive`] accepts and threads this value through |
| 49 | /// without interpreting it; it is never substituted for the tip invariant on |
| 50 | /// a `refs/meta/*` update, which is the one thing `receive.proposal-shape` |
| 51 | /// actually requires today. Defining and enforcing the `refs/heads/*` ACL |
| 52 | /// itself is future work with no requirement id yet to hang it on. |
| 53 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 54 | pub struct TransportAuth { |
| 55 | /// Opaque evidence bytes: a signed-push certificate, a session token, |
| 56 | /// or whatever shape a future frontend needs. `receive` never parses |
| 57 | /// this; only a future `refs/heads/*` ACL check would. |
| 58 | pub evidence: Vec<u8>, |
| 59 | } |
| 60 | |
| 61 | /// The fourth argument to [`crate::receive`]: every proposed ref transition |
| 62 | /// this call attempts, the object ids the proposal introduces, and any |
| 63 | /// transport-auth evidence (`receive.proposal-shape`). |
| 64 | /// |
| 65 | /// # Examples |
| 66 | /// |
| 67 | /// ``` |
| 68 | /// use ents_receive::{Proposal, RefTransition}; |
| 69 | /// |
| 70 | /// let proposal = Proposal { |
| 71 | /// transitions: vec![RefTransition { |
| 72 | /// name: "refs/meta/issues/1".try_into().expect("valid"), |
| 73 | /// old: None, |
| 74 | /// new: Some(gix_hash::ObjectId::null(gix_hash::Kind::Sha1)), |
| 75 | /// }], |
| 76 | /// objects: vec![gix_hash::ObjectId::null(gix_hash::Kind::Sha1)], |
| 77 | /// auth: None, |
| 78 | /// }; |
| 79 | /// assert_eq!(proposal.transitions.len(), 1); |
| 80 | /// ``` |
| 81 | // @relation(receive.proposal-shape, scope=file) |
| 82 | #[derive(Debug, Clone, PartialEq, Eq, Default)] |
| 83 | pub struct Proposal { |
| 84 | /// The proposed ref transitions this call attempts, applied together |
| 85 | /// as one atomic batch (`arch.refstore-read-cas-split`, |
| 86 | /// `gate.atomic-cas`). |
| 87 | pub transitions: Vec<RefTransition>, |
| 88 | /// The object ids this proposal introduces, already durably present in |
| 89 | /// the object store [`crate::receive`] is handed (the frontend's job |
| 90 | /// per `receive.shared-path`: the CLI and local UI write directly, and |
| 91 | /// smart-HTTP unpacks the incoming pack, before `receive` is ever |
| 92 | /// called). `receive` checks each of these against the redaction list |
| 93 | /// at ingest time (`receive.redaction-ingest`). |
| 94 | pub objects: Vec<ObjectId>, |
| 95 | /// Transport-auth evidence the frontend collected, or `None`. See |
| 96 | /// [`TransportAuth`] for why `receive` does not interpret this today. |
| 97 | pub auth: Option<TransportAuth>, |
| 98 | } |