spec: close the identity-binding gaps phase 10 left silent
commit 9047c0e
spec: close the identity-binding gaps phase 10 left silent
The requirements review found gate.identity-binding overclaimed a
cross-namespace admission guarantee the gate does not enforce for
comment/issue (their types live outside the gate crate, so strict
decode never runs for those namespaces), and silently introduced a
redaction-vouching MUST with no implementation. Both are now honest
deferred requirements rather than false guarantees. Also fixes a
"porcelain abbreviates ids" contradiction: --porcelain and lens
machine-readable output carry the full id per lens.parity; only the
plain CLI display and web UI abbreviate.
No reviews of this commit yet — record a verdict below.
Start a review
docs/development-plan.adoc
@@ -275,10 +275,16 @@
history), natural-key tree fields (`Member` and `Effect` gain their
name field), composite review segments, and result trees gaining
effect + target (`model.result-identity`, closing a result-forgery
- replay). Strict genesis decode plus the pairwise schema-disjointness
- test; `gate.owner-mutation`; redaction vouching; `propose_*` grows the
- sign-then-name genesis flow (the ref named from the signed commit's
- own oid — no circularity, since no commit names its own ref anymore).
+ replay). Strict genesis decode for the gate's own types (a result)
+ plus the pairwise schema-disjointness test across every genesis-borne
+ struct; `gate.owner-mutation`; `propose_*` grows the sign-then-name
+ genesis flow (the ref named from the signed commit's own oid — no
+ circularity, since no commit names its own ref anymore). Two gaps ship
+ unclosed, neither assigned a phase: a comment or an issue's type lives
+ outside the gate's own crate, so strict decode never runs for those
+ namespaces (`gate.non-kernel-strict-decode`); and the binding's
+ redaction-vouching clause has no implementation
+ (`gate.redaction-vouching-undefined`).
| forge and surface identity migration (`ents-forge`, `git-ents`,
`ents-web`, `ents-lens`, skills)
@@ -287,9 +293,11 @@
| sonnet
| `uuid` leaves the workspace: comment and issue ids are genesis commit
oids, reviews move to the composite `reviews/<target>/<member>` key
- with re-review's fast-forward advance now a reachable CLI path;
- porcelain and web abbreviate ids the way git abbreviates oids; lens
- and agent skills updated for the id format.
+ with re-review's fast-forward advance now a reachable CLI path; the
+ CLI's plain display and the web UI abbreviate ids the way git
+ abbreviates oids, while the `--porcelain` and lens machine-readable
+ forms keep the full id (`lens.parity`); lens and agent skills updated
+ for the id format.
|===
== Phase gates
@@ -353,7 +361,11 @@
(the pin advancing fast-forward), round-trip through CLI and web
under genesis-oid and composite ids (`model.comment`,
`model.review`). Same clean-break rule as phase 9: no legacy reader,
- ids regenerate with the data.
+ ids regenerate with the data. The two gaps this row's own notes name
+ (`gate.non-kernel-strict-decode`; `gate.redaction-vouching-undefined`)
+ are known exceptions, not silent ones: a comment or issue genesis is
+ not yet refused from double-admission across namespaces, and the
+ binding's redaction-vouching clause is unimplemented.
Backend conformance tests are shared suites written once against each
trait (`RefStore`, `EventSink`, `Executor`) and run per implementation —
docs/spec/gate.adoc
@@ -33,15 +33,22 @@
a signed pass replayed as the result of a different effect or commit,
which is why a result's effect and target are tree fields
(<<model.result-identity>>).
-The creation of a hash-identified entity MUST strictly decode as its
-namespace's entity type, an unknown tree entry refusing, so one signed
-genesis can never be admitted under two namespaces; entity structs
-MUST stay pairwise disjoint under this decode, a property held by test
-rather than by a stored marker (<<meta-ref.typed-tree>>).
+The creation of a hash-identified entity whose type the gate crate
+itself owns MUST strictly decode as that type, an unknown tree entry
+refusing; the gate-owned entity structs MUST stay pairwise disjoint
+under this decode, a property held by test rather than by a stored
+marker (<<meta-ref.typed-tree>>).
+A hash-identified entity whose type lives outside the gate's own crate
+(a comment, an issue) binds by genesis oid and the all-roots walk
+alone (<<meta-ref.identity-binding>>): the gate cannot strictly decode
+a type it does not depend on, so the same signed genesis commit is not
+yet refused from being admitted a second time under a different such
+namespace (<<gate.non-kernel-strict-decode>>).
When an object the binding needs is withheld by redaction, the binding
MUST be vouched by the admin-signed redaction record instead of
recomputed, and a redacted object MUST NOT be re-admitted
-(<<receive.redaction-ingest>>, <<model.redaction>>).
+(<<receive.redaction-ingest>>, <<model.redaction>>); this vouching has
+no implementation yet (<<gate.redaction-vouching-undefined>>).
--
[role="requirement", id="gate.owner-mutation"]
@@ -59,6 +66,52 @@
because the contributor's genesis remains the history's sole root.
--
+=== Identity Binding Gaps
+
+[role="requirement", id="gate.non-kernel-strict-decode"]
+.Non-Kernel Hash-Identified Types Have No Strict-Decode Enforcement (Deferred)
+--
+<<gate.identity-binding>>'s strict-decode refusal only runs for a
+hash-identified entity whose Rust type the gate crate itself owns
+(today, a result); a comment or an issue's type lives in a higher-layer
+crate the gate MUST NOT depend on, so the gate's binding for those
+namespaces checks only the genesis oid and the all-roots walk, never
+the tree's shape.
+The workspace-level pairwise-disjointness test (<<meta-ref.typed-tree>>)
+proves the *types* cannot both decode one tree; it does not by itself
+stop the *gate* from admitting the identical signed genesis commit as
+the tip of two different such namespaces (for example
+`refs/meta/comments/<oid>` and `refs/meta/issues/<oid>`), since neither
+admission path ever calls either type's decoder.
+Closing this gap needs either a decoder a non-kernel crate can register
+with the gate, or moving strict decode to a layer that already depends
+on every entity type (`receive` or above); this is chosen direction,
+not yet designed, the same acknowledged-gap shape as <<gate.bootstrap>>.
+--
+
+[role="requirement", id="gate.redaction-vouching-undefined"]
+.Redaction Vouching for the Binding Has No Implementation (Deferred)
+--
+<<gate.identity-binding>> requires that when an object the binding
+needs has been redacted, the gate vouch by the admin-signed redaction
+record rather than fail trying to re-read withheld bytes.
+No such check exists: the gate treats a redacted (missing) object the
+same as any other unreadable one — for a walk like the all-roots
+reachability check this ends that path early rather than consulting
+`refs/meta/redactions/*`, and for a required tree-entry read it
+surfaces as an evaluation error the mandatory call site MUST block on
+(<<gate.mandatory-hosted>>), not the vouched pass-through the
+requirement describes.
+`receive.redaction-ingest`'s refusal to re-admit already-redacted bytes
+is a different guarantee — it stops refilling a yanked object at
+ingest — and does not give the gate a way to keep verifying later,
+unrelated mutations on a ref whose history happens to reach a redacted
+object.
+Which component consults the redaction list on the gate's behalf, and
+how, is chosen direction, not yet designed, the same acknowledged-gap
+shape as <<gate.bootstrap>>.
+--
+
[role="requirement", id="gate.fast-forward"]
.Fast-Forward Freshness
--
docs/spec/model.adoc
@@ -155,8 +155,11 @@
An issue's identity MUST be the oid of its genesis commit, exactly as a
comment's (<<model.comment>>, <<meta-ref.identity-binding>>) — no
sequential counter exists, because a counter is a coordination point
-and issues are created offline and concurrently; porcelain abbreviates
-ids the way git abbreviates commit oids.
+and issues are created offline and concurrently; the CLI's plain
+(human-facing) display and the web UI abbreviate ids the way git
+abbreviates commit oids, but a machine-readable form — the `--porcelain`
+flag included — MUST carry the full id, since that is what `lens.parity`
+requires to be "sufficient for an agent to enumerate and resolve."
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.