feat(spec,metadata-protocol,metadata,objectql,service-automation): stored metadata replays the full conversion chain at rehydration (#3903) - #4317
Merged
os-zhuang merged 2 commits intoJul 31, 2026
Conversation
…ored metadata replays the full conversion chain at rehydration (#3903) The spec contract stopped at authored source: sys_metadata rows were rehydrated unparsed and unconverted, so the authored and stored contracts silently diverged (option 1 of #3903, chosen because #3855 already concluded operator-run migrations cannot be relied on, and out-of-scope-by-policy would fossilize the accommodations PD #12 names as debt). - spec: new `applyConversionsToStoredItem(type, item, options?)` — one primitive encoding the data-at-rest policy: the FULL chain replays, retiredFromLoadPath entries included, because retirement is an authoring-surface event and a row at rest has no author to teach. - metadata-protocol: wired at loadMetaFromDb, getMetaItems (active+drafts), getMetaItem (active+draft), getMetaItemLayered, duplicatePackage. Flow rows are deliberately skipped (the flow conversions' open-namespace conflict guard needs the automation engine's executor registry). - metadata: DatabaseLoader live-row reads convert; history stays verbatim (the version layer records what was written — checksums must keep pairing). - objectql: authored-action/-hook table reads convert (a stored pre-17 action row's `execute` dispatches via `target` again); the rule-validator's `requiredWhen ?? conditionalRequired` fallback — kept in #3883 with a retirement promise that had no mechanism — is deleted, its job done by the chain at every seam. - service-automation: registerFlow passes includeRetired, so stored flows keep canonicalizing after their conversions graduate out of the window. - registry.ts swallow answered (#3903 closing note): loadMetaFromDb validates the CONVERTED row, returns {loaded, errors, invalid}, and warns with a stable [metadata_spec_invalid] marker; registration proceeds deliberately (refusal at boot unhooks live tables and makes the row unfixable in Studio) — the enforcing gates are saveMetaItem's 422 and _diagnostics on read, and the registerItem hook is now documented as the diagnostic it is. Policy written down in the ADR-0087 addendum ("stored metadata replays the chain") and PD #12. Closes #3903. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…metadata-rehydration-176275
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 5 package(s): 111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 31, 2026
Closed
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.
Closes #3903.
决定:选项 1(在存量读取路径上转换),并回答了 registry 吞异常的问题
#3903 列了三条路。选 1 的理由:#3855 已经证明不能依赖运营方主动跑迁移(选项 2 只帮跑了的人);选项 3 会把 PD #12 点名的债务(裸
??、各消费端自行容错)固化为永久政策。选项 1 让转换层变成真正普适的契约机制,并给了rule-validator.ts那个"永远等不来退休"的??一个真实的退场。数据静置(data at rest)的政策,一句话
退休(
retiredFromLoadPath)是"创作面"的事件;存量行没有作者可教,所以存量读取路径重放完整链(含已退休条目)。 窗口与 tombstone 继续管创作面(normalizeStackInput只跑 live-window、schema 照旧硬拒),saveMetaItem的 422 网关保证新写入永远是 canonical——所以存量转换是一个只会收缩的集合。已写入 ADR-0087 附录与 PD #12。改了什么
applyConversionsToStoredItem(type, item, options?)(conversions/stored.ts),把一条存量行按其元数据类型包进栈集合、以includeRetired: true重放全链。幂等、不抛、不校验。loadMetaFromDb、getMetaItems(active + drafts)、getMetaItem(active + draft)、getMetaItemLayered、duplicatePackage(复制即新写入,先转换再过网关——遗留行终于能被复制,且副本是 canonical)。通知有进程级去重,避免每次列表读取都刷日志。DatabaseLoader活行读取(load/loadMany)转换;history 保持原样(版本层记录"写下的是什么",checksum↔body 必须对应;SysMetadataRepository同理不转换——转换发生在"行变成被服务的元数据"处,不在版本存储处)。execute现在真的能以target派发);rule-validator删除requiredWhen ?? conditionalRequired——feat(spec)!: retire the last three deprecated authorable aliases (#3855) #3883 里我承诺"迁移后退休"、[P2] The spec contract stops at authored source — stored metadata is rehydrated unparsed, unconverted, and ungated #3903 里证明其实没有任何迁移机制,现在链在每个缝隙上跑,它可以退了。registerFlow传includeRetired。这一点刚好被 main 上新合入的wait声明了超时契约但完全没有实现:onTimeout零读取者,timeoutMs被当成定时时长用 —— showcase 自己在依赖它 #4158 现场验证:flow-node-wait-timeout-keys-removed是 17 就 retired 的流转换、FlowSchema同步 tombstone 了timeoutMs/onTimeout——不带includeRetired的话,携带这些键的存量流会从"能跑"直接变成"注册失败"。通用元数据缝隙刻意跳过flow类型:流转换带 ADR-0078 开放命名空间冲突守卫,需要自动化引擎的活 executor 注册表(reservedNodeTypes),只有registerFlow有。registry.ts 吞校验异常的"自己的答案"
维持注册、但不再是一句耸肩:
loadMetaFromDb现在对转换后的行做 spec 校验(转换前校验必然对链上历史误报),返回{ loaded, errors, invalid },每条真实违约行以稳定标记[metadata_spec_invalid]告警并给出处方;saveMetaItem422 + 读取端_diagnostics徽章(同一校验函数,现在因为先转换而不再误报链上历史);registerItem的校验钩子在代码里明确注释为"诊断、非网关",日志同样带稳定标记。测试
spec/conversions/stored.test.ts(8):retired 条目重放、嵌入 action、复数类型名、幂等、不可变、未知类型直通、冲突守卫透传。metadata-protocol/protocol.stored-conversions.test.ts(6):直接把遗留行塞进 stub 引擎(正是绕过网关的静置数据),验证 getMetaItems/getMetaItem/loadMetaFromDb 的转换、_diagnostics不再误报、flow 跳过、invalid计数 + 照常注册。rule-validator.test.ts:原"honors the conditionalRequired alias"改为两条——alias 不再被读(PD Add comprehensive test suite for Zod schema validation #12 钉死)、经存量链转换后规则照常执行。check:generated8/8(api-surface 已再生成)。🤖 Generated with Claude Code