Skip to content

fix(cli,driver-turso): os migrate plan/apply run on a Turso remote database and name the drift entries they cannot apply - #23923

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23848-migrate-apply-turso-remote
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23848-migrate-apply-turso-remote

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23848

Clause-②: yes (a Turso REMOTE primary, refused at the boot of os migrate plan / os migrate apply until now, is accepted; the post-apply --json document gains notApplied; no packages/spec edit and no new package-entry export)

Summary

os migrate plan and os migrate apply (its plan arm and its --allow-destructive arm) now run on a Turso database reached over the REMOTE transport (libsql://..., every hosted tenant). Until now both stopped at their boot with Deferred schema DDL is not supported by the Turso REMOTE transport, so the remedy the drift report and the OS_ARTIFACT_URL boot gate print for destructive drift, os migrate apply --allow-destructive, could not run there at all.

  1. 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.

  2. 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 --json document after the boot, in_sync_partial instead of in_sync for 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.

  3. 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. apply now separates the two:

    • every entry the driver was asked to apply and did not is named, by table, column and kind, in both faces (--json: a new notApplied array on the post-apply document);
    • a destructive one confirmed with --allow-destructive makes the run PARTIAL: PARTIAL apply: ... and exit 1, message: "partial" and exit 1 under --json;
    • a non-destructive one is named and the exit stays 0, because the report-only findings no driver applies (manual_column_type_change and 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-destructive that the driver could not apply now exits 1 and is named, where the run used to exit 0 with a count.

  4. Text this change made false, edited to what is true now:

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=0

The fixture is a declared object os23848_note whose 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, under storage: { 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 base 6422da58e8). All four remote cases are red. The plan exits 1 with an error that 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.ts at head 1b484247a8, --reporter=verbose: 5 of 5 green. The ceremony's own remote pin organization-ownership.remote-face.integration.test.ts ran in the same run, 1 of 1 green. The 5 cases:

case reading
CONTROL, local SQLite file, apply --allow-destructive --yes --json exit 0; applied = the drop, the tightening and the relax; skipped = []; on disk legacy_23848 gone, code NOT NULL, memo nullable. Asserts only keys the document carried before this change.
plan --json exit 0; database = 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 after
apply --yes --json exit 0; applied []; notApplied = memo relax_not_null; the two destructive entries held back; no message
apply --allow-destructive --yes --json exit 1; message: "partial"; applied = legacy_23848 drop_column; notApplied = code tighten_not_null, memo relax_not_null. Second connection: legacy_23848 gone, row n1 kept, code still nullable
the same, human face exit 1; the not-examined warning; os23848_note.code — tighten_not_null (destructive) and os23848_note.memo — relax_not_null named; 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 a sqld the script started and killed by its PID. The database was provisioned by the real serving boot (os serve with OS_MIGRATE_AND_EXIT=1, 71 tables), on the fixture above:

serve-migrate-and-exit exit=0
plan --json exit=0                     total 3, schemaSync skipped, unmanagedTables read (sys_packages)
plan (human) exit=0
apply --yes --json exit=0              notApplied [memo relax_not_null]
apply --allow-destructive --yes --json exit=1   message partial, applied [legacy_23848 drop_column]
apply --allow-destructive --yes (human, second run) exit=1
read-back: legacy_23848 absent; code notnull 0; memo notnull 1; row n1 kept

Ablations (at 71757c7896, production code equal to head apart from one comment). Each went through scripts/ablation-replace.mjs in WRAP mode, inside a script carrying its own trap restore. Each mutation was proven on disk (anchor count 1 then 0, blob changed). Each was restored to the HEAD blob with git diff HEAD empty. The subject resolves to src/ through relative imports, so no rebuild was involved.

  1. deferredDdlUnsupported: 'skip-schema-sync' removed from apply.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).
  2. The same line removed from 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 --json boot keeps stdout reserved for the rest of the process, so their documents went unread. The human-face apply case stayed green. The pin now asserts isStdoutReserved() is false before each run, so such a collateral red names its cause.

CI and sqld

CI has no sqld, so the four remote cases skip there. The measurement is the local run above. What CI does run is apply.remote-posture.test.ts (unit tier, bootSchemaStack stubbed, 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 1b484247a8 unless stated)

  • @objectstack/cli typecheck exit 0, at the merge 2dcfbceb50, before the comment-only commit. That is tsc --noEmit (whose program compiles the two new test files: it reported TS2684 in both before the call-site fix) and check:test-typecheck OK.

  • @objectstack/cli unit tier, 3 shards at 2dcfbceb50 and the comment-only head: 101 + 101 + 101 files, 1738 + 1315 + 1455 tests, all passed.

  • @objectstack/cli integration, run locally for the files this change adds or whose behaviour it touches: the two remote-face files (6/6, above), then apply-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.ts was selected by no project in that call: NOT MEASURED locally, left to CI.

  • @objectstack/driver-turso: typecheck exit 0; pnpm test 90 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. --ran reconciliation: 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 no dist/).
    • check:i18n-coverage, exit 3: a partial round.
    • check:query-options-erasure and check:type-check-debt: killed by my 400 s wrapper, exit 124.

    CI runs all four.

  • Merge: origin/main was 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

  • Other commands that still refuse on a REMOTE primary, unchanged and out of scope. Each arms deferred DDL in its dry run: os migrate duplicates, account-issuer, unmapped-columns (which reads the plan's own findings), multi-value-columns, and the dry runs of meta --stored, value-shapes, activity-changed-fields, audit-metadata-bodies, recorded-by, comment-reactions, resume, files-to-references, security-catalog-overlays and summary-nulls. Outside os migrate: os secret rewrap / orphans and os storage orphans.
  • The caveat's last sentence ("To review the drift, run os migrate plan against a local SQLite copy of this database") is still true but no longer necessary, since os migrate plan now 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.
  • The plan's drift text. On a remote primary, os migrate plan prints the shared differ's message for a tighten_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.
  • A skip-sync boot on the remote face logs 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.
  • The human line for the entries held back without --allow-destructive changes from Skipped N change(s) (destructive without --allow-destructive, or unsupported on this dialect). to Skipped 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

…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>
…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>
…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>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/driver-turso, touching 10 documentable anchor(s).

10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/data-modeling/drivers.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/data-modeling/indexing.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/data-modeling/queries.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/deployment/cli.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/deployment/index.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/deployment/self-hosting.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts))
  • content/docs/kernel/services-checklist.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/protocol/kernel/lifecycle.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/upgrading.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))

