Uh oh!
There was an error while loading. Please reload this page.
feat(cli): os migrate duplicates reports the rows blocking the kernel:ready index tightenings - #11031
Conversation
…el:ready index tightenings Three migrations replace a declared UNIQUE index with the NULL-safe — and sometimes active-rows-only — form it was always meant to have, at `kernel:ready` on a serving boot: `ensureMetadataOverlayIndexes` (`sys_metadata`, one index per overlay state), `ensureViewDefinitionActiveIndex` (`sys_view_definition`) and `ensureSysSettingIdentityIndex` (`sys_setting`). Each is a tightening, so rows an installation already holds can block it; the migration then refuses under ADR-0120 D4 — previous index kept, no row touched, boot continues — and reports at `error` on the boot channel. That channel was the only one, and not by omission. These indexes are invisible to the drift differ by construction, twice over: after the tightening `isRuntimeManagedIndex` excludes the index (without that exclusion a boot would propose rebuilding away the guarantee it had just created), and before it each migration deliberately reuses the DECLARED index's name, so the reconciler's name-matched slot reads as filled whichever physical form is really there. A prior round measured it with a matched control — one database carrying the same duplicate damage under a declared organization-unique index and under `sys_view_definition`'s runtime one — and `os migrate plan` named the declared one in full while saying nothing whatsoever about the runtime one. Maintainer ruling, 2026-08-22: the reporting path is `os migrate duplicates`, which already boots read-only and owns the "inventory, never repair" contract, keeping `os migrate plan`'s drift contract untouched. `plan` describes work `os migrate apply` will do; this work is applied by the next SERVING boot, by a different applier. What lands: * `@objectstack/metadata-protocol` gains `runtime-index-preflight.ts` — `runtimeIndexProbes()` and `collectRuntimeIndexPreflight()`. The descriptors read each migration's OWN exported builders rather than restating the keys, so the pre-flight and the boot report cannot describe different duplicates, and the `sys_setting` probe uses the migration's MySQL spelling on MySQL, where the bare form is ERROR 1064 on the reserved word `key`. * The report gains `runtimeIndexPreflight` (one entry per index: blocked/clear/table-absent/unreadable, a blocked one naming every colliding key group and its row count) plus `summary.runtimeIndexesBlocked` and `summary.runtimeIndexBlockingRows`. `reportVersion` moves 1 → 2: every version-1 field keeps its name, shape and meaning, and the bump says there is more in the document for a consumer that validates it strictly. * `collectDuplicateIdentifierReport`'s new option is REQUIRED rather than optional. An optional section defaults to `[]`, and `[]` is also what a clean database produces, so a caller that forgot to wire it would ship a clean bill of health from a probe that never ran — the #10677 failure, one section over. Required moves that mistake to a compile error. * Liveness is keyed on whether the seam returns a RESULT SET, reusing the sibling migration's `isResultSet` rather than copying it. A no-op seam would otherwise report all four tightenings as `table-absent`, which is that same failure again wearing a different status. * Nine referral sites repointed, not deleted (the ruling is explicit): three conflict-error strings that told the operator to "run `os migrate plan`" — an instruction the measurement proved false — and the six doc comments that state the same referral as part of the D4 disposition. Their pins now assert both that the new command is named and that the false one is gone. Nothing about a migration's behaviour changes: no tightening is armed, deferred or altered, and `plan` is untouched. The pre-flight only makes the refusal's evidence readable one command before the restart. Read-only is pinned LOGICALLY — schema plus every row, ordered — in both the new unit suite and the CLI integration test, never by a file hash: a raw hash over a SQLite file moves on any read-write open and would accuse this command of mutating the install it exists to describe. Fixes#8725 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
…nel-ready-migrations-plan-visibility
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0dc6d8466d2ad037b77502456789c7c4b2cf2be5 && git checkout 0dc6d8466d2ad037b77502456789c7c4b2cf2be5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cec9d239dd8858b759f2d62b4119b1d09dc90a60 e9a589341679af4e174cab28b9bd28bb41ddb87c && git checkout -B drift-repro cec9d239dd8858b759f2d62b4119b1d09dc90a60 && git merge --no-ff e9a589341679af4e174cab28b9bd28bb41ddb87c
node scripts/docs-audit/affected-docs.mjs --json cec9d239dd8858b759f2d62b4119b1d09dc90a60
|
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32570875748 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
…duplicates-inventory check:platform-checklist was red on main: the item carried revision 3 but its history array held only two entries, the last at revision 2. #11031 made a genuine semantic edit to the item's contract (reportVersion 1 -> 2, the runtimeIndexPreflight jq walk step, the kernel:ready seeding step, and a new acceptance clause for the kernel:ready pre-flight) and bumped the revision, but never appended the matching history entry. The revision bump is correct and stays; this adds the missing revision-3 history entry describing what #11031 actually changed, citing #8725/#11031 as provenance, matching the sibling entries' shape and voice. Fixes#11037
…n cli.migrate-duplicates-inventory (objectstack-ai#11047) check:platform-checklist was red on main: the item carried revision 3 but its history array held only two entries, the last at revision 2. objectstack-ai#11031 made a genuine semantic edit to the item's contract (reportVersion 1 -> 2, the runtimeIndexPreflight jq walk step, the kernel:ready seeding step, and a new acceptance clause for the kernel:ready pre-flight) and bumped the revision, but never appended the matching history entry. The revision bump is correct and stays; this adds the missing revision-3 history entry describing what objectstack-ai#11031 actually changed, citing objectstack-ai#8725/objectstack-ai#11031 as provenance, matching the sibling entries' shape and voice. Fixesobjectstack-ai#11037 Co-authored-by: Claude <noreply@anthropic.com>
…dex class D4 decides that `os migrate plan` gains a duplicate pre-flight probe per affected index. That was written for the declared class — the `recreate_index` drift ops the reconciler can see — and it is true there. It cannot reach the three `kernel:ready` runtime index tightenings in `packages/metadata-protocol`, which are invisible to the drift differ by construction. The maintainer's 2026-08-22 ruling on #8725 routes that class to `os migrate duplicates` instead, explicitly leaving `os migrate plan`'s drift contract untouched. PR #11031 shipped that behaviour; this records the split in D4's text, where the three migration modules' doc comments already point. Documentation only: no behaviour, gate or test changes. Governed surface (docs/adr/**) — draft PR, maintainer merges by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
Fixes#8725
Three migrations replace a declared UNIQUE index with the NULL-safe — and sometimes active-rows-only — form it was always meant to have, at
kernel:readyon a serving boot:sys_metadataactive+draftensureMetadataOverlayIndexessys_view_definitionidx_sys_view_def_activeensureViewDefinitionActiveIndexsys_settingensureSysSettingIdentityIndexEach is a tightening, so rows an installation already holds can block it. The migration then refuses under ADR-0120 D4 — previous index kept, no row touched, boot continues — and reports at
erroron the boot channel.Why that channel was the only one
Not by omission. These indexes are invisible to the drift differ by construction, twice over:
isRuntimeManagedIndexexcludes the index, becauseisSyncReproducibleIndexis false for a partial index and for any key part that is aCOALESCEover a non-tenant column. That exclusion is correct — without it a boot would propose rebuilding away the guarantee it had just created;A prior round measured it end to end with a matched control — one database carrying the same duplicate damage under a declared organization-unique index and under
sys_view_definition's runtime one.os migrate plannamed the declared one in full and said nothing whatsoever about the runtime one.The ruling
Maintainer ruling, 2026-08-22: the reporting path is
os migrate duplicates, which already boots read-only and owns the "inventory, never repair" contract, keepingos migrate plan's drift contract untouched.plandescribes workos migrate applywill do; this work is applied by the next SERVING boot, by a different applier, so aplanline an operator reads as "apply will handle it" would be a promiseapplycannot keep.What lands
@objectstack/metadata-protocolgainsruntime-index-preflight.ts—runtimeIndexProbes()andcollectRuntimeIndexPreflight(). The descriptors read each migration's OWN exported builders rather than restating the keys, so the pre-flight and the boot report cannot describe different duplicates. On MySQL thesys_settingprobe uses the migration's MySQL spelling, where the bare form is ERROR 1064 on the reserved wordkey. Four probes from three migrations: the overlay migration builds one index per state and either can be blocked on its own.runtimeIndexPreflight— one entry per index, eachblocked(naming every colliding key group and its row count),clear,table-absent(sys_settingarrives with the optional settings service) orunreadable(with the driver's own message) — plussummary.runtimeIndexesBlockedandsummary.runtimeIndexBlockingRows.reportVersionmoves 1 to 2: every version-1 field keeps its name, shape and meaning, and the bump says there is more in the document for a consumer that validates it strictly.isResultSetrather than copying it. A no-op seam would otherwise report all four tightenings astable-absent— the same failure wearing a different status.os migrate plan", an instruction the measurement proved false, and the six doc comments that state the same referral as part of the D4 disposition. Their pins now assert both that the new command is named and that the false one is gone.kernel:readyindex pre-flight" section incontent/docs/deployment/cli.mdx, and the QA checklist item for this command updated to the new contract.Nothing about a migration's behaviour changes. No tightening is armed, deferred or altered, and
planis untouched. The pre-flight only makes the refusal's evidence readable one command before the restart.Verification
Everything below ran at
e9a5893416(this branch's head, after mergingorigin/main).Non-vacuity — the matched control, reused.
duplicates.integration.test.tsnow carries both damage classes in one database:crm_case.case_numberheld on both sides of the organization partition (the declared class, reported before this change), and two ACTIVE shared views namedcrm_case.all_open(the runtime class). A probe that surfaced only the declared class would still satisfy "the report names some duplicate", so both are asserted separately over one run. The archived pair seeded beside them is outside the partial index and is asserted NOT to be reported.Reverse verification, both directions, each with its rebuild:
metadata-protocol, rebuilt intodist, marker proved present)AssertionError: the pre-flight must cover sys_view_definition: expected undefined to be defined— the failure lands there, after theCASE-00001andliveConditionsassertions passed. Contract test red too (expected [] to deeply equal [ …(4) ])Test Files 2 passed (2) · Tests 6 passed (6)os migrate planTests 5 failed | 77 passed (82), eachexpected '[metadata-protocol] cannot …' to contain 'os migrate duplicates'Test Files 3 passed (3) · Tests 82 passed (82)The ablation legs consume
metadata-protocol's builtdist, so each leg was rebuilt and proved withscripts/ablation-dist-preflight.mjsbefore its colour was read.Read-only, proved LOGICALLY. Schema plus every row of every table, ordered, byte-identical across a full run — in the new unit suite and in the CLI integration test. Deliberately not a file hash: a raw hash over a SQLite file moves on any read-write open and would accuse this command of mutating the install it exists to describe.
Suites:
@objectstack/metadata-protocol1838 passed / 10 skipped;@objectstack/cli1683 passed;pnpm --filter @objectstack/cli typecheckclean.Gates: the set derived by
node scripts/pm/dispatch-gates.mjswith no path arguments — 27 matched families plus the four convention-triggered ones — re-derived after the merge (identical set) and all run again at this head, every one exit 0, each exit code captured before any pipe. With them,pnpm lint(clean) and the two ratchets:check-type-check-coverage: OK — 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledgercheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 257.7s, 1908 raw tsc error(s) total, none above its recorded numbercheck-nul-bytes: OK (scanned 6389 text file(s) … no raw ASCII control bytes)check-test-source-alias OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/One finding for the maintainer, not an edit
ADR-0120 D4 decides that "
os migrate plangains a duplicate pre-flight probe per affected index". That sentence was written for the DECLARED class, which the differ can see andplandoes report. This ruling routes the runtime-managed class — the one D4's mechanism cannot reach — toos migrate duplicatesinstead. The three migrations' doc comments cite D4's disposition while now naming a different command, so ADR-0120 D4 may want an amendment recording the split.docs/adr/is a governed surface and nothing here touches it.Generated by Claude Code