Repository navigation
fix(cli,driver-turso): os migrate plan/apply run on a Turso remote database and name the drift entries they cannot apply - #23923
Conversation
…ith a local control WIP: the pin, written before the fix. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…tabase and name the drift entries they cannot apply WIP: implementation before verification. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
… changes landed Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…e remote pin reads stdout only when no earlier boot left it reserved Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…grate-apply-turso-remote
…olds once the remote remedy runs Comment text only; the gate's behaviour is unchanged. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
…grate-apply-turso-remote # Conflicts: # packages/drivers/driver-turso/src/turso-driver.ts
… merged caveat Comment text only: a table the remote sync created in an earlier release lacks the declared NOT NULL, and the remedy now runs and names what it cannot apply. The gate's behaviour is unchanged. Claude-Session: https://claude.ai/code/session_01PFB9ZGRWYvjhTXsd8i6KBp Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 10 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 — 32 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 64be5534158d34414352b7d2414f09fc48449831 && git checkout 64be5534158d34414352b7d2414f09fc48449831
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2 90e7060f9dd80e4a8c8aaea772ac3da487b3f07f && git checkout -B drift-repro abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2 && git merge --no-ff 90e7060f9dd80e4a8c8aaea772ac3da487b3f07f
node scripts/docs-audit/affected-docs.mjs --json abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2
|
Contract reviewServed-tier: Scope: owed by the ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
✅ ACCEPT (patch round 1): PR #23923 at
|
Fixes #23848
Clause-②: yes (a Turso REMOTE primary, refused at the boot of
os migrate plan/os migrate applyuntil now, is accepted; the post-apply--jsondocument gainsnotApplied; nopackages/specedit and no new package-entry export)Summary
os migrate planandos migrate apply(its plan arm and its--allow-destructivearm) now run on a Turso database reached over the REMOTE transport (libsql://..., every hosted tenant). Until now both stopped at their boot withDeferred schema DDL is not supported by the Turso REMOTE transport, so the remedy the drift report and theOS_ARTIFACT_URLboot gate print for destructive drift,os migrate apply --allow-destructive, could not run there at all.Posture. Both commands pass
deferredDdlUnsupported: 'skip-schema-sync', the posture the organization-ownership ceremony took for the same transport. They still compose and boot identically (the plan adds only its read-only probe), so the plan an operator reads is the one the apply confirms.What the posture loses, said out loud. A skip-sync boot holds no additive work back, so on this transport the commands cannot list or create a missing table or column. They now say so instead of reading as "in sync": a warning under the database line in both commands (
SKIPPED_SCHEMA_SYNC_NOTE),schemaSync: "skipped"on every--jsondocument after the boot,in_sync_partialinstead ofin_syncfor a drift-free apply, and a PARTIAL wording for a drift-free plan. On this transport the serving boot (os serve/os start) performs the additive sync.Naming what the driver did not apply. The REMOTE face applies a column drop and every index entry, and skips every other column entry (it runs no SQLite table rebuild). The driver already returned those in
skipped, mixed with the destructive entries held back for lack of--allow-destructive.applynow separates the two:--json: a newnotAppliedarray on the post-apply document);--allow-destructivemakes the run PARTIAL:PARTIAL apply: ...and exit 1,message: "partial"and exit 1 under--json;manual_column_type_changeand its siblings) are skipped on every dialect and always were.This rule is the same on every database. On a local file or PostgreSQL its only new effect is on a failure path: a destructive change confirmed with
--allow-destructivethat the driver could not apply now exits 1 and is named, where the run used to exit 0 with a count.Text this change made false, edited to what is true now:
turso-driver.ts: only the clause this card owns under the relay with driver-turso remote face: create table and add column never emit NOT NULL, so storage.notNull is unenforced and every such field reads as destructive drift #23847, inREMOTE_DRIFT_REPORT_CAVEAT("a destructive entry has no remedy on this transport (os migrate applyrefuses against it)") and its docblock twin. The caveat's NOT NULL sentences are untouched.artifact-boot-migration.ts: the one comment above thedriftReportCaveatread. This is comment text only, handed to this card by the claim's amended file surface anddomain:engine's pointer 6214810315. It now says that NOT NULL is missing on remote databases synced by an earlier release, and that the remedy runs there but applies only a drop or an index change. The gate's behaviour (warn-and-boot on a caveat) is unchanged. PR fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 merged as 2034975; this branch merged it (ed06c32), and the comment now agrees with the merged caveat (90e7060).schema-migrate.ts: thedeferredDdlUnsupporteddocblock, whose two sentences ("Right for ...os migrate plan/apply" and "today that isos migrate organization-ownership") became false.The driver's return shape is unchanged. The PM's assumption that driver-turso had to start naming the skipped entries was falsified: it already returns them by reference, and the gap was in the CLI's reporting. So the driver-turso half is the caveat clause only.
Evidence: a real
sqld(libsql-server 0.24.32),libsql://127.0.0.1:PORT?tls=0The fixture is a declared object
os23848_notewhose table is created by hand first, with three drifts:legacy_23848, an undeclared column:drop_column, destructive, which the remote face can express;code, nullable, understorage: { notNull: true }:tighten_not_null, destructive, which needs a rebuild;memo, NOT NULL, under no constraint:relax_not_null, needs-confirm, which needs a rebuild.The table is made by hand so the drift does not depend on what the remote sync creates, before or after #23893. The environment's own syncing boot runs over it, and every claim about the server is read back through a second libSQL connection.
Positive control, unchanged production code (
c4e9066e01: the new pin alone on base6422da58e8). All four remote cases are red. The plan exits 1 with anerrorthat opens "Deferred schema DDL is not supported by the Turso REMOTE transport", and each apply arm exits 1 at its boot with the same refusal.With the change,
apply.remote-face.integration.test.tsat head1b484247a8,--reporter=verbose: 5 of 5 green. The ceremony's own remote pinorganization-ownership.remote-face.integration.test.tsran in the same run, 1 of 1 green. The 5 cases:apply --allow-destructive --yes --jsonlegacy_23848gone,codeNOT NULL,memonullable. Asserts only keys the document carried before this change.plan --jsondatabase=libsql://127.0.0.1:PORT;schemaSync: "skipped";pending: []; changes on the table = the three; logical dump (every catalog row and every table's rows, hashed) equal before and afterapply --yes --jsonnotApplied=memo relax_not_null; the two destructive entries held back; nomessageapply --allow-destructive --yes --jsonmessage: "partial"; applied =legacy_23848 drop_column;notApplied=code tighten_not_null,memo relax_not_null. Second connection:legacy_23848gone, rown1kept,codestill nullableos23848_note.code — tighten_not_null (destructive)andos23848_note.memo — relax_not_nullnamed;PARTIAL apply: 1 destructive change(s) ...End to end through the CLI's own entry (
bin/run-dev.js, one process per command, real exit codes), on asqldthe script started and killed by its PID. The database was provisioned by the real serving boot (os servewithOS_MIGRATE_AND_EXIT=1, 71 tables), on the fixture above:Ablations (at
71757c7896, production code equal to head apart from one comment). Each went throughscripts/ablation-replace.mjsin WRAP mode, inside a script carrying its owntraprestore. Each mutation was proven on disk (anchor count 1 then 0, blob changed). Each was restored to the HEAD blob withgit diff HEADempty. The subject resolves tosrc/through relative imports, so no rebuild was involved.deferredDdlUnsupported: 'skip-schema-sync'removed fromapply.ts. Red: the unit pin's apply-boot case, and the three remote apply cases, each with the deferral refusal. Green: the remote plan case, the local control and the other 7 unit cases (4 failed, 9 passed).plan.ts. Red: both unit posture cases, and the remote plan case with the deferral refusal. The two remote JSON apply cases after it also went red as collateral: a failed--jsonboot keeps stdout reserved for the rest of the process, so their documents went unread. The human-face apply case stayed green. The pin now assertsisStdoutReserved()is false before each run, so such a collateral red names its cause.CI and
sqldCI has no
sqld, so the four remote cases skip there. The measurement is the local run above. What CI does run isapply.remote-posture.test.ts(unit tier,bootSchemaStackstubbed, 8 cases), which pins the posture and the verdict without a database, and the local-file control in the integration file.Tests and gates (head
1b484247a8unless stated)@objectstack/clitypecheck exit 0, at the merge2dcfbceb50, before the comment-only commit. That istsc --noEmit(whose program compiles the two new test files: it reported TS2684 in both before the call-site fix) andcheck:test-typecheckOK.@objectstack/cliunit tier, 3 shards at2dcfbceb50and the comment-only head: 101 + 101 + 101 files, 1738 + 1315 + 1455 tests, all passed.@objectstack/cliintegration, run locally for the files this change adds or whose behaviour it touches: the two remote-face files (6/6, above), thenapply-organization-ownership-gate,apply.account-issuer-retirement,organization-ownership-platform-global-column-drop,comment-reactions,schema-migrate.deferred-ddl,schema-migrate.integration(6 files, 30 tests passed).test/migrate-apply-refuses-before-ddl.e2e.test.tswas selected by no project in that call: NOT MEASURED locally, left to CI.@objectstack/driver-turso: typecheck exit 0;pnpm test90 files, 2415 passed and 33 skipped.ESLint over the 7 changed source and test files: exit 0, no output.
Derived gate families:
dispatch-gates --commands, 69 families, each exit captured before any pipe.--ranreconciliation: 65 run with exit 0, 4 NOT MEASURED, 0 UNRUN.check:dual-build-cjs-loads, exit 3: PREREQUISITE NOT MET (packages outside this build closure have nodist/).check:i18n-coverage, exit 3: a partial round.check:query-options-erasureandcheck:type-check-debt: killed by my 400 s wrapper, exit 124.CI runs all four.
Merge:
origin/mainwas merged once before opening this PR (2dcfbceb50); no conflict and no generated-artifact deferral. PR fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 had not merged. Patch round 1: fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 merged as 2034975, and origin/main was merged again as ed06c32. One conflicted path, turso-driver.ts (the caveat docblock), was resolved keeping fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893's NOT NULL sentences verbatim and this card's remedy clause. At head 90e7060: driver-turso 91 files, 2425 passed / 33 skipped; the remote-face pins 6/6 on sqld; CI 40 check-runs, all success or skipped.Acceptance notes
os migrate duplicates,account-issuer,unmapped-columns(which reads the plan's own findings),multi-value-columns, and the dry runs ofmeta --stored,value-shapes,activity-changed-fields,audit-metadata-bodies,recorded-by,comment-reactions,resume,files-to-references,security-catalog-overlaysandsummary-nulls. Outsideos migrate:os secret rewrap/orphansandos storage orphans.os migrate planagainst a local SQLite copy of this database") is still true but no longer necessary, sinceos migrate plannow runs against the remote database. It is outside the clause this card owns under the relay, so it is left as it is for a later caveat edit; fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 merged without changing it.os migrate planprints the shared differ's message for atighten_not_null("Run os migrate apply --allow-destructive"), which the apply then names as not applied on that transport. The plan does not say up front which entries the transport cannot apply. That would need the driver to declare which ops it can express, and no such member exists. It is reported in the dev report for the seat to judge.Paged read of 'sys_metadata_activation' is NOT deterministic: this driver did not create the table ...on stderr, as the ceremony's skip-sync boot already does. This is an observation only.--allow-destructivechanges fromSkipped N change(s) (destructive without --allow-destructive, or unsupported on this dialect).toSkipped N destructive change(s) (re-run with --allow-destructive to include them).. The entries "unsupported on this dialect" now have their own named list.Generated by Claude Code