Skip to content

refactor(metadata-protocol): 删除 saveMetaItem 中已不可达的 legacy raw-engine 写入分支 (#5264) - #5782

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5264-remove-dead-save-legacy-branch
Aug 6, 2026
Merged

refactor(metadata-protocol): 删除 saveMetaItem 中已不可达的 legacy raw-engine 写入分支 (#5264)#5782
os-zhuang merged 1 commit into
mainfrom
claude/issue-5264-remove-dead-save-legacy-branch

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5264

删除 packages/metadata-protocol/src/protocol.tssaveMetaItem 的 legacy raw-engine 写入分支(useRepoPath === false 那一支:直接 engine.insert / engine.updatesys_metadata,无 history 行、无 watch 事件、无 seq),并化简 useRepoPath运行时行为无变化 —— 这条分支在 #5086 / PR #5263 之后已不可达。 deleteMetaItem 侧结构对称的分支一行未动,只补了「为什么这条还活着」的注释。


一、不可达性复核(独立复核,结论与 issue 一致,论证可以更短)

issue 正文按类型逐个枚举(object/flow 恒走 repository、job/agent 被 403、code-only 未解锁必然抛错、解锁后又走回 repository)。复核后发现不必逐类型枚举 —— 两处判据是同一个布尔表达式作用在同一个键上:

  1. saveMetaItem 开头 request = canonicalizeMetaRequestType(request)(protocol.ts:123-131,PLURAL_TO_SINGULAR[type] ?? type)已把类型键折叠成单数。PLURAL_TO_SINGULAR 的每个 value 都不再是它自己的 key(metadata-collection.zod.ts:111),所以此后 singularTypeForRepo === request.type
  2. metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 的 code-only 拒绝是 if (!overlayAllowed && !runtimeCreateAllowed) throw …,不再包在 environmentId !== undefined(这正是 PR fix(metadata-protocol): allowRuntimeCreate:false 在每一种 kernel 上都生效 —— PUT /meta 不再创建注册表声明为 code-only 的 job / agent (#5086) #5263 的修复本身)。
  3. 被删分支的进入条件是 !(isOverlayAllowed(singularTypeForRepo) || isRuntimeCreateAllowed(singularTypeForRepo)) —— 与第 2 步的判据互为反面,且两个标志读取器内部各自还会再折叠一次单复数并同时查两种拼写(isOverlayAllowed / isRuntimeCreateAllowed),所以两次求值不可能给出不同答案。
  4. OS_METADATA_WRITABLE 不构成缺口:在那里解锁一个类型会让 isOverlayAllowed 为真,于是照样走 repository 路径 —— 一扇门,不是绕过。

因此:凡是能走到分叉点的写入,useRepoPath 必为真。结论:复核成立。

唯一能让两次求值分歧的交错:在 saveMetaItem 中途(destructive-diff / ensureOverlayIndex / authoring gate 这几个 await 之间)改写 process.env.OS_METADATA_WRITABLE 并调用 resetEnvWritableCache()。没有任何生产代码这么做,只有测试夹具会碰这两个开关;真出现这种交错,改动后的行为是 repository 的 assertAllowed() 给 403,比原先静默走 legacy 路径更正确。如实记在这里,不算缺口。

二、反向验证(先定方向,再跑)

预测的方向:探针不应该被打中,新增 pin 测试在删除前后都是绿的 —— 因为「删除前后都绿」正是死代码的定义,不是回归测试的失败。三步都按这个预测执行并全部命中:

  1. 探针(负向):在删除前,把 legacy 分支的第一条语句换成 throw new Error('[PROBE-5264] legacy save branch REACHED …'),跑三个包:

    • metadata-protocol 46 files / 431 passed
    • objectql 123 files / 2021 passed
    • runtime 99 files / 1446 passed

    全绿 —— 3898 个测试没有一个进入过这条分支。

  2. 正对照(证明探针会响):把同一个 throw 挪到分叉点 if (useRepoPath) { 之前再跑 metadata-protocol —— 57 个测试立即失败([PROBE-5264-POSITIVE-CONTROL] reached the fork for api/open_things …)。说明这套用例确实高频驱动 saveMetaItem,「探针没响」是真的没到,不是没跑到。

  3. 新增 pin 的前后对照:把实现 git stash 掉、只留新测试,对未修改protocol.tsprotocol.code-only-types.test.ts —— 32 passed;恢复实现后同样 32 passed。前后同绿,与预测一致。

所以新增的 pin 不是「删除的回归测试」,而是不变量守卫:它钉的是「任何被接受的保存都必须给出 repository 回执」,一旦有人再引入第二条无 history 的写路径、或把 #5086 的门重新缩回 environmentId !== undefined,它会红。

三、改动摘要

packages/metadata-protocol/src/protocol.ts

核对命令(用于复审):

git diff -w packages/metadata-protocol/src/protocol.ts \
  | grep -E '^[+-]' | grep -v '^[+-][+-]' \
  | grep -vE '^[+-]\s*(//|\*|/\*|\*/)' | grep -vE '^[+-]\s*$'

输出全部是 - 行,且全部属于 save 侧被删分支。

packages/metadata-protocol/src/protocol.code-only-types.test.ts(#5086 的既有套件,新增一个 #5264 块,10 个用例)

  • stub engine 增加 writes 记录(哪张表被写)。两条路径都会 insert('sys_metadata', …),真正的判别式是有没有同时追加 sys_metadata_history —— 这正是 legacy 分支的定义性缺失。
  • asks the engine for NOTHING when refusing ${type} on a control-plane kernel:按被删注释自称的可达条件(environmentId === undefined + 非 overlay 类型)构造探针请求,断言 403 且 writes空数组 —— 比「没有落行」更强:一次写都没发起。
  • answers a ${type} save with a repository receipt on a ${label}:view / hook / theme × 两种 kernel,断言 seq 是 number、state === 'active'、message 含 [seq=、且 sys_metadata_history 被追加。legacy 回执恰好是这几项全无的形状(Saved customization overlay (env-wide) — type=…,正是 metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 抓到 showcase 对 job 的回答)。
  • routes ${type} back through the repository once OS_METADATA_WRITABLE unlocks it:补上不可达论证的最后一环 —— 逃生阀不开第二扇门,解锁后的保存仍是 repository 保存,回执与 history 行俱全。

packages/metadata-protocol/src/protocol.stored-migration.test.ts

  • 只改注释。该用例钉的是 migrateStoredMetadataagent 的 skip(断言一字未动、仍然有效),但它的理由写的是「否则 saveMetaItem 会走 legacy 分支」。按夹具分诊三分法,这属于「re-spell 理由」而非「随被删肢移除」:它钉的不是被删的肢,恰恰是那个一直把这条路径挡在门外的 skip。

.changeset/save-legacy-raw-engine-branch-removed.md — patch;如实写明「移除不可达代码,wire 无变化」。

四、测试与门禁

项目 结果
pnpm --filter @objectstack/metadata-protocol test 46 files / 441 passed(改动前 431,新增 10)
pnpm --filter @objectstack/objectql test 123 files / 2021 passed
pnpm --filter @objectstack/rest test 55 files / 778 passed
pnpm --filter @objectstack/runtime test 99 files / 1446 passed
tsc --noEmit -p packages/metadata-protocol 63 → 63,与 origin/main 完全一致(该包无 typecheck script,在 check-type-check-coverage.mjs 里是已计量 DEBT 项;逐条对比确认未新增)
pnpm check:nul-bytes PASS
pnpm check:query-options-erasure PASS
pnpm check:adr-anchors PASS(protocol.ts 的锚是 ADR-0119 batchData,与本改动无关)
pnpm check:error-code-casing PASS
控制字符自查(grep -naP 扫 0x00-0x1f) 无命中

消费半径已按「规则的调用方」扫过:save 路径的真实调用方在 objectql / runtime / rest,三个包的夹具都跑过(探针版与最终版各一遍)。

五、范围外发现(本 PR 不动)

删掉这条分支后,OVERLAY_PERSISTENCE_FAILED 失去了唯一的生产端发射点(原 protocol.ts:7952),但仍留在 packages/spec/src/api/error-code-ledger.zod.ts@objectstack/metadata-protocol 名下;packages/rest 的两个测试自行构造该 error 来断言 5xx 脱敏与状态带,现在钉的是一个没有生产者的码。同族还有两处已过期的注释:packages/spec/src/kernel/metadata-plugin.zod.ts(称 saveMetaItem 会把 agent 路由到 legacy 分支)与 packages/rest/src/rest-5xx-message-sanitization.test.ts(称该分支对 artifact-backed 的 code-only 类型仍可达 —— 这一条其实在 #5263 当天就已过期)。都属观察类、今天没有用户会碰到,且都落在本单硬性文件面之外(spec / rest),故未在此 PR 处理,另行记录。


Generated by Claude Code

… save branch (#5264)

`saveMetaItem` had two persistence routes: the repository write path
(history row + watch event + monotonic `seq`) and a legacy raw-engine
branch after it (`engine.insert`/`engine.update` straight into
`sys_metadata` — no history, no watch event, no `seq`). The legacy branch
ran when `isOverlayAllowed(type) || isRuntimeCreateAllowed(type)` was
false.

#5086 (PR #5263) made that condition unreachable here: the code-only
refusal earlier in the same method throws on exactly that predicate, on
every kernel (no longer keyed on `environmentId`), over the same
canonicalized type key. `OS_METADATA_WRITABLE` is not a hole either —
unlocking a type there makes `isOverlayAllowed` true and routes the save
back through the repository. No behaviour change.

`deleteMetaItem`'s structurally symmetric branch is untouched: it is
still reachable and still necessary (a control-plane delete of a
code-only row that predates the refusal is the repair action #5263
deliberately left open). It gained a comment saying why it survives.

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

vercel Bot commented Aug 6, 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 Aug 6, 2026 5:19am

Request Review

@github-actions github-actions Bot added the size/l label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

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.

Copy link
Copy Markdown
Contributor Author

正文第五节的三处范围外残留已开单记录:#5783(finding,未认领)。其中「OVERLAY_PERSISTENCE_FAILED 在本 PR 之后没有生产者了」需要维护者裁一下摘码还是保留;另两条是纯注释。三处都在本单硬性文件面之外(packages/spec / packages/rest),故未随本 PR 一并改。


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

CI 全绿(21/21):Test Core 三个分片、TypeScript Type CheckESLintDogfood Regression Gate 三个分片、Dogfood Verify CLITemporal Conformance (live PG + MySQL)Check Changeset 均 success。Test Core 覆盖到了本地扫描半径之外的 cli / plugin-security / spec / services,与「删的是不可达代码、wire 无变化」的判断一致。

保持 draft,等待复审。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 05:51
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 5ab0842 Aug 6, 2026
24 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5264-remove-dead-save-legacy-branch branch August 6, 2026 06:03
os-zhuang pushed a commit that referenced this pull request Aug 6, 2026
合并 origin/main 后带进 PR #5782(#5264):`saveMetaItem` 的 legacy raw-engine
分支已被删除,现在只剩唯一一个成功 return。这与本单改前的独立实测结论一致,
并且让 `version` / `seq` / `state` 定 required 的依据更干净 —— 不再是"另一条
返回不可达",而是"只有一条返回"。相应更新 schema 头注释、conformance 测试注释
与 changeset 的措辞。

同时,新声明的 `state: z.enum(['draft','active'])` 让
`ObjectStackProtocolImplementation` 不再满足 `MetadataProtocol.saveMetaItem`
(TS2416):对象字面量属性会把两个字面量的三元表达式**加宽成 `string`**,所以
实现的返回类型说 `state: string`,而契约现在说的是它一直在发的那个闭集。

这正是收紧契约要抓的松动:此前消费者读到的 `state` 是 `string`,把它和拼错的
`'publshed'` 比较不会有任何类型错误。修在生产端(Prime Directive #12 契约优先),
只加一处类型断言,**取值不变、运行时行为不变**。

未采用的替代方案:把 spec 里的 `state` 降级为 `z.string()` 或
`z.string().refine(...)` 就能不动 metadata-protocol,但那是为了绕过文件面边界而
明知故犯地弱化契约(Prime Directive #5「不要 workaround」),且恰好丢掉在编写期
阻止错误的那一半价值。

证据:`tsc --noEmit -p packages/metadata-protocol` 在本改动下为 **63** 个错误,
与 origin/main 基线一致(该包无 typecheck script,是已计量 DEBT 项);若只改 spec
而不加这一行则为 64 —— 多出来的正是上述 TS2416。

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 size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

saveMetaItem 的 legacy raw-engine 分支在 #5086 的门之后已不可达 —— 一段 grep 找得到、运行时永不执行的代码

2 participants