receive(refs, objects, events, proposal) MUST be the sole entry point
through which a meta-ref or branch ref is mutated.
Gate evaluation (<<gate.tip-signed>> through <<gate.atomic-cas>>), effect
matching, and enqueue MUST live inside receive, above its trait
parameters, never duplicated in a caller.
This requirement governs origination — proposing new state to a store.
Replicating refs a trusted remote has already admitted through its own
receive (<<sync.forge-transfer>>) MAY apply them directly: the source’s
gate already judged that state, so re-verification on fetch is an opt-in
audit rather than an obligation, and the work set stays reconstructible
by scan regardless (<<receive.reconstructible>>).
Commits the replicating machinery authors itself — divergence and
adoption merges (<<gate.same-actor-divergence>>, <<gate.adoption-merge>>)
— are origination, not replication.
docs/spec/receive.adoc
Receive
"Push" conflates object transfer with a verified ref transaction; locally,
transfer is vacuous.
The library exposes exactly one unit for mutation: receive(refs, objects,
events, proposal).
Gate evaluation, effect matching, and enqueue live inside receive, above
the RefStore, object-store, and EventSink traits it is handed.
receive’s `proposal argument MUST carry: the set of proposed ref
transitions, each a (refname, old-oid, new-oid) triple; the objects
accompanying those transitions; and any transport-auth evidence the
frontend collected (a signed-push credential, a smart-HTTP session), or
none, for frontends where the transport itself carries no separate
authentication.
Gate evaluation (<<receive.unit>>) MUST be checkable against exactly
this shape, independent of which frontend constructed the proposal.
This transport-auth evidence is a connection-level ACL input for
refs/heads/* (<<gate.principled-split>>) only; gate evaluation for
meta-ref admission MUST ignore it entirely and MUST NOT consult it in
place of the tip invariant (<<gate.signature-artifact>>).
No policy for that refs/heads/* ACL input is specified yet
(<<gate.branch-acl-undefined>>); receive MUST thread the evidence
through uninterpreted until one is.
Some entities are declared to require more than one ref at once as part
of their canonical shape — a review and its retention pin are the
motivating case: two refs that MUST both exist for the review to exist
at all.
Creating or updating such an entity MUST carry every one of its refs'
transitions in a single Proposal (<<receive.proposal-shape>>) through
one call to receive, never as two or more independent proposals.
The ref-store’s atomic multi-ref compare-and-swap
(<<arch.refstore-read-cas-split>>) then admits or refuses the whole
batch together, so such an entity is never observable with only some of
its refs written.
This is distinct from the inbox/canonical relationship
(<<meta-ref.inbox>>), where a contributor’s ref and the canonical ref
are deliberately written at different times by different actors, not as
one unit.
The RefStore seam receive is handed MUST satisfy the read/CAS split
specified in <<arch.refstore-read-cas-split>>.
Object access inside receive MUST use gix_object::Find, Exists, and
Write; a private object-access trait MUST NOT be introduced where
gitoxide already has one.
A receive running inside a git hook MUST read existing repository state
through the common object directory, never through the quarantine
directory git injects via GIT_OBJECT_DIRECTORY during a push:
quarantined incoming objects are the proposal under verification, not
durable state, and MUST NOT be treated as stored until the transaction
commits.
EventSink MUST be the sole destination for post-receive matches: null
locally, a durable queue hosted.
receive MUST statically extract each effect query’s ref footprint so a
ref transition maps to the affected queries, and MUST enqueue
re-evaluation for each match.
receive MUST NOT block a push on effect evaluation; the durable enqueue
in <<receive.event-sink>> MUST be the entire synchronous cost.
Post-receive MUST remain a dumb matcher with no evaluation logic of its
own.
The queue fed by receive is at-least-once; the dedup key MUST be
(effect, oid), so that redelivery yields exactly-once outcomes with zero
state kept outside the repository.
The set of pending obligations an EventSink enqueues MUST be derivable
from repository state alone, per the work set defined in
<<query.workset>>.
An EventSink implementation MAY lose queued events on crash, provided
the composition root reconciles obligations from repository state at
startup before serving further pushes.
A durable queue MUST be treated as a performance optimization, never a
correctness requirement; the dedup key in <<receive.dedup>> is unchanged
by reconciliation.
A push to refs/meta/redactions/* MUST be rejected unless the pushing
member is admin-registered, regardless of any other role rule
refs/meta/config may define.
Authorizing a yank withholds bytes from every future reader of the
repository, which needs more trust than an ordinary branch push, so
this rule MUST be enforced explicitly and MUST be statable as the
single refname glob refs/meta/redactions/*, mirroring
<<effect.admin-only>>.
receive MUST check every incoming object against the redaction list
recorded under refs/meta/redactions/* at ingest time, so a redacted hole
cannot be silently refilled by re-pushing the same bytes.
This binds new admission only: for an object redacted after it was already
accepted, receive refusing future pushes of the same bytes does not by
itself withhold the bytes already sitting in the object store.
A redacted object’s bytes MUST be withheld from the object store and from
every generated pack; the oid MUST remain in history as evidence.
Guaranteeing this for bytes already stored before their redaction is not
`receive’s job — it belongs to whichever component actually generates
outgoing packs, which is not yet specified for every root
(<<roots.redaction-pack-serving>>).
A reader resolving a redacted object MUST receive a redaction marker,
never an error.