Repository navigation
Commit 84c4fff
fix(service-package, cli): os migrate plan / apply cover sys_packages, and the boot honours OS_SKIP_SCHEMA_SYNC for it (#23060)
Fixes #23022
Clause-②: no (the seat cannot read it without a build: a newly exported
object definition or name flips it; the dev measures on the built
declaration closure and stops on a flip)
## What this does, in the card's order
1. **The documented migration path covers `sys_packages`.**
`PackageServicePlugin.init()` now declares the table as a kernel
service, `package.storage`: a read-only catalog probe (SQLite
`sqlite_master`, PostgreSQL `information_schema.tables` at
`current_schema()`, MySQL `information_schema.tables` at `DATABASE()`)
and the boot's own `ensurePackageTable`. `bootSchemaStack` (the funnel
`os migrate plan` and `os migrate apply` share) lists the table as
`create_table` when it is absent, and `flushSchemaDdl` creates it with
those same statements. A migrate boot runs a provider's `init()` and
never its `start()`, which is why the declaration lives in `init()`.
2. **Then the boot honours the flag.** `start()` reads the value
`ObjectQLPlugin` resolved and published on the engine (`skipSchemaSync`,
the seam #22984 added for plugin-audit and service-messaging; no second
environment read). When it is set, `start()` sends no DDL and no probe,
logs one `info` line naming `sys_packages`, the flag and `os migrate
apply`, and still registers the `package` service. Unset, the boot is
unchanged.
Where the table's presence cannot be read (a default driver that names
no SQL dialect, or a seam that returns no result set), the run says so
in its composition notes and neither lists nor creates the table: "could
not tell" is never read as "absent".
## Why not a registered object (the triage's preferred shape): measured,
A3 holds
A declared `sys_packages`, through `SqlDriver.initObjects` on SQLite
(better-sqlite3) at this branch's base:
- fresh database: `` `id` varchar(255) ``, `created_at` / `updated_at`
typed `datetime` with a `strftime` default, every other column nullable,
and `primary key (id)` alone. A second version of one package id is
refused: `SQLITE_CONSTRAINT_PRIMARYKEY ... UNIQUE constraint failed:
sys_packages`.
- the same object adopted over a raw-DDL table: the table is untouched
and `detectManagedDrift()` reports 0 entries.
So fresh and existing deployments would get different tables, and the
fresh one breaks multi-version publish. That is the A3 stop shape, so
the managed-path entry keeps the raw DDL instead. `package-table.ts`
already recorded the same result on PostgreSQL 16.14 and MySQL 8.0.46
(from the #21243 work). The create statements are unchanged, so `apply`
creates exactly what the boot creates. On SQLite, `sqlite_master` holds
byte-identical `CREATE TABLE` and `CREATE INDEX` text for both
(integration reading below). PostgreSQL and MySQL were not reachable
from this container: the new presence probes on those dialects are
pinned by statement, not run live.
## Measured through the public doors (`os serve` with
`OS_MIGRATE_AND_EXIT=1`, `os migrate plan --json`, `os migrate apply
--yes --json`, SQLite)
| reading | base `9f4b652e3` | this branch `6f0d23fde` |
|---|---|---|
| boot, flag unset, fresh | `sys_packages=true` | `sys_packages=true` |
| boot, flag set, table dropped | `sys_packages=true` (DDL behind the
flag) | `sys_packages=false`, one `NOT provisioned at boot` info line |
| `os migrate plan --json`, table absent | `pending: []` | `pending:
["create_table:sys_packages"]` |
| `os migrate apply --yes --json`, flag set | `message=in_sync`, still
absent | `created: ["create_table:sys_packages"]`, present |
| apply-created table equals boot-created table | n/a | `VERDICT shape
apply == boot: true` |
| boot, flag unset, table dropped (CONTROL) | re-created | re-created |
## A4: `Clause-②: no` holds, measured on the built declarations
BASE sources of the three changed source files were built in this
worktree (`service-package` tsup, `cli` tsc), then HEAD's were. The
comparison covers 40 declaration files: service-package
`dist/index.d.ts` and `index.d.cts`, the CLI's entry closure (`index` /
`console` / `hook-body`, 37 files) and `dist/utils/schema-migrate.d.ts`.
They were compared after the TypeScript printer stripped comments:
`VERDICT IDENTICAL: 40 identical, 0 differ`. Positive control: injecting
one member into `declare class PackageServicePlugin` and one export into
`schema-migrate.d.ts` reads `DIFFER` for both. The restore was proven by
blob hashes and an empty `git status --porcelain`, and two HEAD builds
read identical. New names are module-private, and `package.storage` is a
service name, not a declared one.
## Tests
- `@objectstack/service-package`: new `managed-path.test.ts` (18 cases).
On a real `node:sqlite` database, the declaration creates the `(id,
version)` table and its index, and the probe is a read. Each dialect is
asked its own catalog. "Could not tell" is `undefined`, and no SQL is
sent. With the flag set, `start()` sends no DDL, the table stays absent,
there is one info line, and the service is registered. The CONTROL rows
are unset, `false` and the string `'true'`. `package-table.test.ts`'s
context now captures only the `package` service, because `init()`
registers one more. Suite: 7 files / 113 passed; `typecheck` exit 0 (its
`tsc --listFiles` includes the new test).
- `@objectstack/cli`: new
`src/utils/schema-migrate.package-table.integration.test.ts`, through
the real funnel with `OS_SKIP_SCHEMA_SYNC=1` set. It covers three steps.
First, a plan on a never-started project lists `create_table
sys_packages` with its seven columns and creates no file. Second, apply
has nothing for the table before the flush; the flush creates it as
`PRIMARY KEY (id, version)` with `TEXT` columns and `idx_packages_latest
(id, created_at)`. Third, a re-plan lists nothing for it.
- At HEAD `9c4be9e42` (after merging `main`):
- CLI `--project unit`: 283 files / 4270 passed.
- CLI `--project integration`, the 10 files on this path: the new one,
`unmanaged-tables`, `schema-migrate.host-composition` / `readonly-probe`
/ `deferred-ddl` / `one-shot-family` / plain, `plan.boot-parity`,
`plan.telemetry-sibling` and `apply.account-issuer-retirement`. 10 files
/ 119 passed.
- CLI `typecheck`, including `check:test-typecheck`: exit 0.
- The rest of the integration project is declared to CI.
### Ablations: each negative pin was put back and watched go red, then
restored
Mutations went through `scripts/ablation-replace.mjs`, with an
EXIT/INT/TERM trap that restores from `HEAD`. Each restore was proven by
blob hash and an empty `git diff HEAD`.
- **The boot creates nothing under the flag.** Mutation: `if (false &&
bootSchemaSyncSkipped(objectql))`. Result: `managed-path.test.ts` 2
failed | 16 passed, `expected [ …(2) ] to deeply equal []` (the two DDL
statements were sent) and `expected true to be false` (the table
exists).
- **The migrate boot does not create the table before the flush.**
Mutation: the CLI calls `ensure()` while planning. Result: 1 failed,
`expected [] to deeply equal [ { table: 'sys_packages', …(2) } ]`.
- **The producer, across the package boundary.** Mutation:
`PACKAGE_STORAGE_SERVICE` renamed to `'package.storage.ablated'` in
service-package, which was then rebuilt. `ablation-dist-preflight`
reported "marker present in 2 built files". Result: the CLI test failed
1, `expected [] to deeply equal [ { table: 'sys_packages', …(2) } ]`.
Restore leg: rebuilt, preflight `--absent` reported "marker absent from
all 6 built files", and the test passed 1.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `9c4be9e42` derived 68 families over 7
changed paths, with merge base `df7a13792`. All 68 ran, and all exited
0. `check:dual-build-cjs-loads` and `check:i18n-coverage` first answered
`PREREQUISITE NOT MET` (exit 3: packages outside the CLI closure were
not built). They were re-run green on the same HEAD after a full turbo
build. `--ran` reports: `68 derived famil(ies) accounted for — 68 run, 0
NOT-MEASURED`.
Lint was narrowed and the narrowing proven. `eslint --no-inline-config
--format json` over the 6 changed `.ts` files reports 6 files, 0 errors,
0 warnings, ESLint exit 0. The config lints all 6 files (no ignore
warning) and enables no type-aware linting (no `parserOptions.project`),
so no untouched file's verdict can move. The changeset `.md` is outside
eslint's population.
## Acceptance notes (not filed)
- The seam is a string-named kernel service, `package.storage`. Like
`skipSchemaSync`, it is an undeclared surface, chosen so that no name is
added to either package's declarations. It is held together by the CLI
integration test booting the real plugin, and the producer ablation
shows that test failing when the name moves. A declared accessor would
be the contract-first form and widen the published surface. Carrier:
none.
- The flag-half pins in service-package use an engine double that
carries `skipSchemaSync: true`. service-package does not depend on
`@objectstack/objectql`, and the unaliased-test-import ledger is
shrink-only. The real producer was exercised once, through `os serve`
(table above). Its publication is pinned by plugin-audit's and
service-messaging's real-producer seam tests. Carrier: none.
- On a flag deployment that boots before running `os migrate apply`,
hydration's `list()` meets the missing table. The SQL driver logs its
own `DATABASE_ERROR — no such table: sys_packages` line, and hydration
logs at `debug`. That is loud enough to find, and the changeset says to
run `os migrate apply` first. Carrier: none.
- The presence probe asks for the table, not for `idx_packages_latest`.
A table that exists without its index (reachable only by hand) is not
re-planned. Carrier: none.
- `os migrate plan`'s unmanaged-table sweep still lists `sys_packages`
when the table exists. Its wording, "declared by no object in this
plan", stays literally true: the table is declared by a service, not by
an object. The boot-parity pins still expect it. Carrier: none.
- `os migrate plan` on a fresh project now lists one more
`create_table`. That is intended: it is what `apply` will create.
- Merged `main` at `9c4be9e42` before opening. The incoming commits
touch `packages/spec`, the security package-verb walls and CI, and none
of this diff. #22985's region of `schema-migration-plugins.ts` is
untouched: this change lives in `schema-migrate.ts`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CeRCvvvjxbxu2iYJAc5Cce)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ec7f7f8 commit 84c4fff
7 files changed
Lines changed: 702 additions & 19 deletions
File tree
- .changeset
- packages
- cli/src/utils
- services/service-package/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
244 | 244 | | |
245 | 245 | | |
246 | 246 | | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
247 | 323 | | |
248 | 324 | | |
249 | 325 | | |
| |||
925 | 1001 | | |
926 | 1002 | | |
927 | 1003 | | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
928 | 1008 | | |
929 | 1009 | | |
930 | 1010 | | |
| |||
1008 | 1088 | | |
1009 | 1089 | | |
1010 | 1090 | | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
1011 | 1097 | | |
1012 | 1098 | | |
1013 | 1099 | | |
| |||
0 commit comments