git-ents.gitmain
⌘K
foforge
commit 8848919
docs: merge spec-review fixes into main

Applies the accepted findings from the multi-agent spec-vs-authority review: results-field and receive-polarity contradictions, issue and redaction coverage gaps, and single-owner rule ids.

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

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 @@ -126,10 +126,11 @@ trigger: CommitQuery, // a commit set as a function of ref state toolchains: Vec<ToolchainRef>, // e.g. ["rust-1.88"] run: Command, - results: RefName, // e.g. "refs/meta/results/<name>/*" } ---- +Results location is derived by convention, never declared by the effect: an effect cannot choose where its verdicts land. + *Trigger semantics:* the trigger denotes a set of commits; the effect fires once per commit that *enters* the set. The query algebra is deliberately tiny:
docs/development-plan.adoc @@ -45,7 +45,7 @@ | Medium | sonnet | Read/CAS trait split per `arch.refstore-read-cas-split`; loose-ref impl - ports the CAS discipline from `pre-redo` (`receive.loose-cas-discipline`: + ports the CAS discipline from `pre-redo` (`arch.loose-cas-discipline`: no shelling to `git update-ref`). Fable reviews the trait before phase 2 consumes it.
docs/spec/gate.adoc @@ -1,8 +1,8 @@ == The Gate Verification is a pure function over ref-store reads: it MUST depend only on -the read half of the ref store, never on write access or on any state -outside `refs/meta/*`. +the read half of the `RefStore` seam (<<arch.refstore-read-cas-split>>), +never on write access or on any state outside `refs/meta/*`. The gate is the one admission judgment in the design; every mutation, human or machine, is admitted or refused by this same function (see <<receive.unit>>).
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/overview.adoc @@ -228,6 +228,14 @@ performs a write. -- +[role="requirement", id="arch.loose-cas-discipline"] +.Loose Ref CAS Discipline +-- +A loose-ref `RefStore` implementation MUST write refs through its own +compare-and-swap discipline and MUST NOT shell out to `git update-ref`, +so local mutations honor the same CAS guarantee hosted storage provides. +-- + The honesty test for these boundaries — a third single-node composition root constructible from library crates alone — is a deployment requirement and lives in `roots.adoc` as <<roots.honesty-test>>.
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 -- @@ -27,25 +40,19 @@ object database. Smart-HTTP MUST unpack the incoming wire pack into the `ObjectStore` before calling `receive`. -Local and hosted MUST NOT diverge into separate push paths; they share -`receive`, which is the correctness anchor for writes. +Local and hosted MUST NOT share a push path; they MUST share `receive`, +with only the trait implementations swapped, which is the correctness +anchor for writes. -- [role="requirement", id="receive.refstore-seam"] .RefStore Seam -- -The `RefStore` seam MUST expose reads and atomic multi-ref -compare-and-swap, with reads split into their own trait so a read-only -caller (<<gate.tip-signed>>) cannot depend on write access. +The `RefStore` seam `receive` is handed MUST satisfy the read/CAS split +specified in <<arch.refstore-read-cas-split>>. -- -[role="requirement", id="receive.loose-cas-discipline"] -.Loose Ref CAS Discipline --- -A loose-ref `RefStore` implementation MUST write refs through its own -compare-and-swap discipline and MUST NOT shell out to `git update-ref`, -so local mutations honor the same CAS guarantee hosted storage provides. --- +// moved: receive.loose-cas-discipline -> arch.loose-cas-discipline (overview.adoc) [role="requirement", id="receive.object-access"] .Object Access Via gitoxide @@ -88,6 +95,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 --
docs/spec/roots.adoc @@ -10,11 +10,11 @@ [role="requirement", id="roots.composition"] .Deployment Lives Only at the Root -- -The four seams (`RefStore`, the object store via `gix_object::Find` / -`Exists` / `Write`, `EventSink`, `Executor`) MUST be wired together only in -a composition root. -Library code outside a root MUST NOT select among implementations of -these seams. +A composition root is the sole place all four seams — `RefStore`, the +object store via `gix_object::Find` / `Exists` / `Write`, `EventSink`, +`Executor` — are wired together, applying <<arch.store-composition-root>> +and <<arch.no-hosted-branch>> across the full seam set, not just store +implementations. -- [role="requirement", id="roots.local"]
docs/spec/sync.adoc @@ -55,7 +55,7 @@ [role="requirement", id="sync.local-advisory"] .Local Store Accepts Regardless of Verdict -- -The local store MUST accept a write regardless of the gate's verdict; the -gate's advisory role (<<gate.advisory-local>>) governs annotation only, and -sync MUST NOT use a failing local verdict to block a local write. +Sync MUST honor the gate's advisory role locally (<<gate.advisory-local>>): +it MUST NOT use a failing local verdict to block a local write. +The rejection consequence sync owns instead is <<sync.inbox-routing>>. --