Repository navigation
fix(cli): os migrate meta --stored and audit-metadata-bodies preview on a read-only boot - #21389
Conversation
…on a read-only boot Without --apply, both commands now boot the stack the way `os migrate plan` does: `deferSchemaDdl` holds schema DDL back and suppresses the artifact's inline seed loader, and `readOnlyProbe` keeps a missing sqlite file from being created. The plain boot they used before ran schema sync and the seed loader, whose upserts rewrote every seeded row of the app's tables during a preview. `--apply` keeps the plain boot. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…ve PostgreSQL Runs the real `os migrate meta --stored` and `os migrate audit-metadata-bodies` commands against a database a plain boot seeded, an operator edited, and that carries one legacy flow row and one cleartext audit copy. Without --apply the schema and every row stay byte-identical while the report still names the work; --apply still does that work; and a preview at a missing SQLite file creates no file. The live PostgreSQL cell runs in a database derived from this file's path and is a named skip without OS_TEST_POSTGRES_URL. Adds the patch changeset for @objectstack/cli. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…grate-preview-read-only
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 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 82df0792bc82bd1063d5d566e7529bb375c0fd06 && git checkout 82df0792bc82bd1063d5d566e7529bb375c0fd06
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 51550933dbc5c7cbd89349a6beb5145363fc881d 0166f86ba29896dc08238613b88a19f048c69388 && git checkout -B drift-repro 51550933dbc5c7cbd89349a6beb5145363fc881d && git merge --no-ff 0166f86ba29896dc08238613b88a19f048c69388
node scripts/docs-audit/affected-docs.mjs --json 51550933dbc5c7cbd89349a6beb5145363fc881d
|
…wing (minor, BREAKING banner, ADR-0087 not-required) A preview of `os migrate meta --stored` or `os migrate audit-metadata-bodies` at a database that lacks the table it reads now exits 1 where it exited 0, so the changeset takes the narrowing arm: `minor`, `Clause-②: yes (narrowing)`, the BREAKING banner, and one ADR-0087 disposition, `not-required (no-migration-prescription)`. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…grate-preview-read-only
Contract reviewServed-tier: Reviewed against card #21349 (body, triage direction ① Derived judgments(a) Write paths closed by
(b) Reuse of (c) The pins — right as delivered, with one unpinned declaration and one CI gap. (d) Changeset and PR prose — true at the head, two sentences qualified.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #21349
Clause-②: yes (narrowing)
What changed
Without
--apply,os migrate meta --storedandos migrate audit-metadata-bodiesnow boot through the read-only bootos migrate planalready uses:bootSchemaStack({ deferSchemaDdl: true, readOnlyProbe: true }). Schema DDL is held back, the artifact's inline seed loader is suppressed (deferSchemaDdlimpliesskipSeedData), and a SQLite file that does not exist is opened as an empty in-memory stand-in instead of being created.--applykeeps the plain boot, unchanged.The code change is one spread per command (
meta.ts,audit-metadata-bodies.ts). There is no new mechanism, no table skip list and no driver change.Measured before the fix (the card's steps, on
examples/app-crm)Setup:
os build, thenos dev -d file:base.dbonexamples/app-crm. The seed load ran (28 rows) and the first admin was seeded. The server was stopped and the file copied. The copy has 82 tables and 150 rows. Each command ran on its own copy; the row hashes were read on a separate read-only connection.meta --stored, BASE3937ad2f32audit-metadata-bodies, BASEmeta --stored, this PRaudit-metadata-bodies, this PRThe changed columns were
updated_at(bumped) andorganization_id(stamped with the admin's organization on seeded rows that had none). The log line was[Seeder] Seed loading complete {"inserted":0,"updated":28,...}. After the fix it is[Seeder] skipSeedData — inline seed suppressed. The report each preview prints is the same as before, apart from one paged-read notice the seed loader itself caused.The write paths found, and what closes each
AppPlugin.start): an upsert of every seeded row. In the pin below it also puts an operator's edit to a seeded row back to the seed's value (status: 'won'back to'open'). This is closed byskipSeedData.--applytakes, which is the boot both previews took before this PR: a droppedcrm_lead.phonecolumn was added back, andsys_audit_logwas created with five named indexes. This is closed bydeferSchemaDdl. On the same two edge databases, the fixed previews left the schema and every row identical.readOnlyProbe.These are not write paths on this boot: the platform repair migrations (already off:
runPlatformMigrations: false), a hostonEnable(these commands compose no host config, and a compiled artifact cannot carry one), and the lifecycle sweep (its first run comes after the one-shot command has exited).Pins
packages/cli/src/commands/migrate/preview-read-only.integration.test.tsruns the real commands through oclif: parse, occupancy probe, boot, walk and shutdown. Each case runs against a database that a plain boot seeded and an operator then edited, and that carries one legacy flow row and one cleartext audit copy of a datasource body. Per dialect cell:meta --storedwithout--apply: the schema and every row are byte-identical, and the report still names the one pending row.meta --stored --apply --yes: the legacy row is rewritten (the control).audit-metadata-bodieswithout--apply: byte-identical, and the report still counts the one copy to rewrite.audit-metadata-bodies --apply --yes: the copy is redacted (the control).-wal,-shmor-journal.Cells: SQLite always runs. Live PostgreSQL runs with
OS_TEST_POSTGRES_URL, in a database derived from the file's path (os_lv_+ slug + 12 hex characters of sha256;check-live-db-isolationPASS with this file in its 43 scanned files). Without the URL the cell is a named skip, and a failure underOS_EXPECT_LIVE_DIALECT_MATRIX=1.Ablations
The fix was committed first (
88e37aa343). Each leg ran throughscripts/ablation-replace.mjsin WRAP mode: the anchor hit once, the mutation was confirmed on disk (anchor 1 to 0, marker 0 to 1, blob changed), and the restore was proven (blob == HEAD,git diff HEADempty). The pin imports the commands by relative path, so it resolves tosrc, notdist. No rebuild was needed between legs. Runs used the live PostgreSQL cell.meta.ts: plain boot (spread removed)audit-metadata-bodies.ts: plain bootmeta.ts:deferSchemaDdlonlymeta.ts:readOnlyProbeonlyaudit-metadata-bodies.ts:deferSchemaDdlonlyaudit-metadata-bodies.ts:readOnlyProbeonlyThe L1 diff shows the operator's edit reverted on both dialects:
"status": "won"became"open"with a newupdated_at.The 17.5.0 comparison
This did not reproduce on
examples/app-crm. The 17.5.0 CLI (tag@objectstack/cli@17.5.0, built in a comparison worktree) ran on a database it had seeded itself, andmeta --storedrewrote the same 28 rows. That was measured twice, and both runs logged"updated":28.meta --storedhas booted without the read-only options since it landed in83cf2d3082(#4464).audit-metadata-bodieshas done the same since it landed in336e191441, which is in 17.6.0 and not in 17.5.0 (git merge-base --is-ancestorexit 1; control leg, the root commit, exit 0; full clone). Why the HotCRM 17.5.0 run left its hashes identical cannot be measured from this repository.One edge behaves differently
A preview pointed at a database that lacks the table it reads now fails and exits 1. Before, it created the table and reported nothing to examine. Measured:
meta --storedon a missing file, or on a database withoutsys_metadata, exits 1 with the driver's refusal forsys_metadata, and no file is created.audit-metadata-bodieson a database without the audit tables exits 1 withcould not read sys_audit_log — its rows were NOT examined. Before the fix, the same runs created the file or the tables and answeredNo stored metadata to examineorNothing to rewritewith exit 0. The changeset states this.Verification at
a67e290cc7(this branch merged withorigin/main11905a4f8b)pnpm --filter @objectstack/cli build && pnpm --filter @objectstack/cli typecheck: exit 0, andcheck:test-typecheckOK. The new test is undersrc, whichtsconfig.jsonincludes.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 246 files, 3489 tests passed.meta.stored-flow-resolution,schema-migrate.deferred-ddlandplatform-migrations-arminggave 4 files and 23 tests passed, with the live PostgreSQL cell (local PostgreSQL 16 on a private port). Without the URL: 6 passed and 1 named skip.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 63 families, each run one by one with its exit code recorded before any pipe.--ranreported 63 run, 0 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loadsandcheck:i18n-coveragefirst answered exit 3 (PREREQUISITE NOT MET: nine packages outside the CLI closure had nodist). After a turbo build of those nine (42 of 42 cache hits), both exited 0.--no-inline-configover the 3 changed TS files (--format json) reported 3 files linted, 0 errors and 0 warnings, and no file was reported as ignored. The config enables no type-aware linting (eslint.config.mjssays so: noparserOptions.project, no typed rules), so this diff cannot move the verdict on any untouched file. The fullpnpm lintis left to CI.Acceptance notes
a67e290cc7on the same app-crm database:os migrate value-shapes,summary-nulls,recorded-by,resume,files-to-references,os secret orphansandos storage orphanseach ran the seed loader and rewrote the same 28 rows. All seven are dry-run by default or report-only.os migrate account-issuerandmulti-value-columnsalready boot read-only and left the database identical. This is reported to the seat as one family.--applyon these two commands still runs the boot's seed loader and schema sync, as before. The direction keeps--applyunchanged.runPlatformMigrationsblock inschema-migrate.tsand the non-deferred case inplatform-migrations-arming.integration.test.tslistos migrate metaamong the boots withoutdeferSchemaDdl. After this PR that is true only with--apply. They are not edited here because both files are outside this claim's surface.OS_TEST_POSTGRES_URLto@objectstack/cli, so in CI the cell is a named skip. Wiring it takes one build step and one run step in the Temporal Conformance job of.github/workflows/ci.yml, which is also outside this claim's surface.content/docs/deployment/cli.mdxalready says themeta --storedpreview "writes nothing", and this PR makes that true, so the docs are not edited.cli.mdxhas no entry foraudit-metadata-bodies.Generated by Claude Code