Skip to content

fix(devx): check:engine-double-contract 看不见零/单形参的假引擎 delete —— 放宽判据 + 全量记债 - #5694

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5629-double-contract-arity
Aug 6, 2026
Merged

fix(devx): check:engine-double-contract 看不见零/单形参的假引擎 delete —— 放宽判据 + 全量记债#5694
os-zhuang merged 1 commit into
mainfrom
claude/issue-5629-double-contract-arity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5629

scripts/check-engine-double-contract.mjsisEngineDeleteShape() 首行是形参个数判据(params.length 少于 2 就 return false)。它把不声明形参的假引擎 delete() 在任何其他判据之前就丢掉了:既不进 PINNED,也不进台账,不产生任何输出。这不是「已登记的债」,是检测器够不到 —— 正是本脚本 DISCOVERED 不变量自己针对的 #4868 形状(检查在跑、是绿的、结构上够不到它要检查的对象)。

本 PR 是这条线的第一批:放宽判据 + 全量记债,不做任何收编。按包分批的收编是后续单。

重测口径(立单数已漂移,以下为本分支实测)

基线 414395b9a。立单时是 cc5b048a0 上的 59/58、25 pinned、34 台账。

改动前 放宽判据、未记债 本 PR 落地后
发现的 engine double 59 / 58 文件 108 / 91 文件 108 / 91 文件
pinned 27 27 27(不变)
台账条目 32 32 66
门禁退出码 0(绿) 1 —— 36 个问题 0(绿)

中间那一栏就是「门禁现在看得见了」的证据:34 个文件报 PINNED: … declares N engine double(s) whose delete() does not route through assertEngineDeleteDispatch,另外 2 个文件报计数上升(action-body-identity.test.ts 1 到 2、data-engine.test.ts 1 到 5)。

判据为什么不是无条件放行

立单正文给的理由是「零形参不可能是驱动 —— 驱动签名 delete(object, id, options) 必有主键位」。这一条经实测不成立:假驱动同样会省掉自己不用的形参。本分支上 92 个短形参 delete43 个是驱动 double,包括 packages/spec/src/contracts/data-driver.test.ts 本身,以及 packages/objectql/src/engine-aggregate-having.test.ts 里注释自述 /** A driver WITH native aggregate() */ 的那个。无条件 return true 会把门禁指向错误的契约 43 次。

所以形参数少于 2 时回落到 sibling 证据,要求两个条件同时成立:

  • 声明了只有引擎才有的成员(insert / insertMany / aggregate / getSchema / registry);
  • 没有只有驱动才有的成员(connect / create / checkHealth / bulkCreate / 事务与 schema 那一组)。

两半都是承重的,self-test 各有用例钉住:只看正面证据会把上面那个 native-aggregate 驱动当引擎(驱动可以做聚合下推);只看反面证据会把任何 { find, findOne, update, delete } 的 store mock 报出来(它两个契约都不是)。名单按 IDataDriver / IDataEngine 的实际成员划:两边都有的 findfindOneupdatecountexecute(引擎也声明 execute?)一个都不算证据。

ID_PARAM、pinned 判定、接缝声明(只管 delete dispatch、只管引擎 double)一律未动;形参数 2 及以上的路径逐字未改,所以 pinned 计数不变。

台账

