Filed by the domain:identity execution seat, from a measurement taken while landing #8315 (PR #8325). Unassigned and unlabeled for triage — I am an execution seat and do not grade findings or route domain:*. It lands in the ADR-0087 / release-tooling surface, not identity's.
What was measured (on a real merge, not reasoned about)
PR #8325 registered an ADR-0087 semantic entry. While it was open, two sibling PRs landed on main that each registered one too — #8324 (view-export-options-pdf-removed, #8010) and #8327 (engine-dotted-projection-refused, #7589).
Merging origin/main into #8325's branch produced no textual conflict, and yet:
| generated artifact |
plain-merge result |
after regeneration |
packages/spec/src/migrations/registry.ts |
correct |
byte-identical — nothing to fix |
packages/spec/spec-changes.json |
missing both siblings' entries |
+26 lines restored |
docs/protocol-upgrade-guide.md |
missing both siblings' entries |
+10 lines restored, incl. #8324's row in the "Mechanical (applied for you)" table |
Gates run against the un-regenerated merge:
check:spec-changes FAIL (exit 1)
check:upgrade-guide FAIL (exit 1)
check:migration-registry PASS
⚠️ The mechanism — and why this is NOT "git silently loses data"
Stating this precisely because the alarming reading is the wrong one.
.gitattributes routes both files through merge=os-regen (scripts/git-merge-regen.mjs) — a driver that deliberately refuses to text-merge generator-owned artifacts. It takes one side, marks them deferred, and a commit hook blocks the commit until the generator has been re-run. On the local merge the hook fired and cleared itself once both were current:
os-regen: 2 generated artifact(s) were merged WITHOUT a text merge … ✓ current — marker cleared
So the missing entries were the designed intermediate state, working exactly as intended. Locally, this class cannot ship silently.
registry.ts differed because it is deliberately not driver-managed (NOT_DRIVER_MANAGED) and text-merges normally — #7297's per-entry-file sharding working at the source, which is also why a card registering a new entry must add a new file rather than extend an existing one.
The actual concern
.gitattributes states the bound itself:
the GitHub merge queue rebuilds each PR server-side, where no custom merge driver runs
The three hottest artifacts were sharded for exactly that reason. spec-changes.json and protocol-upgrade-guide.md are single files and were not sharded — so their only protection is the local driver plus the local commit hook, neither of which exists in the environment where the merge that actually ships is computed.
What is measured: locally, without regeneration, two of the three gates fail. What is NOT measured, and is the open question for triage: what the queue's driver-less server-side rebuild actually produces for these two files when two ADR-0087 entries are in flight — a clean-but-stale text merge, a conflict, or something correct. I could not check that from a PR branch; by construction it is only observable in the queue.
If it is stale-but-clean, every pair of in-flight ADR-0087 registrations is a queue-ejection generator, and an ejection re-runs the whole queue and takes unrelated PRs down with it. This shift has already seen two ejections from unrelated cross-PR interactions, and one of them (#8199) was collateral from a neighbour.
Why it does not show up as a steady drip today
It needs two ADR-0087 registrations in flight simultaneously. That was rare; it is getting less rare — three landed within roughly one hour today (#8324, #8327, #8325).
Suggested directions (⛔ not decided here)
- Shard them, as the three hottest artifacts already are — the fix that removes the dependency on a driver the queue cannot run.
- Make the un-regenerated state fail loudly server-side rather than relying on the local hook (a CI check that regenerates and diffs, so a stale projection reddens on the PR rather than in the queue).
- Accept and document the merge-then-regenerate lap as a required step before queueing any ADR-0087 PR — cheapest, but it is a rule humans and agents must remember, which is the category this repo's guardrails generally try to design away.
Recorded plainly per the file-plainly rule; severity is triage's call. Credit where due: the measurement, the correction of the mechanism, and the durability bound are all the #8315 dev's — it also corrected its own commit message for overstating this as data loss, which is how the distinction above got established.
Related: #7297 (the sharding that already protects registry.ts), ADR-0087, #8324, #8327, #8325.
Filed by the
domain:identityexecution seat, from a measurement taken while landing #8315 (PR #8325). Unassigned and unlabeled for triage — I am an execution seat and do not grade findings or routedomain:*. It lands in the ADR-0087 / release-tooling surface, not identity's.What was measured (on a real merge, not reasoned about)
PR #8325 registered an ADR-0087 semantic entry. While it was open, two sibling PRs landed on
mainthat each registered one too — #8324 (view-export-options-pdf-removed, #8010) and #8327 (engine-dotted-projection-refused, #7589).Merging
origin/maininto #8325's branch produced no textual conflict, and yet:packages/spec/src/migrations/registry.tspackages/spec/spec-changes.jsondocs/protocol-upgrade-guide.mdGates run against the un-regenerated merge:
Stating this precisely because the alarming reading is the wrong one.
.gitattributesroutes both files throughmerge=os-regen(scripts/git-merge-regen.mjs) — a driver that deliberately refuses to text-merge generator-owned artifacts. It takes one side, marks them deferred, and a commit hook blocks the commit until the generator has been re-run. On the local merge the hook fired and cleared itself once both were current:So the missing entries were the designed intermediate state, working exactly as intended. Locally, this class cannot ship silently.
registry.tsdiffered because it is deliberately not driver-managed (NOT_DRIVER_MANAGED) and text-merges normally — #7297's per-entry-file sharding working at the source, which is also why a card registering a new entry must add a new file rather than extend an existing one.The actual concern
.gitattributesstates the bound itself:The three hottest artifacts were sharded for exactly that reason.
spec-changes.jsonandprotocol-upgrade-guide.mdare single files and were not sharded — so their only protection is the local driver plus the local commit hook, neither of which exists in the environment where the merge that actually ships is computed.What is measured: locally, without regeneration, two of the three gates fail. What is NOT measured, and is the open question for triage: what the queue's driver-less server-side rebuild actually produces for these two files when two ADR-0087 entries are in flight — a clean-but-stale text merge, a conflict, or something correct. I could not check that from a PR branch; by construction it is only observable in the queue.
If it is stale-but-clean, every pair of in-flight ADR-0087 registrations is a queue-ejection generator, and an ejection re-runs the whole queue and takes unrelated PRs down with it. This shift has already seen two ejections from unrelated cross-PR interactions, and one of them (#8199) was collateral from a neighbour.
Why it does not show up as a steady drip today
It needs two ADR-0087 registrations in flight simultaneously. That was rare; it is getting less rare — three landed within roughly one hour today (#8324, #8327, #8325).
Suggested directions (⛔ not decided here)
Recorded plainly per the file-plainly rule; severity is triage's call. Credit where due: the measurement, the correction of the mechanism, and the durability bound are all the #8315 dev's — it also corrected its own commit message for overstating this as data loss, which is how the distinction above got established.
Related: #7297 (the sharding that already protects
registry.ts), ADR-0087, #8324, #8327, #8325.