Skip to content

feat(migrate): 平台形态的迁移门禁 —— 带自检的数据迁移 + 部署级标记 (#3617)#3638

Merged
os-zhuang merged 4 commits into
mainfrom
claude/platform-migration-gating-764pf2
Jul 27, 2026
Merged

feat(migrate): 平台形态的迁移门禁 —— 带自检的数据迁移 + 部署级标记 (#3617)#3638
os-zhuang merged 4 commits into
mainfrom
claude/platform-migration-gating-764pf2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3617 的第 1、2 项(命令 + 门禁基础设施)。第 3、4 项(#3459 PR-5b 回收、#3438 strict 翻转)改读本 PR 落的标记,留给各自的 change。

错在哪,改了什么

wave 2 给回收定的验收门是「在真实租户上跑回填 → verifyFileReferences 连续 ≥7 天零阻断项」。这句话的每个成分都假设我们运营那个租户、我们观察它、我们翻开关。但 ObjectStack 是开发平台:第三方部署自己决定何时升级,数据我们看不到,也不该看到。不存在一个我们的观察窗口能让别人的数据在里面等——这样表述的门禁不是严格,而是无处可满足

R4 的实质没变:一本账在被证明与现实一致之前,不能被授予删除字节的权力。变的是证据由谁产生:从我们一次,变成每个部署为自己产生。

os migrate files-to-references            # dry-run:出报告,什么都不写
os migrate files-to-references --apply    # 回填 → 对账 → 零阻断项才写标记

apply 且对账零阻断项 → 写入 sys_migration { id: 'adr-0104-file-references', verified_at, blocking: 0 }

由此得到的性质

顺带修掉的一个缺陷

端到端跑的时候暴露的:活体 kernel 上,engine 的读解析器会在扫描看到之前就把存储的 id 展开成 { id, url, … },于是对账把每一条持有中的引用都报告为不存在——制造一堆 stale_owner 噪音,而且一条被漏掉的 unowned_reference 会让这个门禁假通过,正是本 PR 要建立的那个门禁。现在读路径可以通过 spec 的 RAW_FILE_VALUES_CONTEXT_KEY 退出解析,storage 服务的扫描与记账读取都带上了它。

改了哪些包

内容
spec DataMigrationFlagSchemaFILE_REFERENCES_MIGRATION_IDisDataMigrationFlagVerified(两个消费方共用的唯一谓词)、RAW_FILE_VALUES_CONTEXT_KEY
platform-objects sys_migration 对象 + readDataMigrationFlag / isDataMigrationVerified / recordDataMigrationRun读取一律倒向"未验证":读不到证据的门禁必须保持关闭
service-storage runFilesToReferencesMigration(回填 → 对账 → 记标记);注册 sys_migration
objectql 读路径尊重 raw 标记
cli os migrate files-to-references

验证

  • 新增单测:migration-flag.test.ts(9 例,含"读失败必须关门"、"失败运行清空 verified_at")、files-to-references-migration.test.ts(7 例,含"dry-run 什么都不写"、"截断的扫描即使零阻断项也不通过"、"回退后重新关门")、engine 的 raw-marker 回归例。
  • 全量 pnpm test 绿(132/132 任务)。
  • 端到端在 showcase 应用 + SQLite 上实跑:10 个遗留值被转换并认领,sys_file.ref_* 正确写入,对账干净,标记落库;对同一个数据库跑 dry-run 则数据与标记都原样未动。

文档

  • ADR-0104 增补 2026-07-27 第二则:门禁按部署而非按发版,以及 D1/D2 为何埋着同一个错误。原 R4 的「≥7 天」表述改为指向该增补,而不是被悄悄删掉。
  • content/docs/deployment/cli.mdx 新增「Data migrations」一节。
  • changeset(5 个包)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01V9uWWyKq6pNPQthwCXL8ma


Generated by Claude Code

…ot per release (#3617)

The ADR-0104 D3 wave 2 acceptance gate was written as an operations runbook —
run the backfill on the tenant, watch `verifyFileReferences` stay clean for
seven days, then flip the switch. Every clause assumes we run the tenant, we
observe it, and we flip.

ObjectStack is a development platform. Third-party deployments upgrade on their
own schedule and their data is not visible to us, so there is no observation
window of ours that anyone else's data can wait inside. A gate phrased as one is
not strict, merely unlocatable.

R4's substance is unchanged — a ledger may not be given the power to delete
bytes until it has been shown to agree with reality. What changes is who
produces the evidence: each deployment, for itself.

  os migrate files-to-references            # dry run: reports, writes nothing
  os migrate files-to-references --apply    # converts, verifies, records the flag

An apply run whose reconciliation reports zero blocking discrepancies records
`sys_migration { id: 'adr-0104-file-references', verified_at, blocking: 0 }`.
That row — never the platform version — is what may later open released-file
collection (#3459 PR-5b) and strict media value-shape enforcement (#3438); both
read the same flag through one spec-level predicate so the two gates cannot
disagree about the same fact. Not run, or not passed, leaves files retained
forever: storage cost, zero data loss. A later failing run clears the verified
state, so a database that has drifted closes its own gate.

A dry run writes nothing at all — not the conversions, and not the flag either,
even when the self-check would pass — so whether a run changed the deployment's
posture never depends on what it happened to find. The command also refuses to
run when no app metadata is loaded: an empty scan is indistinguishable from a
clean one, and this verdict authorises irreversible behaviour.

Also fixes a defect the end-to-end run surfaced: on a live kernel the engine's
read resolver expanded stored ids to their `{ id, url, … }` form before the
scan saw them, so the reconciliation reported every held reference as absent —
noisy `stale_owner` findings, and a missed `unowned_reference` would have been a
false pass of the very gate this work builds. Reads may now opt out via the
spec's `RAW_FILE_VALUES_CONTEXT_KEY`; the storage service's scan and bookkeeping
reads do.

Verified end-to-end against the showcase app on SQLite: 10 legacy values
converted and claimed, reconciliation clean, flag recorded; a dry run against
the same database left both the data and the flag untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9uWWyKq6pNPQthwCXL8ma
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 12:58pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/cli, @objectstack/objectql, @objectstack/platform-objects, packages/services, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via packages/services, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/platform-objects, packages/services, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

claude added 2 commits July 27, 2026 12:17
…Flag

The generated `content/docs/references/system/migration.mdx` is derived from
`migration.zod.ts` and CI checks the two agree — adding the schema without
regenerating left them out of sync.

The page's intro is the file's first JSDoc block, and `migration.zod.ts` had
none, so the regenerate lifted the `DATA_MIGRATION_FLAG_OBJECT` constant's
TSDoc into that slot. Given the module now carries both kinds of migration, a
real file-level docblock is what the page wants anyway: it says why the two are
separate — a ChangeSet reshapes the database from metadata, while whether a data
migration has run is a fact about one deployment's rows that no release can
assert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9uWWyKq6pNPQthwCXL8ma
The snapshot gate diffs the built export surface against the committed file so
a removed or renamed export cannot silently break a pinned consumer. This
change adds six exports and removes none:

  RAW_FILE_VALUES_CONTEXT_KEY, DATA_MIGRATION_FLAG_OBJECT,
  FILE_REFERENCES_MIGRATION_ID, DataMigrationFlagSchema, DataMigrationFlag,
  isDataMigrationFlagVerified

Ran every gate in the type-check job locally this time — the spec's ten
check:* scripts, the example-app and downstream-contract typechecks — rather
than fixing them one CI round at a time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9uWWyKq6pNPQthwCXL8ma
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

平台形态的迁移门禁:带自检的数据迁移 + 部署级标记 —— file-as-reference 回收与 strict 翻转都依赖它

2 participants