⛔ 6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-3.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-4.mdx (via os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-5.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-6.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))
  • content/docs/releases/v17/17-7.mdx (via os migrate apply (command, read off packages/cli/src/commands/migrate/apply.ts), os migrate plan (command, read off packages/cli/src/commands/migrate/plan.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 32 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 64be5534158d34414352b7d2414f09fc48449831 — the merge of head 90e7060f9dd80e4a8c8aaea772ac3da487b3f07f into base abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs abb6df0e0109f32c8a51f94a4deaaa4518a0b5a2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 90e7060f9dd80e4a8c8aaea772ac3da487b3f07f
Local-runs: none

Scope: owed by the Clause-②: yes declaration alone (no packages/spec/src/**, no governed path, no (narrowing) declaration on this PR's committed tree). Read: the PR's diff at this head, card #23848 and its claim 6213346689, and the head's check-runs.

① Derived judgments

  1. A Turso REMOTE primary is accepted by os migrate plan and os migrate apply. Before: both refused at the boot (Deferred schema DDL is not supported by the Turso REMOTE transport). After: both boot with deferredDdlUnsupported: 'skip-schema-sync', the posture os migrate organization-ownership already uses, and still pass identical boot options, as apply.ts's own comment requires. This is the widening the declaration names, and it is correct: the remedy the platform prints for destructive drift (os migrate apply --allow-destructive) could not run on that transport at all.
  2. The additive half on that boot is reported as not examined, never as none. Human output: SKIPPED_SCHEMA_SYNC_NOTE under the database line, plus PARTIAL wording on a drift-free result. --json: schemaSync: 'skipped'; an apply with no drift answers in_sync_partial, not in_sync. Correct: an empty pending from a boot that examined nothing would otherwise read as "every declared table exists".
  3. notApplied (new --json field) and a PARTIAL verdict. skipped keeps both kinds; notApplied is skipped minus the entries held back for lack of --allow-destructive (deferred, by reference). A confirmed destructive entry the driver did not apply answers message: 'partial', exit 1; a non-destructive one is named and exits 0. The field is additive. The exit change applies on every dialect, and only on a path where a confirmed destructive change was not performed: a run that used to exit 0 with a count now exits 1. That narrows the success exit toward the truth ("absence must be loud"), and the changeset states it in its own paragraph. No published schema carries this document.
  4. Published surface. @objectstack/cli: SKIPPED_SCHEMA_SYNC_NOTE is a new export of src/utils/schema-migrate.ts, which none of the package's entries (., ./console, ./hook-body) re-exports, so it is not a published symbol. @objectstack/driver-turso: entry . only; the diff changes the text of REMOTE_DRIFT_REPORT_CAVEAT (the value of driftReportCaveat) and one docblock, no signature. The caveat's one reader, runArtifactBootMigrationGate, branches on its presence and prints it, so the gate's behaviour (warn and boot on remote) is unchanged.
  5. The caveat text is true after both PRs. On this head it keeps 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 / PR fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893's NOT NULL sentences byte-for-byte as origin/main has them, and replaces only the clause this card made false ("a destructive entry has no remedy … os migrate apply refuses against it"). The gate comment in artifact-boot-migration.ts states the same two facts.

② Semver level

Clause-②: yes takes at least minor. The changeset declares @objectstack/cli: minor and @objectstack/driver-turso: minor, carries the line-initial Clause-②: yes, and names the accepted input, the new notApplied field, schemaSync / in_sync_partial, and the exit-code change on other dialects. Consistent.

③ Boundary flags

Implemented-by: claude/issue-23848-migrate-apply-turso-remote
Reviewed-by: session_01PFB9ZGRWYvjhTXsd8i6KBp

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

✅ ACCEPT (patch round 1): PR #23923 at 90e7060f9d (#23848, Fixes #23848). Contract review of record: PASS (6216896076); it queues on a true-green head

domain:cli seat 1 (#6024) · marchtian · session session_01PFB9ZGRWYvjhTXsd8i6KBp · 2026-10-16T13:47Z · holder of claim 6213346689 (the maintainer's direct dispatch). Reports: os-dev-report 6215991369 (round 0) and 6216843607 (patch round 1).

席位核对

  1. Shape, read on GitHub: draft against main, head 90e7060f9d. Line 1 Fixes #23848, the only closing keyword. Line 3 Clause-②: yes (…), line-initial. Assignee marchtian. 8 files: apply.ts, plan.ts, schema-migrate.ts, artifact-boot-migration.ts (comment only), driver-turso/src/turso-driver.ts, two new test files, one changeset. check-governed-merges --pr 23923: NOT governed. git merge-tree against current origin/main is clean. The non-merge commits carry the model-free trailer pair.
  2. Patch round 1 (the merge conflict), read against origin/main: PR fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 (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) merged as 2034975a47, and this branch merged main as ed06c3238d. turso-driver.ts on this head differs from main only in this card's clause, in the docblock and in REMOTE_DRIFT_REPORT_CAVEAT; fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893's NOT NULL sentences are byte-identical to main's. The relay 6213357665 is kept on both sides. The gate comment in artifact-boot-migration.ts states the same two facts as the merged caveat.
  3. The change, read against the diff: os migrate plan and apply pass deferredDdlUnsupported: 'skip-schema-sync' with otherwise identical boot options. On such a boot the additive half is reported as not examined in both faces (SKIPPED_SCHEMA_SYNC_NOTE; --json schemaSync: 'skipped'; in_sync_partial / PARTIAL wording). notApplied names every entry the driver was asked to apply and did not, kept apart from the entries held back for lack of --allow-destructive. A confirmed destructive one makes the run PARTIAL, exit 1; a non-destructive one is named, exit 0.
  4. Contract review: the Clause-②: yes declaration owes a TIER_DEFAULT review by the lane seat; posted as 6216896076 on this head, PASS. No other face: no packages/spec/src/**, no governed path, no (narrowing) declaration.
  5. Pins and measurement: on a real sqld 0.24.32 (libsql://127.0.0.1:PORT?tls=0), the remote-face integration file runs the local CONTROL, the plan, apply --yes, apply --allow-destructive (PARTIAL, the drop landed and was read back from the server) and the human face; 6/6 at this head. Positive control on unchanged code: the 4 remote cases red with the deferral refusal. Ablations of the posture in apply.ts and in plan.ts each turn their cases red; both restored to the HEAD blob. ⚠️ CI has no sqld, so those cases skip there; the dev's local run is the measurement of record.
  6. CI at 90e7060f9d: 40 check-runs, 34 success, 6 skipped (each on the EXPECTED_SKIPS roster); the five required are success.
  7. The changeset, checked sentence by sentence (@objectstack/cli and @objectstack/driver-turso, both minor, line-initial Clause-②: yes): the remote boot's refusal and its replacement, the drift half and what it applies, the not-examined additive half, notApplied and PARTIAL, the exit change on other dialects, the caveat's new clause, and the other os migrate commands left unchanged each match the diff.
  8. PR body: the seat applied the dev's three corrections (the fix(driver-turso): a Turso remote database enforces the storage.notNull a field declares #23893 state, the caveat-sentence note, the merge bullet) through the relay; read back byte-identical (13,021 bytes), one footer.
  9. Out of scope: out_of_scope_findings[0] (class a, the plan's --allow-destructive remedy for an entry the remote face cannot apply, measured at the door): filed migrate plan on a Turso remote primary: a tighten_not_null (and every column entry the remote face cannot apply) is printed with the remedy os migrate apply --allow-destructive, which then names it as not applied and exits 1 #23925. The three carrier items (the other os migrate dry runs that still refuse on remote; the caveat's last sentence; a paged-read stderr line on a skip-sync boot): Acceptance notes, carrier none.

Landing: once the check-runs are re-read on this head (the body edit re-runs Check Changeset), this seat flips ready and arms auto-merge through the relay.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os migrate apply cannot run on a Turso remote database (it arms deferred DDL), so destructive drift on a remote primary has no CLI remedy

2 participants