git-ents.gitmain
⌘K
foforge
commit 76888a3
spec: self-run namespace, member-scoped inboxes, adoption rule re-home, query.rev subset

Amends the .adoc requirement blocks in place rather than adding new history: results-mirror refs move to refs/meta/self/<member>/<effect>/<short-oid>, inbox refs gain a member segment at refs/meta/inbox/<member>/<id> and are owner-only, gate.adoption-no-cherry-pick is re-homed as sync.adoption-no-cherry-pick, and query.rev is narrowed to the rev() subset the evaluator actually implements. Folds the abstractions.adoc, faq.adoc, and development-plan.adoc corrections that followed from the same fixes.

Assisted-by: Claude:claude-sonnet-5

Joseph D. Carpinelli · 1 month ago

Reviews

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

Start a review

verdict

docs/abstractions.adoc @@ -25,7 +25,7 @@ Two namespaces are consequences of the granularity rule plus the gate (5): * `refs/meta/inbox/*` — entities authored by someone not authorized for a canonical ref, awaiting adoption. -* `refs/meta/results/~<member>/*` — results produced by a member's own executor rather than a designated worker. +* `refs/meta/self/<member>/*` — results produced by a member's own executor rather than a designated worker. Both hold the same typed trees as their canonical counterparts; only the refname rule differs. @@ -172,7 +172,7 @@ *Identity discipline:* the runner is a member, not an ambient authority. A result signature proves who pushed the result, not that the run was faithful; official results are official because canonical results refs are writable only by designated worker keys — a refname rule, not a runtime property. -Any member may run any effect on their own executor and account; their results land in `refs/meta/results/~<member>/*` or the inbox, adoptable by merge like everything else, with the trust decision explicit in the adoption. +Any member may run any effect on their own executor and account; their results land in `refs/meta/self/<member>/*` or the inbox, adoptable by merge like everything else, with the trust decision explicit in the adoption. Anyone can run CI; nobody can impersonate the verdict. *Where the line holds:* no content predicates beyond `results(…, status)`, no time atoms, no external-event atoms.
docs/development-plan.adoc @@ -87,7 +87,8 @@ | sonnet | Entity structs, namespaces, trailers, taxonomy — declarative, but it is the vocabulary every crate imports, so spec fidelity and the public API - review matter more than the code. Absorbs git-metadata's trailer work. + review matter more than the code. Trailer parsing is new work, not a + port: no `git-metadata` crate exists at `pre-redo`. | `ents-gate` | 3
docs/faq.adoc @@ -178,7 +178,7 @@ Cherry-pick is forbidden as an adoption verb because it would destroy the signature it is meant to honor. *I want to prove my patch passes CI, without any access.* -Run the project's real effects on your own executor; results land in your namespace (`refs/meta/results/~you/*`), signed by you, shareable and verifiable. +Run the project's real effects on your own executor; results land in your namespace (`refs/meta/self/you/*`), signed by you, shareable and verifiable. A maintainer can adopt them, with the trust decision explicit in the adoption merge. *I want to know why this feels like the old email-patch workflow.*
docs/spec/effect.adoc @@ -148,7 +148,7 @@ -- Any member MAY run any effect on their own executor and account. Such a member's results MUST land under the self-run namespace -`refs/meta/results/~<member>/<effect>/<short-oid>` (<<meta-ref.inbox>>), +`refs/meta/self/<member>/<effect>/<short-oid>` (<<meta-ref.inbox>>), never directly on the canonical results ref, and MUST be adoptable onto the canonical ref only through the same adoption merge as any other contribution (<<gate.adoption-merge>>,
docs/spec/gate.adoc @@ -13,9 +13,13 @@ .Tip Signature -- The new tip of a meta-ref MUST be signed by a member authorized for that -refname. -This is checkable after the fact by anyone with a clone, not only by -whoever ran the server. +refname, judged against the member entity in force at acceptance time — +the member ref's tip in the same snapshot the gate reads +(<<model.member-revocation>>). +Anyone with a clone can run the same judgment against the same snapshot, +not only whoever ran the server; reconstructing what a past acceptance +saw is an audit function over the deployment's out-of-scope op log, not +a gate path. -- [role="requirement", id="gate.refname-binding"] @@ -45,8 +49,12 @@ .Signature as Repository Data -- A commit signature MUST be treated as a data artifact, not a transport -artifact: it replicates with the repository and MUST verify offline in -every clone, so verification evidence is itself repository state. +artifact: it replicates with the repository and MUST verify +cryptographically offline in every clone, so the signature evidence is +itself repository state. +Whether the signing key was authorized is a property of the snapshot the +gate reads (<<gate.policy-as-state>>, <<model.member-revocation>>), not +of the signature bytes. A push certificate carries no meta-ref semantics; it MUST NOT be consulted by the gate. -- @@ -124,6 +132,15 @@ === Adoption +The requirements below are consequences the gate enforces by judging only +the tip plus DAG descent (this file's own introduction: verification is a +pure function over ref-store reads). The prohibition on cherry-picking as +an adoption mechanism is not among them: a cherry-picked commit, signed +by the authorized member placing it, satisfies <<gate.tip-signed>> exactly as +a hand-authored commit would, so no function over the resulting ref state +can tell the two apart. That prohibition binds the adoption tooling +instead — <<sync.adoption-no-cherry-pick>>. + [role="requirement", id="gate.adoption-merge"] .Adoption Is a Merge -- @@ -135,14 +152,6 @@ commit remains in ancestry with attribution intact. -- -[role="requirement", id="gate.adoption-no-cherry-pick"] -.Cherry-Pick Forbidden as Adoption --- -Cherry-picking a contributor's commit MUST NOT be used as an adoption -mechanism: it creates a new commit object and destroys the original -author's signature. --- - [role="requirement", id="gate.adoption-no-fast-forward"] .Direct Fast-Forward Forbidden --
docs/spec/meta-ref.adoc @@ -41,9 +41,13 @@ [role="requirement", id="meta-ref.inbox"] .Inbox and Self-Run Namespaces -- -`refs/meta/inbox/<member>/<canonical-suffix>` MUST hold entities authored -by someone not authorized for the corresponding canonical ref, awaiting -adoption. +`refs/meta/inbox/<member>/<canonical-suffix>` MUST hold one entity +authored by `<member>`, who is not authorized for the corresponding +canonical ref, awaiting adoption; `<member>` MUST be the leading segment, +symmetric with `refs/meta/self/<member>/*` below, so authorization +(<<gate.tip-signed>>) keys off the refname alone, matching the granularity +rule's one-entity-per-ref shape (<<meta-ref.granularity>>) instead of +leaving `<member>` unencoded. `<canonical-suffix>` MUST be the corresponding canonical ref's entire path below `refs/meta/`, not a bare entity id: `refs/meta/issues/42` routes to `refs/meta/inbox/<member>/issues/42`, and a multi-segment canonical ref @@ -52,10 +56,28 @@ `refs/meta/inbox/<member>/results/<effect>/<short-oid>` in full — the namespace segment MUST be preserved so two different entity kinds can never collide under the same inbox id. -`refs/meta/results/~<member>/<effect>/<short-oid>` MUST hold results a +`<member>` MUST be authorized to create and update refs under only its +own `refs/meta/inbox/<member>/*` segment; no member, including an +admin-registered one, MAY write into another member's inbox segment. +Adoption happens by an authorized member merging the inbox entity onto +the canonical ref (<<gate.adoption-merge>>, <<sync.adoption-machinery>>), +never by writing the contributor's inbox ref directly, so admin write access +to another member's segment is never needed. +An inbox ref MUST NOT be deleted on adoption or at any other time: it +remains the contributor's own audit trail, exactly as a member entity is +never deleted on revocation (<<model.member-revocation>>). +`refs/meta/self/<member>/<effect>/<short-oid>` MUST hold results a member produced on their own executor rather than a designated worker, mirroring the canonical results pattern (<<effect.results-writeback>>) under the member's own namespace. +`self` MUST be its own top-level namespace under `refs/meta/*`, a fixed +segment from the spec's own namespace table (<<meta-ref.namespace>>) rather +than a marker nested inside `refs/meta/results/*`: an effect name and a +member id are both otherwise-unconstrained ref-path segments +(<<effect.definition>>), so a marker sharing their position could collide +with one; a sibling top-level segment cannot, and it keeps the canonical +results glob (`refs/meta/results/<effect>/*`, <<effect.official>>) and the +self-run glob (`refs/meta/self/<member>/*`) disjoint by construction. Both namespaces MUST hold the same typed trees as their canonical counterparts (<<meta-ref.typed-tree>>); only the refname rule differs. --
docs/spec/model.adoc @@ -42,10 +42,19 @@ -- Revoking a member MUST record a revoked state on the member entity and MUST NOT delete the entity. -A revoked key MUST be explicitly rejected for verifying any signature -made after revocation, while a signature the key made before revocation -MUST remain verifiable — deleting the entity instead would make old -signatures unverifiable, the opposite of what an audit needs. +Admission MUST consult the member entity currently in force — the tip of +the member's ref in the same snapshot the gate reads (<<gate.tip-signed>>) — +so a revoked key's new pushes are refused from the moment the revocation +lands, regardless of any committer timestamp the pushed commit claims. +A ref accepted before the revocation landed MUST remain valid: acceptance +is never re-judged. +The moment of acceptance is witnessed by the deployment's op log, which is +out of scope for this specification and for every crate in this +repository; verifying what a key signed while it was valid is an audit +function over that witness and the retained entity history, never a gate +admission path. +The entity MUST be retained rather than deleted precisely so that audit +stays possible. Unrevoking a member MUST be supported, returning the key to authorizing new signatures without altering the record of the period it was revoked. --
docs/spec/query.adoc @@ -39,10 +39,20 @@ [role="requirement", id="query.rev"] .rev() Over Code Refs -- -`rev(expr)` MUST denote the commit set produced by evaluating `expr` as an -ordinary Git revspec or ref glob against refs outside `refs/meta/*` — -`refs/heads/*`, `refs/tags/*`, a range such as `main ^release`, an ancestry -expression, or a merge-base expression. +`rev(expr)` MUST denote the commit set produced by evaluating `expr` +against refs outside `refs/meta/*` using the rev-list-shaped subset of +gitrevisions(7): a refname, short (resolved through the standard +gitrevisions lookup order) or full, such as `refs/heads/main`; a ref glob +in full `refs/...` form, such as `refs/heads/*`; a full hex object id; a +`^`-negated term excluding its ancestry; and `A..B` two-dot sugar for +`^A B` — a range such as `main ^release`. +`~n`/`^n` ancestry suffixes, `A...B` merge-base (symmetric-difference) +expressions, `@{...}` reflog or upstream syntax, and abbreviated hex MUST +each be rejected as a malformed query (<<effect.validation>>), never +silently evaluated to the empty set or to the wrong set. Growing this +subset to cover more of gitrevisions(7) is a compatible, additive +extension to this requirement; nothing about the query language depends +on the subset staying this size. `refs/meta/*` MUST be outside `rev()`'s domain by definition: an `expr` naming a `refs/meta/*` pattern MUST be rejected as a malformed query (<<effect.validation>>), never silently evaluated to the empty set. @@ -58,6 +68,15 @@ refname encodes the tested commit's oid (<<effect.results-writeback>>), resolution MUST be a scan of refname patterns under the effect's results namespace, never a walk of commit history. +A commit's membership in `results(effect, status)` MUST be decided solely +by whether a matching results ref exists, never by the commit's +reachability from `refs/heads/*` or any other ref outside the query's own +footprint (<<query.footprint>>); a transition on such an outside ref — +including deleting and recreating it at that same commit — is a non-event +for this atom's entry set. +A recorded result is already the computed answer for that commit, which is +why its results ref, not the commit's standing in unrelated ref history, is +what membership tracks. -- [role="requirement", id="query.meta"]
docs/spec/sync.adoc @@ -32,8 +32,9 @@ Any negative advisory verdict against a canonical meta-ref — the local UI verdict at commit time (<<gate.advisory-local>>), push pre-flight (<<sync.pre-flight>>), or the canonical store's actual rejection — MUST -cause sync to offer routing the same commit to the author's -`refs/meta/inbox/*` ref instead of discarding it. +cause sync to offer routing the same commit to a new ref under the +author's own `refs/meta/inbox/<member>/*` segment (<<meta-ref.inbox>>) +instead of discarding it. The offer MUST appear the moment the verdict goes negative, not only after a push is actually attempted and refused. -- @@ -55,6 +56,23 @@ <<gate.adoption-merge>>, not a separate adoption code path. -- +[role="requirement", id="sync.adoption-no-cherry-pick"] +.Cherry-Pick Forbidden as Adoption +-- +The adoption machinery (<<sync.adoption-machinery>>) MUST NOT cherry-pick a +contributor's commit as a substitute for merging it: cherry-picking +creates a new commit object, placed and signed by the adopting member, +and destroys the original author's signature. +This binds the tooling, not the gate: a cherry-picked commit, signed by +the authorized member placing it, satisfies the tip invariant +(<<gate.tip-signed>> through <<gate.atomic-cas>>) exactly as a hand-authored +commit would, so nothing in the resulting ref-store state lets a pure +verifier tell the two apart after the fact (gate.sdoc's Adoption +section). Preserving attribution is therefore a property this +requirement demands of the merge machinery itself, not one the gate can +check. +-- + [role="requirement", id="sync.local-advisory"] .Local Store Accepts Regardless of Verdict --