Repository navigation
fix(cli): os migrate plan's unmanaged-tables sweep gates on whether the composition mirrors the served boot, sweeps every planned database, and states only a reason that holds (#22580) - #22732
Conversation
…ded served-boot fact and sweeps every planned database The composition records once, where it decides it, whether its object set mirrors what `os serve` registers (`servedBoot`), with the reason when it does not. `collectUnmanagedTables` reads that fact instead of inferring it from `hostConfigLoaded`, states the recorded reason when it does not run, and reads the catalog of every driver the plan diffs, the telemetry sibling included. Claude-Session: https://claude.ai/code/session_019SvPnd2bzECRNmAU9i6E4k Co-authored-by: Claude <noreply@anthropic.com>
…n artifact project, its reason on the controls, and the sibling sweep Claude-Session: https://claude.ai/code/session_019SvPnd2bzECRNmAU9i6E4k 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 — 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 282c708643555897bb6b61fac9c8a2f135f76149 && git checkout 282c708643555897bb6b61fac9c8a2f135f76149
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a360cee92e2eff7dda2f3e5a2baf8dfd013d77b7 d7b741efefd088729b4f5b7e87e90cc377ae77e4 && git checkout -B drift-repro a360cee92e2eff7dda2f3e5a2baf8dfd013d77b7 && git merge --no-ff d7b741efefd088729b4f5b7e87e90cc377ae77e4
node scripts/docs-audit/affected-docs.mjs --json a360cee92e2eff7dda2f3e5a2baf8dfd013d77b7
|
Fixes #22580
Clause-②: no
What changed
os migrate plan's unmanaged-tables sweep reports platform-prefixed tables that no object declares. It used to decide whether it could run by readingcomposition.hostConfigLoaded. That flag isfalseon a project with a compiled artifact and no host config too. Since PR #22574, that project's composition is whatos serveregisters, so the sweep was withheld there with a reason that no longer held.SchemaMigrationCompositiongainsservedBoot, which is either{ mirrored: true }or{ mirrored: false, reason }. The reason is one ofnot-composed,nothing-to-compose,config-unloadableorstack-only.buildSchemaMigrationPluginssets it at the two places that decide it: the catch that finds the host config unloadable, and the branch that composes whatservemounts around the stack (PR fix(cli,driver-sql): os migrate plan/apply compose what os serve mounts around the stack, so sys_account.issuer is a drop (#22506) #22574'scomposeServedPlatform).NOTHING_COMPOSEDandbootSchemaStack's non-composing literal carry their own values.collectUnmanagedTablesruns exactly whenservedBoot.mirroredis true, and it never infers fromhostConfigLoaded. When the fact says no, thedetailis the sentence for the recorded reason, one sentence per reason. ARecordkeyed by the reason union means a new reason does not compile until it has its own sentence.stack.drivers), so thetelemetrysibling is included. The known set is the union of every planned driver's managed set and the declared names. If any one database cannot be read, the whole report isunreadable, and the detail names that database.The
--jsonshape ofunmanagedTablesis unchanged.physicalTablesnow counts every database swept, and the human line says "in the database(s) this plan covers". The changeset is.changeset/22580-unmanaged-tables-served-boot.md(@objectstack/clipatch).Measured through the public door (
os migrate plan --json, built CLI)Fixtures and script: an artifact project declaring
requires: ['auth'], provisioned first byos serve's own provision-and-exit (OS_MIGRATE_AND_EXIT=1), with asys_-prefixed orphan table planted in it.maine5899a6OS_TELEMETRY_DB=0): 75 objects composed, 75 of 75 examinedunreadable: "no host config, so the composed object set is the compiled artifact plus the platform floor". False.read,physicalTables: 77, tablessys_os22580_orphan,sys_packagesunreadablewith the same sentence. False: there is no artifact and no platform floor.unreadable: "neither a host config nor a compiled artifact, so this plan's object set is the data stack alone". True.unreadable, names the config. True.telemetrysibling, orphans planted in both databasesread,physicalTables: 70, primary orphan only. The sibling'ssys_os22580_sibling_orphanwas never looked at.read,physicalTables: 78, both orphans plussys_packagesPM mechanism assumption 4 (the sibling) was measured as a false statement: a
readanswer was given beside atelemetryDatabasethe sweep never read. Per the dispatch, that makes it in scope.Tests (all on
d7b741efe)pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 280 files, 4169 tests passed.pnpm --filter @objectstack/cli typecheck: exit 0. That istsc --noEmit, pluscheck:test-typecheck"OK — 3 file(s) / 28 error(s) / 6 pinned signature(s) held". Counted with--listFiles: every touched test file is in exactly one program. The five undersrc/are intsconfig.json; thetest/e2e file is intsconfig.test.json.src/utils/unmanaged-tables.integration.test.ts: 3 passed. Its boot now also passescomposeServedPlatform, asplandoes, and assertsservedBoot.src/commands/migrate/plan.boot-parity.integration.test.ts: 3 passed. The card's pin: the sweep mustreadon every shape, the compiled-artifact project included, and name exactlysys_packages. Before, this check was conditional on the sweep having run.src/commands/migrate/plan.telemetry-sibling.integration.test.ts: 2 passed. A retired-table orphan planted in the sibling is reported.OS_TEST_TIERS=nightly,test/migrate-unloadable-host-config-exit.e2e.test.ts: 12 passed. The card's control: unloadable config givesunreadablenaming the config; no config and no artifact givesunreadablenaming that absence; loadable config givesread. This file is in the nightly tier, so per-PR CI does not run it. Per PR, the unit tests carry the same control.servedBootper project shape, inschema-migration-plugins.test.ts. The artifact case assertshostConfigLoaded: falsebesidemirrored: true.unmanaged-tables.test.ts: every reason, with distinct details and named subjects; the artifact-shaped composition sweeps; the multi-database cases.node scripts/ablation-replace.mjs, each restored with "blob == HEAD (79269b2509df) andgit diff HEADis empty":unmanaged-tables.test.tswent to 2 failed / 39 passed: the artifact sweep, and the reason that holds. A first A1 attempt was a no-op: the tool refused it because the replacement contained the anchor ("anchor count moved 1 -> 1"), and nothing ran. It was re-run with a non-overlapping anchor.Gates (all on
d7b741efe)node scripts/pm/dispatch-gates.mjs --commandsre-derived 15 more on the real change. Reconciliation with--ran: "65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN".check:dual-build-cjs-loadsandcheck:i18n-coverage, first answered exit 3PREREQUISITE NOT METbecause unrelated packages had nodist/. Afterturbo run build --filter=!@objectstack/docs, both are green:pnpm lint(the fulleslint . --no-inline-config): exit 0 in 120 s. As a narrowed cross-check, the 10 touched.tsfiles gave 10 results in the--format jsonoutput, 0 errors, 0 warnings, none ignored. This repo'seslint.config.mjsenables no type-aware linting, so this diff cannot move any untouched file's verdict.@objectstack/cli's published entry points.dist/index.d.ts,console.d.tsandhook-body.d.tseach return 0 hits. As a positive control,dist/utils/unmanaged-tables.d.tsdoes hit. No export is added there.Deviations, stated
schema-migrate.tsas where the fact is recorded. On the merged code,hostConfigLoadedis computed inbuildSchemaMigrationPlugins(schema-migration-plugins.ts). That file also declaresSchemaMigrationCompositionand holds PR fix(cli,driver-sql): os migrate plan/apply compose what os serve mounts around the stack, so sys_account.issuer is a drop (#22506) #22574'scomposeServedPlatform.schema-migrate.tsholds only the literal for a boot that composes nothing. This partly falsifies mechanism assumption 1. The ruling places the fact in "PR fix(cli,driver-sql): os migrate plan/apply compose what os serve mounts around the stack, so sys_account.issuer is a drop (#22506) #22574's composition", so the fact is set there.schema-migrate.tsonly carries the non-composing literal's value. Nothing listed as out of surface was touched.commands/migratepins and the e2e control above. They are the public-door half of the card's pins.e5899a67dbefore any edit.origin/mainhas since moved toa360cee92, which touches onlycloud-connection, so it was not merged.Acceptance notes
--jsonshape is kept as the claim required. In a two-database plan, a finding names the table, and the human line says the databases the plan covers.servedBootis not added to the--jsoncompositionblock. Consumers keep readinghostConfigLoaded, which the earlier unloadable-config ruling pinned.stack-onlyandnot-composedcannot be reached throughos migrate plan, its only caller, because that caller asks for both compositions. They exist so the fact is true for every composition:security-catalog-overlays, and boots that do not compose.applycreated there before the telemetry sibling was provisioned, which that release's changeset already says it leaves in place.Generated by Claude Code