Repository navigation
fix(cli): os migrate plan no longer lists sys_packages as unmanaged after os migrate apply created it - #24226
Conversation
…eclares outside the object set os migrate plan listed sys_packages under "declared by no object in this plan" right after os migrate apply created it. The sweep's known set was the planned drivers' managed tables plus the declared objects; sys_packages is neither. bootSchemaStack now reads the package.storage declaration once (findPackageStorage), plans the table from it as before, and exposes its table name on every boot as SchemaStack.nonObjectTables, which plan.ts hands to collectUnmanagedTables and buildKnownTableNames adds verbatim. Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 29 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 175b2c74c2983d8085d58561b6cef0ea446704b5 && git checkout 175b2c74c2983d8085d58561b6cef0ea446704b5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b9ac35cbfe60afcea88947ed4eaf8c50e3efc214 7a3803bd32656fc4b15a47d1c61c48c786fc345c && git checkout -B drift-repro b9ac35cbfe60afcea88947ed4eaf8c50e3efc214 && git merge --no-ff 7a3803bd32656fc4b15a47d1c61c48c786fc345c
node scripts/docs-audit/affected-docs.mjs --json b9ac35cbfe60afcea88947ed4eaf8c50e3efc214
|
席位核对 — ACCEPT · PR #24226 at
|
Fixes #24059
Clause-②: no
What changes
os migrate planno longer listssys_packagesunder "declared by no object in this plan" afteros migrate applyhas created it. A genuinely strandedsys_*table is still listed.sys_packagesis the package registry's table. It is not an object.PackageServicePlugin.start()creates it with raw DDL, and since #23022os migrate applycreates it from the registry'spackage.storagedeclaration. The plan's unmanaged-table sweep built its known set from the planned drivers' managed tables and the declared objects only.sys_packagesis neither, so every plan after an apply reported the live registry table as if it were stranded.packages/cli/src/utils/schema-migrate.ts:findPackageStorage(kernel)is now the one reader of thepackage.storagedeclaration (the lookup and the shape check that used to openplanPackageTable).bootSchemaStackreads it once per boot.planPackageTableplans the table from that object as before, on a deferring boot only. The same object'stableis exposed asSchemaStack.nonObjectTables. It is filled on every boot, whether or not the table exists and whether or not anything is planned for it, and it is[]when the boot composed no package registry.packages/cli/src/utils/unmanaged-tables.ts:buildKnownTableNames(managed, declaredObjects, nonObjectTables)adds those names verbatim.collectUnmanagedTablestakes a requirednonObjectTablesoption and passes it through. The module header's list of legitimate platform-prefixed families gains this one as item 4. No table name is spelled in the sweep.packages/cli/src/commands/migrate/plan.ts: passesstack.nonObjectTablesto the sweep. The sweep reads the declaration from the same boot and kernel the plan runs in.Measured before changing anything (PM hypotheses)
aa31569b2:buildKnownTableNamesreturned the managed set plus each declared object'snameand resolved table name, andselectUnmanagedTablesreported every platform-prefixed table outside it.planPackageTablereturnsnullwhen the table is present. Door reading below: after the apply, the plan'spendingis[], so the planned work names nothing. The fix therefore reads the declaration, not the planned work. The sweep runs in the same boot as the plan:bootSchemaStackreads the declaration from the kernel it booted, andplan.tshands it to the sweep from the returned stack.nonObjectTablesis[], and asys_packagestable left in that database is still reported. Through the door this case does not arise on a mirrored boot. The sweep runs only when the composition mirrors the served boot, and that composition always mountsPackageServicePluginthrough the always-onpackage-registryslate (composeServedPlatforminschema-migration-plugins.ts, and the base reading's composition note lists it). So the decision is pinned at unit level ("DECISION: a boot that composed no package registry …").aa31569b2onexamples/app-crm. Setup: a fresh sqlite file withsys_legacy_widgetcreated up front, freshOS_HOME, DB env unset, run throughpackages/cli/bin/run-dev.jswith--database-url file:DB. Thenmigrate apply --yes, thenmigrate plan, thenmigrate plan --json:+ sys_packages [create_table, 7 column(s)]andCreated/extended 80 table(s). The plan printed2 table(s) … declared by no object in this plan:with• sys_legacy_widgetand• sys_packages.--json:unmanagedTables.tables=sys_legacy_widget,sys_packages,physicalTables81,pending[].04c0d95ec: same apply. The plan printed1 table(s) …:with• sys_legacy_widgetonly.--json:tables=sys_legacy_widget,physicalTables81,pending[]. Both tables are physically present after each run.Pins
unmanaged-tables.test.ts, new describe[#24059] a table the composition declares outside the object set. It has three cases: the known set holds the name only when it is declared; after an apply the registry table is not reported andsys_legacy_widgetstill is; and the H3 decision. Every existingbuildKnownTableNames/collectUnmanagedTablescall statesnonObjectTablesexplicitly.unmanaged-tables.integration.test.tsuses a real boot with the compositionplanuses.sys_packagesjoins the negative controls, and the premise case asserts thatstack.nonObjectTablesequals thetableof the kernel's ownpackage.storageservice while nothing is pending for it.schema-migrate.package-table.integration.test.ts:nonObjectTablesis['sys_packages']on the plan of a never-started project and on the plan after the apply, whenpendingholds nothing for the table.plan.boot-parity.integration.test.tsexpected the sweep to name exactlysys_packages, and now expects it to name nothing.plan.telemetry-sibling.integration.test.tsexpectedsys_packagesplus the sibling's retired table, and now expects only the retired table. Both pins were green at base with the old expectation, so the new one fails against the old sweep. The H4 base reading shows the same thing.Ablation (fix committed first, at
04c0d95ec)node scripts/ablation-replace.mjs --file packages/cli/src/utils/unmanaged-tables.ts --anchor 'for (const table of nonObjectTables) known.add(table);' --delete -- pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/utils/unmanaged-tables.test.ts, under the verify lock:99e653e8d5d0to33c383315c85;Tests 2 failed | 43 passed (45). The two red cases are the "known set" case and the "after an apply" case. The DECISION case stayed green, as expected, because it asserts the direction without a declaration;99e653e8d5d0),git diff HEADis empty, andgit status --porcelainis empty.The unit suite imports
./unmanaged-tables.jsrelatively, so it readssrc/and nodist/rebuild applies.Tests and gates
All readings are at head
7a3803bd3unless another commit is named.04c0d95ecis the fix commit.7a3803bd3mergesorigin/mainb9ac35cbf, which touches CI workflows and only one file inpackages/cli:test/published-entry-node-env-source-reroute.test.ts.pnpm --filter @objectstack/cli typecheckat04c0d95ec: exit 0. That istsc --noEmit, thencheck:test-typecheck, which held 3 files, 28 errors and 6 signatures.tsc -p packages/cli/tsconfig.json --listFilesOnlyputs all 5 touched test files in its 1611-file program.04c0d95ec,pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2:Test Files 2 failed | 308 passed (310),Tests 4573 passed | 29 skipped. The two files,test/published-subpath-console.pin.test.tsandtest/published-subpath-hook-body.pin.test.ts, were refused before their bodies ran ("packages/cli is not built"). Afterpnpm --filter @objectstack/cli build, both pass:Tests 29 passed (29).04c0d95ec, the four touched files with--project integration:unmanaged-tables.integrationandschema-migrate.package-table.integration4 passed,plan.boot-parity.integration3 passed,plan.telemetry-sibling.integration2 passed.test/published-entry-node-env-source-reroute.test.tsandsrc/utils/unmanaged-tables.test.ts, 50 passed.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat7a3803bd3: 64 commands, merge baseb9ac35cbf, 9 paths, 232 changed lines. All 64 ran, with each exit code captured before any pipe. 62 exited 0 on the first run.check:dual-build-cjs-loadsandcheck:i18n-coverageexited 3 (PREREQUISITE NOT MET); both exited 0 afterturbo run build --filter='!@objectstack/docs' --concurrency=2(76 tasks, 75 cached).--ran:64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.pnpm exec eslint --no-inline-configover the 8 changed.tsfiles: exit 0. Repo-widepnpm lintis left to CI.Acceptance notes
findPackageStoragenow runs on every boot, not only a deferring one. Apackage.storageservice of the wrong shape (a version skew between@objectstack/cliand@objectstack/service-package) now also fails the skip-schema-sync boot ofplan,applyandorganization-ownership(the Turso remote transport). It fails with the release-mismatch error those commands already gave on every deferring boot. Only those three commands, plusunmapped-columns, compose the registry. The changeset says so.nonObjectTablesis a union across every planned database, like the managed set beside it. The registry writes to the default driver, so asys_packagestable in thetelemetrysibling would also go unreported. Over-excluding is the module's stated safe direction. Not measured; no carrier.Generated by Claude Code