refactor(metadata-protocol): 删除 saveMetaItem 中已不可达的 legacy raw-engine 写入分支 (#5264) - #5782
Conversation
… 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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
正文第五节的三处范围外残留已开单记录:#5783( Generated by Claude Code |
|
CI 全绿(21/21): 保持 draft,等待复审。 Generated by Claude Code |
Fixes #5264
删除
packages/metadata-protocol/src/protocol.ts中saveMetaItem的 legacy raw-engine 写入分支(useRepoPath === false那一支:直接engine.insert/engine.update写sys_metadata,无 history 行、无 watch 事件、无seq),并化简useRepoPath。运行时行为无变化 —— 这条分支在 #5086 / PR #5263 之后已不可达。deleteMetaItem侧结构对称的分支一行未动,只补了「为什么这条还活着」的注释。一、不可达性复核(独立复核,结论与 issue 一致,论证可以更短)
issue 正文按类型逐个枚举(
object/flow恒走 repository、job/agent被 403、code-only 未解锁必然抛错、解锁后又走回 repository)。复核后发现不必逐类型枚举 —— 两处判据是同一个布尔表达式作用在同一个键上: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。if (!overlayAllowed && !runtimeCreateAllowed) throw …,不再包在environmentId !== undefined里(这正是 PR fix(metadata-protocol): allowRuntimeCreate:false 在每一种 kernel 上都生效 —— PUT /meta 不再创建注册表声明为 code-only 的 job / agent (#5086) #5263 的修复本身)。!(isOverlayAllowed(singularTypeForRepo) || isRuntimeCreateAllowed(singularTypeForRepo))—— 与第 2 步的判据互为反面,且两个标志读取器内部各自还会再折叠一次单复数并同时查两种拼写(isOverlayAllowed/isRuntimeCreateAllowed),所以两次求值不可能给出不同答案。OS_METADATA_WRITABLE不构成缺口:在那里解锁一个类型会让isOverlayAllowed为真,于是照样走 repository 路径 —— 一扇门,不是绕过。因此:凡是能走到分叉点的写入,
useRepoPath必为真。结论:复核成立。唯一能让两次求值分歧的交错:在
saveMetaItem中途(destructive-diff /ensureOverlayIndex/ authoring gate 这几个await之间)改写process.env.OS_METADATA_WRITABLE并调用resetEnvWritableCache()。没有任何生产代码这么做,只有测试夹具会碰这两个开关;真出现这种交错,改动后的行为是 repository 的assertAllowed()给 403,比原先静默走 legacy 路径更正确。如实记在这里,不算缺口。二、反向验证(先定方向,再跑)
预测的方向:探针不应该被打中,新增 pin 测试在删除前后都是绿的 —— 因为「删除前后都绿」正是死代码的定义,不是回归测试的失败。三步都按这个预测执行并全部命中:
探针(负向):在删除前,把 legacy 分支的第一条语句换成
throw new Error('[PROBE-5264] legacy save branch REACHED …'),跑三个包:metadata-protocol46 files / 431 passedobjectql123 files / 2021 passedruntime99 files / 1446 passed全绿 —— 3898 个测试没有一个进入过这条分支。
正对照(证明探针会响):把同一个
throw挪到分叉点if (useRepoPath) {之前再跑metadata-protocol—— 57 个测试立即失败([PROBE-5264-POSITIVE-CONTROL] reached the fork for api/open_things…)。说明这套用例确实高频驱动saveMetaItem,「探针没响」是真的没到,不是没跑到。新增 pin 的前后对照:把实现
git stash掉、只留新测试,对未修改的protocol.ts跑protocol.code-only-types.test.ts—— 32 passed;恢复实现后同样 32 passed。前后同绿,与预测一致。所以新增的 pin 不是「删除的回归测试」,而是不变量守卫:它钉的是「任何被接受的保存都必须给出 repository 回执」,一旦有人再引入第二条无 history 的写路径、或把 #5086 的门重新缩回
environmentId !== undefined,它会红。三、改动摘要
packages/metadata-protocol/src/protocol.tsoverlayAllowedForRepo/runtimeCreateAllowedForRepo/useRepoPath三个变量与if (useRepoPath) { … }包裹,repository 路径整体外提一层缩进;删掉其后 93 行 legacy 分支。singularTypeForRepo保留(下面仍在用)。deleteMetaItem:可执行代码零改动。git diff -w里所有非注释变更行都是-,没有一行+代码(见下方核对)。该分支上新增注释,说明它仍然可达且必要:两级删除授权只在environmentId !== undefined时才跑,control-plane kernel 上 code-only 的删除就落在这里;fix(metadata-protocol): allowRuntimeCreate:false 在每一种 kernel 上都生效 —— PUT /meta 不再创建注册表声明为 code-only 的 job / agent (#5086) #5263 特意没收紧删除侧,因为删除 code-only 历史遗留行是修复动作。注释里明确写了「不要为了对称把它一起删掉」。migrateStoredMetadata的 skip 理由、historyMetaItem返回空数组的理由)—— 二者的行为未改,改的只是它们对原因的陈述。核对命令(用于复审):
输出全部是
-行,且全部属于 save 侧被删分支。packages/metadata-protocol/src/protocol.code-only-types.test.ts(#5086 的既有套件,新增一个#5264块,10 个用例)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.tsmigrateStoredMetadata对agent的 skip(断言一字未动、仍然有效),但它的理由写的是「否则saveMetaItem会走 legacy 分支」。按夹具分诊三分法,这属于「re-spell 理由」而非「随被删肢移除」:它钉的不是被删的肢,恰恰是那个一直把这条路径挡在门外的 skip。.changeset/save-legacy-raw-engine-branch-removed.md— patch;如实写明「移除不可达代码,wire 无变化」。四、测试与门禁
pnpm --filter @objectstack/metadata-protocol testpnpm --filter @objectstack/objectql testpnpm --filter @objectstack/rest testpnpm --filter @objectstack/runtime testtsc --noEmit -p packages/metadata-protocoltypecheckscript,在check-type-check-coverage.mjs里是已计量 DEBT 项;逐条对比确认未新增)pnpm check:nul-bytespnpm check:query-options-erasurepnpm check:adr-anchorsbatchData,与本改动无关)pnpm check:error-code-casinggrep -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