观察类发现,不是今天用户会撞到的缺陷;由 #714 的修复过程中量到,单独记录以免下次再被同一个盲区骗一次。
现象
test/helpers/hook-harness.ts 的 makeHarness 与 test/helpers/action-sandbox.ts 的 makeSandboxEngine,insert / update 都是「原样存下」,不对 lookup / reference 列的取值形态做任何校验 。于是一个 hook 把 boolean、数字、对象写进 lookup 列,两个 harness 都会照单全收,测试全绿,而真内核在 strict value-shape(ADR-0104)下会直接 ValidationError: … has an invalid lookup value: Invalid input: expected string, received boolean。
证据
#714 就是这个盲区放行的:quote_on_accepted 把 boolean false 当 lookup 值写了很久,而 test/hook-write-shape.test.ts:237 的 quote_on_accepted 用例恰好就是 「报价不挂 contact」的那条路径——它一直绿,因为 stub 收下了 crm_contact: false;test/hooks-runtime-sales.test.ts 的 quote_on_accepted 一组也是同理。真实环境(rc.2 验收)那一刻是 400 + 整个 handler 中止。
两个 harness 的注释里其实都写着这条纪律:
hook-harness.ts:「A fake replacement that accepts inputs the real thing rejects cannot prove anything」(为此把 filter 拼写做成了 loud failure);
action-sandbox.ts:「a stand-in that accepts calls the real engine rejects is worse than no test at all」(为此把 update 契约钉在了真 ObjectQL 上)。
取值形态这一维是这条纪律的漏项——两处都只钉了调用形状 ((data, options) / where vs filter),没钉取值形态 。
影响面
不是用户可见缺陷:它只让测试失去发现能力。但覆盖半径是全部 24 个 hook + 26 个 action 里所有写 lookup 列的派生写。#714 的 PR 只为 crm_contract 那一处补了「把 stub 判决钉在真 ObjectQL 判决上」的三层测试(test/quote-accepted-lookups.test.ts),没有做成通用能力。
可能的收口方向(未决,交 PM 三分类)
在两个 harness 的 insert / update 里,对已知 reference 列做最小取值形态断言(非 string 且非 null/undefined 即 throw),让 junk 值当场红;
或者按 action-sandbox.test.ts 既有做法,把「取值形态」也纳入「stub 判决 = 内核判决」的 pin 测试,只钉规则不改 stub;
或者维持现状,依赖每个 case 自己去真引擎里过一遍(即 [17.0-rc2验收] quote_on_accepted 把 boolean false 传入合同 lookup:凡缺 contact 或 opportunity 的报价被接受后,合同不起草、close-won 也不执行(静默失败) #714 PR 的做法),代价是每次都要手写一遍。
方向 1 的顾虑是 harness 并不持有 metadata(不知道哪一列是 lookup),要么传入 schema,要么维护一张名单——名单本身又是会腐烂的东西,所以这里不自行拍板。
环境:hotcrm@acc37e65 + @objectstack 17.0.0-rc.3。
观察类发现,不是今天用户会撞到的缺陷;由 #714 的修复过程中量到,单独记录以免下次再被同一个盲区骗一次。
现象
test/helpers/hook-harness.ts的makeHarness与test/helpers/action-sandbox.ts的makeSandboxEngine,insert/update都是「原样存下」,不对 lookup / reference 列的取值形态做任何校验。于是一个 hook 把 boolean、数字、对象写进 lookup 列,两个 harness 都会照单全收,测试全绿,而真内核在 strict value-shape(ADR-0104)下会直接ValidationError: … has an invalid lookup value: Invalid input: expected string, received boolean。证据
#714 就是这个盲区放行的:
quote_on_accepted把 booleanfalse当 lookup 值写了很久,而test/hook-write-shape.test.ts:237的quote_on_accepted用例恰好就是「报价不挂 contact」的那条路径——它一直绿,因为 stub 收下了crm_contact: false;test/hooks-runtime-sales.test.ts的quote_on_accepted一组也是同理。真实环境(rc.2 验收)那一刻是 400 + 整个 handler 中止。两个 harness 的注释里其实都写着这条纪律:
hook-harness.ts:「A fake replacement that accepts inputs the real thing rejects cannot prove anything」(为此把filter拼写做成了 loud failure);action-sandbox.ts:「a stand-in that accepts calls the real engine rejects is worse than no test at all」(为此把 update 契约钉在了真 ObjectQL 上)。取值形态这一维是这条纪律的漏项——两处都只钉了调用形状(
(data, options)/wherevsfilter),没钉取值形态。影响面
不是用户可见缺陷:它只让测试失去发现能力。但覆盖半径是全部 24 个 hook + 26 个 action 里所有写 lookup 列的派生写。#714 的 PR 只为
crm_contract那一处补了「把 stub 判决钉在真 ObjectQL 判决上」的三层测试(test/quote-accepted-lookups.test.ts),没有做成通用能力。可能的收口方向(未决,交 PM 三分类)
insert/update里,对已知 reference 列做最小取值形态断言(非 string 且非 null/undefined 即 throw),让 junk 值当场红;action-sandbox.test.ts既有做法,把「取值形态」也纳入「stub 判决 = 内核判决」的 pin 测试,只钉规则不改 stub;方向 1 的顾虑是 harness 并不持有 metadata(不知道哪一列是 lookup),要么传入 schema,要么维护一张名单——名单本身又是会腐烂的东西,所以这里不自行拍板。
环境:
hotcrm@acc37e65 + @objectstack 17.0.0-rc.3。