发现于 #4987(台账处方文字修正)执行过程中。#4987 的文件面被显式限定为 `scripts/engine-double-contract.baseline.json` 的 `why`/`closes` 文字,**下沉本身没有落点**,故按 Prime Directive #10 单开、未指派。 ## 现象 `scripts/engine-double-contract.baseline.json` 里现有 **7 条** `packages/metadata-protocol/**` 条目,全部因为同一个结构原因无法 pin: - `protocol-publish-drafts-endpoint-gate.test.ts`(#5206) - `protocol-publish-drafts-org-scope.test.ts`(#4987 修文字) - `protocol.runtime-authoring-gate.test.ts`(#4987 修文字) - `protocol.save-flow-canonicalization.test.ts`(#4987 修文字) - `sys-metadata-repository.draft-drain.test.ts`(#4981) - `sys-metadata-repository.history-counters.test.ts`(#4867) - `sys-metadata-repository.recorded-by.test.ts`(#4987 修文字) `@objectstack/objectql` 的 `dependencies` 含 `@objectstack/metadata-protocol`(`workspace:*`),所以反向加 devDependency 即成环,turbo 2.10.7 直接拒绝任务图(本轮 #4987 在 worktree 上实测复现,`build` 与 `test` 两个 task graph 都被拒,exit 1,随后回退): ``` WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol x Cyclic dependency detected: | @objectstack/objectql#build, @objectstack/metadata-protocol#build ``` `#4987` 已把这 7 条的 `why`/`closes` 全部改成实测的环 + 下沉路线,但**下沉代码本身未做** —— 而三条早先已改好的条目(#4867 / #4981 / #5206)的 `closes` 写的是「tracked as #4987」。#4987 一旦按其真实文件面(仅台账文字)关闭,这个引用就指向一个只改了措辞的已关闭 issue。本 issue 就是接住那个引用的落点。 ## 为什么下沉是可做的(已静态核实) 判据来自同文件 `packages/spec/src/contracts/data-engine.test.ts` 那条 EXEMPT:反向 import 不可行时,唯一出路是下沉到**两边都已依赖**的包。 - 生产者 `packages/objectql/src/engine-delete-dispatch.ts`(168 行)**没有任何 import** —— 纯自包含模块,导出 `ENGINE_DELETE_REJECT_MESSAGE` / `EngineDeleteDispatch` / `EngineDeleteDispatchInput` / `scalarDeleteId` / `resolveEngineDeleteDispatch` / `assertEngineDeleteDispatch` / `EngineDeleteDispatchCase` / `ENGINE_DELETE_DISPATCH_CASES`。下沉是一次**搬移**,不是重构。 - `@objectstack/metadata-core` 是现成共同依赖:`objectql -> metadata-core`(`workspace:*`)与 `metadata-protocol -> metadata-core`(`workspace:*`)都已存在;`metadata-core` 的 `dependencies` 只有 `{ @objectstack/spec, zod }`,**不含 objectql**,故不引入新环。 - `@objectstack/spec/contracts` 是另一候选(`spec` 的 `dependencies` 只有 `{ zod }`),但仅当「该谓词属于契约层」成立时才对 —— 需要拍板,不要顺手选。 ## 完成范围 1. 把 `engine-delete-dispatch.ts` 搬到 `@objectstack/metadata-core`(或拍板后的 `spec/contracts`),`@objectstack/objectql` 改为 re-export 以保持现有 24 个 pinned 调用点与公共 API 不变。 2. 7 个 metadata-protocol 测试文件的 fake `delete()` 接上该谓词(从新落点 import),跑 `@objectstack/metadata-protocol` 套件 —— 注意其中若有 fixture 断言 predicate delete 成功,真引擎是拒绝的,按 #5393 之后可用 `multi: true` 表达。 3. 从基线里**删掉**这 7 条(gate 是 shrink-only 双向校验:文件已无 unguarded double 时条目必须整条消失),`pnpm check:engine-double-contract` 计数从 24 pinned / 34 baseline 变为 31 pinned / 27 baseline。 4. 顺带(在本 issue 范围内,不必另开):#4867 / #4981 / #5206 三条的 `closes` 里「tracked as #4987」应改指本 issue;这三条还写着「the four/five sibling metadata-protocol entries」,而现在同族共 7 条(各有 6 个 sibling)—— 硬编码计数已漂移。#4987 修的四条已改用免计数措辞(「every other metadata-protocol entry in this ledger」)以避免再漂。若第 3 步整批删除,这两处随之消失。 ## 影响(如实说明,请分诊定级) 不是用户今天会撞到的缺陷,而是**测试替身比真契约松**的现存缺口:这正是 #4434 让一整段 REST 路由死掉而套件全绿的形状,gate(#4550)就是为消除它而建。7 个文件里的 fake delete 目前可以接受真引擎会拒绝的调用。域应为 `domain:engine-core`(搬移生产者模块 + 改 7 个测试)。 ## 参考 - #4550 / PR #4948(gate 与基线落地)、#4434(起因) - #4987(本 issue 的来源:台账处方文字修正)、#4867 / PR #4980、#4981、#5206 - #5480(objectql 的 UPDATE dispatch 无共享判定函数 —— 相邻不同题:那条是 update 侧缺函数,本条是 delete 侧函数位置错)
发现于 #4987(台账处方文字修正)执行过程中。#4987 的文件面被显式限定为
scripts/engine-double-contract.baseline.json的why/closes文字,下沉本身没有落点,故按 Prime Directive #10 单开、未指派。现象
scripts/engine-double-contract.baseline.json里现有 7 条packages/metadata-protocol/**条目,全部因为同一个结构原因无法 pin:protocol-publish-drafts-endpoint-gate.test.ts(api不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206)protocol-publish-drafts-org-scope.test.ts([engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987 修文字)protocol.runtime-authoring-gate.test.ts([engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987 修文字)protocol.save-flow-canonicalization.test.ts([engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987 修文字)sys-metadata-repository.draft-drain.test.ts([metadata-protocol] SysMetadataRepository.publishDraft() 把 draft 清理的全部失败都当「并发发布者已抽走」,静默留下一条永远 pending 的 draft 行 #4981)sys-metadata-repository.history-counters.test.ts([metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867)sys-metadata-repository.recorded-by.test.ts([engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987 修文字)@objectstack/objectql的dependencies含@objectstack/metadata-protocol(workspace:*),所以反向加 devDependency 即成环,turbo 2.10.7 直接拒绝任务图(本轮 #4987 在 worktree 上实测复现,build与test两个 task graph 都被拒,exit 1,随后回退):#4987已把这 7 条的why/closes全部改成实测的环 + 下沉路线,但下沉代码本身未做 —— 而三条早先已改好的条目(#4867 / #4981 / #5206)的closes写的是「tracked as #4987」。#4987 一旦按其真实文件面(仅台账文字)关闭,这个引用就指向一个只改了措辞的已关闭 issue。本 issue 就是接住那个引用的落点。为什么下沉是可做的(已静态核实)
判据来自同文件
packages/spec/src/contracts/data-engine.test.ts那条 EXEMPT:反向 import 不可行时,唯一出路是下沉到两边都已依赖的包。packages/objectql/src/engine-delete-dispatch.ts(168 行)没有任何 import —— 纯自包含模块,导出ENGINE_DELETE_REJECT_MESSAGE/EngineDeleteDispatch/EngineDeleteDispatchInput/scalarDeleteId/resolveEngineDeleteDispatch/assertEngineDeleteDispatch/EngineDeleteDispatchCase/ENGINE_DELETE_DISPATCH_CASES。下沉是一次搬移,不是重构。@objectstack/metadata-core是现成共同依赖:objectql -> metadata-core(workspace:*)与metadata-protocol -> metadata-core(workspace:*)都已存在;metadata-core的dependencies只有{ @objectstack/spec, zod },不含 objectql,故不引入新环。@objectstack/spec/contracts是另一候选(spec的dependencies只有{ zod }),但仅当「该谓词属于契约层」成立时才对 —— 需要拍板,不要顺手选。完成范围
engine-delete-dispatch.ts搬到@objectstack/metadata-core(或拍板后的spec/contracts),@objectstack/objectql改为 re-export 以保持现有 24 个 pinned 调用点与公共 API 不变。delete()接上该谓词(从新落点 import),跑@objectstack/metadata-protocol套件 —— 注意其中若有 fixture 断言 predicate delete 成功,真引擎是拒绝的,按 flow 的delete_record/update_record无法表达批量意图 —— 节点 schema 无键、执行器不传options.multi,谓词批量写对所有 flow 平台级不可达,而节点描述符宣称支持 #5393 之后可用multi: true表达。pnpm check:engine-double-contract计数从 24 pinned / 34 baseline 变为 31 pinned / 27 baseline。api不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206 三条的closes里「tracked as [engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987」应改指本 issue;这三条还写着「the four/five sibling metadata-protocol entries」,而现在同族共 7 条(各有 6 个 sibling)—— 硬编码计数已漂移。[engine-double-contract] 四条 metadata-protocol 基线条目的closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987 修的四条已改用免计数措辞(「every other metadata-protocol entry in this ledger」)以避免再漂。若第 3 步整批删除,这两处随之消失。影响(如实说明,请分诊定级)
不是用户今天会撞到的缺陷,而是测试替身比真契约松的现存缺口:这正是 #4434 让一整段 REST 路由死掉而套件全绿的形状,gate(#4550)就是为消除它而建。7 个文件里的 fake delete 目前可以接受真引擎会拒绝的调用。域应为
domain:engine-core(搬移生产者模块 + 改 7 个测试)。参考
closes指向一个不可能的动作:加 @objectstack/objectql devDependency 会让 turbo 直接判环 #4987(本 issue 的来源:台账处方文字修正)、[metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867 / PR fix(metadata-protocol): never invent event_seq/version from a failed history read (#4867) #4980、[metadata-protocol] SysMetadataRepository.publishDraft() 把 draft 清理的全部失败都当「并发发布者已抽走」,静默留下一条永远 pending 的 draft 行 #4981、api不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206resolveEngineDeleteDispatch,update 的同款三分支只是 engine.ts 里的一个内联 throw #5480(objectql 的 UPDATE dispatch 无共享判定函数 —— 相邻不同题:那条是 update 侧缺函数,本条是 delete 侧函数位置错)