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/devin 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

vercelBot commented Jun 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJun 22, 2026 11:31am

Request Review

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

github-actionsBot 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 mainJun 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

3 participants

@xuyushun441-sys@github-advanced-security@os-zhuang