feat(w05): S2 关系表拆两笔写、S1 自投影类型字典、e2e 跨案例隔离 —— 真跑逼出来的三处

① S2:substitution.relation 拆成两笔同主键覆盖写,中间夹 difference_item ——
   difference_item.substitution_relation_id 声明了 refersTo(要求父行已存在),
   而 relation 的等级/风险/摘要要读 difference_item 的 tally,两张表互相要对方先写。

② S1:总把选中的那一行 chip_type / type_param 原样投一遍(幂等),
   于是这一单的草稿层永不为空 —— parts.chip_type 得以放回 gateLedgers,
   新建的类型随「数据工单确认即入库」一起落进正式表,不再留悬空引用。
   (此前把它删出名单只顾了闸、没顾落地:引用它的表落了地而它自己没落。)

③ e2e:案例 0 之后做跨案例隔离(withDb + isolationCleanup,不论成败都跑、
   失败只告警不拖垮整套)—— LAND_MASTER=1 之下案例 0 为满足 distinct=1 必须把
   共享的原 PN 转挂新类型,那一改会落进主数据,案例 1 共用同一颗料就看到假的类型不一致。
   断言 expectRevised 改名 expectFinalValue:「点名驳回归属」那条对 W05 不适用
   (四步 guide 全空、唯一的 agent 调用发生在意见之前,没有轮次会读到那条观察)。

包版本 2.1.5。pnpm gates 退出码 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
xcs
2026-08-30 07:06:52 +08:00
co-authored by Claude Opus 5
parent 6636abfc39
commit d420284887
5 changed files with 357 additions and 73 deletions
@@ -50,10 +50,19 @@ describe('清单本身合规', () => {
});
/* 2026-08-29 重写:spec §7 的判据表从九条改成八条(五条机检 L1/L2 + 两条 L4 裁量 + 一条 L5),L4 从三条收到两条 —— 证据链与硬约束两条留 L4,「推荐等级与明细一致」已升级成 L2 机检(level-by-rule) */
it('L1/L2 判据都给了谓词;L4 两条不给(裁量项);L5 恰一条', () => {
/*
* 2026-08-30:两条 L4 从「裸的(无 predicate)」改成「带谓词」——真跑 e2e 撞见的结构性
* 发现:T_release 全程 auto 驱动,`submit` 是五个声明式动词之一,没有第六个 `judge`;
* 裸 L4 只能靠 `judgement`(Agent 的 judge 行动)结算,而这个活动没有 Agent 会走到这一步,
* `submittable()` 于是永远读到 pass:false,`raiseAcceptance` 每一拍都拒、不算错误、
* 下一拍原样重来,稳定烧光步数预算到 exhausted。谓词只覆盖客观前提(有没有值、
* 值哪来的),主观那一半已经由 S3 人工验收清单接住(`engines.ts` 的 `criterionCheck`:
* 「裁量项带谓词时谓词说了算」,本仓 2026-08-02 就立的纪律,W05 这两条当时没跟上)。
*/
it('L1/L2/L4 判据都给了谓词(L4 走「谓词为真即达标」那条路,主观部分交人工验收清单接住);L5 恰一条', () => {
for (const c of manifest.contract.acceptanceCriteria) {
if (c.level === 'L1' || c.level === 'L2') expect(c.predicate, c.key).toBeDefined();
if (c.level === 'L4') expect(c.predicate, c.key).toBeUndefined();
if (c.level === 'L1' || c.level === 'L2' || c.level === 'L4') expect(c.predicate, c.key).toBeDefined();
if (c.level === 'L5') expect(c.predicate, c.key).toBeUndefined();
}
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L4')).toHaveLength(2);
expect(manifest.contract.acceptanceCriteria.filter((c) => c.level === 'L5')).toHaveLength(1);