Repository navigation
Conversation
A roadmap node's id was its authored slug, so a rename was structurally a delete
plus a create: edges stop resolving, acceptance receipts name a node that no
longer exists, and any attempt or session joined to it is orphaned. Not
hypothetical — the 2026-07-27 authority refresh deleted the node carrying the
only execution contract and no claim noticed.
std.occurrence_identity already states the right law: identity may not derive
from filename, span, authored name, structural equality or content hash. The LAW
is reused; the TYPE deliberately is not. OccurrenceId is an ordinal from a
per-graph allocator starting at zero, so inserting a node at the front renumbers
everything after it and two branches mint the same integer — positional identity
wearing an allocator's clothes. Right for occurrences inside one parse, wrong for
an entity that must survive edits, reordering, regeneration and independent
branches. What occurrence identity does not claim is PERSISTENCE SCOPE, and that
is the only thing missing.
IDENTITY IS A REQUIRED FIELD, NOT A SIDE REGISTRY. The first cut carried a
hand-written (identity, slug) list beside the authority. It compiled cleanly and
was wrong twice over: two authorities that must be kept in step (DESIGN 3), and a
node still authorable WITHOUT an identity, detectable only by a later witness —
validation standing where construction was available (DESIGN 5). As a field it is
unwritable instead, and the index is DERIVED from declared_roadmap_nodes() so the
two cannot disagree.
The change measured its own necessity: adding the field produced 50 hard failures
— 28 struct literals the compiler rejected outright, then 22 constructor call
sites failing with `undefined variable: identity`. Every one was a site where a
node could previously come into existence without durable identity, and the side
registry would have compiled past all fifty.
62 collision-resistant identities are minted and persisted at AUTHORING time.
Nothing mints during render, compile or regen; a value re-derivable by evaluation
would reintroduce the property this exists to remove.
TOMBSTONES, because a base-versus-head diff alone cannot enforce non-reuse.
Without persisted retirement: PR A removes node X, a diff observes the removal
once, after merge X is simply absent from base, PR B reuses X's identity, and the
next comparison sees present-in-head/absent-in-base — indistinguishable from a
legitimately new identity. The reuse passes every check. So retirement is a STATE:
TombstonedIdentity keeps the identity forever, carries the slug it last wore so
historical links resolve, and names its successor. The roster is empty today and
the witnesses are arithmetic (registry == active + tombstoned) rather than
pinning zero, so the first real retirement will not red them.
RESOLUTION IS EXACTLY-ONE. filter-map-first answers a duplicate registry with a
plausible first row — the fabricated-plausible-output arm DESIGN 5 forbids, and
it fails exactly when the registry is corrupt and a confident wrong answer is
worst. IdentityResolved | IdentityUnknown | IdentityAmbiguous{matches} keeps the
three remedies distinct.
The discriminating witness is identity_survives_a_slug_rename. Every other claim
here — uniqueness, totality, resolution — is equally true of a broken registry
where identity IS the slug; only this one fails if the separation collapses.
STILL OWED: the permanence gate. Every claim reads the current tree, so an
identity silently REPLACED between base and head satisfies all of them. The
carrier lists the admitted and refused transitions concretely; the tombstone
above is what makes that gate implementable at all.
ROADMAP.md is unchanged: identity is machinery, the slug remains display.
Verified: whole-tree compile EXIT=0, 0 blocking errors; ROADMAP.md regenerated;
241 witnesses green across 15 roadmap suites. Seven wet witnesses fail
identically on plain main (hermetic mode refusing to fabricate a host Check) and
are unrelated — confirmed by running them on origin/main before attributing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
Contributor
Author
|
Superseded by #7440, which consolidates this with the other roadmap-belt PRs. Four branches racing a moving |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilt from the review of #7417. Identity is now a required field on
RoadmapNode, not a side registry.Why the side registry was wrong
My first cut carried a hand-written
(identity, slug)list beside the authority. It compiled cleanly and was wrong twice: two authorities that must be kept in step, and a node still authorable without an identity — detectable only by a later witness. Validation standing where construction was available.The change measured its own necessity. Adding the field produced 50 hard failures: 28 struct literals the compiler rejected outright, then 22 constructor call sites failing with
undefined variable: identity. Every one was a site where a node could previously come into existence without durable identity — and the side registry would have compiled past all fifty.The index is now derived from
declared_roadmap_nodes(), so the two cannot disagree.The law is reused; the type deliberately is not
std.occurrence_identitystates it correctly: identity may not derive from filename, span, authored name, structural equality or content hash. ButOccurrenceIditself is unusable here —a per-graph counter from zero. Insert a node at the front and every id renumbers; two branches mint the same integer. Positional identity wearing an allocator's clothes — right for occurrences inside one parse, wrong for an entity that must survive edits, reordering, regeneration and independent branches. What occurrence identity does not claim is persistence scope, and that's the only thing missing.
62 collision-resistant identities, minted and persisted at authoring time. Nothing mints during render, compile or regen.
Tombstones — required for the gate to work at all
A base-vs-head diff alone cannot enforce non-reuse:
XXis simply absent from baseX's identityThe reuse passes every check. So retirement is a state:
TombstonedIdentitykeeps the identity forever, carries the slug it last wore so historical links resolve, and names its successor.The roster is empty today, stated honestly. The witnesses are arithmetic (
registry == active + tombstoned) rather than pinning zero, so the first real retirement won't red them.Resolution is exactly-one
filter → map → firstanswers a duplicate registry with a plausible first row — and fails exactly when the registry is corrupt and a confident wrong answer is worst. NowIdentityResolved | IdentityUnknown | IdentityAmbiguous{matches}, because the three remedies differ.The discriminating witness
identity_survives_a_slug_rename. Every other claim here — uniqueness, totality, resolution — is equally true of a broken registry where identity is the slug. Only this one fails if the separation collapses, which is the entire property the module exists for.Still owed
The permanence gate. Every claim reads the current tree, so an identity silently replaced between base and head satisfies all of them. The carrier lists admitted transitions (retained across rename, added, tombstoned) and refused ones (replaced, tombstone reactivated, vanished rather than tombstoned, two records sharing an id, one slug → two ids). The tombstone carrier above is what makes that gate implementable rather than illusory.
Validation
gunbc compile --target dag—EXIT=0, 0 blocking errorsROADMAP.mdregenerated and unchanged — identity is machinery, slug remains displaySeven wet witnesses fail identically on plain
origin/main(hermetic mode "refusing to fabricate Unit" for a hostCheck) — confirmed by running them on main before attributing, so they're unrelated to this change.🤖 Generated with Claude Code