Closes coverage gaps found by spec review: issues and redactions were
treated as first-class by abstractions.adoc/faq.adoc but had no entity
requirement in model.adoc, receive()'s proposal argument had no shape
definition, and the tip invariant claimed readability by the current
binary when only the owning binary can actually parse a given entity
type.
adds: model.issue entity requirement (refs/meta/issues/)
adds: model.redaction entity requirement (refs/meta/redactions/)
adds: model.extensibility (schema extensibility is source-level, gate/receive are content-agnostic)
adds: receive.proposal-shape (receive()'s fourth argument)
adds: receive.redaction-admin-only (mirrors effect.admin-only)
adds: refs/meta/issues/* to meta-ref.granularity enumeration
rescopes: tip invariant to readable-by-owning-binary in abstractions.adoc and meta-ref.tip-invariant
No reviews of this commit yet — record a verdict below.
Start a review
docs/abstractions.adoc
@@ -42,7 +42,7 @@
Two trailers are reserved: `Schema-Version:` for explicit encoding detection if it is ever needed, and `Ents-Ref:` for refname binding (see 4).
Versioning or binding in the tree would pollute both the schema and the merge path.
-*Tip invariant:* the tip of a meta-ref is always readable by the current binary; history is archival.
+*Tip invariant:* the tip of a meta-ref is always readable by the binary that owns the entity type; a non-owning binary treats the tree as opaque and degrades to generic display (`git store show`); history is archival.
(Redacted entries are the one qualification: readers surface a withheld object as a redaction marker, never an error — see Derived.)
=== 3. Anchor
docs/spec/meta-ref.adoc
@@ -27,8 +27,9 @@
.Granularity Rule
--
A meta-ref MUST hold exactly one independently-authored entity:
-`refs/meta/member/*`, `refs/meta/comments/*`, `refs/meta/effects/*`, and
-`refs/meta/results/*` each decompose one ref per entity.
+`refs/meta/member/*`, `refs/meta/issues/*`, `refs/meta/comments/*`,
+`refs/meta/effects/*`, and `refs/meta/results/*` each decompose one ref
+per entity.
Repository-global state with a single writer-of-record MUST instead live
on one fixed ref, such as `refs/meta/account` or `refs/meta/config`.
Entities that different actors write concurrently MUST NOT share a ref.
@@ -78,8 +79,11 @@
[role="requirement", id="meta-ref.tip-invariant"]
.Tip Invariant
--
-The tip of a meta-ref MUST always be readable by the current binary;
-only history is archival.
+The tip of a meta-ref MUST always be readable by the binary that owns
+the entity type; a non-owning binary MUST treat the tree as opaque and
+degrade to generic display (`git store show`), never an error
+(<<model.extensibility>>).
+Only history is archival.
A reader that encounters a withheld (redacted) object while reading a
meta-ref's tip MUST surface a redaction marker, never an error
(<<receive.redaction-ingest>>) — the sole qualification to this
docs/spec/model.adoc
@@ -5,6 +5,22 @@
This file specifies that semantics — what each entity must mean, not its
field-by-field Rust representation.
+[role="requirement", id="model.extensibility"]
+.Schema Extensibility Is Source-Level
+--
+Extending an entity MUST mean changing its `#[derive(Facet)]` struct and
+recompiling the tools that interpret it; runtime-defined schemas MUST
+NOT be treated as a v1 goal.
+A facet shape MUST be compile-time-defined: runtime-readable, never
+runtime-constructible.
+This is possible because the gate and `receive` are content-agnostic —
+verification depends on signature, refname, trailer, and DAG descent
+(<<gate.tip-signed>> through <<gate.fast-forward>>), never on tree
+contents — so a stock server MUST be able to carry entity types it
+cannot parse; only the clients that interpret a given entity type need
+to recompile.
+--
+
'''''
=== Member
@@ -74,6 +90,21 @@
specified in `anchor.adoc` and apply to a Comment's anchor unchanged.
--
+=== Issue
+
+[role="requirement", id="model.issue"]
+.Issue Is a Typed Tree, One Ref Per Issue
+--
+An Issue entity MUST be a typed tree under its own ref in
+`refs/meta/issues/*`, one ref per issue (<<meta-ref.granularity>>),
+written, gated, synced, and audited exactly like a comment.
+An Issue entity MUST carry a title, a body, a state, assignees, and
+labels as struct fields; multiple assignees and custom states are
+schema, not platform features.
+This field list MAY be extended — extending the issue model is a
+typed-tree change, not a platform request (<<model.extensibility>>).
+--
+
=== Effect
[role="requirement", id="model.effect-definition"]
@@ -113,6 +144,20 @@
right.
--
+=== Redaction
+
+[role="requirement", id="model.redaction"]
+.Redaction Records a Yank, Not the Content
+--
+A Redaction entity MUST be a signed entity under `refs/meta/redactions/*`
+carrying the target object's oid, a human-readable reason, and the
+admin signature authorizing the yank (<<receive.redaction-admin-only>>).
+It MUST NOT carry the redacted content itself.
+Enforcement at ingest and the reader-facing marker are receive and
+meta-ref semantics, specified in <<receive.redaction-ingest>> and
+<<meta-ref.tip-invariant>>.
+--
+
=== Account
[role="requirement", id="model.account"]
docs/spec/receive.adoc
@@ -17,6 +17,19 @@
parameters, never duplicated in a caller.
--
+[role="requirement", id="receive.proposal-shape"]
+.Proposal Is the Fourth Argument's Shape
+--
+`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.
+--
+
[role="requirement", id="receive.shared-path"]
.One Receive, Every Frontend
--
@@ -89,6 +102,19 @@
state kept outside the repository.
--
+[role="requirement", id="receive.redaction-admin-only"]
+.Redaction Records Are Admin-Only
+--
+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>>.
+--
+
[role="requirement", id="receive.redaction-ingest"]
.Redaction Enforcement at Ingest
--