Skip to content

feat(migrate): os migrate value-shapes —— 引用/结构化 JSON 值形态的按部署门禁(#3438 第 2 项) - #4235

Merged
os-zhuang merged 4 commits into
mainfrom
claude/d1-d2-strict-default-flip-vbly5z
Jul 30, 2026
Merged

feat(migrate): os migrate value-shapes —— 引用/结构化 JSON 值形态的按部署门禁(#3438 第 2 项)#4235
os-zhuang merged 4 commits into
mainfrom
claude/d1-d2-strict-default-flip-vbly5z

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

ADR-0104 D1 的第二个证据门禁(#3438 第 2 项)

媒体类已由 #3681 按部署翻转;本 PR 给引用(lookup/master_detail/user/tree)与结构化 JSON(location/address/composite/repeater/record/vector建立它们自己的门禁,而不是去借文件迁移那个标记——后者断言的是「文件值已迁移、归属已对账」,对 lookup id 或 location 载荷是否规范什么都没说

os migrate value-shapes           # 扫描:只报告,不写任何东西
os migrate value-shapes --apply   # 扫描 + 零违规时记录部署标记

刻意没有回填

姊妹迁移会转换遗留文件值,因为是平台收窄了那个存储形态、因此欠这笔转换。而畸形的 location 是应用数据,正确值只有作者知道。所以本命令只报告并给出处方(点名对象、字段、类型、条数、样本 record id、parse issue),由运维修完重跑。

由此得到一个漂亮的简化:没有可转换的东西,--apply 唯一的写入就是标记行,于是 #3617 的不变量平凡成立——dry run 什么都不改,且「这次运行是否改变了本部署的姿态」从不取决于它发现了什么。

反漂移:扫描器与校验器共用同一个谓词

valueShapeViolation / isScannableValueShapeFieldrecord-validator 导出、被扫描器 import。两份「什么算畸形」的实现只要差一个子句,部署就会通过扫描却仍被拒写——那等于标记在断言一个校验器不认的事实,正是 ADR 上一层禁止的「借来的证据」。有一个测试直接钉住这条属性:同一个值必须既被扫描器计数、又被 strict 写入路径拒绝。

分层:扫描器不写标记

scanValueShapes 是只读的(它接受的 engine 类型根本没有写入面)。标记的读者用 spec 契约,只有写者依赖 platform-objects——这是 sys-migration.object.ts 自己写下的设计意图。让引擎去写标记会反转依赖方向,所以组合放在 CLI 命令里。

其他

  • OS_ALLOW_LAX_VALUE_SHAPES 逃生阀,优先级与媒体半边一致:opt-out > 全类 opt-in > 标记。矛盾配置落在安全侧(错误地宽松只是没人看的告警,错误地严格是能工作的应用停止写入)。
  • 引擎两个标记的读取重构成一个按 id 参数化的 helper,"所有不知道的方式都答 false" 只写一遍。
  • 截断或有对象读不出来的扫描,即使零违规也不开门——「我们读到的那部分里没有」不是标记所断言的命题。

#4215 的相遇(合并 main 后)

本 PR 开着的时候,#4215#3438 第 3 项(新库从空创建时记账两个标记)落到了 main 上,并顺带在 migration.zod.ts 里声明了 VALUE_SHAPES_MIGRATION_ID——两边同名常量,这是本次唯一的冲突,解法是留一份声明加 main 的 CREATION_ATTESTED_MIGRATION_IDS。已核对 #4215engine.ts 的改动(getSchemaSyncStats 聚合)在合并后完整保留。

合并暴露出文档三处不自洽,一并修掉(见 13606e0):

  • 新小节插进了 files-to-references 小节中间,把它的收尾段和「写入规则」callout 劈到了新小节后面——挪回各自命令名下。
  • 那句「其他值类不受影响,lookup/location 保持 warn-first」现在有了自己的门禁,改为指向它。
  • v17 发布说明由 feat(migrate): 从空创建的数据库在创建时刻自我认证数据迁移 — ADR-0104 fresh-datastore attestation (#3438) #4215 写下「attests both flags」,但第二个标记及其命令在发布说明里从未出现过;升级清单里「OS_DATA_VALUE_SHAPE_STRICT_ENABLED 会打开一些背后没有迁移的值类」也不再成立。补上命令、分开标记的理由,以及自己的升级清单条目。
  • 「从空创建」那节的逃生阀只提了 OS_ALLOW_LAX_MEDIA_VALUES,补上本 PR 引入的 OS_ALLOW_LAX_VALUE_SHAPES

验证(合并 main 之后重跑)

套件 结果
@objectstack/objectql 1277 passed
@objectstack/spec 7163 passed
@objectstack/cli 622 passed
@objectstack/service-storage 256 passed
@objectstack/platform-objects 246 passed
@objectstack/driver-memory 265 passed
spec check:generated(8 项生成物) 全 PASS
check-release-notes / check-doc-authoring PASS
ESLint(全部改动 TS 文件) 干净
docs 站点构建 PASS

全量构建 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

…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
@vercel

vercel Bot commented Jul 30, 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 30, 2026 6:39pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

114 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 @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/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/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • 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, @objectstack/spec)
  • 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/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 @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 packages/objectql, @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/v17.mdx (via @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/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 3 commits July 30, 2026 17:05
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
os-zhuang marked this pull request as ready for review July 30, 2026 19:20
@os-zhuang
os-zhuang merged commit 3aef718 into main Jul 30, 2026
26 of 27 checks passed
@os-zhuang
os-zhuang deleted the claude/d1-d2-strict-default-flip-vbly5z branch July 30, 2026 19:21
os-zhuang added a commit that referenced this pull request Jul 31, 2026
#4243) (#4258)

`sys_migration`(#3617 的部署级迁移标记账本)此前由 service-storage 注册,理由写在对象头注释里:「由第一个消费它的服务注册」。那在只有一个消费者时成立,但账本早已有了与存储无关的消费者——#4235 的 value-shapes 门禁扫的是 lookup/location 值形态,整条路径不碰文件;#4215 的从空创建记账同理。代价是 `data-migration-plugins.ts` 里那个绕过:一个和文件毫无关系的迁移,也得把 StorageServicePlugin 启起来,只为让内核里有这张表。

注册点改为 `PlatformObjectsPlugin`。它通过 manifest 服务注册 `SysMigration`(scope: system),并声明 `optionalDependencies: ['com.objectstack.engine.objectql']`,使两者同在时 init 顺序落在 ObjectQL 的 manifest 服务之后,不在时(纯翻译内核)照常降级。`os serve` 本就自动注入该插件,因此每个被服务的内核都带账本,与是否装了存储无关。

#4215 的从空创建记账随注册一起搬家。它留在 storage 的理由原文是「This service owns the call because it registers sys_migration」——前提搬走,调用跟着走。storage 侧只保留读者(file-references 门禁的 `isDataMigrationVerified`),reap guard 行为不变。

`buildDataMigrationPlugins()` 的耦合随之放松:默认只注册 PlatformObjectsPlugin,账本随之而来;`{ storage: true }` 才追加 settings + storage,且只有 `os migrate files-to-references` 传它,因为只有它真的要对着部署的真实存储树做对账。`os migrate value-shapes` 从此不再为一张表启动整个存储服务——该命令严格只读,`--apply` 只写标记本身。

唯一语义变化面:手工组装 StorageServicePlugin 而不组 PlatformObjectsPlugin 的嵌入式内核不再获得账本。方向安全(fail toward retention),changeset 已写明迁移句;cloud 的宿主内核走 serve 装配,不受影响。

Closes #4243
Closes #4236
os-zhuang pushed a commit that referenced this pull request Jul 31, 2026
The branch was recreated after #4235 merged and neither the PR's opened nor
its reopened event produced a workflow run, while other branches pushed in the
same window did. This empty commit fires `synchronize`; it collapses away on
squash merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
os-zhuang pushed a commit that referenced this pull request Jul 31, 2026
…false (#3438)

The boot line that names an open value-shape gate (#4253) fired on every
deployment there is, and stated something untrue on any deployment that had
already settled the posture with an environment switch. Both are one failure:
an advisory that speaks where it does not apply is exactly how readers learn to
ignore it, and the line's whole value is that a warn-first deployment is told
once, credibly, which command ends warn mode.

Neither defect is reachable from the suite that shipped with the announcement.
`engine.test.ts` mocks `./registry`, and a mocked registry hands the engine
exactly the fields the test wrote — so these cases get a sibling file built on
the genuine `registerApp → registry` path, which is the only way a test sees
what a deployment sees.

`objectHasCoveredValueField` — the dormancy short-circuit meant to spare an
object with no covered field the flag query — tested raw type membership, while
the real registry INJECTS covered-type fields into every object it registers:
`organization_id` and `owner_id` (both `system`), `created_by` and `updated_by`
(both in `SKIP_FIELDS`), four `lookup`s. `validateRecord` skips every one of
them before it reaches the value-shape check, so the short-circuit answered
`true` for literally every object, never fired, and its WeakMap memoized a
constant. It now counts by the validator's own `isScannableValueShapeField` —
the predicate the scanner already imports. Three readings of "a covered field"
drifting by one clause is how a gate ends up governing fields nothing enforces,
which is the shape #4235 argued against.

The announcement also consulted no environment switch, though both postures it
reports on short-circuit ahead of the deployment flag it reads. Under
`OS_DATA_VALUE_SHAPE_STRICT_ENABLED` enforcement is already on for both
classes, so "checked but NOT enforced here" was false; under either opt-out the
operator chose leniency deliberately, so naming a migration that cannot change
what they get is noise. Each gate consults its own pair — `mediaPostureSetByEnv`
and `valueShapePostureSetByEnv`, siblings for the reason `mediaStrictEffective`
and `valueShapeStrictEffective` are siblings, since the opt-outs are per-class
while the opt-in opens both. Cheapest test first, so a kernel with nothing to
say still reaches no flag query.

Enforcement is unchanged: same gates, same flags, same default. The flag read
was already memoized per process, so what this corrects is the property
ADR-0104 states, not a user-visible cost.

Verified: objectql 1345 (7 new), cli 628, spec check:generated 8/8, objectql
build, check:release-notes and check:doc-authoring pass, ESLint clean. One
pre-existing failure on main is untouched by this branch and reproduces with
zero of its source changes: `query-expression-conformance.test.ts` >
"the removed nested-select object form is refused BY NAME", from #4268.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
os-zhuang pushed a commit that referenced this pull request Jul 31, 2026
…false (#3438)

The boot line that names an open value-shape gate (#4253) fired on every
deployment there is, and stated something untrue on any deployment that had
already settled the posture with an environment switch. Both are one failure:
an advisory that speaks where it does not apply is exactly how readers learn to
ignore it, and the line's whole value is that a warn-first deployment is told
once, credibly, which command ends warn mode.

Neither defect is reachable from the suite that shipped with the announcement.
`engine.test.ts` mocks `./registry`, and a mocked registry hands the engine
exactly the fields the test wrote — so these cases get a sibling file built on
the genuine `registerApp → registry` path, which is the only way a test sees
what a deployment sees.

`objectHasCoveredValueField` — the dormancy short-circuit meant to spare an
object with no covered field the flag query — tested raw type membership, while
the real registry INJECTS covered-type fields into every object it registers:
`organization_id` and `owner_id` (both `system`), `created_by` and `updated_by`
(both in `SKIP_FIELDS`), four `lookup`s. `validateRecord` skips every one of
them before it reaches the value-shape check, so the short-circuit answered
`true` for literally every object, never fired, and its WeakMap memoized a
constant. It now counts by the validator's own `isScannableValueShapeField` —
the predicate the scanner already imports. Three readings of "a covered field"
drifting by one clause is how a gate ends up governing fields nothing enforces,
which is the shape #4235 argued against.

The announcement also consulted no environment switch, though both postures it
reports on short-circuit ahead of the deployment flag it reads. Under
`OS_DATA_VALUE_SHAPE_STRICT_ENABLED` enforcement is already on for both
classes, so "checked but NOT enforced here" was false; under either opt-out the
operator chose leniency deliberately, so naming a migration that cannot change
what they get is noise. Each gate consults its own pair — `mediaPostureSetByEnv`
and `valueShapePostureSetByEnv`, siblings for the reason `mediaStrictEffective`
and `valueShapeStrictEffective` are siblings, since the opt-outs are per-class
while the opt-in opens both. Cheapest test first, so a kernel with nothing to
say still reaches no flag query.

Enforcement is unchanged: same gates, same flags, same default. The flag read
was already memoized per process, so what this corrects is the property
ADR-0104 states, not a user-visible cost.

The predicate fix is pinned by reverting it: with raw-type membership restored,
`stays silent when no object declares a covered field` fails, because a `note`
declaring only text still carries the four injected lookups.

Verified on a full workspace build: objectql 1345 (7 new), cli 628, spec
check:generated 8/8, check:release-notes and check:doc-authoring pass, ESLint
clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…false (#3438) (#4284)

The boot line that names an open value-shape gate (#4253) fired on every
deployment there is, and stated something untrue on any deployment that had
already settled the posture with an environment switch. Both are one failure:
an advisory that speaks where it does not apply is exactly how readers learn to
ignore it, and the line's whole value is that a warn-first deployment is told
once, credibly, which command ends warn mode.

Neither defect is reachable from the suite that shipped with the announcement.
`engine.test.ts` mocks `./registry`, and a mocked registry hands the engine
exactly the fields the test wrote — so these cases get a sibling file built on
the genuine `registerApp → registry` path, which is the only way a test sees
what a deployment sees.

`objectHasCoveredValueField` — the dormancy short-circuit meant to spare an
object with no covered field the flag query — tested raw type membership, while
the real registry INJECTS covered-type fields into every object it registers:
`organization_id` and `owner_id` (both `system`), `created_by` and `updated_by`
(both in `SKIP_FIELDS`), four `lookup`s. `validateRecord` skips every one of
them before it reaches the value-shape check, so the short-circuit answered
`true` for literally every object, never fired, and its WeakMap memoized a
constant. It now counts by the validator's own `isScannableValueShapeField` —
the predicate the scanner already imports. Three readings of "a covered field"
drifting by one clause is how a gate ends up governing fields nothing enforces,
which is the shape #4235 argued against.

The announcement also consulted no environment switch, though both postures it
reports on short-circuit ahead of the deployment flag it reads. Under
`OS_DATA_VALUE_SHAPE_STRICT_ENABLED` enforcement is already on for both
classes, so "checked but NOT enforced here" was false; under either opt-out the
operator chose leniency deliberately, so naming a migration that cannot change
what they get is noise. Each gate consults its own pair — `mediaPostureSetByEnv`
and `valueShapePostureSetByEnv`, siblings for the reason `mediaStrictEffective`
and `valueShapeStrictEffective` are siblings, since the opt-outs are per-class
while the opt-in opens both. Cheapest test first, so a kernel with nothing to
say still reaches no flag query.

Enforcement is unchanged: same gates, same flags, same default. The flag read
was already memoized per process, so what this corrects is the property
ADR-0104 states, not a user-visible cost.

The predicate fix is pinned by reverting it: with raw-type membership restored,
`stays silent when no object declares a covered field` fails, because a `note`
declaring only text still carries the four injected lookups.

Verified on a full workspace build: objectql 1345 (7 new), cli 628, spec
check:generated 8/8, check:release-notes and check:doc-authoring pass, ESLint
clean.


Claude-Session: https://claude.ai/code/session_016zgA8CQMJeJbFEjnbib1Uv

Co-authored-by: Claude <noreply@anthropic.com>
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 protocol:system size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants