feat(migrate): os migrate value-shapes —— 引用/结构化 JSON 值形态的按部署门禁(#3438 第 2 项) - #4235
Merged
Conversation
…r reference and structured-JSON value shapes (#3438) ADR-0104 D1's second evidence gate. Media value shapes already enforce once a deployment verifies its file migration (#3681); the reference and structured-JSON classes now get a gate of their own rather than borrowing that one — the file flag attests that file values were migrated and reconciled and says nothing about whether a `lookup` id or a `location` payload is well formed. - spec: `VALUE_SHAPES_MIGRATION_ID` beside its file sibling. - objectql: `scanValueShapes` walks every stored value of the covered classes against the write path's own predicate — `valueShapeViolation` / `isScannableValueShapeField` are exported from record-validator and imported by the scan, so the two cannot drift into disagreeing about "malformed". A test pins that property directly. - objectql: `validateRecord` gains `valueShapeStrict` beside `mediaValueShapeStrict`, and the engine reads the second flag through the same memoized seam. The two flag reads are refactored into one id-parameterised helper so "every way of not knowing answers false" is written once. - cli: `os migrate value-shapes`, dry-run by default. No backfill — a malformed `location` is application data whose correct value only its author knows, so the run reports and prescribes. With nothing to convert, `--apply`'s only write is the flag row, which preserves #3617's invariant trivially. - New escape hatch `OS_ALLOW_LAX_VALUE_SHAPES`, same precedence as its media sibling: opt-out beats all-class opt-in beats the flag. The scanner deliberately does NOT record the flag: readers of a migration flag use the spec contract and only writers depend on platform-objects, so having the engine write one would invert the layering. The CLI composes the two. A truncated or partially-unreadable scan fails the gate even at zero violations — "none in the part we read" is not the claim the flag makes. Verified: objectql 1272, spec 7167, cli 621, service-storage 252 green; all ten spec artifact gates pass (api-surface regenerated for the new exports); ESLint clean on every changed file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The docs-drift check on the command's own PR flagged content/docs/deployment/cli.mdx, and that one was a real gap rather than package-level noise: the file documents `os migrate files-to-references` in full, so shipping a second gated data migration without an entry would have left it discoverable only by reading the source. Covers the deliberate asymmetry (this run converts nothing — a malformed `location` is application data whose correct value only its author knows), the truncation rule (a partial scan cannot open the gate), and why the flag is separate from the file migration's rather than reusing it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
…efault-flip-vbly5z # Conflicts: # packages/spec/src/system/migration.zod.ts
…otes #4215 landed #3438's third item (a store created from empty attests both flags) while this branch was open, so the merged docs claimed "both flags" without the second one ever being named. - cli.mdx: the file migration's closing paragraph and writing-rules callout had been split off from their own section by the new one inserted above them; move them back so each command's rules sit under that command, and point the "other value classes keep warning" sentence at the gate that now exists for them. - cli.mdx: the created-from-empty section offers an escape hatch per class, so it names both — the media one and the one this branch adds. - v17.mdx: document `os migrate value-shapes` and why its flag is separate, and give the upgrade checklist its own bullet. The old wording told readers OS_DATA_VALUE_SHAPE_STRICT_ENABLED opts in "classes with no migration behind them", which stopped being true here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
os-zhuang
marked this pull request as ready for review
July 30, 2026 19:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ADR-0104 D1 的第二个证据门禁(#3438 第 2 项)
媒体类已由 #3681 按部署翻转;本 PR 给引用(
lookup/master_detail/user/tree)与结构化 JSON(location/address/composite/repeater/record/vector)建立它们自己的门禁,而不是去借文件迁移那个标记——后者断言的是「文件值已迁移、归属已对账」,对lookupid 或location载荷是否规范什么都没说。刻意没有回填
姊妹迁移会转换遗留文件值,因为是平台收窄了那个存储形态、因此欠这笔转换。而畸形的
location是应用数据,正确值只有作者知道。所以本命令只报告并给出处方(点名对象、字段、类型、条数、样本 record id、parse issue),由运维修完重跑。由此得到一个漂亮的简化:没有可转换的东西,
--apply唯一的写入就是标记行,于是 #3617 的不变量平凡成立——dry run 什么都不改,且「这次运行是否改变了本部署的姿态」从不取决于它发现了什么。反漂移:扫描器与校验器共用同一个谓词
valueShapeViolation/isScannableValueShapeField从record-validator导出、被扫描器 import。两份「什么算畸形」的实现只要差一个子句,部署就会通过扫描却仍被拒写——那等于标记在断言一个校验器不认的事实,正是 ADR 上一层禁止的「借来的证据」。有一个测试直接钉住这条属性:同一个值必须既被扫描器计数、又被 strict 写入路径拒绝。分层:扫描器不写标记
scanValueShapes是只读的(它接受的 engine 类型根本没有写入面)。标记的读者用 spec 契约,只有写者依赖 platform-objects——这是sys-migration.object.ts自己写下的设计意图。让引擎去写标记会反转依赖方向,所以组合放在 CLI 命令里。其他
OS_ALLOW_LAX_VALUE_SHAPES逃生阀,优先级与媒体半边一致:opt-out > 全类 opt-in > 标记。矛盾配置落在安全侧(错误地宽松只是没人看的告警,错误地严格是能工作的应用停止写入)。与 #4215 的相遇(合并 main 后)
本 PR 开着的时候,#4215 把 #3438 第 3 项(新库从空创建时记账两个标记)落到了 main 上,并顺带在
migration.zod.ts里声明了VALUE_SHAPES_MIGRATION_ID——两边同名常量,这是本次唯一的冲突,解法是留一份声明加 main 的CREATION_ATTESTED_MIGRATION_IDS。已核对 #4215 对engine.ts的改动(getSchemaSyncStats聚合)在合并后完整保留。合并暴露出文档三处不自洽,一并修掉(见
13606e0):lookup/location保持 warn-first」现在有了自己的门禁,改为指向它。OS_DATA_VALUE_SHAPE_STRICT_ENABLED会打开一些背后没有迁移的值类」也不再成立。补上命令、分开标记的理由,以及自己的升级清单条目。OS_ALLOW_LAX_MEDIA_VALUES,补上本 PR 引入的OS_ALLOW_LAX_VALUE_SHAPES。验证(合并 main 之后重跑)
@objectstack/objectql@objectstack/spec@objectstack/cli@objectstack/service-storage@objectstack/platform-objects@objectstack/driver-memoryspec check:generated(8 项生成物)check-release-notes/check-doc-authoring全量构建 71 包通过。(objectql +5 / spec −4 相对合并前,来自 #4215 新增测试与 #4233 退休 plugin 生命周期钩子。)
一个顺带发现(另开 issue,未在本 PR 扩大范围)
sys_migration目前只由 service-storage 注册——这在只有存储一个消费者时说得通,现在有了第二个与存储无关的消费者。实际部署路径没问题(os serve会自动装配 storage),但这层耦合应当解开:账本是平台基础设施,不该由一个可选服务持有。已在data-migration-plugins.ts注释中记录。关联
#3438 三项:第 1 项已合并(
698cbc2),第 3 项由 #4215 合并,本 PR 是第 2 项——合入后 #3438 全部完成。🤖 Generated with Claude Code
https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv