Skip to content

feat(objectql)!: 媒体值形态 strict 按部署生效 —— #3438 中标记真正覆盖的那一半#3681

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

feat(objectql)!: 媒体值形态 strict 按部署生效 —— #3438 中标记真正覆盖的那一半#3681
os-zhuang merged 2 commits into
mainfrom
claude/platform-migration-gating-764pf2

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

承接 #3617 落下的机制,做 #3438标记真正覆盖的那一半。前置 PR #3638 已合并。

变更

媒体字段(file/image/avatar/video/audio)的值形态校验,在本部署跑过 os migrate files-to-references --apply 且自检通过之后,从告警变为 invalid_type 拒绝。没迁移的部署完全不受影响。

理由是证据对得上:这里会被拒的遗留形态——inline {url, name, …} blob、裸 URL——正是那个迁移负责转换的东西。跑过并通过的部署已经被证明没有残留;没跑过的部署如果跟着版本号一起翻,升级瞬间每个 media 字段的 update 都会开始失败。

为什么只做媒体这一半

#3438 把三件事打包成一次翻转,而标记只覆盖其中一件。它断言的是「本部署的文件字段值已迁移并对过账」——对 lookup 的 id、location 的载荷是否规范什么都没说,对 D2 的动作参数更是毫无关系。把它们挂上去就是拿一个事实去给另一个不相干的事实背书,和 #3617 修掉的读解析器缺陷是同一个形状:让一个权威去回答没有问它的问题。

证据来源 本 PR
D1 媒体类型 adr-0104-file-references 标记 ✅ 按部署翻转
D1 引用 + 结构化 JSON 尚无对应证据 保持 warn-first 不动
D2 动作参数 与任何数据迁移无关 不动

后两行不是被机制卡住——机制已经在跑了;它们卡在「什么才算是这个部署的 location 值健全的证据」这个尚未有人回答的问题上。已在 #3438 留言说明。

环境变量

OS_DATA_VALUE_SHAPE_STRICT_ENABLED 语义未变(仍是全类别 opt-in,也仍能在未迁移的部署上强制 media 严格),所以不需要改名或兼容层——#3438 当初预期要改名,是因为假设了全体翻转。

新增逃生阀 OS_ALLOW_LAX_MEDIA_VALUES=1(按 Prime Directive #9OS_ALLOW_* 形式),在已验证的部署上重新放宽。两个都设时宽松的一方胜出:没人看的告警,代价小于停止写入的应用。

代价

事实存在数据库里,而校验器是同步且逐次写入的,所以 engine 读一次并 memoize——并且除非写入对象真的声明了 media 字段,否则连这一次都不读(沿用 file-reference-lifecycle.ts 已写明的休眠原则)。存文件的应用每进程一次查询,不存文件的应用零次。

实现过程中这一点是被测试逼出来的:我最初无条件读标记,4 个既有测试因为多出一次查询而失败——那不是测试太严,是我真的给每个 update 都加了一次无谓查询。

所有「不知道」的情形(没有存储服务、没有行、表读不了、行格式坏)一律答「未验证」:读不到证据的部署继续写入,而不是开始拒绝。

验证

  • 新增 8 条 validator 用例 + 8 条 engine 用例(含 memoize 只读一次、无 media 字段零查询、失败运行后回到宽松、逃生阀、opt-out 胜过 opt-in)。
  • 端到端在 showcase + SQLite 的同一个数据库上:同一次写入,迁移前放行 → 迁移后 invalid_type 拒绝 → 加 OS_ALLOW_LAX_MEDIA_VALUES 再次放行。
  • 全量 pnpm test 132/132 绿;spec 十道产物门、ESLint、example/downstream typecheck 本地全过。

文档

🤖 Generated with Claude Code

https://claude.ai/code/session_01V9uWWyKq6pNPQthwCXL8ma


Generated by Claude Code

…file migration verified (#3438)

#3438 scheduled the strict flip for "a later minor once telemetry is quiet" —
our telemetry, deciding for their deployments. #3617 built the mechanism that
replaces that: a deployment's own migration flag. This lands the half that flag
actually covers.

A malformed media value now rejects with `invalid_type` instead of warning, but
only where `os migrate files-to-references --apply` has run and passed its
self-check. The legacy forms this rejects — inline `{url, name, …}` blobs, bare
URLs — are exactly what that migration converts, so a verified deployment has
been shown to hold none, while an unmigrated one is untouched rather than having
every media update start failing on upgrade.

## Only media, deliberately

#3438 packaged three things as one flip, and the flag covers one of them. It
asserts "this deployment's FILE values are migrated and reconciled". It says
nothing about whether a `lookup` id or a `location` payload is well formed, and
nothing at all about D2's action parameters. Gating those on it would be
borrowing evidence for a fact it does not cover — an authority answering a
question it was not asked, which is the same shape as the read-resolver defect
#3617 fixed one layer down. Reference and structured-JSON classes keep their
own warn-first rollout until something can vouch for them.

`OS_DATA_VALUE_SHAPE_STRICT_ENABLED` is unchanged (still opts every class in,
still forces media strict on an unmigrated deployment). New escape hatch
`OS_ALLOW_LAX_MEDIA_VALUES=1` re-opens media on a verified deployment; when both
are set the lenient one wins, because a warning nobody reads costs less than an
app that stops writing.

## Cost

The fact lives in the database while the validator is synchronous and per-write,
so the engine reads it once and memoizes — and stays dormant unless the written
object declares a media field, mirroring the rule the storage module already
states. One query per process for apps that store files, zero for those that
do not. Every way of not knowing (no storage service, no row, unreadable table,
malformed row) answers "not verified": a deployment whose evidence cannot be
read keeps writing rather than starting to reject.

Verified end-to-end on showcase + SQLite against one database: the same write
was accepted before the migration, rejected with `invalid_type` after it, and
accepted again under OS_ALLOW_LAX_MEDIA_VALUES.

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 2:56pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/objectql.

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

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/objectql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/objectql)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 14:56
@os-zhuang
os-zhuang merged commit 6169615 into main Jul 27, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/platform-migration-gating-764pf2 branch July 27, 2026 15:09
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.

2 participants