git-ents.gitmain
⌘K
foforge
lib.rs108 lines · 4.9 KB · rusthistorycomment on this file
1//! `receive`: the one write path every mutation frontend shares
2//! (`docs/spec/receive.adoc`).
3//!
4//! This crate's single responsibility is orchestration above traits that
5//! already exist by the time it lands: gate policy (mandatory hosted,
6//! advisory local), redaction enforcement at ingest, and effect-footprint
7//! matching plus enqueue — never the gate's own judgment (`ents-gate`),
8//! never the query algebra (`ents-query`), and never an executor
9//! (`ents-effect`, a later phase this crate must never link,
10//! `arch.query-effect-split`).
11//!
12//! # Spec coverage
13//!
14//! From `docs/spec/receive.adoc`:
15//!
16//! - `receive.unit`, `receive.shared-path` — [`receive`]: the sole
17//! mutation entry point, identical for every frontend; only the trait
18//! implementations and [`Mode`] differ.
19//! - `receive.proposal-shape` — [`Proposal`], [`RefTransition`],
20//! [`TransportAuth`].
21//! - `receive.refstore-seam` — [`receive`] takes `&dyn RefStore`, the full
22//! read/CAS seam (`arch.refstore-read-cas-split`).
23//! - `receive.object-access` — object access uses only `gix_object::Find`
24//! and `gix_object::Write`; see [`receive`]'s own doc for the one
25//! deliberate deviation (`gix_object::Exists` omitted — a fixture gap,
26//! not a design choice) and for the quarantine-directory note.
27//! - `receive.event-sink`, `receive.never-blocks` — [`EventSink`]; enqueue
28//! is the entire synchronous cost `receive` adds, and it is computed via
29//! each effect's static footprint, never a re-scan of every effect on
30//! every push.
31//! - `receive.dedup` — [`MemoryEventSink`]'s `(effect, oid)` set.
32//! - `receive.reconstructible` — [`reconcile`], the boot-time scan that
33//! rebuilds the exact obligations incremental `receive` calls would have
34//! enqueued, from repository state alone (`query.workset`).
35//! - `receive.redaction-admin-only` — a consequence of composition, not new
36//! code: `refs/meta/redactions/*` already falls through `ents-gate`'s
37//! default authorization arm, which requires admin-registered provenance
38//! for every namespace without its own carve-out; this crate's own test
39//! suite pins that composition at the `receive` level.
40//! - `receive.redaction-ingest` — [`receive`]'s first step: any proposal
41//! object matching a recorded redaction target refuses the whole batch.
42//!
43//! [`propose_entity`], [`propose_genesis`], and [`propose_delete`] are the
44//! shared mechanism every entity-mutation frontend builds its call to
45//! [`receive`] through: they serialize a typed tree and hand a signed
46//! commit to `receive`, whose ref name the gate recomputes from the signed
47//! content (`meta-ref.identity-binding`) — no commit trailer binds it. An
48//! owner-keyed mutation advances a known ref ([`propose_entity`]); the
49//! creation of a hash-identified entity names its ref from the genesis
50//! commit's own oid ([`propose_genesis`]). One place signs, one place
51//! calls `receive`, shared by `git-ents`'s `members`, `account`, `effect`,
52//! `toolchain`, `comment`, and `redact` commands (and, later,
53//! `ents-forge`'s own comment command) alike.
54//!
55//! # Examples
56//!
57//! An end-to-end local write path: advisory gate, null sink — the shape
58//! `receive.adoc`'s phase-4 exit criterion runs.
59//!
60//! ```
61//! use ents_gate::Config;
62//! use ents_model::{Provenance, namespace};
63//! use ents_receive::{Mode, NullEventSink, Proposal, RefTransition, TxResult, receive};
64//! use ents_testutil::{Keypair, MemRefStore, ObjectStore, enroll_member, write_meta_entity};
65//!
66//! let refs = MemRefStore::default();
67//! let objects = ObjectStore::default();
68//! let admin = Keypair::from_seed(1);
69//!
70//! enroll_member(&refs, &objects, "admin", &admin, Provenance::AdminRegistered, 100);
71//! let config_ref: gix::refs::FullName = namespace::CONFIG_REF.try_into().expect("valid");
72//! let tip = write_meta_entity(
73//! &refs, &objects, config_ref.clone(), &Config { epoch: Some(200) }, Some(&admin), 200,
74//! );
75//!
76//! // The fixture already moved the ref; re-propose the same tip through
77//! // `receive` against a pre-write copy, the way a CLI would.
78//! let before = refs.fetched_copy();
79//! before.remove(config_ref.as_ref());
80//! let proposal = Proposal {
81//! transitions: vec![RefTransition { name: config_ref, old: None, new: Some(tip) }],
82//! objects: vec![tip],
83//! auth: None,
84//! };
85//!
86//! let outcome = receive(&before, &objects, &NullEventSink, &proposal, Mode::Advisory)
87//! .expect("evaluates");
88//! assert_eq!(outcome.result, TxResult::Applied);
89//! ```
90
91mod error;
92mod outcome;
93mod proposal;
94mod propose;
95mod receive;
96mod reconcile;
97mod sink;
98
99pub use error::{Error, Result};
100pub use outcome::{Mode, Outcome, TxResult};
101pub use proposal::{Proposal, RefTransition, TransportAuth};
102pub use propose::{
103 Identity, entity_transition, propose_delete, propose_entity, propose_entity_with_pin,
104 propose_genesis, propose_genesis_retaining, propose_pin,
105};
106pub use receive::receive;
107pub use reconcile::reconcile;
108pub use sink::{EventSink, MemoryEventSink, NullEventSink};