Skip to content

feat: schema-drift detection + dev auto-reconcile + os migrate (#2186)#2196

Merged
xuyushun441-sys merged 2 commits into
mainfrom
worktree-issue-2186-schema-migrate
Jun 22, 2026
Merged

feat: schema-drift detection + dev auto-reconcile + os migrate (#2186)#2196
xuyushun441-sys merged 2 commits into
mainfrom
worktree-issue-2186-schema-migrate

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Closes #2186.

Problem

The metadata→DB schema sync is additive-only: it creates tables and adds columns but never alters/drops existing ones. Any non-additive metadata change (relax required, change type/length, drop/rename a field) silently diverges from an existing DB, and the physical column wins at write time.

Diagnosis correction

The issue attributed the repro's 400 "organization_id is required" to a "two sources of truth" bug in the validator. It isn't: record-validator reads required from metadata and passes once the field is optional. The 400 is a DB NOT NULL violation that rest-server paraphrases as "is required". So the AC item "validation derives required from metadata" was already satisfied; the real fix is reconciling the physical schema + de-misleading the message.

What's here

P1 — detection + warnings. New schema-drift.ts diffs metadata vs physical columns (reusing SchemaDiffEntry), categorising safe / needs_confirm / destructive. SqlDriver.initObjects warns once per divergence with an actionable os migrate hint. rest-server's NOT NULL branch now carries a drift-aware hint — the VALIDATION_FAILED/fields envelope is unchanged for back-compat.

P2 — dev auto-reconcile. New SqlDriverConfig.autoMigrate: 'safe' (wired into serve/dev in dev only) auto-applies the loosening subset (relax NOT NULL, widen varchar) so an existing dev DB self-heals on restart. Never destructive; force-disabled under NODE_ENV=production.

P3 — os migrate plan / apply. Categorised dry-run + reconcile, --allow-destructive gate, confirm prompt, --json. SQLite reconciles via the official table-rebuild (copy→swap, data preserved); Postgres/MySQL alter in place. Only examines objects in the loaded artifact (run os build first); never auto-drops a table absent from metadata.

Acceptance criteria

  • Boot warns for every metadata↔physical divergence (P1)
  • In dev, required:true→false reflects in an existing DB after restart, no reset (P2)
  • os migrate plan/apply reconciles non-additive changes with preview + destructive guardrail (P3)
  • Validation derives required from metadata — already true; reframed (see diagnosis)
  • SQLite verified live; Postgres/MySQL DDL covered by generation tests

Tests

Caveats

  • Postgres/MySQL ALTER path is standard SQL but validated via generated-DDL assertions (no live PG in CI — matches the repo's SQLite-only DDL test posture).
  • os migrate destructive ops assume the full app/plugin set is loaded; a column that looks "orphaned" under a partial build is reported but a table absent from metadata is never dropped.

🤖 Generated with Claude Code

The metadata→DB sync was additive-only (create table / add column), so any
non-additive metadata change — relax `required`, change type/length, drop or
rename a field — silently diverged from an existing database. The physical
column won at write time, surfacing a misleading `organization_id is required`
400 even though `/meta` reported the field optional.

Root-cause note: that 400 is NOT a validator bug. `record-validator` already
reads `required` from metadata and passes once the field is optional; the 400
is a DB NOT NULL violation that `rest-server` paraphrases as "is required".
So the fix is reconciling the physical schema + de-misleading the message,
not changing the validator's source of truth.

P1 — detection + warnings (schema-drift.ts, wired into SqlDriver.initObjects):
  diff metadata vs physical columns, categorise safe / needs_confirm /
  destructive (reusing SchemaDiffEntry). Boot warns once per divergence with an
  actionable `os migrate` hint. rest-server's NOT NULL translation now carries a
  drift-aware `hint` (VALIDATION_FAILED / fields envelope unchanged for back-compat).

P2 — dev auto-reconcile (SqlDriverConfig.autoMigrate:'safe', wired into
  serve/dev in dev only): auto-applies the loosening subset (relax NOT NULL,
  widen varchar) so an existing dev DB self-heals on restart. Never destructive;
  force-disabled under NODE_ENV=production.

P3 — os migrate plan/apply (cli/commands/migrate/*): categorised dry-run +
  reconcile, --allow-destructive gate, confirm prompt, --json. SQLite reconciles
  via the official table-rebuild (copy→swap, data preserved); Postgres/MySQL
  alter in place. Only examines objects in the loaded artifact; never auto-drops
  a table absent from metadata.

Tests: driver-sql +12 (incl. #2178 repro self-heal with data preserved, prod
guard, destructive gating, PG/MySQL DDL-gen), cli integration (real
createStandaloneStack boot → detect → apply → in-sync), rest hint. Full build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 22, 2026 11:31am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/xl labels Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/driver-sql, @objectstack/rest.

23 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/cloud-artifact-api.mdx (via packages/cli)
  • content/docs/concepts/core/plugins.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/cli, @objectstack/driver-sql, @objectstack/rest)
  • content/docs/concepts/packages.mdx (via @objectstack/cli, @objectstack/rest)
  • content/docs/concepts/terminology.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/cli.mdx (via @objectstack/cli)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/cli)
  • content/docs/guides/api-reference.mdx (via @objectstack/rest)
  • content/docs/guides/authentication.mdx (via @objectstack/cli)
  • content/docs/guides/client-sdk.mdx (via @objectstack/cli)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/driver-sql)
  • content/docs/guides/hook-bodies.mdx (via packages/cli)
  • content/docs/guides/packages.mdx (via @objectstack/cli, @objectstack/driver-sql, @objectstack/rest)
  • content/docs/guides/plugins.mdx (via @objectstack/rest)
  • content/docs/guides/project-scoping.mdx (via @objectstack/cli)
  • content/docs/guides/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/guides/runtime-services/index.mdx (via packages/cli)
  • content/docs/guides/skills.mdx (via packages/cli)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/objectos/realtime-protocol.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@@ -0,0 +1,256 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
@xuyushun441-sys
xuyushun441-sys merged commit 517dad9 into main Jun 22, 2026
16 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the worktree-issue-2186-schema-migrate branch June 22, 2026 11:35
xuyushun441-sys added a commit that referenced this pull request Jun 22, 2026
…ollow-up) (#2205)

Follow-up to #2196 — the schema-drift / `os migrate` feature shipped with no docs.

- cli.mdx: new "Schema migrations" section — `os migrate plan/apply`, the
  safe/needs-confirm/destructive categories, `--allow-destructive`, and the
  dev-only `autoMigrate:'safe'` self-heal (force-disabled in production).
- objectstack-platform SKILL: add `os migrate plan/apply` to the CLI cheat sheet.
- objectstack-data SKILL: "Schema evolution on an existing database" — the sync
  is additive-only; relaxing `required` / type changes / drops on a live DB need
  `os migrate` (dev auto-heals loosening). Names the tell-tale: `/meta` says
  optional but a write 400s "<field> is required" = stale NOT NULL, not a
  validator bug.

Body-only skill edits (frontmatter unchanged → generated skill docs don't drift).

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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/xl tests tooling

Projects

None yet

3 participants