#5945 / PR #6311 实施时的范围外发现,按第十条军规立案,unassigned。观察类:今天没有用户会撞到,两处已由 baseline EXEMPT 兜住;记录的是这道门的判据从此少了一支。
事实
scripts/check-engine-double-contract.mjs 的归属判据是二分的:一个测试里的对象字面量,要么是 engine double,要么是 driver double。
- 发现:字面量含
slice.verb + 至少 2 个其它 ENGINE_SIBLINGS(find / findOne / insert / update / delete / count / aggregate / getSchema / registry / insertMany);
- 归属:参数形状答不了时看
DRIVER_ONLY_MEMBERS 与 ENGINE_ONLY_MEMBERS(insert / insertMany / aggregate / getSchema / registry)。
#5945 引入了 IScopedObjectRepository(packages/spec/src/contracts/scoped-context.ts)—— 第三种数据访问对象:它绑定在单个对象上,所以对象名根本不是参数。三者的写动词并排:
IDataEngine.update(objectName, data, options?) // 对象名在第一位
IDataDriver .update(objectName, id, data, ...) // 主键在第二位
IScopedObjectRepository.update(data, options?) // 没有对象名
而它的成员集是 find / findOne / count / insert / update / updateById —— 四个 ENGINE_SIBLINGS,其中 insert 还在 ENGINE_ONLY_MEMBERS 里。于是任何一个该接口的类型符合性见证都会被发现并归到 engine 侧。这对扫描能看见的信息而言是准确的,对那个对象究竟是什么而言是错的。
后果(小,但会累积)
PR #6311 的两个见证(packages/spec/src/contracts/scoped-context.test.ts、packages/spec/src/data/hook.test.ts)因此被判 PINNED-fail,只能手写 baseline 条目。而 spec 原则上无法路由 assertEngineUpdateDispatch:它在 @objectstack/objectql / @objectstack/metadata-core,两者都依赖 @objectstack/spec,import 会反转依赖 —— 与 data-engine.test.ts 已有的两条 EXEMPT 同一个理由。两条新条目已按 #5629 的方法实测过 dormancy(stderr 探针静默,同一次运行的对照标记打印),所以今天是干净的。
问题在于这不是一次性的:此后每一个 IScopedObjectRepository 的实现或见证——包括语料库将来按 #5943 类型化后可能出现的测试替身——都会撞同一堵墙,每次都要手写一条 EXEMPT。一个把正确代码判红、且只能靠 ledger 消化的门,ledger 会越长越像噪音,而 ledger 的可读性正是这道门的价值所在(它的 $comment 明确说 shrink-only、手工评审)。
可能的方向(不预设结论)
- 加一支 scoped-repository 归属:写动词的第一个参数不是对象名/主键、且字面量含
object-less 的 repo 特征(如 updateById)时判为 scoped repository,直接出扫描范围(像 driver double 那样),不进 ledger。
- 按声明类型归属:字面量带
: IScopedObjectRepository 标注时出范围 —— 更窄更保守,但只覆盖有显式标注的写法。
- 维持现状,把 baseline 当作正常出口,并在脚本头把这第三种写进"deliberately not covered"。
关联
未加 pm:queue,交 PM 分诊定级。
#5945 / PR #6311 实施时的范围外发现,按第十条军规立案,unassigned。观察类:今天没有用户会撞到,两处已由 baseline EXEMPT 兜住;记录的是这道门的判据从此少了一支。
事实
scripts/check-engine-double-contract.mjs的归属判据是二分的:一个测试里的对象字面量,要么是 engine double,要么是 driver double。slice.verb+ 至少 2 个其它ENGINE_SIBLINGS(find/findOne/insert/update/delete/count/aggregate/getSchema/registry/insertMany);DRIVER_ONLY_MEMBERS与ENGINE_ONLY_MEMBERS(insert/insertMany/aggregate/getSchema/registry)。#5945 引入了
IScopedObjectRepository(packages/spec/src/contracts/scoped-context.ts)—— 第三种数据访问对象:它绑定在单个对象上,所以对象名根本不是参数。三者的写动词并排:而它的成员集是
find/findOne/count/insert/update/updateById—— 四个ENGINE_SIBLINGS,其中insert还在ENGINE_ONLY_MEMBERS里。于是任何一个该接口的类型符合性见证都会被发现并归到 engine 侧。这对扫描能看见的信息而言是准确的,对那个对象究竟是什么而言是错的。后果(小,但会累积)
PR #6311 的两个见证(
packages/spec/src/contracts/scoped-context.test.ts、packages/spec/src/data/hook.test.ts)因此被判 PINNED-fail,只能手写 baseline 条目。而 spec 原则上无法路由assertEngineUpdateDispatch:它在 @objectstack/objectql / @objectstack/metadata-core,两者都依赖 @objectstack/spec,import 会反转依赖 —— 与data-engine.test.ts已有的两条 EXEMPT 同一个理由。两条新条目已按 #5629 的方法实测过 dormancy(stderr 探针静默,同一次运行的对照标记打印),所以今天是干净的。问题在于这不是一次性的:此后每一个
IScopedObjectRepository的实现或见证——包括语料库将来按 #5943 类型化后可能出现的测试替身——都会撞同一堵墙,每次都要手写一条 EXEMPT。一个把正确代码判红、且只能靠 ledger 消化的门,ledger 会越长越像噪音,而 ledger 的可读性正是这道门的价值所在(它的$comment明确说 shrink-only、手工评审)。可能的方向(不预设结论)
object-less 的 repo 特征(如updateById)时判为 scoped repository,直接出扫描范围(像 driver double 那样),不进 ledger。: IScopedObjectRepository标注时出范围 —— 更窄更保守,但只覆盖有显式标注的写法。关联
HookContext.api声明为z.unknown()—— 按(ctx: HookContext)标类型的 hook 无法调用ctx.api.object(…),而文档/技能全在这么教 #5945 / PR feat(spec)!:HookContext.api从 z.unknown() 收窄为最小 IScopedContext (#5945) #6311(引入IScopedObjectRepository的那单)resolveEngineDeleteDispatch,update 的同款三分支只是 engine.ts 里的一个内联 throw #5480(update slice 的 producer-side 判定函数)、check:engine-double-contract 看不见「delete 少于两个形参」的假引擎 —— 实测 150 个 double 里 91 个(49 个文件)因此不在扫描面内 #5629(delete 批次与 dormancy 探针方法)sys_user_role全仓 0 命中,门禁结构上抓不到任何重复解析器 #6286(另一道门的判据词表被改名废掉 —— 同科不同门)未加
pm:queue,交 PM 分诊定级。