Archived
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:
@@ -241,10 +241,10 @@ export const ASSESSMENT_STEPS = [
|
||||
intent: '核对两颗料在不在库、归没归同一个类型、参数值齐不齐;缺什么就派一张数据工单让人补',
|
||||
guide: '',
|
||||
/* Task 7:发给人的数据工单摆的就是这一张 —— 三块竖排(存在性与主数据 / 芯片类型字段 / 参数值),
|
||||
与 auto.do 第 ⑤ 笔 `card` 的 `gateLedgers` 同源(parts.type_param / parts.profile / parts.parameter ——
|
||||
`parts.chip_type` 不列入,理由见那笔 `card` 定义处的注释)。 */
|
||||
与 auto.do 第 ⑥ 笔 `card` 的 `gateLedgers` 同源(parts.chip_type / parts.type_param /
|
||||
parts.profile / parts.parameter —— 四张表都在闸上,理由见那笔 `card` 定义处的注释)。 */
|
||||
view: 'input-supplement',
|
||||
writes: ['sub_eval.part', 'sub_eval.case', 'parts.profile', 'parts.parameter'],
|
||||
writes: ['sub_eval.part', 'sub_eval.case', 'parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter'],
|
||||
reads: ['originalInternalPn', 'candidateInternalPn', 'case:openedAt', 'parts.profile', 'parts.parameter', 'parts.chip_type', 'parts.type_param'],
|
||||
tools: [],
|
||||
doneWhen:
|
||||
@@ -278,7 +278,82 @@ export const ASSESSMENT_STEPS = [
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ② 主数据骨架:**两颗料都铺、值回填现值**。
|
||||
* ② **选中的那一行类型字典,原样投一遍**(2026-08-30,Ruling H35 —— 真跑 e2e 撞见的
|
||||
* 平台侧连带问题:「数据工单确认即入库」按 `gateLedgers` 整表并入正式表,而
|
||||
* `parts.chip_type` 从 Task 7 起就没列在 `gateLedgers` 里 —— 人一旦真的新建一个类型,
|
||||
* `parts.profile`/`parts.type_param` 引用着它一起落地,**字典行本身**却只留在这一单
|
||||
* 草稿里、从没落地过。下一个任务读到同一颗料,查这个 `chip_type_id` 时字典里空空如也,
|
||||
* `refersTo` 当场拒——是一条悬空引用,不是「查无此值」那种输入错误)。
|
||||
*
|
||||
* **这一笔不看「新建还是已有」,统统投一遍**(不选型的选也一样、新建的选也一样):
|
||||
* 读的是 `sub_eval.part` 每一行当下的 `chip_type_id`(空串时下面这层 `each` 天然查
|
||||
* 不到、不产生任何行 —— 人还没选类型的那颗不会被提前铺出假行),按它去
|
||||
* `parts.chip_type` 里找到那一行、原样把 `type_name`/`description` 抄回自己身上。
|
||||
* 值没变,`draftWrite` 同 PK 覆盖静默跳过(幂等,不产生多余的写入痕迹)——
|
||||
* 但**这一单的草稿层从此真的有这一行**,`mine:parts.chip_type` 不再天然为空。
|
||||
*
|
||||
* 这条把 `parts.chip_type` 重新放回下面 ⑥ 的 `gateLedgers` 铺平了路:闸不会再拿
|
||||
* 「零行」的旧理由误拦(那个前提不成立了),而选中的类型(新建的也好、已有的也好)
|
||||
* 会随「数据工单确认入库」一起落进正式表 —— 不再留悬空引用。
|
||||
*/
|
||||
{
|
||||
kind: 'project' as const,
|
||||
into: 'parts.chip_type',
|
||||
/*
|
||||
* **不带 `prune`**(2026-08-30 第二处修正,e2e 案例 2 撞出来的第二个坑)——
|
||||
* `parts.chip_type` 在 ⑦ 的 `gateLedgers` 上,人交回数据工单那一刻这张表就跟着
|
||||
* 「数据工单确认即入库」落进正式表(`landsOnConfirm`);已落地的行 `draftDrop`
|
||||
* (`prune` 的撤行机制)撤不掉,配方原样抛 `AutoRecipeError`(原文:「这一行是本单
|
||||
* 在数据工单里建好、人确认后已入库的,撤不掉」)。案例 2(类型不一致·硬拦)第一次
|
||||
* 提交时 LM2596S-5.0 还在 type_id=1、随首次确认落地;人接着把它改挂到 type_2 后,
|
||||
* 这一笔重投只剩 type_2 一行,`prune:true` 想把已经落地的 type_1 那行撤掉 —— 直接炸。
|
||||
* 去掉 `prune` 就不会再触发这条撤行:陈旧的选型历史留在草稿层不是错误(字典行本就
|
||||
* 该积累、不该因为这一单后来改了主意就被追溯撤销),`parts.profile`/`parts.parameter`
|
||||
* 那几笔**仍然** `prune:true`(它们是这一单自己的骨架,换料/换类型确实要清旧行,
|
||||
* 且它们不会像字典这样跨料共享、也不会被单独确认入库)。
|
||||
*/
|
||||
from: {
|
||||
each: 'mine:sub_eval.part',
|
||||
then: { each: 'parts.chip_type', where: { type_id: { outer: 'chip_type_id' } } },
|
||||
},
|
||||
fields: {
|
||||
type_id: { col: 'type_id' },
|
||||
type_name: { col: 'type_name' },
|
||||
description: { col: 'description' },
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ③ **选中类型的核心参数字段清单,同一套道理原样投一遍**(2026-08-30,与 ② 同一次
|
||||
* 发现、同一个技法):`parts.type_param` 只有人在件上「新建类型时加字段」才会写它 ——
|
||||
* 挑一个已有类型时,这张表天然一行都没有,而它此刻仍在 ⑥ 的 `gateLedgers` 上,
|
||||
* 「零行 = 没干完」的闸对这条路一样是误判(2026-08-30 真跑 e2e 五案例实测坐实:
|
||||
* 「归到已有类型」的候选缺库 / 类型不一致 / 原PN缺库 / 两颗都缺库四例,全部卡死在
|
||||
* 同一条「芯片类型核心参数字段 这一单还一行都没有」)。
|
||||
*
|
||||
* 修法与 ② 完全同构:按当下选中的 `chip_type_id` 把那个类型的核心参数字段清单
|
||||
* 整套原样抄回自己身上(值没变,幂等,不产生多余写入)——**新建的类型也走这条路**:
|
||||
* 人在件上新增的字段行本就已经在 `mine:parts.type_param` 里,这一笔重新投一遍
|
||||
* 原样命中、照样幂等跳过,不会覆盖人刚填的内容。`prune:true` 清掉的只是「先前选了
|
||||
* 某个类型、后来改选了另一个」时那些不再相关的陈旧行。
|
||||
*/
|
||||
{
|
||||
kind: 'project' as const,
|
||||
into: 'parts.type_param',
|
||||
/* 同上一笔同一个理由,同一次修正 —— 见 ② 那笔头上的大注释,不重复贴 */
|
||||
from: {
|
||||
each: 'mine:sub_eval.part',
|
||||
then: { each: 'parts.type_param', where: { type_id: { outer: 'chip_type_id' } } },
|
||||
},
|
||||
fields: {
|
||||
type_id: { col: 'type_id' },
|
||||
param_name: { col: 'param_name' },
|
||||
param_desc: { col: 'param_desc' },
|
||||
constraint_type: { col: 'constraint_type' },
|
||||
param_unit: { col: 'param_unit' },
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ④ 主数据骨架:**两颗料都铺、值回填现值**。
|
||||
* 在库那颗回填出它此刻的样子(人不必重填);要建那颗全空等人填。
|
||||
* **「在库的老料还没归类型」于是也被拦住** —— 那一格回填出来是空的,而 `chip_type_id` 必填。
|
||||
* 人不必在件上「加一行」:要建哪颗料是启动参数就定死的事,行由系统摆好,他只填格子。
|
||||
@@ -300,7 +375,7 @@ export const ASSESSMENT_STEPS = [
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ③ 参数骨架 —— 三件事一笔办完:
|
||||
* ⑤ 参数骨架 —— 三件事一笔办完:
|
||||
* · 人还没挑类型 → `{outer:'chip_type_id'}` 是空串,`where` 一行不命中,**什么也不铺**;
|
||||
* 他挑了、按了保存,行就出来 —— **「保存即铺格」不是特判,是这一笔的自然结果**;
|
||||
* · 在库那颗料**也**铺,值回填现值 → 屏上是它此刻的样子,`_missing` 不会误报;
|
||||
@@ -339,15 +414,15 @@ export const ASSESSMENT_STEPS = [
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ④ 案卷一行:`gate_reason` 是**摆在件上给人看**的那句话(条件判断另用基座读数,见 ⑤)。
|
||||
* ⑥ 案卷一行:`gate_reason` 是**摆在件上给人看**的那句话(条件判断另用基座读数,见 ⑦)。
|
||||
*
|
||||
* **排在 ②③ 之后 —— 这是承重的次序,不是随手写的**(2026-08-29 评审 Important 修正):
|
||||
* **排在 ④⑤ 之后 —— 这是承重的次序,不是随手写的**(2026-08-29 评审 Important 修正):
|
||||
* `gate_reason` 的 when 链最深一层要读 `parts_profile_missing + parts_parameter_missing`,
|
||||
* 而这两个数是 ②③ 刚铺出来的骨架撑出来的基座读数。这一笔若排在 ②③ **之前**
|
||||
* 而这两个数是 ④⑤ 刚铺出来的骨架撑出来的基座读数。这一笔若排在 ④⑤ **之前**
|
||||
* (原来的次序),它读到的是这一拍**刷新之前**的旧数:当某颗料的 `chip_type_id`
|
||||
* 刚在这一拍被 ① 设上、②③ 即将把它的主数据与参数骨架铺出来时,那一刻 ③ 即将新建的
|
||||
* 刚在这一拍被 ① 设上、④⑤ 即将把它的主数据与参数骨架铺出来时,那一刻 ⑤ 即将新建的
|
||||
* 参数骨架行还不存在,`parts_parameter_missing` 读到 0(不存在的行不算「缺」),
|
||||
* 片刻之后 ③ 一跑就变成 1。收步与发卡的把关本身没坏(⑤ 排在 ②③④ 之后,`card.when`
|
||||
* 片刻之后 ⑤ 一跑就变成 1。收步与发卡的把关本身没坏(⑥ 排在 ④⑤ 之后,`card.when`
|
||||
* 用的是刷新之后的真实读数,卡照样正确发出),但案卷里存的这句话、连同卡面
|
||||
* `basis` 里「还差什么」那一格,会是一句空话 —— 人最需要一句解释的时候,读到的是空的。
|
||||
* **每一笔之前刷读数**(auto-runner 的硬规矩)保证的是「下一笔看得见上一笔的结果」,
|
||||
@@ -385,7 +460,7 @@ export const ASSESSMENT_STEPS = [
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ⑤ 缺什么就派一张数据工单。
|
||||
* ⑦ 缺什么就派一张数据工单。
|
||||
* **条件只用基座读数**:`gate_reason` 为空串时那一列压根不写、于是未绑定,
|
||||
* 而 S-FEEL 里未绑定的名字 `!= ""` 求值为 **true** —— 拿它当条件,恰恰在一切正常时把卡发出去。
|
||||
*/
|
||||
@@ -411,19 +486,22 @@ export const ASSESSMENT_STEPS = [
|
||||
],
|
||||
standard: '红带消失、提交钮变绿:两颗料都在库、归了同一个类型、每个核心参数字段都有值。',
|
||||
/*
|
||||
* **`parts.chip_type` 永久不列在这里** —— 不是等 Task 7b 补了「新建类型」那张编辑表
|
||||
* 就该加回来(2026-08-29 评审逮到的 Critical:这张件此刻没有任何 `data-edit`
|
||||
* 能写 `parts.chip_type`,列了它,`mine:parts.chip_type` 永远是空数组,
|
||||
* `desk.ts` 的 `emptyGate` 就永远判「这一单还一行都没有 —— 这张卡就是来建它的」,
|
||||
* 提交钮当场且永久按不下去,三张表填得再全也一样)。
|
||||
* 更深一层:即便 Task 7b 真的补上了那张编辑表,也不该把它放回来 —— `emptyGate`
|
||||
* 的语义是「这张卡**就是来建**这张表的,一行都没有 = 没干活」,而
|
||||
* `parts.chip_type` 的主路是**从下拉挑一个已有类型**,那时 `mine:parts.chip_type`
|
||||
* 天然就是零行(没新建、没问题);只有走「新建类型」那条岔路时才会写它。
|
||||
* 一张**只是有时候才写**的表,套「零行 = 没干完」这条闸的前提本身就不成立 ——
|
||||
* 该拦的是 `parts.type_param`(核心参数字段清单,新建类型时**一定**要写)。
|
||||
* **`parts.chip_type` 与 `parts.type_param` 都在闸上**(2026-08-30 改,Ruling H35)——
|
||||
* 从前 `parts.chip_type` 被永久排除在外,理由是「它没有编辑手段,`mine:` 天然为空,
|
||||
* 列了就永久卡死」;那条理由**从 ②③ 两笔自我投影加上之后就不再成立**——
|
||||
* `mine:parts.chip_type`/`mine:parts.type_param` 现在恒不为空(只要 `sub_eval.part`
|
||||
* 里已经选定了类型),闸不会再误判「一行都没有」。
|
||||
*
|
||||
* **既然要落地就必须在名单里,这不是碰运气**:「数据工单确认即入库」把 `gateLedgers`
|
||||
* 列出的表整批并入正式表;`parts.chip_type` 若不在这份名单里,人新建的类型字典行
|
||||
* 就只留在草稿层,而引用它的 `parts.profile`/`parts.type_param` 却随名单一起落地 ——
|
||||
* 落地那一刻起,正式表里就有一行指着字典里查无此号的悬空引用(2026-08-30 真跑
|
||||
* e2e 撞见的平台侧连带发现:范围放行没有对 `refersTo` 做传递闭包,并一张表却不
|
||||
* 并它引用的表,必然留下悬空引用;那条更普遍的平台缺陷已记 `docs/debt.md`,
|
||||
* 这里只在活动自己的声明层堵住这一处具体触发点)。四张表都在闸上、也都会一起
|
||||
* 落地,才是「选类型」这件事完整、闭环的样子。
|
||||
*/
|
||||
gateLedgers: ['parts.type_param', 'parts.profile', 'parts.parameter'],
|
||||
gateLedgers: ['parts.chip_type', 'parts.type_param', 'parts.profile', 'parts.parameter'],
|
||||
},
|
||||
{ kind: 'say' as const, text: '已核对两颗料的在库状态与参数齐备情况。' },
|
||||
],
|
||||
@@ -507,7 +585,27 @@ export const ASSESSMENT_STEPS = [
|
||||
},
|
||||
},
|
||||
/*
|
||||
* ② 逐条明细 —— **行由字典 `each` 出来**,AI 的出参只是拿 `param_name` 去 `lookup` 的一张查表。
|
||||
* ②→④ **`RELATION_PROJECTION` 写两遍,中间夹着差异明细** —— 不是笔误
|
||||
* (2026-08-30,真跑 e2e 第一次撞见的循环依赖,`docs/ACT-WORK-05-端到端测试记录.md`
|
||||
* 追加记录):`substitution.difference_item.substitution_relation_id` 声明了
|
||||
* `refersTo: 'substitution.relation.substitution_relation_id'`(要求引用的那一行
|
||||
* 已经真实存在),而 `substitution.relation` 的 `recommendation_level`/`risk_level`/
|
||||
* `difference_summary`/`recommendation_reason` 这几列**要读差异明细的
|
||||
* `judgement_class` 分布才算得出来** —— 两张表互相要对方先写。
|
||||
* 单趟写不出这个顺序:关系表写在明细表前面,`refersTo` 就拦住明细表(「要指的那一行
|
||||
* 还不存在」,亲测的报错原文);写在后面,关系表自己先炸(明细表还没有,`refersTo`
|
||||
* 挡在关系表自己那两列 `original_internal_pn`/`candidate_internal_pn` 上倒是不会,
|
||||
* 但那几个必填的计算列无从算起)。
|
||||
* **第一遍**(紧跟在 `analyze` 后面)只为把关系表那一行**先摆出来**满足 `refersTo`——
|
||||
* 这一刻明细表还没有一行,所有 `substitution_difference_item_judgement_class_*`
|
||||
* 读数都是基座铺的 0,`when` 链逐级落到默认分支(`recommendation_level` 落
|
||||
* 「recommended」),字段值不对但每一列都写得出、不会报错。**第二遍**(紧跟在明细表
|
||||
* 后面)用这一刻已经真实存在的明细行重算一遍,把第一遍的占位值覆盖成真值 ——
|
||||
* 与 S3 每次人改了四态就要重算关系表,是**同一套幂等重算机制**,不是新发明的招式。
|
||||
*/
|
||||
RELATION_PROJECTION,
|
||||
/*
|
||||
* 逐条明细 —— **行由字典 `each` 出来**,AI 的出参只是拿 `param_name` 去 `lookup` 的一张查表。
|
||||
* 它漏一条,那一行的四态就是空 → `required` 立刻变成 `_missing > 0`,提交闸与放行门当场拦住。
|
||||
* 从前是 `from each(AI 出参)`:它少给两条就少两行,**而每一行看着都合规**。
|
||||
*/
|
||||
|
||||
@@ -54,7 +54,7 @@ export const manifest: ActivityManifest = {
|
||||
activityId: ACTIVITY_ID,
|
||||
name: 'ACT-WORK-05 · 差异评估',
|
||||
/* 重写不是改 —— 同版重装是 no-op(静默丢失,踩过) */
|
||||
packageVersion: '2.1.1',
|
||||
packageVersion: '2.1.5',
|
||||
category: '需求分析与方案匹配',
|
||||
sdkVersion: '0.1.0-m5',
|
||||
checksum: 'dev-checksum-part-substitution-assessment',
|
||||
@@ -179,8 +179,39 @@ export const manifest: ActivityManifest = {
|
||||
level: 'L2',
|
||||
predicate: 'validation_record_rows = 1 and validation_record_missing = 0',
|
||||
},
|
||||
{ key: 'evidence-credible', label: '证据链可信:每条差异的影响说明与判断依据站得住', level: 'L4' },
|
||||
{ key: 'values-traceable', label: '参数值与原厂资料对得上:候选与原 PN 的取值来源可追溯', level: 'L4' },
|
||||
/*
|
||||
* **两条 L4 补了谓词**(2026-08-30,真跑 e2e 撞见的结构性发现,记入
|
||||
* `docs/ACT-WORK-05-端到端测试记录.md`)——「裁量项带谓词时谓词说了算」这条纪律
|
||||
* (2026-08-02 立,`engines.ts` 的 `criterionCheck`:`settledByData`)本仓已有先例,
|
||||
* 从前 W05 这两条 L4 是裸的(无 `predicate`),而 T_release 全程 `auto` 驱动、
|
||||
* `submit` 是五个声明式动词之一(`packages/contracts/src/auto-recipe.ts`),
|
||||
* **没有第六个 `judge` 动词** —— 裁量项没谓词就只能等 `judgement`(Agent 的 `judge`
|
||||
* 行动)来结算,而这个活动压根不会有 Agent 走到这一步。结果是 `submittable()`
|
||||
* 对这两条永远读到 `pass:false`,`T_release` 的 `submit` 每一拍都被
|
||||
* `raiseAcceptance` 拒,而拒绝不算错误(不触发 `failedAt`)、下一拍原样重来 ——
|
||||
* 稳定地把步数预算耗到 `exhausted`,而不是报错崩溃,所以静态审计与包内自测都没照见它。
|
||||
*
|
||||
* 两条谓词只覆盖**客观、基座算得出来的那一半**(有没有值、值哪来的),
|
||||
* **主观那一半(写得好不好、可信不可信)已经由人工验收清单接住**——S3 审核卡
|
||||
* 第 1/4 条「逐条对过原料与候选料的参数值…」「影响说明站得住…」正是这两条 L4
|
||||
* 标签里「站得住」「对得上」那两个字的着落点,人签字通过那一刻已经确认过。
|
||||
* 谓词为真只说「客观前提具备」,不越权替人下「写得好」这个主观判断。
|
||||
*/
|
||||
{
|
||||
key: 'evidence-credible',
|
||||
label: '证据链可信:每条差异的影响说明与判断依据站得住',
|
||||
level: 'L4',
|
||||
/* 客观前提:每一行都有值(必填不缺)——写得「站不站得住」是 S3 人工验收清单的事 */
|
||||
predicate: 'substitution_difference_item_missing = 0',
|
||||
},
|
||||
{
|
||||
key: 'values-traceable',
|
||||
label: '参数值与原厂资料对得上:候选与原 PN 的取值来源可追溯',
|
||||
level: 'L4',
|
||||
/* 客观前提:原始参数集不缺、差异明细里逐条的原值候选值也不缺 —— 两值均来自
|
||||
parts.parameter 的 lookup,不是模型现编;「对不对得上」是 S3 人工验收清单的事 */
|
||||
predicate: 'parts_parameter_missing = 0 and substitution_difference_item_missing = 0',
|
||||
},
|
||||
/* L5 是责任关:那张卡本身就是这道关,卡面上不摆它(基座统一滤) */
|
||||
{ key: 'gate03-release', label: 'ACT-GATE-03 评审放行:签下去 P-11 落账、P-06 落账,结论供下游消费', level: 'L5' },
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user