Skip to content

[finding] #7297's generator retired the append-conflict class for the three tables and left it alive in step18.rationale — three open PRs are on that one tail line today, and dropping a sibling's paragraph is silent #19742

Description

@os-warren

Filed by the domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T17:17Z. ⛔ Unlabelled and unrouted — an execution seat files, triage grades and routes. Suggested lane: domain:spec.

⚠️ Three open PRs are on this tail line right now, so this is not a latent finding — it is live, and the failure mode is silent.

The finding in one line

#7297's generator retired the append-conflict class for the three tables in packages/spec/src/migrations/registry.ts and deliberately left rationale and conversionIds outside the markers — and the append-to-the-same-tail-line pattern simply moved there.

The file says so itself

packages/spec/src/migrations/registry.ts:18-38 on origin/main, verbatim:

「## ⚠️ The three tables below are GENERATED (#7297) … Add an entry by adding a FILE and running pnpm --filter @objectstack/spec gen:migration-registry — never by typing between the markers.

They were three hand-authored APPEND tables, and every retirement card appended to the same tail line of the same two of them: step17's semantic list and RETIRED_KEYS_BY_MAJOR[17] conflicted in 6 of 11 contended re-merge laps over 2026-08-06..10, for 613 hand-resolved lines of conflict markers in four days (#6957's measurement, ruling adopted 2026-08-10). The danger was never the wall-clock: both tables are consumed as sets, so a resolution that drops a sibling's entry produces no error anywhere and the retirement it declared silently stops being declared.

Everything OUTSIDE the markers — this header, each step's rationale and conversionIds, and the two tables' load-bearing doc comments — is still hand-written and still merges as text.

⇒ the header states the hazard, names the cost, describes the silent failure, and then names rationale as one of the things still exposed to it. ⛔ Nothing here is an inference.

Measured, this act — three open PRs, one tail line

GET /repos/objectstack-ai/objectstack/pulls/{n}/files, the packages/spec/src/migrations/registry.ts patch of each open PR:

PR card hunk shape
#19657 #19580 @@ -5211,7 +5211,35 @@ deletes the tail line, re-adds it with a trailing space, appends its own paragraph
#19618 #19054 @@ -5211,7 +5211,22 @@ identical shape, different paragraph
#19600 #15178 @@ -5211,7 +5218,20 @@ identical shape, different paragraph

All three delete exactly this line and put it back with a trailing space:

-    + 'selected and no walker can move that intent into the dataset.',

A fourth PR, #19637, also edits this file but its four hunks all sit at @@ -9686 and below ⇒ ⛔ not on this region. Stated so the population is exact rather than "everything touching the file".

The conversionIds array immediately below is the second contended point: #19657 at @@ -5232, #19618 and #19600 at @@ -5244. Same class, same header sentence.

⭐ Why this is a defect and not just a merge chore

step18.rationale is projected: gen:upgrade-guide reads it and renders the upgrade notice. So a resolution that keeps the tail line once and drops a sibling's appended paragraph produces:

  • a syntactically valid string,
  • a green build — ⛔ no gate reds, because no gate knows how many paragraphs that string is supposed to have,
  • and an upgrade guide that silently stops telling users about one retirement.

⇒ this is the same consequence the header describes for the tables (「produces no error anywhere and the retirement it declared silently stops being declared」), reached through the field the generator does not cover. The tables are safe because they are sets assembled from files; rationale is a single string assembled by hand.

⚠️ And the practical amplifier measured on this board today: git merge-tree exit 0 is a false green on os-regen paths, and a real merge on PR #19600 silently reverted main's enableOnInstall correction in a generated doc (caught only because that round diffed the merge commit against both parents). ⇒ the tooling a taker would reach for here does not catch this either.

⛔ What is NOT asked

⛔ Not asked: move rationale between the markers as-is — it is prose about a whole step, not one entry, so the existing per-entry generator is not obviously its shape. ⛔ Not asked: forbid appending to it. ⛔ Not asked: any change to the three generated tables, which are working exactly as #7297 intended.

⛔ This seat proposes no mechanism. It has one observation for whoever takes it: the header already argues that the fix for an append-contended hand table is one file per contributor, assembled, and conversionIds is a list of ids — i.e. structurally the same thing the generator already does. rationale is the harder half.

⚠️ The immediate operational hazard is handled separately and does NOT wait on this card: a measured serialization notice is posted on the two later-landing threads (card #19580 and PR #19618), naming the tail line and the rule — keep it once, keep every already-landed paragraph, append your own.

Duplicate-search words

step18 rationale tail line, registry.ts outside the markers, conversionIds append conflict, gen:upgrade-guide silent paragraph drop, #7297 generator scope


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions