Skip to content

fix(driver-sql,cli): os migrate plan previews a _id-keyed table as apply treats it; one refused table no longer stops apply - #30750

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-8g2jia
Nov 6, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-8g2jia

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #30636

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:os migrate plan 遇到用 _id 当主键、没有 id 列的已有表,不再预报「补列」,而是照 apply 的真实做法预报:有数据就列为「拒绝」(带表名、_id、行数和拒绝原文),空表就列为「重建」。os migrate apply 遇到这种被拒的表,不再整批停下:其余表照常建表补列,被拒的表单独点名,原样不动,最后 PARTIAL、退出码 1。
  • 为什么改:实测 main 上 plan 对这两种表都说「补列」(无害),apply 却在第一张有数据的 _id 表上抛错退出,另外 80 张该建的表一张没建、该补的列一列没补,已完成清单也随异常丢了。
  • 风险与回滚:只动 driver-sql 的预览与延迟 DDL 冲刷、CLI 的 plan/apply 渲染;正常表(有 id)的预览与执行一字未变。另加一条保护:被拒表上的 drift(其 _id 列看起来是「孤儿列」)不再下发,即使带 --allow-destructive 也不会删它。回滚即撤本 PR。
  • 先看:sql-driver-deferred-ddl.ts(按预览把被拒表排除出冲刷);sql-driver-schema-sync.ts 的 previewDeferredSchemaWork 新分支;apply.ts 冲刷之后那段(拆分被拒、扣下被拒表的 drift、PARTIAL)。
  • 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-schema.test.ts,预期 sqlite 格 7 个 schema sync never drops a table holding rows 用例全绿(其中 4 个是本 PR 新增)。
  • 需要你做的:无,席位落地。

What changes

An existing table keyed by _id with no id column is the one shape the schema sync does not adopt by adding columns: initObjects rebuilds it while it is empty and, since PR #30632, refuses it (ExistingTableAdoptionRefusal) while it holds rows.

  • Plan (previewDeferredSchemaWork, sql-driver-schema-sync.ts): that shape is now planned as the sync treats it, with the same two probes and the same row count. Holding rows: a refuse_table entry, columns: ['_id'], rows: N, and refusal, the text rendered by renderExistingTableAdoptionRefusal, the renderer the sync throws through. Empty: a rebuild_table entry (rows: 0, the declared columns). Never add_columns. A rotation-declared object never reaches that branch of the sync, so it does not reach this one.
  • Kinds (schema-drift.ts): PendingSchemaWorkKind gains rebuild_table and refuse_table; PendingSchemaWork gains the optional refusal; isRefusedSchemaWork(kind) is exported beside isInPlaceSchemaWork, which now answers true for rebuild_table (it is not additive) and false for refuse_table (it is no work at all).
  • Apply, driver side (flushDeferredSchemaDdl, sql-driver-deferred-ddl.ts): the flush already captures the preview before running the DDL; a table the preview refused is now left out of the one initObjects(pending) re-entry, so it stops nothing. Every other deferred object gets its DDL and the refusal comes back as its entry in the returned list instead of a throw that took that list with it. The sync's own refusal still guards the rows if the table fills between preview and DDL.
  • Apply, CLI side (apply.ts, schema-migrate.ts): splitRefusedSchemaWork splits what the flush returned; created lists performed work only, each refused table is printed with its refusal text, and the run ends PARTIAL with exit 1 (--json: refused beside created, message: "partial"). Drift entries on a refused table are withheld from the driver and reported in skipped: the table's _id column reads as an orphaned column, and with --allow-destructive the driver would otherwise drop it from the table the sync just left alone (on main the throw stopped the run before that call, so this path is new with "carry on").
  • Rendering: renderPendingSchemaWork lists a refused table under its own heading Refused (left exactly as it is when you apply) with the refusal text, and a rebuild under the in-place heading; summarizePendingSchemaWork adds N empty table(s) to rebuild / N table(s) REFUSED when there are some and no longer counts them as temporal columns. addRefusedTableLimits gives each drift entry on a refused table that refusal as its apply limit, so neither plan nor the listing apply prints before its prompt offers --allow-destructive for it.

Changed lines (git diff origin/main --stat): 12 files, +553 / −18 (driver-sql src +65/−4, CLI src +131/−12, tests +344/−2, changeset +13).

Tests

  • packages/drivers/driver-sql/src/sql-driver-schema.test.ts, the schema sync never drops a table holding rows matrix (every dialect cell): the plan pin on both arms (rows: exactly one refuse_table entry whose refusal equals the renderer's output AND the message syncSchema throws for the same table; empty: rebuild_table, then the flush performs it), the CONTROL (id-keyed table: add_columns ['qty']), and the scope addition (a refused table between two good ones in deferred order: both get their DDL, the returned list carries both create_table entries and the refuse_table entry, the refused table is byte-for-byte as before, the deferral is disarmed).
  • packages/cli/src/utils/schema-migrate.pending-render.test.ts: the Refused heading and text, the rebuild line, the summary, the split.
  • packages/cli/test/migrate-plan-refused-table.e2e.test.ts (nightly e2e tier): the CLI measurement below as a pin, plan --json then apply --yes --allow-destructive --json on a seeded SQLite file.
Measured through the CLI (fixture: `legacy_full` _id + 3 rows, `legacy_empty` _id empty, `plain_ok` id-keyed missing `note`, `zz_fresh` absent)

origin/main (driver-sql dist and CLI src from 89dbc74):

  • os migrate plan --json: legacy_full and legacy_empty both kind: "add_columns" (8 columns each), exit 0.
  • os migrate apply --yes: lists legacy_full [add_columns: …], then ✗ Schema sync REFUSED for the existing table 'legacy_full' …, exit 1. Afterwards: legacy_full 3 rows untouched, but legacy_empty not rebuilt, plain_ok without note, zz_fresh absent: none of the 80 creates / 25 columns listed was performed.

This branch:

  • os migrate plan: + plain_ok [add_columns: …, note], ~ legacy_empty [rebuild_table: empty table dropped and recreated, 9 column(s)], ✗ legacy_full [refuse_table: _id — 3 row(s)] + the refusal text; summary 80 table(s) to create, 9 column(s) to add, 1 empty table(s) to rebuild, 1 table(s) REFUSED; legacy_full._id [drop_column] says "os migrate apply" cannot apply it on this database: the schema sync refuses the table 'legacy_full' ….
  • os migrate apply --yes: ✓ Created/extended 82 table(s), ✗ REFUSED legacy_full [refuse_table: _id — 3 row(s)] + text, ✗ PARTIAL apply: schema sync REFUSED 1 existing table(s) …, exit 1. Afterwards legacy_full ["_id","title"] 3 rows; legacy_empty rebuilt with id; plain_ok has note; zz_fresh created.
  • os migrate apply --yes --json: created 82 entries, refused [legacy_full, 3 rows, refusal text], notApplied: [], message: "partial", exit 1.
  • os migrate apply --yes --allow-destructive: same, and legacy_full still ["_id","title"] with 3 rows.
Ablation (commit aa54f87 on the fixed tree, driver tests read src/)
  • Flush leg: initObjects(refused.size === 0 ? pending : pending.filter(…)) → initObjects(pending) via scripts/ablation-replace.mjs (anchor 1 → 0, blob aa5c540e → 5d90f111): Tests 1 failed | 26 passed (the refused-between-two test). Restored, git diff HEAD empty.
  • Plan leg: the preview branch condition prefixed with false && (blob 2de56b06 → c89e4d34): Tests 3 failed | 24 passed (both plan arms and the flush test). Restored: blob == HEAD, git diff HEAD empty.

Acceptance notes

  • Published type widened. PendingSchemaWorkKind is exported by @objectstack/driver-sql (and re-exported as PendingSchemaWork from the CLI's schema-migrate.ts); it gains two members, PendingSchemaWork gains the optional refusal, and isRefusedSchemaWork is a new export. The os migrate plan --json pending array and the os migrate apply --json document (new refused key) carry them. The seat re-derived Clause-② for this as yes (widening) (claim amendment 6673377065); the changeset carries the same two lines, both packages stay patch (Metadata-protocol: none), and its body names the widening for a driver-sql consumer: a caller of flushDeferredSchemaDdl() must treat a returned refuse_table entry as refused, not performed.
  • Behaviour change on apply: a run that met a refused _id table used to stop with the refusal and do nothing else; it now performs every other table's DDL and the drift, and still ends exit 1. Nothing in the tree depended on the stop (the only caller of flushDeferredSchemaDdl is SchemaStack.flushSchemaDdl).
  • Not changed: the drift listing still shows legacy_empty._id [drop_column] for an empty _id table the flush rebuilds; once rebuilt the column is gone, and --allow-destructive reports the entry as applied. Pre-existing on main for an empty-only database; not this card's surface.

Verification

Head 77b3053a adds one changeset-only commit over 87ec2b98 (the Clause-② line and one sentence); the changeset gates were re-run on it (see the end of this section). Everything else below is on head 87ec2b98 (this branch merged with origin/main 4564d6f), after pnpm install --frozen-lockfile and a whole-tree turbo run build (lock VERDICT command-exit 0).

  • pnpm --filter @objectstack/driver-sql typecheck exit 0; pnpm --filter @objectstack/cli typecheck exit 0 (on aa54f87, before the merge).
  • pnpm --filter @objectstack/driver-sql test: Test Files 238 passed | 12 skipped (250), Tests 3866 passed | 218 skipped, exit 0 (aa54f87).
  • pnpm --filter @objectstack/cli exec vitest run --project unit: Test Files 389 passed (389), Tests 5435 passed (5435), exit 0 (aa54f87). The integration tier is declared to CI; the new e2e file ran by hand: OS_TEST_TIERS=nightly … --project integration test/migrate-plan-refused-table.e2e.test.ts → Tests 5 passed (5).
  • sql-driver-schema.test.ts against a local PostgreSQL 16 (OS_TEST_POSTGRES_URL): Tests 34 passed | 1 skipped (the live postgres cell of the matrix, all 7 cases green). MySQL not provisioned here; the Temporal Conformance (live PG + MySQL) job runs the cell.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 66 families, each run and exit 0; --ran → 66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED.
The 66 gate families (each `:: exit 0`)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-test-child-env :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-conformance :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 0
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:object-def-param-keys :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:tenant-chokepoint :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

On head 77b3053a (the changeset-only commit), with this body as the pull_request event payload: each :: exit 0.

node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:issue-citations :: exit 0

check-changeset-no-major LEVEL AXIS line: "this PR declares clause-② yes (widening) and metadata-protocol none, and it introduces no changeset entry graded minor or above" (pass).

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cy3CHSvvtQP754FaLB

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/driver-sql, touching 20 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/drivers/driver-sql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/drivers/driver-sql/src/index.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 43 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 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c9ee75951004aa0a66a96327e0c2dd9cb8d04dd1 — the merge of head 77b3053a09f62c1c4d5774960d7dd06e8f8149f6 into base 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11, 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 c9ee75951004aa0a66a96327e0c2dd9cb8d04dd1 && git checkout c9ee75951004aa0a66a96327e0c2dd9cb8d04dd1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11 77b3053a09f62c1c4d5774960d7dd06e8f8149f6 && git checkout -B drift-repro 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11 && git merge --no-ff 77b3053a09f62c1c4d5774960d7dd06e8f8149f6

node scripts/docs-audit/affected-docs.mjs --json 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11

⚠️ 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 40a9265ff3c8c3e96b1e2c13ea5edd1fb32e6d11 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… for a flushDeferredSchemaDdl caller

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cy3CHSvvtQP754FaLB
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

Review owed by the Clause-②: yes (widening) declaration (claim amendment 6673377065, card #30636). The lane seat writes it at its own tier · 2026-11-06T00:41Z

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 77b3053a09f62c1c4d5774960d7dd06e8f8149f6
Local-runs: none

① Derived judgments

  • @objectstack/driver-sql . entry, widened.
  • isInPlaceSchemaWork now answers false for refuse_table. A refusal is no work, so it is neither additive nor in place. rebuild_table stays in place (it is not additive, though it deletes no row). Right.
  • flushDeferredSchemaDdl() changes behaviour. A table the preview refused is left out of the re-entry into initObjects, and it comes back as a refuse_table entry in the returned list instead of a throw that lost the list. The sync's own refusal still guards the rows if the table fills between the preview and the DDL. Right. I checked every caller on this head (git grep): the only one is the CLI's flushSchemaDdl in schema-migrate.ts, which os migrate apply reads through splitRefusedSchemaWork. No caller treats a refusal as performed work or swallows it. The changeset names the duty for an outside consumer.
  • os migrate apply behaviour.
    • The refused table is named with the refusal's text, and the run ends PARTIAL with exit 1. In --json output, refused sits beside created and message is "partial".
    • Drift on a refused table is withheld from the driver, so --allow-destructive cannot drop the _id column the sync left alone. That drift is reported under skipped, not under notApplied.
    • os migrate plan and apply's listing mark those entries with the refusal limit.
    • A refusal still never reads as success. Right.
  • Rotation-declared objects skip the new branch, as they skip the sync's adoption branch. Right.

② Semver level

'@objectstack/driver-sql': patch and '@objectstack/cli': patch. The changeset and the PR body both carry Clause-②: yes (widening) and Metadata-protocol: none. Nothing a metadata author writes changes, so per AGENTS.md Post-Task 3 a non-protocol widening ships patch. The dev's run of check-changeset-no-major on this head reports the level axis passes. Matches.

③ Boundary flags

  • The dev's one flag (the Clause-② re-derivation) is answered by amendment 6673377065 and applied on this head. That is the changeset-only commit 77b3053a over 87ec2b98.
  • PM assumption: "keep going must not turn a refusal into a silent success". It holds: the run is PARTIAL with exit 1, and each refused table is named.
  • The CLI names the new kinds in its renderer: a Refused section and a rebuild_table line under in-place work. Answered.
  • No open_questions.

Implemented-by: claude/project-thread-8g2jia
Reviewed-by: session_016WV4czvZykUqhYxYXSSDt4

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4czvZykUqhYxYXSSDt4

维护者速读

  1. 改了什么:os migrate plan 遇到以 _id 为键、没有 id 列的旧表时,按 apply 的实际做法预告:表空时预告「重建」,有数据时预告「拒绝」,并写明表名、行数和拒绝原因,不再错报成「加列」。os migrate apply 遇到一张被拒的表,不再整批中断:其余表照常建,被拒的表单独点名,结果标 PARTIAL,退出码 1。
  2. 为什么改:以前 plan 说「只加列,无害」,apply 却在这张表上报错停下,后面 80 张表都没建,已做的清单也随报错丢了(os migrate plan previews "add columns" for a _id-keyed table that apply then refuses (rows) or rebuilds (empty) #30636,PR fix(driver-sql): schema sync never drops an existing table that holds rows #30632 复审时发现)。
  3. 风险:低。被拒的表原样不动,不会因为 --allow-destructive 删掉它的 _id 列。对外类型有加宽(Clause-② yes (widening)):PendingSchemaWorkKind 新增两种取值,新增导出 isRefusedSchemaWork。席位复核记录 6673466288 为 PASS。
  4. 先看:packages/drivers/driver-sql/src/sql-driver-schema-sync.ts 的 previewDeferredSchemaWork 新分支,以及 sql-driver-deferred-ddl.ts。
  5. 两分钟验证:pnpm --filter @objectstack/driver-sql exec vitest run src/sql-driver-schema.test.ts。
  6. 需要你做的:无,席位落地。非 BREAKING,不属 Tier H,571 行 ≤ 3000。

席位意见:ACCEPT(#30636)。

席位核对

Head-sha: 77b3053a09f62c1c4d5774960d7dd06e8f8149f6 · read 2026-11-06T01:09Z

Item Reading
Shape Draft → main; Fixes #30636. 12 files, +553/−18. ⛔ packages/spec/**; no new error code.
Seat diff read The preview uses the same two probes and row count as initObjects, and the same renderer (renderExistingTableAdoptionRefusal). The deferred flush leaves a refused table out of the re-entry and returns its entry instead of throwing. isInPlaceSchemaWork excludes the refusal. In apply, drift on a refused table is withheld from the driver and reported under skipped; PARTIAL ⇒ exit 1; --json gains refused. The only caller of flushDeferredSchemaDdl is the CLI (git grep on this head).
Evidence Plan pins on both arms plus an ordinary-table CONTROL. Apply test with a refused table between two good ones. Ablation: 2 legs red, then restored. driver-sql 3866 pass, cli unit 5435 pass, typechecks 0, PG live cell green (os-dev-report 6673336475). Review record PASS 6673466288 (TIER_DEFAULT).
Gates (check-runs on this head) 52: 37 success, 15 skipped, 0 failed. mergeable_state clean.
Accepted deviations Clause-② re-derived by the seat from no to yes (widening) (amendment 6673377065), applied on this head, changeset only.
Semver @objectstack/driver-sql, @objectstack/cli: patch. Clause-②: yes (widening), Metadata-protocol: none.
Landing Not governed (check-governed-merges --pr 30750: NOT governed), 571 lines ≤ 3000, not BREAKING, so per pm-dispatch the seat lands it: pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 6, 2026 01:10
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 6, 2026 01:10
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit 6990b0d Nov 6, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-8g2jia branch November 6, 2026 01:34
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os migrate plan previews "add columns" for a _id-keyed table that apply then refuses (rows) or rebuilds (empty)

1 participant