49 个新可见 double、36 个文件全量进 MEASURED 台账:34 条新条目 + 2 条既有条目因重测而改计数。既有 32 条里 30 条逐字未动;改动的 2 条是计数被判据放宽推上去的(1 -> 21 -> 5),各自在 why 里以 RE-MEASURED (#5629) 单独说明「不是回归,也不是抬棘轮:double 一直在,是扫描够不到」。

每条 why 的休眠判定都是实测的,不是推断的:把一句 process.stderr.write 标记注入每个 delete 的第一行,跑所在包的测试 —— 49 个 delete 全部没有打印(36 个文件的 suite 全绿)。对照组在同一次运行里:run-summary.test.ts#5197 已 pinned 的那个 delete 标记正常打印。所以「静默」是证据,而不是探针坏了。注入只用于探测,已全部还原(本 PR 不含 packages/** 任何改动)。

closes 按各包相对生产者的位置分四类,都在本分支实测过:

#5624 的教训,台账文字里没有写任何硬编码 sibling 计数。

验证

  • pnpm check:engine-double-contract(self-test + 全量)绿;台账 JSON.parse 过,66 条条目字段齐全、按 file 排序。
  • self-test 断言 14 到 19,只增不减。反向验证方向是预判后测的:只把判据回退成 return false,恰好 2 条新用例转红(「零形参引擎 delete 在扫描面内」「单形参引擎 delete 在扫描面内」),3 条否决用例保持绿 —— 它们防的是「错误的修法」,本来就不该先红。
  • lint job 本地等价 24/24 全绿(pnpm lint + 该 job 的全部 check:*,门禁本体在内)。
  • node scripts/check-nul-bytes.mjs 绿(5600 个文件);改动文件另做 C0 自扫,零命中。
  • 抽查 5 条不同包的条目(cli:121 / cloud-connection:81 / metadata-protocol:60 / plugin-webhooks:34 / runtime:18):行号处确为零形参 delete 的引擎 double,delete 体分别是 { deleted: 0 } / ({ deleted: 1 }) / { deleted: 0 } / { affected: 0 } / ({}) —— 对任何 dispatch 都返回成功形状,正是 sharing: DELETE /sharing/rules/:idOrName answers 500 for both address forms — rules cannot be deleted over REST #4434 的宽松类。

非发布面(scripts/ 门禁 + 台账),走 skip-changeset 标签路线。


Generated by Claude Code

`isEngineDeleteShape()` 的首行 `params.length < 2 → return false` 把「不声明
形参」的假引擎 delete 在任何其他判据之前就丢掉了。这些 double 既不进 PINNED
也不进台账,不产生任何输出 —— 正是本脚本 DISCOVERED 不变量针对的 #4868 形状
(检查在跑、是绿的、结构上够不到它的对象)。

形参数少于 2 时改为回落到 sibling 证据,而不是无条件放行:立单时设想的
「零形参不可能是驱动(驱动签名必有主键位)」经实测不成立 —— 假驱动同样会省掉
不用的形参,本分支上 92 个短形参 delete 里有 43 个是驱动 double(其中包括
spec/src/contracts/data-driver.test.ts 本身,以及 objectql 里自述
「driver WITH native aggregate()」的那个)。所以判据要求 object 同时满足:
声明了只有引擎才有的成员,且没有只有驱动才有的成员。两半都是承重的。

本 PR 只做「放宽判据 + 记债」,不做任何收编:49 个新可见 double(36 个文件)
全量进 MEASURED 台账,pinned 计数不变(27),门禁落地后全绿,台账对这批
double 从此 shrink-only。逐条 why 都实测过:注入 stderr 标记后 49 个 delete
全部未被所在测试调用(休眠宽松),同一次运行里 run-summary.test.ts 已 pinned
的那个 delete 标记正常打印,作为「静默是证据而非探针坏了」的对照。

Fixes #5629
@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 12:32am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

判据的一处刻意不声明,写在这里给后续分批的人:

形参数少于 2 的分支只看 sibling 证据,不看形参名。所以理论上 delete(id) 这种「单形参且形参名是主键」的对象,只要它声明了引擎独有成员且没有驱动独有成员,仍会被判为引擎 double。之所以没有在这个分支里也套用 ID_PARAM:本单的硬边界是「ID_PARAM、pinned 判定、接缝声明一律不动」,而且本分支实测这种形状当前零实例 —— 92 个短形参 delete 里只有一个是单形参(action-body-identity.test.ts:71,形参名 o,是 createContext().object() 的作用域门面)。

即:这不是「已验证不会误报」,是「当前无实例,故未加判据也未写断言」。真出现时它的后果是多一条台账条目(误报方向),不是漏报,所以不会让门禁变绿说谎。哪个分批第一次遇到 delete(id) 形状,再决定是把 ID_PARAM 引到这个分支还是照实记债。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant