Skip to content

spec-changes.json and protocol-upgrade-guide.md are unsharded generated artifacts whose merge safety rests on a LOCAL-only git driver — the merge queue rebuilds server-side, where no custom driver runs #8344

Description

@os-zhuang

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.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions