spec: self-run namespace, member-scoped inboxes, adoption rule re-home, query.rev subset
commit
76888a3spec: 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
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
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
--