Archived
fix(w05): 终审两条 Critical + 地基缺陷的另一半 + 文档与欠账收口
**C1 新建芯片类型会改写既有字典行(共享主数据被静默污染)**
链条逐行复核成立:件上种子行漏挂 data-col → barred 与 isSeq 双保护同时失效
(isSeq 那条是因为 Task 7b 把 type_id 从 integer 改成了 text)→ 落进「全表一致」,
而 H35 的自投影恰好保证这张表只有一行 → 人新建的类型被认成既有 type_id=1 →
确认时随范围放行把既有字典行改名并落进正式表,屏上只说「改了 1 行」。
三条各自成立的裁定叠出来的静默污染。
修法:种子行改成 disabled 的真实 <input data-col=type_id>(desk.ts 零改动),
格子里摆「由系统生成」——不能一声不吭地拿掉,那样人会以为这张档少了一格。
测试不止断言结果:另加两条用**真实 resolveRowKey** 重放病灶/修法两种 cells 形状,
直接证明因果机制。平台根治(全表一致应排除 auto 列)记 debt。
**C2 两个启动参数都选填 → 留空则整单永久无解**
gate_reason 说「在下面把两格补上」,而块① 没有加一行、也不在 gateLedgers ——
一句指向空气的指路。改为必填;第一档改口为唯一真无解的那条路径(两格填了同一个号)。
**I1 地基缺陷只修了一半:recipe.let 同样按对象键序求值**
与 fields 是一模一样的假设,同样住 jsonb 列、同样被 PG 重排;今天不炸只因 W05 那五条
互不引用,而 ADR-014 是给后续所有活动用的。新增 orderLets/letReferences
(把 {in:X} 视为依赖 —— 语义与 fields 不同,故单独实现),auto-runner 与静态审同步接入。
**I2 审核卡按「重做」照样收步推进** —— 坐实为真实平台缺陷(「卡答过了就收步」快道不看裁决),
并证明活动侧加选项救不了、chosenGuidance 那条也救不了(那是喂模型的提示词,
而这条快道存在的目的就是跳过模型)。按裁定只留复现测试,平台未改。
**I4** 同一堵墙撞第二次即发兜底执行工单交人 + 立监听(头一次软化留痕),
不再空转到 exhausted 而人无处可改。
debt +4(C1 根治 / modules→app / thead-after-tbody / data-by 落回外层),
并补写此前被两处引用却从不存在的「refersTo 传递闭包」条目。
文档:ADR-014:201「按声明顺序求值」改对(照它写配方的人会重犯本轮的地基缺陷)、
陈旧注释四处、spec 四处分叉逐条判断处置、端到端测试记录标为历史、
CLAUDE.md 纠正「新建类型端到端通了」那句(它没通过真人路)并更新版本与文档表。
包版本 2.1.9(已装,SQL 直查库内 version/required/gate_reason/视图 HTML 四项核实)。
pnpm gates 退出码 0(2476 通过 / 34 跳过);E04 92/92、I01 55/55 无回归。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -1,5 +1,24 @@
|
||||
# ACT-WORK-05 差异评估 —— 端到端测试记录
|
||||
|
||||
> **历史记录,已停止更新(2026-08-30 标注)**:这份文档记的是**旧九步案例模型**
|
||||
> (`S0_bind` / `S0d_p08_master` / `S0d_p10_document` / `S1_input_confirm` /
|
||||
> `S2_normalize` / `S3_assess` / `S4_review` / `S5_prepare_landing` / `T_release`)
|
||||
> 从零到 released 的走查账,时间跨度到 2026-08-27 为止。**2026-08-29 的整体重写**
|
||||
> (Task 3,`docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md` §4)
|
||||
> 把九步砍成了四步(`S1_inputs` / `S2_assess` / `S3_review` / `T_release`,行为改由
|
||||
> `PlanStep.auto` 声明式配方接管,ADR-014)——**下面九个计划项的表、逐轮走查、A–Z
|
||||
> 发现表,描述的都是这个已经不存在的旧结构**,不代表当前 `case-model.ts` 的真实形状。
|
||||
> 保留只为存档:六轮浏览器走查与脚本 e2e 逼出的 26 条发现(A–Z)里,相当一部分
|
||||
> (措辞纪律、`refersTo`/`requiredWhen` 这类结构治法、「机检只读基座数」的又一次违反)
|
||||
> **后来被折进了四步重写的设计与 `docs/debt.md`**,不是白跑的,只是不该再被当成
|
||||
> 「这个活动现在长什么样」的说明书来读。**四步重写之后的发现不再追加进这份文档**——
|
||||
> 散落在源码注释(`case-model.ts`/`manifest.ts` 的大注释)、`docs/debt.md`、以及
|
||||
> `.superpowers/sdd/2026-08-29-act-work-05-rewrite/` 下的过程记录里。
|
||||
>
|
||||
> 以下原文未删改。
|
||||
|
||||
---
|
||||
|
||||
> 这份文档记的是**每一轮实跑逼出了什么、改了什么、怎么验证的**。
|
||||
> 活动包:`activities/part-substitution-assessment`;业务出处 `docs/SS02-ACT-WORK-05-html/`。
|
||||
> 台账字段口径见 `docs/数据域字段说明-W05.md`。
|
||||
|
||||
@@ -198,7 +198,12 @@ type AutoExpr =
|
||||
| { when: string; then: AutoExpr; else: AutoExpr }; // S-FEEL 条件(可嵌套)
|
||||
```
|
||||
|
||||
**字段按声明顺序求值,已算出的字段进同一行的 S-FEEL 作用域** —— 「先 lookup 出依据,再按依据判 found/absent」
|
||||
**字段按依赖拓扑序求值,不按声明/存储顺序**(2026-08-30 全分支终审 Important 1 改写此段 ——
|
||||
`fields` / `let` 存进 `sys.activity_package.manifest` 这个 jsonb 列之后,对象键的原始书写顺序
|
||||
不再保证,PG 会按「先短后长、同长字典序」重排;`orderFields`/`orderLets`
|
||||
按字段名之间的真实依赖排出一个安全序,真正的环在装包静态审那一关就被拒装,不留到运行时
|
||||
静默算错。见 `apps/api/src/modules/loop-engine/internal/domain/auto-order.ts`),
|
||||
已算出的字段进同一行的 S-FEEL 作用域 —— 「先 lookup 出依据,再按依据判 found/absent」
|
||||
这种写法靠它成立。
|
||||
|
||||
### 4.1 例:S0d_p10_document(整步确定,最小的那个)
|
||||
|
||||
+171
-3
@@ -1277,6 +1277,101 @@ pnpm 硬链到全局 store,真实占盘远小于这个数,但 `du` 看着吓人
|
||||
但也有活动是让人在件上加第一行的(那时零行合法)—— 得由活动声明区分。
|
||||
**现状**:活动侧把「发卡前清单必须先有行」写进说明书(每拍都在),配测试。
|
||||
|
||||
## `modules → app` 是全仓第一条这个方向的 import(2026-08-30,Ruling H9,Task 4/7b 装 `@app/*` 别名时记的)
|
||||
|
||||
`apps/api/src/modules/loop-engine/__tests__/w05-gate-reason-order.test.ts` 与
|
||||
`w05-s3-review-recompute.test.ts` 两份测试用新增的路径别名 `@app/*`(`tsconfig.base.json` +
|
||||
`vitest.config.ts` 各加一行,与既有 `@modules/*`/`@shared/*`/`@features/*`/`@web-shared/*`
|
||||
同一种写法)直接 import `apps/api/src/app/wiring/engines.ts` 的具名导出(`rowReadings`/
|
||||
`missingReadings`/`tallyReadings`/`distinctReadings`)——按本仓的模块层级(`docs/编码规范.md`:
|
||||
`apps/api/src/app/` 是「唯一组装层」,`modules/*` 是被组装的那一层,依赖方向只许
|
||||
`app → modules`),这是**全仓第一条 `modules → app`** 的 import,且目前只出现在测试文件里
|
||||
(production 代码里一条都没有)。
|
||||
|
||||
**为什么会有它**:这两份测试要把真 `createAutoRunner` 跑起来验证「引擎逐 action 刷新
|
||||
时机」这类只有真求值才测得出的回归;读数层四个函数(`missingReadings` 等)只在
|
||||
`engines.ts` 里有实现,第一版手抄了一份副本,**同一笔提交里就漂了**(`missingReadings`
|
||||
拼缺项名的真身多带一句 `label ?? `,手抄件漏掉)——「手抄会漂」这个仓已经踩过三次
|
||||
(读数函数 / `let` 镜像 / 选择器复刻),这次选择 import 真身而不是再抄一遍。
|
||||
|
||||
**要治**(方向,未裁定,是全仓架构决定不是单个活动包能裁的):
|
||||
- `.dependency-cruiser.cjs` 现在只有 `packages-are-leaves`(禁 `packages → apps`)管这类
|
||||
方向,`modules → app` 完全不受约束——该不该补一条规则(禁 `modules → app`,但要给
|
||||
测试文件开口子,否则这条测试会被打回手抄、重蹈「手抄会漂」的覆辙),需要专门讨论。
|
||||
- 或者认为「测试文件例外」本就是可接受的常态(`packages/ui-kit/__tests__/
|
||||
view-container-collect.test.ts` 已有 `apps → packages` 方向的先例),把这条也当先例
|
||||
记下就够,不必新增规则。
|
||||
|
||||
**排查法**:`grep -rn "from '@app/" apps/api/src/modules` 排除 `__tests__` 目录,
|
||||
确认这个方向至今只活在测试里。
|
||||
|
||||
## `<thead>` 排在 `<tbody>` 之后:违反 HTML5 表格内容模型、读屏器列头关联失效(2026-08-30,Ruling H31)
|
||||
|
||||
`activities/part-substitution-assessment/src/views/report.ts` 的 `CHIP_TYPE_TABLE` /
|
||||
`TYPE_PARAM_TABLE`(Task 7b 修「新建行按钮克隆到表头」那次 Critical 回归)把 `<thead>`
|
||||
写在两个 `<tbody>` 之后——**挪位本身是那次修复的正确解法**(`data-edit` 两处写法让
|
||||
`ViewContainer.bodyOf()` 解析到外层 `<table>`,`addRow()` 在这个 host 里找
|
||||
`tr:last-of-type`;`<thead>` 若排在数据行前面会被优先命中、克隆出表头文字而不是数据格),
|
||||
但这个修法本身留了一笔新账:
|
||||
|
||||
- **违反 HTML5 的 table 内容模型**:标准写法是 `<thead>` 排最前(`<tfoot>` 可以排在
|
||||
`<thead>` 之后、`<tbody>` 之前,但 `<thead>` 本身不许排在 `<tbody>` 之后)。
|
||||
- **会让读屏器的自动列头关联失效**:无障碍算法按**文档序**从数据格向上扫、找同列的
|
||||
`<th>` 来读出「这一格是哪一列」,`<thead>` 排在最后时这条自动关联找不到表头。
|
||||
|
||||
呈现不受影响(CSS `table-header-group` 让 `<thead>` **视觉上**永远排最前、不看它在
|
||||
DOM 里排第几,`querySelector`/`:last-of-type` 认的却是文档序——呈现与选择器认的不是
|
||||
同一回事),走查看不出来,只有读屏器 / 标记校验器能测出来。复审当时判断:本仓从未
|
||||
提出过可访问性(a11y)要求,故非违反既有标准、是此前不存在的隐性缺口;真要治得动
|
||||
`bodyOf`/`addRow` 或重构成单个 `<tbody>`,都超出了那次修复「零平台改动」的前提。
|
||||
|
||||
**要治**(方向,未验证,留给认真做无障碍的时候一起评估):
|
||||
- 平台侧治本:`bodyOf`/`addRow` 改成不依赖 `<thead>`/`<tbody>` 的相对顺序(直接按
|
||||
`data-rows` 标记的那个 `<tbody>` 取,不用 `tr:last-of-type` 在整个 `<table>` 范围里找),
|
||||
`<thead>` 就能挪回最前面,两全。
|
||||
- 或者把两个 `<tbody>`(动态行 + 静态种子行)合并成一个,`<thead>` 天然留在最前——但
|
||||
种子行「不受 `data-rows` 管辖、行数再少也在」这条设计意图需要另一种表达方式。
|
||||
|
||||
**影响面**:目前只有这两张表这么写(其余活动的可编辑表 `<thead>` 都在最前,因为它们
|
||||
没有「零行也要有种子行」这个需求、`data-edit` 也没有写两遍)。
|
||||
|
||||
**排查法**:`grep -n '<thead>' activities/*/src/views/*.ts`,看它是不是排在最后一个
|
||||
`</tbody>` 之后——目前全仓只有这两张表命中。
|
||||
|
||||
## `data-by` 嵌套查表:父键在本行里缺席时会悄悄落回外层同名字段(2026-08-30,终审 I3,浏览器真人走查缺陷①逮到)
|
||||
|
||||
**现象**(ACT-WORK-05 task-12,浏览器走查):数据工单件上,候选料那一行的「芯片类型」
|
||||
只读态**显示着一个值**,而红带同时说它「缺芯片类型」——编辑态与直接查库都核实过
|
||||
那一格确实是空的,屏上两处自相矛盾。
|
||||
|
||||
**病根**:`apps/api/src/modules/interaction/internal/domain/view-render.ts` 的
|
||||
`renderRows`(:191)递归处理嵌套 `data-rows` 时用 `{ ...scope, ...one }` 合并「外层
|
||||
作用域」与「本行」——`one`(本行)**没有**的字段会悄悄落回 `scope`(外层)里同名的
|
||||
那一个。这个「悄悄落回」平时无害(未填的草稿行缺一个键,落回外层同名字段多半也是
|
||||
空的),但当外层作用域**恰好**有一个同名、非空的字段时,`data-by` 嵌套查表
|
||||
(`byParent`,:201)会拿这个不该被拿到的值去查表,查出一个不属于这一行的结果。
|
||||
|
||||
ACT-WORK-05 恰好撞上这个「恰好」:`engines.ts` 的 `taskData.view()` 会把「各对象最新
|
||||
一行的字段名」铺到顶层(`task.data`),而 `chip_type_id` 这个名字同时是 `sub_eval.part`
|
||||
/ `sub_eval.case` / `parts.profile` 三张表的列名——`case-model.ts:134` 那条注释早就为
|
||||
同一个撞名问题给 S-FEEL 求值加过护栏(「这个名字在三张表上都有,顶层同名以后写的赢,
|
||||
改用对准一张表的 `lookup`,不歧义」),但那条护栏只保护了 `case-model.ts` 自己的配方
|
||||
求值,没有覆盖到 `view-render.ts` 这一侧的 `data-by` 嵌套查表——同一类撞名在这里
|
||||
原样重演。
|
||||
|
||||
**已钉进套件、未修**(`apps/api/src/modules/interaction/__tests__/view-render.test.ts:460`
|
||||
的 `it.fails`,已用最小复现核对过不是测试自己写错):这是共享面(E04/I01 也用
|
||||
`data-by`),改动需要先报给设计者裁定修法,当时只坐实、不改 `view-render.ts`。
|
||||
|
||||
**要治**(方向,未验证):`renderRows` 合并作用域时,`one` 里**没有**的键不该落回
|
||||
`scope` 同名字段——要么显式置 `undefined`(阻断落回),要么 `byParent` 取父键值时
|
||||
只认 `one`(本行)自己有没有这个键,不看合并后的结果。两条都要过现有测试(「父值
|
||||
缺席一行不留」那条已经钉死了「本行显式给出空字符串」这个不同的场景,不能被误伤)。
|
||||
|
||||
**排查法**:嵌套 `data-by` 查出来的值与红带/编辑态对不上,先怀疑这条——尤其当查表用
|
||||
的那个列名在**多张表上撞名**、且当前这一行**没有**这个字段(不是空字符串,是键都不
|
||||
存在)时。
|
||||
|
||||
## 运营中心 · 活动模块接口(2026-08-27 落地时记下的)
|
||||
|
||||
- **实例清单的过滤 / 分页在内存里做**:`listInstances` 全量取该活动的 `sys.task`,非终态行逐单读记忆与
|
||||
@@ -1500,6 +1595,79 @@ declared type 是不是 `integer`/`number`(`isSeq`),**不看这一列是否 `aut
|
||||
`type:'integer'` 且没有其它非 auto 主键列(单主键、无前缀的场景下,`resolveRowKey` 续出的
|
||||
号从 `1` 起,最容易撞上种子数据里最早的几行)。
|
||||
|
||||
## `resolveRowKey` 的「全表一致」没有排除 `auto` 列 —— 第二形(2026-08-30,全分支终审 Critical 1)
|
||||
|
||||
上一条记的是 `type:'integer'` 那一形(§③ 续号步骤抢跑);把 `type_id` 改成 `text` 绕开它
|
||||
之后,**同一个函数的另一段**(`resolveRowKey` §② 「全表一致」,`apps/api/src/app/routes/
|
||||
desk.ts:144-168`)在**一个更窄的组合**下现出第二形:一张表的主键**唯一**一列就是
|
||||
`auto:true`(没有别的非 auto 主键列陪衬,如 W05 的 `parts.chip_type.type_id`)。
|
||||
|
||||
**病灶链条**(`part-substitution-assessment@2.1.8` 的 `views/report.ts` 曾经这样写):
|
||||
视图把这一列的种子行(件上「新建一行」时用来兜底提交的那一条静态行)干脆不给 `data-col`
|
||||
(想法是「自增列不问人,索性连格子都不摆」)——`ViewContainer.collect()` 只认
|
||||
`input/select/textarea[data-col]`,这一列于是**压根不出现在回传的 `cells` 里**。
|
||||
`resolveRowKey` 的 `barred` 判断认的是 `k in cells`(这一列在回传里露没露过面),**不是
|
||||
「值是否非空」**——漏挂 `data-col` 在它眼里等同「这张表压根没有这一列」,专门为「auto 列
|
||||
件上摆着却空着」设计的 `barred` 保护(:135)因此从未触发,`known` 退回
|
||||
`findKnownRow`(对空 `key` 恒返回 `undefined`),`isSeq('type_id')` 因为声明是 `text`
|
||||
也是假 —— 两条已有保护都不成立,直接落进「全表一致」:表里此刻**恰好一行**(活动侧
|
||||
的自投影机制保证了这一点,是巧合但不是意外)时,`seen.size===1` 把新建的那一行判成
|
||||
「就是那唯一一行」,一次保存就把既有那一行的其余字段**静默覆盖**。
|
||||
|
||||
**这次只在活动侧绕开**(给种子行的这一格换一个真的、`disabled` 的 `<input data-col=
|
||||
"type_id">`,值恒是空字符串——`barred` 就会正确命中,交回 `undefined` 让
|
||||
`mintable`/`autoKeys()` 接手续号),`resolveRowKey` 一行没改,见 `views/report.ts` 里
|
||||
`CHIP_TYPE_TABLE` 头上的大注释与 `apps/api/src/app/__tests__/view-table-edit.test.ts`
|
||||
「Critical 1」那组用真实函数逐字重放的两种 `cells` 形状。
|
||||
|
||||
**要治**(与上一条「§③ 续号加 `if (input.autos?.includes(k)) continue;`」是**同一刀**,
|
||||
建议一起做):`resolveRowKey` 的 §②(全表一致循环,:144-168)在**进入这个 `for` 循环给
|
||||
某一列赋值之前**,先判 `input.autos?.includes(k)` 就直接 `continue`(与 §③ 那条建议
|
||||
补的守卫完全对称)。这样一来,声明了 `auto` 的列会**无条件**被这两段循环跳过 ——
|
||||
不再依赖视图是否记得给它挂一个「看不见但露面」的 `data-col`;§① 那段「件上摆了、人也
|
||||
填了」的正常改行路径不受影响(它在这两段循环**之前**就已经把值定下来,轮不到 §②③)。
|
||||
补了这一刀,`barred` 这个专门的旁路判断其实就可以退休(它存在的唯一理由就是在 §②③
|
||||
没有这条守卫时,给「视图记得开个空位」这种个例开个后门)——但退休 `barred` 是更大的
|
||||
改动,不必和这次的守卫补丁绑在一起做。
|
||||
|
||||
**排查法**:与上一条同一句判词,但**不要求** `type:'integer'`——只要该表的主键里
|
||||
`auto:true` 的那一列**是唯一的主键列**(没有别的非 auto 主键列陪衬,靠不上
|
||||
`findKnownRow` 的「至少一格对得上」),且视图的种子/新增行**没有**给它挂 `data-col`,
|
||||
就该怀疑这条坑,不论声明的类型是 `integer` 还是 `text`。
|
||||
|
||||
## 范围放行(`releaseWrite` 的 `only` 形)没有对 `refersTo` 做传递闭包(2026-08-30,ACT-WORK-05 e2e 撞见,H35 落地时在活动侧绕开)
|
||||
|
||||
**现象**:「数据工单确认即入库」(`apps/api/src/app/data-order-landing.ts`,2026-08-27)
|
||||
把**这张卡的提交闸盯着的那几张表**(`dataOrderLedgers` 算出的 `only` 名单,取自卡绑的
|
||||
`blocks` 或它自报的 `gateLedgers`)并进正式表(`ledger.releaseWrite(taskId,
|
||||
{ only, cause })`)——只并名单上的表,不看这些表**引用**着哪些别的表。
|
||||
|
||||
如果 A 表(如 `parts.profile`)在 `only` 名单里、被并进正式表,而 A 表某一列声明了
|
||||
`refersTo: 'B.x'`(如 `parts.profile.chip_type_id` 指向 `parts.chip_type.type_id`),
|
||||
B 表却**不在**同一份 `only` 名单里——B 表对应的那一行仍留在草稿层,**正式表里就会
|
||||
出现一条引用着「只在草稿层存在、正式表查无此号」的悬空引用**:下一个任务(或同一个
|
||||
任务的下一步)在正式表侧查 B 表,查无此值,`refersTo` 校验拒它,或 `data-by` 嵌套
|
||||
查表显示不出来。
|
||||
|
||||
**这一次没有真的炸出悬空引用**——`part-substitution-assessment` 在活动自己的声明层
|
||||
把 A/B 两张表都放进了同一张卡的 `gateLedgers`(Ruling H35,`case-model.ts` 那一步
|
||||
`card` 定义处的大注释),绕开了这个触发点。但那是**这一个活动的选择**,不是平台
|
||||
保证:下一个活动如果只把「主表」列进某张卡的 `gateLedgers`、忘了把它引用的「字典表」
|
||||
也列进去,同样的悬空引用会在**正式表**里真实出现,不是测试环境才有的问题。
|
||||
|
||||
**要治**(方向,未验证):`releaseWrite` 的 `only` 形在计算真正要并的表集合时,应该
|
||||
对 `only` 名单里每张表声明的 `refersTo` 做一次**传递闭包**——A 引用 B、B 又引用 C,
|
||||
落地范围就该自动扩成 {A, B, C},而不是原样信任调用方给的 `only` 名单已经把引用链
|
||||
配全了。
|
||||
|
||||
**同族**(下一条,紧接在后):都是「数据工单确认即入库」这个 2026-08-27 新加的机制,
|
||||
与**别的**既有机制(这一条是 `refersTo` 的传递闭包,下一条是 `prune`)之间没有对齐
|
||||
接缝造成的。
|
||||
|
||||
**排查法**:数据工单确认之后,别的任务查某张「被引用」的表查无此值(而草稿层里其实
|
||||
有),先看确认那张卡的 `gateLedgers`/绑定 `blocks` 是不是只列了「主表」、漏了它
|
||||
`refersTo` 指向的「字典表」。
|
||||
|
||||
## `prune` 与「数据工单确认即入库」的架构性冲突(2026-08-30,ACT-WORK-05 案例 2 真跑撞见)
|
||||
|
||||
**现象**:数据工单**确认落地之后**再改动会影响行集合的字段(如 W05 的芯片类型),
|
||||
@@ -1525,9 +1693,9 @@ declared type 是不是 `integer`/`number`(`isSeq`),**不看这一列是否 `aut
|
||||
撤掉之后任务视图少一行、`mine:`/`_rows` 这类读数说谎。**合起来对同一批行形成死锁**:
|
||||
`prune` 想撤、平台不让撤。
|
||||
|
||||
与 2026-08-30 已记的另一条(**范围放行没有对 `refersTo` 做传递闭包**,见「ACT-WORK-05 走查
|
||||
逼出来的三条平台欠账」附近的记录)同族——两条都是「数据工单确认即入库」这个 2026-08-27
|
||||
新加的机制与**别的**既有机制(`prune`、`refersTo` 的传递闭包)之间没有对齐接缝造成的。
|
||||
与上一条(**范围放行没有对 `refersTo` 做传递闭包**)同族——两条都是「数据工单确认即
|
||||
入库」这个 2026-08-27 新加的机制与**别的**既有机制(这一条是 `prune`,上一条是
|
||||
`refersTo` 的传递闭包)之间没有对齐接缝造成的。
|
||||
|
||||
**要治**(方向,未验证,留给真正动手时评估):
|
||||
- 已落地行改为「先撤『已落地』记号,再撤行」——即 `prune` 遇到已落地行时先做等价于
|
||||
|
||||
@@ -261,6 +261,17 @@ doneWhen sub_eval_part_exists_status_found = 2
|
||||
- 人点「提交」= 数据工单确认 → **`chip_type` / `type_param` / `profile` / `parameter` 当场进正式表**
|
||||
(平台 2026-08-27 的范围放行)。它们是主数据,该立刻可用,不等整单放行。
|
||||
|
||||
> **2026-08-30 补记**:实现是**八笔**,不是上面写的六笔 ——「提交」那一句已经点出原因:
|
||||
> `chip_type` / `type_param` 要跟着数据工单一起进正式表(范围放行只并 `gateLedgers`
|
||||
> 名单上的表,见 `docs/debt.md`「范围放行没有对 `refersTo` 做传递闭包」),而它们此刻
|
||||
> 只在人新建类型时才有草稿行——挑一个已有类型时天然零行,闸拿「零行」误判成没干完。
|
||||
> Ruling H35(e2e 真跑撞见)在②③之间插了两笔**自投影**(原样把选中的类型字典行 / 字段
|
||||
> 清单行抄回自己身上,值不变、幂等,唯一目的是让 `mine:` 不再天然为空),使这两张表
|
||||
> 能安全回到 `gateLedgers` 上。次序变成:①两颗料 → ②类型字典自投 → ③字段清单自投 →
|
||||
> ④主数据骨架 → ⑤参数骨架 → ⑥案卷 → ⑦卡 → ⑧交代(与上面①③④⑤⑥对应,原④⑤⑥ 顺移成
|
||||
> ⑥⑦⑧)。这两笔**不带 `prune`**(与③节 §11.4 那条改口同一个理由:字典/字段清单是共享
|
||||
> 的,历史选型的陈旧行留着不算错)。详见 `case-model.ts` 该两笔头上的大注释。
|
||||
|
||||
### 4.3 S2 差异分析 —— 纯系统 + AI 唯一一次出场
|
||||
|
||||
```
|
||||
@@ -313,14 +324,22 @@ doneWhen substitution_difference_item_rows > 0
|
||||
|
||||
### 4.4 S3 结论审核 —— 一张验收工单,人就地改
|
||||
|
||||
配方只有一笔:`card` 审核卡(`variant: 'review'`,件 `difference-review`,**单一选项「确认通过」**,
|
||||
带人工验收清单四条)。
|
||||
**2026-08-30 改口**:这一节写下时以为配方只有一笔(`card`),下一段自己就说漏了 ——
|
||||
真正的配方是**五笔**:①差异明细**重算笔**(2026-08-30 task-12「结论纹丝不动」缺陷补的,
|
||||
见下)→ ②③ 复制自 S2 的 `RELATION_PROJECTION`/`VALIDATION_PROJECTION`(同一个对象引用,
|
||||
不是抄了两份)→ ④ `card` 审核卡(`variant: 'review'`,件 `difference-review`,**单一
|
||||
选项「确认通过」**,带人工验收清单四条)→ ⑤ `say`。
|
||||
|
||||
人在件上改四态 / 影响 / 依据 → 保存 → tick → 配方重跑 → S2 的第③笔把推荐等级、摘要、风险
|
||||
**重算一遍**(同 PK 覆盖)→ 件刷新。改几次都行,**不重跑 AI**。
|
||||
人在件上改四态 / 影响 / 依据 → 保存 → tick → 配方重跑 → **①重算笔**先把人改的
|
||||
`difference_status` 转译成 `judgement_class`/`risk_level`(这一步的存在理由:走查发现⑤
|
||||
逮到的真实缺陷——最初的设计只复制了②③,人改的四态没有任何一笔把它转成
|
||||
`judgement_class`,②③读到的 tally 纹丝不动,人在件上改了却什么都没变;`docs/debt.md`
|
||||
已记「S3 存在的全部意义就是让人纠正 AI;纠正流不下去,S3 就是摆设」)→ ②③把推荐等级、
|
||||
摘要、风险**重算一遍**(同 PK 覆盖)→ 件刷新。改几次都行,**不重跑 AI**。
|
||||
|
||||
> 重算发生在 S2 那一步的配方里,而 S2 已收步 —— 所以 S3 的配方里**复制一份 ③④ 两笔**
|
||||
> (纯投影、幂等),不去回退步骤。这是刻意的:一步一份配方,不做跨步回跳。
|
||||
> ②③ 复制的是 S2 那一步配方**同一个对象引用**(纯投影、幂等),不去回退步骤 —— S2 已
|
||||
> 收步,S3 不能跨步回跳,只能在自己的配方里再摆一份同样的投影逻辑。这是刻意的:
|
||||
> 一步一份配方,判定分类/风险等级的 `when` 链只许有一处真源(共用引用而非各写一份)。
|
||||
|
||||
这一步**不给 `doneWhen`**(它触达世界),人点了「确认通过」即收步。
|
||||
|
||||
@@ -499,7 +518,7 @@ tick
|
||||
|
||||
| key | level | 谓词大意 |
|
||||
|---|---|---|
|
||||
| `input-ready` | L1 | 两颗都在库 · 类型一致 · 参数行数 = 字段数×2 · 参数值不缺 |
|
||||
| `input-ready` | L1 | 两颗都在库 · 类型一致 · 主数据与参数值都不缺必填(**不写**「参数行数 = 字段数×2」——2026-08-30 与实现一起改口:那不是基座数得出来的数,§4.2 doneWhen 尾注已经讲过同一条道理,这里当时漏改) |
|
||||
| `every-param-judged` | L2 | `substitution_difference_item_missing = 0` 且四态四档计数之和 = `_rows`(AI 漏一条 → 那行四态为空 → 两边都不成立) |
|
||||
| `level-by-rule` | L2 | 推荐等级与 `judgement_class` 计数一致(硬不符>0 必须是「不推荐」…) |
|
||||
| `unknown-has-disposition` | L2 | `substitution_difference_item_missing = 0` |
|
||||
@@ -574,7 +593,15 @@ tick
|
||||
而判断正是人在 S3 要核的东西。
|
||||
3. **类型字典会被建脏**(同一类型建两遍、字段名不一致)。件上下拉优先选已有、新建是次选;
|
||||
MVP **不做去重合并**,记 debt。
|
||||
4. **`prune` 会撤本单草稿层的行** —— 只在 S1 参数骨架这一笔用,别处不许用。静态审可以只放行这一处。
|
||||
4. **`prune` 会撤本单草稿层的行**(2026-08-30 改口:这一条与实现一起对照过 —— 实际用在**三处**
|
||||
`project`,不是一处:S1 的 `parts.profile`(④ 主数据骨架)/ `parts.parameter`(⑤ 参数骨架)、
|
||||
S2 的 `substitution.difference_item`(②,§4.3)。§4.2/§4.3 的伪码从写下那一刻起就是这三处
|
||||
都带 `prune`,这里当时漏改成了「只一处」。真正的约束不是「只许一处」,是**只许用在
|
||||
「这一笔的数据源就是这张表在本单该有的全部」这一类骨架/明细投影上**——H35 后来新增的
|
||||
`parts.chip_type`/`parts.type_param` 两笔**自投影**(4.2 节之外,原样抄字典行那两笔)
|
||||
就**刻意不带** `prune`:它们写的是共享字典/字段清单,不是这一单独占的骨架,历史选型的
|
||||
陈旧行留着不算错,带 `prune` 反而会在人改选类型后把已经落地的陈旧行撤成死锁——静态审
|
||||
要放行的是「哪一类数据源」,不是「哪一个固定笔数」)。
|
||||
5. **保存一次 = 一拍**。人反复保存十几次会吃步数,预算给到 40。
|
||||
6. **表达力有上限**。上限之外的不要硬塞 —— 退回模型,或者把那一段切成独立的 agent 步。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user