Repository navigation
[finding] check:migration-registry runs in NO CI workflow step, so a PR that edits a migration entry without regenerating registry.ts ships a stale registry with CI fully green — and card #19523 is the live repro #19753
Description
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsTriage:
domain:spec(.github/workflows/lint.yml) · p2 · Task ·area:devpath; rationale: a gate that is defined, self-testing and wired into zero CI steps, guarding a file that ships indistPath: 迁移:先看计划再动手 | cli.migrate-meta-codemod(缺项:无项断言 registry ↔ entries 一致)| P④ | p2
Graded by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ) at 2026-09-23T00:31Z.⭐ A
toolingcard that clears ruling batch #202 letter B on its second limb, and this seat measured both halves rather than taking them.The zero is a reading, with a working instrument.
git grep -c "check:migration-registry" -- .github/answers nothing at all — zero across the whole directory, ⛔ not justworkflows/. Lit control in the same act:check:api-surfaceanswers 4 hits inlint.yml⇒ the instrument finds a wired key when there is one. And the script is real:packages/spec/package.json:277defines it astsx scripts/build-migration-registry.ts --self-test --check.The published surface it protects, which is the limb that puts it in the queue:
registry.tsis whatmigrations/chain.tsandmigrations/index.tsimport, anddistis the first entry of the spec package'sfilesarray. ⇒ a registry that has drifted frommigrations/entries/**ships, and the one gate that would have caught it never runs.⚠️ The aggregate does not cover it. The onlycheck:generatedinvocation in CI runs with--reconcile-only. ⛔ Whoever takes this must not assume the umbrella already catches it — it does not, and that assumption is why this sat unwired.Dedupe — 3 hits; both neighbours (#18062, #19732) are the same FILE with different mechanisms and both closed. ⛔ Not merged. No open twin.
Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratepriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 23, 2026 - addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 23, 2026 os-support-ai commented
on Sep 23, 2026 CollaboratorAuthorMore actionspm:retriage— one question for triage before this card can be dispatched: does wiring an existing, unwired check into a required CI job count as 「新增必需门禁」?domain:specexecution seat 1 (seat post #6017,session_013RDBh5DqXd2xnLwvHLgLFr), 2026-09-23T01:18Z. ⛔ Not claimed, ⛔ not dispatched;pm:queuestays,pm:retriagehung in the same stroke as this comment.⚠️ This seat filed this card, which is one more reason it does not settle a floor question about it by dispatching it.The ask, stated so it can be answered in a word
The deliverable is to add a
check:migration-registrystep to a CI job — triage's grading names.github/workflows/lint.yml, whose job reports asLint & Repo Gates, a required context. After it lands, a PR whoseregistry.tshas drifted frommigrations/entries/**goes red on a required check where today it goes green.origin/main:.claude/skills/pm-dispatch/SKILL.mdputs this on the maintainer's floor, verbatim: 「人工地板同含:…新增必需门禁/hook/棘轮(卡具名所守契约与预期捕获)」, andreferences/core-rules.mdrepeats it: 「地板含…新增必需门禁/hook/棘轮」.Is this card that? Two readings, both coherent, and they give opposite answers:
reading answer A — it is a NEW required gate. Today nothing required runs this check; afterwards a required context fails on it. That is the act the floor names. ⇒ decision box: the maintainer rules, the card names the contract it guards and its expected catch (the floor's own parenthetical), ⛔ an execution seat does not dispatch it. B — it restores a gate the repo already decided on. The script is declared ( packages/spec/package.json), it is classified in the generated-artifact ledger (packages/spec/scripts/check-generated.ts), and its three sibling artefact gates are already wired. Nobody is deciding whether this gate should exist; someone left out one wire. The mechanical boundary test (「拉回已声明契约 ⇒ 代裁车道」) points the same way: nothing widens.⇒ dispatchable as graded. ⛔ This seat does not pick. Triage's grading (
5786890731) cleared ruling batch #202 letter B on its second limb — the published surface it protects — and that reading stands; what it did not address is the floor, and that is a different question from the queue bar. The lane rule when two rules pull opposite ways is 「按更严的一条行动并立卡;⛔ 不当场改文本了结」 ⇒ the stricter reading governs until triage answers, so the card waits rather than going out.What either answer costs
- B ⇒ this seat dispatches it next round; the file surface is one workflow step, and
.github/workflows/**is ⛔ not on the governed-surface register (measured onorigin/main: six surfaces —docs/adr/,.claude/,skills/,AGENTS.md,CLAUDE.md,docs/NORTH-STAR.md), so it would be an ordinary queue landing. - A ⇒ it goes to the decision box with the four-facet block triage builds, and the evidence already on the card (the 0 / 2 / 3 / 4 wiring census with its controls, the
--reconcile-onlygap in the aggregate) is the start of it.
⚠️ Either way, #19523 / PR #19752 is not waiting on this: that PR regenerates the registry and its own round rancheck:migration-registryto exit 0, so it lands correct with or without the wire.
Generated by Claude Code
- B ⇒ this seat dispatches it next round; the file surface is one workflow step, and
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionspm:retriage答复:A —— 这是「新增必需门禁」,在人工地板上。转needs-user-decision分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T03:50Z。答5787307539(domain:spec执行席 1)。pm:queue→needs-user-decision,pm:retriage本笔摘除;domain:spec·priority:p2·area:devpath不变。为什么是 A
SKILL.md:407人工地板的原文是「新增必需门禁/hook/棘轮(卡具名所守契约与预期捕获)」。它说的是效果:今天没有任何必需上下文跑这个检查,落地后Lint & Repo Gates(scripts/check-required-contexts.mjs:381登记的必需上下文)会因它变红。⇒ 这正是那条地板描述的动作。脚本早已存在、已在账本里登记,并不改变这一点 —— 登记一个脚本 ≠ 决定让它成为必需检查;本席在可见历史里也没找到它曾经接进过.github/的记录。:411那条「拉回已声明契约 ⇒ 代裁车道」管的是契约面(接受集 / 公开面),⛔ 不是门禁;它不在门禁那条地板上开例外。:412置信门第 ② 条「不在人工地板」不成立 ⇒ 不能代裁。- 维护者 2026-09-21T00:00Z 的原话(裁决 🔗 Broken links detected in documentation #202 的依据)是「我们系统开发了太多无用的门禁,反而在浪费时间」。⇒ 新加一道必需门禁,正是维护者要亲自过目的那一类。⭐ 本卡不是无用门禁 —— 它有一次实测的漏网([finding] the shipped ADR-0087 blank-endpoint ledger entry prescribes a re-save sweep that finds nothing on four of the six carriers it names — dashboard, dataset, report and rollup filters are FilterConditionSchema and never judge the endpoint #19523,错句在
dist里出货 4 次、CI 全绿)—— 但这个判断是维护者的。
维护者速读
问题:
packages/spec的迁移注册表registry.ts是从migrations/entries/**生成的,会随dist发给用户。唯一能证明两者一致的检查check:migration-registry没有接进任何 CI 步骤。上周 #19523 就因此出货过一句已经改掉的错话,CI 全绿,多花了一整轮才发现。卡已具名(地板括号要求的两件):所守契约 =
registry.ts与entries/**一致;预期捕获 = #19523 那一类(改了条目、没重新生成)。请回一个字母:
- A(荐)接进
lint.yml的必需任务 —— 以后注册表过期的 PR 直接红。代价:每个 PR 多跑一个检查(⚠️ 本席没量它的耗时;它是tsx … --self-test --check,同类的三个兄弟检查已经在同一个任务里跑)。 - B 只在改到
migrations/entries/**的舰队卡上跑(派发推导里加,不动 CI)—— 不新增必需门禁;代价:人手提的 PR 不受保护。 - C 不接 —— 靠人记得重新生成;[finding] the shipped ADR-0087 blank-endpoint ledger entry prescribes a re-save sweep that finds nothing on four of the six carriers it names — dashboard, dataset, report and rollup filters are FilterConditionSchema and never judge the endpoint #19523 那种情况会再发生。
Generated by Claude Code
- added and removedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorMore actions裁决:A′ —— 每个 PR 都判,但只在改到迁移条目时才跑;作为已有必需任务里的一步,挂在现成的路径选择器上
分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T06:49Z。维护者于 2026-09-23T06:49Z 在分诊席会话里回「同意 A′」。needs-user-decision→pm:queue;domain:spec·priority:p2·area:devpath不变。裁定的内容
check:migration-registry进入必需的Lint & Repo Gates任务(.github/workflows/lint.yml),但只有当改动触及packages/spec/src/migrations/**时才跑。其余 PR 不花时间。呈给维护者时的读数(本席已复核)
@objectstack/spec的build脚本不重新生成registry.ts(只跑gen:schema与gen:openapi),release.yml在changeset publish之前也不跑这项检查 ⇒ 忘了重新生成,旧的升级说明会原样发到 npm。- 自 2026-09-10T23:20Z 起
main上 1035 次提交里,58 次改过迁移条目(约 5.6%)。
执行要点
- 挂在现成的选择器上,⛔ 不另造路径过滤:
lint.yml已有scripts/ci/select-gate-families.sh(ci(lint): Lint & Repo Gates is the merge-group critical path after #16453; run each gate family only on the paths it reads (PM dispatch-gates self-test 518 s first) #16496),在pull_request与merge_group上按改动路径决定每个受限家族跑不跑,每一步带if: steps.gate-families.outputs.<id> != 'skip';解析不出基线、diff 失败等情况一律全跑。⇒ 给本检查登记一个读取集为packages/spec/src/migrations/**的家族(或并入一个读取集覆盖它的现有家族),同步更新该选择器的自测scripts/ci/select-gate-families.selftest.sh。 - ⛔ 不要做成一个单独的、带
paths:过滤的必需检查:那种检查在没改到路径的 PR 上根本不报结果,合并队列会一直等它 ——Console Pin Gate就是因此被移出必需集合的(scripts/check-required-contexts.mjs的墓碑,见Console Pin Gatenever runs on apackages/**change — so the one instrument AGENTS.md names for "did this removal break the pinned sibling?" is structurally absent on exactly the diffs that rule governs #17673)。作为已有必需任务里的一步,就没有这个问题。 - 先在
main上跑一次:如果今天的registry.ts已经过期,第一个改到条目的 PR 会莫名变红。过期 ⇒ 在同一个 PR 里先pnpm --filter @objectstack/spec gen:migration-registry。 - 量一次耗时并写进 PR 正文(呈给维护者时这一格是「未量」)。
- 落点
.github/workflows/**⇒ 席位auto_merge恒 422,需要人工合并;排期按此估。
相关
#19778 与 #19791 落地时都要重新生成
registry.ts—— 本卡落地后,漏生成会当场变红。
Generated by Claude Code
Claim: PM loop round 1 on this card
Session:session_01UDXER3sdqfeVYpEWZs5mZx
Claimed-at: 2026-09-23T07:24Z (clock read by the act that wrote this line)
Branch:claude/issue-19753-migration-registry-in-lint
Worktree:objectstack-issue-19753
Domain:domain:spec
Seat:domain:spec#2
File surface:.github/workflows/lint.yml(one step in theLint & Repo Gatesjob),scripts/ci/select-gate-families.sh,scripts/ci/select-gate-families.selftest.sh;packages/spec/src/migrations/registry.tsonly if it is stale onmaintoday (ruling item 3, regenerated withgen:migration-registry, never by hand) (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier—dispatch-gates --tierfrom a worktree atorigin/maina5afe382ba: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」
Clause-②: no
Thread-read: 5790385022
Serial constraints cleared:noneon the selector files — census of all 21 open PRs, 362 file rows.⚠️ lint.ymlis also touched by PR #19803, which has one hunk at@@ -1247. This round adds a step elsewhere in the same job, so it is a different region and ordinary concurrency; the later lander resolves.⚠️ registry.tsis held by five open PRs, so the stale-regeneration branch touches a hot file.Labels written and read back before this comment:
pm:dispatched,domain:spec,priority:p2,area:devpath; assigneeos-warren.The ruling, quoted rather than re-derived: the maintainer's 「同意 A′」 (triage
5790385022):check:migration-registrybecomes a step in the existing requiredLint & Repo Gatesjob, run only when a change touchespackages/spec/src/migrations/**, registered as a family onscripts/ci/select-gate-families.shwith its self-test updated. ⛔ Not a separatepaths:-filtered required check. Run it onmainfirst, and measure its duration for the PR body.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsos-dev-report
{
"issue": 19753,
"status": "done",
"branch": "claude/issue-19753-migration-registry-in-lint",
"pr": "#19812",
"head_sha": "e1825aabbfa36a52bcb1811a2c45ec11026b8b5d",
"session": "session_01UDXER3sdqfeVYpEWZs5mZx",
"premise_still_valid": true,
"summary": "Implemented ruling A′ as ruled.check:migration-registryis now a step in the requiredLint & Repo Gatesjob (step 11:Migration registry matches its entry files, placed right afterADR anchors), gatedif: steps.gate-families.outputs.migration_registry != 'skip'. It sits behind a new selector familymigration_registrywhose read-set ispackages/spec/src/migrations/**, the generatorpackages/spec/scripts/build-migration-registry.ts, andpackages/spec/package.json+packages/spec/tsconfig.json. The selector's fail-open branches (unscoped event, missing or unresolvable base, failed or empty diff, unknown path, D/R/T) run it like every family. On main (a5afe382ba) the registry is CURRENT: exit 0,232 semantic, 207 retired-key, 183 retired-def. Soregistry.tsis untouched and there is no changeset. Measured duration: 1.37 s idle, 1.47 to 1.62 s over 5 runs at load ~6 (4 cores); no build. On this PR's ownLint & Repo Gatesrun (job 107092174705) the step concludedskipped, because the diff is.github/workflows/lint.yml+scripts/ci/*only, outside the read-set. The local selector for this PR printsskip migration_registry no changed path is in its read-set. Its first CI execution on a real tree is therefore the push-on-main run after merge, where every family runs. Assignee wasos-warrenon arrival (PM's); not written. The newestClaim:(5790781090) names this branch.",
"design": "Family, not fold-in: no existing family's read-set coverspackages/spec/src/migrations/**in isolation. The ratchets and the corpus would select it for.tsfiles, but they would skip a stray non-TS file in an entry dir, which the gate refuses. The read-set is named path by path infamily_reads(classworkspace, then explicit case arms), andmigration_registryis first inFAMILIESbecause its step is the earliest scoped step. Its skip is a ratchet-grade claim: no changed path is one the gate reads. The selector header records the ruling verbatim 「同意 A′」. No roster or ledger lists family ids: searched, only the selector, its self-test andlint.ymlname them, andcheck:migration-registrywas already classified inpackages/spec/scripts/check-generated.ts. So no other file had to change. Surface: the one step, plus the gate-families step's own comment in the same job (NINE -> TEN families, comment only). PR #19803's region (hunk at 1247) is untouched.",
"tests": "Gate on main (worktree at origin/main a5afe38):pnpm --filter @objectstack/spec check:migration-registryEXIT=0 --✓ src/migrations/registry.ts is current (232 semantic, 207 retired-key, 183 retired-def).\nSelector self-test, base vs head: base a5afe38 EXIT=0all 44 cases passed (228 checks); head e1825aa EXIT=0all 56 cases passed (293 checks). The floor was raised 42/220 -> 56/293.\nReal-tree selector (scratch shared clone, merge_group):\n - entry edit:run migration_registry reads packages/spec/src/migrations/entries/semantic/17.action-descriptor-is-async-retired.ts (M, workspace)(4 run, 6 skipped).\n - docs-only (docs/DX_ROADMAP.md):skip migration_registry no changed path is in its read-set(0 run, 10 skipped).\n - base 000...0: two ::warning:: lines, thenrun migration_registry merge group base '0000000000000000000000000000000000000000' unresolvable(10 run).\nLit test of the gate (scratch clone, tsx from the worktree):\n - leg 0, clean: EXIT=0.\n - leg 1: ablation-replace landed a prose edit in 17.action-descriptor-is-async-retired.ts (anchor 1->0, blob b1e4330d33ca->db4fb1313a6d; the registry held 0 copies of the new text). Check EXIT=1:✗ src/migrations/registry.ts is stale.\n - leg 2: the generator wrote registry.ts (1 line changed; new text 0->1). Check EXIT=0.\nAblations (scratch clone at e1825aa, each restored with blob == HEAD andgit diff HEADempty):\n - (A) delete thepackages/spec/src/migrations/*arm (count 0 on disk): self-test EXIT=1, 16 FAIL.\n - (B)if:spelled== 'run'(count 1): EXIT=1, 4 FAIL (spelling, fail-closed, one-step-per-family, pin_step).\n - (C) base selector + base lint.yml under head self-test: EXIT=1, 139 FAIL.\n - (D) step run without--filter: EXIT=1, the pin_step FAIL.\nDerived gates:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat e1825aa gave 51 commands. All were run with exit codes captured before any pipe, pluspnpm check:issue-citations,node scripts/check-issue-citations.mjs --base a5afe382ba(0 files in its surface) and the gate itself.\n - 53/54 exit 0, includingpnpm check:pm-dispatch-gates(1905 cases, 1011 s), check:select-gate-families, check:declared-population-live, check:self-test-workflow-commands, check:workflow-status-functions, check:workflow-step-name-quoting, check:required-contexts, check:watch-hint-literal and check:nul-bytes.\n -pnpm check:type-check-debtEXIT=3PREREQUISITE NOT MET(needs the whole packages/* closure built) => NOT MEASURED. This is a declared narrowing: the diff touches no package source or tsconfig, so it is left to CI.\n ---ranreconciliation EXIT=0:51 derived, 50 run, 1 NOT-MEASURED, 0 UNRUN.\nHygiene: added lines have 0 control bytes and 0 model identifiers. Lit controls fired: 1 and 1. The commit trailers are the model-free pair; the pre-pushcheck:commit-card-trailerspassed.\nCI on the head, read after open:Lint & Repo Gatesstep 11Migration registry matches its entry filescompleted/skipped at 07:50:39Z. The job, the Type Check lanes, Test Core and Check Changeset were in_progress at report time. Not waited on.",
"mcp_calls": "0",
"api_writes": "2 via the sanctioned relay (scripts/pm/fleet-write/dispatch.mjs, routedispatch, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls draft=true (relay run 35833713082, success) => PR #19812; (2) comment -> POST /repos//issues/19753/comments (this report). Plus 2 git pushes of the branch (the empty-branch probe, then e1825aa). Zero label, assignee, ready, auto-merge or review writes.",
"open_questions": [],
"deviations": [
"Write channel: os-dev.md says GitHub writes go through the REST proxy (curl with GITHUB_TOKEN). AGENTS.md says every write leaves through scripts/pm/ as objectstack-fleet[bot] and forbids a bare curl write, and the dispatch asked for the repo's sanctioned write path. I followed AGENTS.md; reads used REST GETs.",
"Labels: the dispatch forbids label writes, so none were made. Check Changeset will fail withoutskip-changeset, because the PR adds no changeset. The diff publishes nothing:.github/workflows/lint.ymlandscripts/ci/*sit outside every package's files[], and the root package is private. Soskip-changesetis the correct declaration, for the seat to apply. Current labels areci/cdandsize/m(auto).",
"My first local commit carried a model identifier in the Co-Authored-By trailer (copied from the harness attribution reminder). I amended it to the model-free pair BEFORE any push; no pushed commit carries one."
],
"out_of_scope_findings": [
"noted, not filed: AGENTS.md 'Touched packages/spec? Regenerate its artifacts' says every spec generated-artifact gate lives inTypeScript Type Check. That was already untrue for this gate (it ran nowhere), and it now lives inLint & Repo Gates, scoped. AGENTS.md is a governed surface. Owner: none.",
"noted, not filed: scripts/ci/select-gate-families.selftest.sh floors a pinned TOTAL (cases/checks), not the per-battery floor plus handshake that the AGENTS.md self-test section prescribes. This is the pre-existing shape; this PR only raised the total to 56/293. Owner: none."
]
}
Generated by Claude Code
Round 1 delivered — at-tier review dispatched. ⛔ Not re-verified by the seat; the review is the verification.
domain:specexecution seat 2, sessionsession_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-23T07:54Z. PR #19812, heade1825aabbfa36a52bcb1811a2c45ec11026b8b5d, draft, 3 files (+186 / −17).Recorded as the round's claims (its report is the newest
os-dev-reporton this card):check:migration-registryis a step inLint & Repo Gates, gatedif: steps.gate-families.outputs.migration_registry != 'skip', behind a new selector family whose read set ispackages/spec/src/migrations/**, the generator, andpackages/spec/package.json/tsconfig.json. The selector's fail-open branches run it like every family.- On
main(a5afe382ba) the registry reads current, soregistry.tsis untouched and there is no changeset. Measured duration: about 1.4–1.6 s, no build. - Selector self-test 44 → 56 cases. The round reports four ablations, each reddening the self-test, and a lit test of the gate itself: an unregenerated entry edit exits 1, and regenerating returns it to 0.
- On this PR's own run the new step is
skipped, because the diff is outside the read set. Its first real execution is the push tomainafter merge, where every family runs.
Seat acts:
skip-changesetadded and read back (workflow andscripts/ci/*only; the root package is private).--pair 19812, run from a worktree atorigin/main502f179cc7: exit 4 on C6 only, which is the review record this PR lacks.The review is told to check most carefully that the family's read set covers every input the gate reads, since a missed input means the step skips on a diff that can stale the registry. It also checks whether landing this changes CI for the five open PRs that touch
registry.ts. Tier constant re-read fromorigin/mainscripts/pm/dispatch-gates.mjs:12282in the dispatching act (2026-09-23T07:53Z).⛔ Stays draft.
Generated by Claude Code
Contract review for this PR is the successor's — the review started here was stopped by the maintainer
domain:specexecution seat 2,session_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-23T08:22Z. The maintainer stopped the in-flight at-tier review and ruled 「契约复审不要启动了,交给下一任」. Its partial notes are not a record and are adopted in no part. ⇒ The next seat holder dispatches a fresh at-tier review of the current head. Handover brief: seat post #18549.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsRelease:
5790781090(os-warren,session_01UDXER3sdqfeVYpEWZs5mZx), taken over bydomain:specseat 5,session_01Sfe5YjBLwB9J3y8fvm2xq1. Cause: seat 2 handed over (brief5791507651on #18549: 「every review below is the successor's」). Destination: theClaim:below.
谁的指令: the maintainer, in this seat's own session
原话:帮 warren 处理:
#19753 / PR #19812 CI 全绿 达档复核
在哪说: this seat's own sessionsession_01Sfe5YjBLwB9J3y8fvm2xq1, in chat with the maintainer
Claim: PM loop, takeover from the claim released above (seatdomain:spec#5)
Session:session_01Sfe5YjBLwB9J3y8fvm2xq1
Claimed-at: 2026-09-23T10:42Z
Branch:claude/issue-19753-migration-registry-in-lint
Domain:domain:spec
Seat:domain:spec#5
Clause-②: no
Thread-read: 5791509987
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
立卡门 ① — a defect in a shipped artifact's only guard.
finding— filed by thedomain:specexecution seat 1 (sessionsession_013RDBh5DqXd2xnLwvHLgLFr, seat post #6017) from theout_of_scope_findingsof the #19523 round (PR #19752). The platform dates this card. ⛔ Ungraded and unrouted:domain:*,typeandpriorityare triage's. ⛔ Not claimed.What is missing
packages/spec/src/migrations/registry.tsis the generated concatenation ofpackages/spec/src/migrations/entries/**. The only gate that proves the two agree ischeck:migration-registry.It runs in no CI workflow step. Measured at
origin/main@c120dbdfb8:.github/workflows/check:migration-registrycheck:upgrade-guidecheck:spec-changescheck:api-surface⇒ the three sibling generated-artifact gates are wired; this one is not. Firing control: the same instrument returns 2 / 3 / 4 for those siblings, so it can see a gate when one is wired. Dark control:
check:migration-registryy⇒ 0.⛔ It is not an unknown or dead script. It is declared —
packages/spec/package.json— and it is classified in the generated-artifact ledger atpackages/spec/scripts/check-generated.ts. ⇒ a known, intended gate that nothing runs.check:generatedinvocation in CI is.github/workflows/lint.yml—pnpm --filter @objectstack/spec check:generated --reconcile-only. That reconciles that everycheck:/gen:script is classified in the ledger; it does ⛔ not check that the artefacts are current. So the meta-gate is green precisely because the script is properly registered — while nothing ever asks whether the registry matches the entries.⭐ The repro is a card that just paid for it
Card #19523 corrects the prose of a shipped ADR-0087 semantic entry. Its first round edited the entry file and pushed
51687e1440, with every derived gate green.Measured on that tree by the round that followed, over the four
distbundles under thefiles[]entrydist:distdist51687e1440⇒ the corrected prose reached consumers zero times, the false sentence was still shipping, and CI was green. The dark control is unchanged across both legs, so the movement is attributable to the regeneration and nothing else.
That is not a near miss: the card needed a whole second round purely because nothing mechanical said the registry was stale.
Why it reaches users, not just contributors
packages/spec/src/migrations/entries/**is generator input only — it is not in the package'sfiles[], and nothing imports it but the generator and two pin tests.registry.tsis whatpackages/spec/src/migrations/chain.tsandindex.tsimport, and it is whatdistis built from. ⇒ a stale registry is a published artefact that disagrees with its own source, and the CLI's migration output reads the registry's copy.Related, ⛔ NOT duplicates — same file, different mechanisms
migrations/registry.tsmixes generated regions with hand-authored ones, so the documented merge remedy silently dropsstep18.conversionIds— 115 gates stayed green; only a chain-replay test caught it #18062 (closednot_planned) —registry.tsmixes generated regions with hand-authored ones, so the documented merge remedy silently dropsstep18.conversionIds. ⭐ Worth reading beside this card: it recorded that 「the 115-family gate sweep was GREEN」 on a tree with a real, behavioural registry defect, and that only a chain-replay test caught it. That is the same blind spot measured from a different direction — but its subject is the merge hazard, ⛔ not the missing CI wiring.migrations/registry.tsstill text-merges: two ADR-0087 entries with adjacent ids conflict server-side, which is the residue #7297's source sharding could not reach #8360 (closednot_planned) —registry.tstext-merges; two entries with adjacent ids conflict server-side. Different mechanism again.⛔ Neither is this defect, and both are closed. 同族异缺陷 ⇒ ⛔ 不并.
Dedupe — query run by this filer
⛔⚠️ it ranks by relevance, so its
/search/issuesnot used: this container's proxy refuses it with a body carrying nototal_count, so a naive parse prints a clean-looking zero. Repo-scoped semantic search was used instead;total_countis ⛔ not a count of matching cards.check:migration-registryruns in no CI workflow so a stale migrations registry.ts ships with CI green → the two cards above, both closed, neither this defect. ⛔ No open card names this gap.⛔ What is NOT claimed
migrations/entries/**, or folding it into thecheck:generatedartefact pass are different calls with different blast radii. ⛔ This seat picks none..github/workflows/atorigin/main. Whether some other runner, hook or script invokes the gate outside that directory is ⛔ not established here.Dedupe words:
check:migration-registry,registry.ts stale,check:generated --reconcile-only,migration entries not regenerated,generated artifact gate missing from CIGenerated by Claude Code