git-ents.gitmain
⌘K
foforge
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.

spec: narrow gate.identity-binding strict decode to gate-owned types spec: add gate.non-kernel-strict-decode (deferred) spec: add gate.redaction-vouching-undefined (deferred) spec: correct model.issue id-abbreviation to spare --porcelain output 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/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.