You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d3d0644
Browse filesBrowse the repository at this point in the historyBrowse files
fix(cli): os migrate plan no longer lists sys_packages as unmanaged after os migrate apply created it (#24226)
Fixes#24059
Clause-②: no
## What changes
`os migrate plan` no longer lists `sys_packages` under "declared by no
object in this plan" after `os migrate apply` has created it. A
genuinely stranded `sys_*` table is still listed.
`sys_packages` is the package registry's table. It is not an object.
`PackageServicePlugin.start()` creates it with raw DDL, and since #23022
`os migrate apply` creates it from the registry's `package.storage`
declaration. The plan's unmanaged-table sweep built its known set from
the planned drivers' managed tables and the declared objects only.
`sys_packages` is 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 the
`package.storage` declaration (the lookup and the shape check that used
to open `planPackageTable`). `bootSchemaStack` reads it once per boot.
`planPackageTable` plans the table from that object as before, on a
deferring boot only. The same object's `table` is exposed as
`SchemaStack.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. `collectUnmanagedTables` takes a required
`nonObjectTables` option 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`: passes
`stack.nonObjectTables` to the sweep. The sweep reads the declaration
from the same boot and kernel the plan runs in.
## Measured before changing anything (PM hypotheses)
- **H1, confirmed** at `aa31569b2`: `buildKnownTableNames` returned the
managed set plus each declared object's `name` and resolved table name,
and `selectUnmanagedTables` reported every platform-prefixed table
outside it.
- **H2, confirmed**: `planPackageTable` returns `null` when the table is
present. Door reading below: after the apply, the plan's `pending` is
`[]`, 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: `bootSchemaStack` reads the declaration from the kernel it
booted, and `plan.ts` hands it to the sweep from the returned stack.
- **H3, pinned as a decision**: when no package registry is composed,
`nonObjectTables` is `[]`, and a `sys_packages` table 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 mounts `PackageServicePlugin`
through the always-on `package-registry` slate (`composeServedPlatform`
in `schema-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 …").
- **H4, re-measured at base `aa31569b2`** on `examples/app-crm`. Setup:
a fresh sqlite file with `sys_legacy_widget` created up front, fresh
`OS_HOME`, DB env unset, run through `packages/cli/bin/run-dev.js` with
`--database-url file:DB`. Then `migrate apply --yes`, then `migrate
plan`, then `migrate plan --json`:
- base: apply printed `+ sys_packages [create_table, 7 column(s)]` and
`Created/extended 80 table(s)`. The plan printed `2 table(s) … declared
by no object in this plan:` with `• sys_legacy_widget` and `•
sys_packages`. `--json`: `unmanagedTables.tables` = `sys_legacy_widget`,
`sys_packages`, `physicalTables` 81, `pending` `[]`.
- head `04c0d95ec`: same apply. The plan printed `1 table(s) …:` with `•
sys_legacy_widget` only. `--json`: `tables` = `sys_legacy_widget`,
`physicalTables` 81, `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 and `sys_legacy_widget` still is; and the
H3 decision. Every existing `buildKnownTableNames` /
`collectUnmanagedTables` call states `nonObjectTables` explicitly.
- `unmanaged-tables.integration.test.ts` uses a real boot with the
composition `plan` uses. `sys_packages` joins the negative controls, and
the premise case asserts that `stack.nonObjectTables` equals the `table`
of the kernel's own `package.storage` service while nothing is pending
for it.
- `schema-migrate.package-table.integration.test.ts`: `nonObjectTables`
is `['sys_packages']` on the plan of a never-started project and on the
plan after the apply, when `pending` holds nothing for the table.
- Two door-level pins encoded the defect, and they flip with this
change. `plan.boot-parity.integration.test.ts` expected the sweep to
name exactly `sys_packages`, and now expects it to name nothing.
`plan.telemetry-sibling.integration.test.ts` expected `sys_packages`
plus 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:
- the anchor went 1 to 0, and the blob went `99e653e8d5d0` to
`33c383315c85`;
- `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;
- restore: the blob equals HEAD (`99e653e8d5d0`), `git diff HEAD` is
empty, and `git status --porcelain` is empty.
The unit suite imports `./unmanaged-tables.js` relatively, so it reads
`src/` and no `dist/` rebuild applies.
## Tests and gates
All readings are at head `7a3803bd3` unless another commit is named.
`04c0d95ec` is the fix commit. `7a3803bd3` merges `origin/main`
`b9ac35cbf`, which touches CI workflows and only one file in
`packages/cli`: `test/published-entry-node-env-source-reroute.test.ts`.
- `pnpm --filter @objectstack/cli typecheck` at `04c0d95ec`: exit 0.
That is `tsc --noEmit`, then `check:test-typecheck`, which held 3 files,
28 errors and 6 signatures. `tsc -p packages/cli/tsconfig.json
--listFilesOnly` puts all 5 touched test files in its 1611-file program.
- Unit tier at `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.ts` and
`test/published-subpath-hook-body.pin.test.ts`, were refused before
their bodies ran ("packages/cli is not built"). After `pnpm --filter
@objectstack/cli build`, both pass: `Tests 29 passed (29)`.
- Integration tier at `04c0d95ec`, the four touched files with
`--project integration`: `unmanaged-tables.integration` and
`schema-migrate.package-table.integration` 4 passed,
`plan.boot-parity.integration` 3 passed,
`plan.telemetry-sibling.integration` 2 passed.
- After the merge:
`test/published-entry-node-env-source-reroute.test.ts` and
`src/utils/unmanaged-tables.test.ts`, 50 passed.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `7a3803bd3`: 64 commands, merge base
`b9ac35cbf`, 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-loads` and `check:i18n-coverage` exited 3
(PREREQUISITE NOT MET); both exited 0 after `turbo 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-config` over the 8 changed `.ts` files:
exit 0. Repo-wide `pnpm lint` is left to CI.
## Acceptance notes
- **One more refusal path, stated.** `findPackageStorage` now runs on
every boot, not only a deferring one. A `package.storage` service of the
wrong shape (a version skew between `@objectstack/cli` and
`@objectstack/service-package`) now also fails the skip-schema-sync boot
of `plan`, `apply` and `organization-ownership` (the Turso remote
transport). It fails with the release-mismatch error those commands
already gave on every deferring boot. Only those three commands, plus
`unmapped-columns`, compose the registry. The changeset says so.
- The section's sentence still reads "declared by no object in this
plan". The rows it lists are still declared by nothing, so the wording
is unchanged.
- `nonObjectTables` is a union across every planned database, like the
managed set beside it. The registry writes to the default driver, so a
`sys_packages` table in the `telemetry` sibling would also go
unreported. Over-excluding is the module's stated safe direction. Not
measured; no carrier.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb)_
Co-authored-by: Claude <noreply@anthropic.com>
fix(cli): `os migrate plan` no longer lists `sys_packages` among the tables "declared by no object in this plan" after `os migrate apply` has created it
6
+
7
+
Clause-②: no
8
+
9
+
`sys_packages` is the package registry's table. It is not an object: `PackageServicePlugin` creates it with its own DDL, and `os migrate apply` creates it from the registry's `package.storage` declaration. The plan's unmanaged-table sweep built its known set from the planned drivers' managed tables and the declared objects only, so on every applied deployment the plan reported the live registry table as if it were stranded.
10
+
11
+
-**Now:** the sweep also counts the tables the boot's composition declares outside the object set. It reads that name from the same `package.storage` declaration the plan creates the table from. It reads it on every boot, whether or not the table exists yet, the skip-schema-sync boot of a Turso remote primary included. On `examples/app-crm` after `os migrate apply --yes`, the plan lists only a genuinely stranded `sys_*` table.
12
+
-**Still reported:** a `sys_packages` table in the database of a deployment that composes no package registry. Nothing there declares it.
13
+
-**Refused in one more case:** a `package.storage` service whose shape this CLI does not read now fails `plan`, `apply` and `organization-ownership` on a skip-schema-sync boot too, with the error those commands already gave on every other boot: `@objectstack/cli` and `@objectstack/service-package` must come from the same release.
14
+
-**Unchanged:** the `--json` keys, the section's wording, and what `apply` creates.
0 commit comments