Commit Graph
367 Commits
Author SHA1 Message Date
xcsandClaude Opus 5 9e2f7387b2 fix(loop-engine): 有卡在等人交回时配方写下新行,收拍前重推画面 —— 短路收拍从不推帧
浏览器走查实测(ACT-WORK-05 数据工单):人给候选料选好芯片类型、按保存,S1_inputs 的
配方在 quiet 模式下铺出 7 行新参数骨架、红带同步点名要填这 7 格,而 `runTurn` 的
「叫了人就等人」分支(spec §5.4)写完就直接 `return { status: 'awaiting' }` —— 真正的
「推一帧」逻辑住在同一函数里更靠后的位置(action 循环之后 / `issuedCardId` 分支),
这条早退根本到不了那里。人刷新页面看到的仍是保存前那份服务端快照(件是渲好推来的,
重放不重渲),被红带点名要填的格子屏上根本不存在,走查到此卡死。

修法:quiet 分支里捕获 `autoRunner.run()` 的返回值,若这一拍真写下或撤掉过行
(`wrote>0 || dropped>0`)且当前在办的这一步声明了 `view`,收拍前调用同一个
`showView` 重推 —— 与 `issuedCardId` 分支「声明了画面才推」同一条纪律;quiet 模式
不迁移计划项状态,`runtime` 用的还是拍首那份,不会重犯 2026-08-23 那次「推错哪一步
画面」的错(这次没跨步,只是把同一步最新的草稿重渲一遍)。

回归测试用真 `createTurnRunner`/真 `createAutoRunner`(不手抄收拍逻辑),给共享 harness
加 `pushedScopes` 记录每次推帧的 scope,断言画面里真带着刚写的那一行(内容比对,不是
计数)。变异验证:git stash 掉修复后该测试从 157/157 变 156 passed + 1 failed
(`expected [] to deeply equal ['input-supplement']`),stash pop 后回到 157/157。

`progress.md` H49 记录过一个高度相似的怀疑:e2e 反复撞见的「答完卡不推进」(案例4×3、
案例3×1,advancing_until 全程为空、零报错)——两者终态日志的最后一行逐字相同
(`等人交回:数据工单… —— 这一拍不动`,task_memory 定格在同一条、之后再无记录)。
候选缺库类案例的两阶段表单(选类型→保存→配方铺出新参数格→还要再填)恰好依赖画面
重推来发现第二轮要填什么;这条早退不推帧能完整解释「零报错 + 永久卡死」。未另跑
e2e 坐实(任务明令不跑,浏览器里留着一单要用来验证),记为高置信度推断。

pnpm gates 退出码 0(2448 passed | 34 skipped,基线 2447 + 本次新增 1 条)。
E04(crm-relationship-coverage)92/92、I01(brand-content-toolkit)55/55,均 exit 0。
manifest.ts 里那处与本次无关的未提交版本号改动(2.1.5→2.1.6)未动、未纳入本提交。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 11:49:27 +08:00
xcsandClaude Opus 5 8586f7dac8 docs(w05): 重写收口 —— 头注核正、字段说明按现状重写、规范补四条 auto 纪律、CLAUDE.md 记战果
Task 12 的非走查部分(浏览器走查卡在登录、归人工,不在本次范围)。

① index.ts 头注仍写着旧的「三条技能 + 五张模版」,重写后是 0 技能 3 张件,核正
(Ruling H25);包内其余「三条技能/五张/两枚工具/九步」等提法逐处核过,均为带日期的
「从…改成…」历史叙述,不是失实断言,未动。

② docs/数据域字段说明-W05.md 整篇重写(不是打补丁)。旧文档写的是重写前的十张表
(customer.profile/parts.document/demand.request 与 sub_eval 三张旧单据,本活动这次
重写已不再声明),现按 manifest.ts 的 declarations.ledger 逐表核对重写:9 张表、66 列、
20 人填列。写入者词表另定一套(人(件上)/AI(S2 唯一一次 analyze)/配方(project 算出来
的)/基座)——E04/I01 那套多轮 agent 编排的词表在这里说不清楚。逐个 data-edit 核对过
report.ts 才确认写入者归属:S3 的四态等列改成人在件上直接编辑(不再是「人写意见→AI 改」),
sub_eval.part/case、substitution.relation、validation.record 四张表没有任何人工编辑
入口、全部配方算。顺手核了 seed-mvp-data.ts 的基线物理列,注了每张表里这次重写不再
声明的旧列(旧九步版本留下的痕迹),并记下 type_id 这条身份链路是本仓唯一一处
integer→text 加宽声明。

③ docs/活动开发规范.md §4.6 补四条纪律(六条→十条):jsonb 会重排 fields 键序、字段
求值改按依赖拓扑排序不能拿书写顺序当依赖顺序;跨笔顺序仍要自己排(一笔读的是另一笔
写出来的数就要排后面);两笔方向相反的依赖拆两笔写中间夹子表;gateLedgers 同时管
「闸要求非空」与「确认时并进正式表」两件事,漏想一半会悬空引用或永久卡死。§6.5 补第 9
条「测试要能咬住」(镜像测试与读真文件测试证明的是两件事;变异测试本身可能是空的;
能 import 真东西就别手抄)。§7 补两行排障(reset-runs 前先捞证据;dev 进程可能在跑
几小时前的代码,连 tsup --watch 监视进程本身都可能已死)。八条内容逐条去任务报告核过
原始事故与提交号,没有凭印象转述。

④ CLAUDE.md 现状快照追加一条:四步全 auto、两张类型字典、三张件、退役清单;e2e 三例
released 被拒 0(候选缺库/都在库/原PN缺库,模型调用恰 1 次);类型不一致那例撞上已记
debt.md 的 prune 死锁,两颗都缺库那例卡在「答完卡不推进」、真因未定位如实记录;
judgement_class 7/7 逐行核对通过;jsonb 键序地基缺陷的发现与修法(拓扑排序,6636abf)。
顺手核正文档地图里 W05 那一行的旧字段数(十张/110列→九张/66列,与②同步)。

pnpm gates 退出码 0(2447 passed | 34 skipped),与 Task 11 最后一轮记录一致,无回归。
未碰 manifest.ts 里那处与本任务无关的未提交版本号改动(2.1.5→2.1.6,疑似上一个卡在
登录的浏览器走查会话留下)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 11:22:56 +08:00
xcsandClaude Opus 5 d420284887 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
2026-08-30 07:06:52 +08:00
xcsandClaude Opus 5 6636abfc39 fix(auto): 配方字段按依赖拓扑求值 —— jsonb 会重排对象键,声明顺序靠不住
真跑 e2e 逼出来的地基缺陷。活动清单存在 sys.activity_package.manifest 这个
**jsonb 列**里,而 PG 按「先短后长、同长字典序」重排所有嵌套对象的键。
auto-runner 原先逐字段**按声明顺序**求值、后面的字段靠 ctx.computed 看前面的值 ——
顺序一乱,依赖就断,而且**一声不响**。

W05 实证:judgement_class(15B)被排到它依赖的 difference_status(17B)前面,
求值时后者恒 undefined、when 三级全落空,摔进 constraint_type 兜底档 ——
7 行差异结论全判错,与实际状态无关;而 difference_status 走
lookup:'result:items' 不依赖字段序,所以它是对的。「一个对一个全错」由此而来。
隔离测试复现不出来,因为它直接读 dist/manifest.json,从不经 DB 往返。

修法:字段求值改为**依赖拓扑排序**(Kahn),不用迭代到不动点 ——
后者的数值收敛判据会漏掉「A/B 互相 when 为假、恰好在错误状态下稳定」这种
结构有环却数值收敛的配方;拓扑是结构性判定,不会假阴性。
通用图算法 topoOrder 提进 @utils(契约新增,非破坏);
AutoExpr→依赖名提取因 L4 不能 import L5,在 loop-engine 与 activity-registry
各留一份并互相指路。真正的环由装包静态审当场拒装 —— 不兜底,兜底正是这次出事的形态。

变异验证:git stash 只撤 auto-runner.ts 那一处,两条新测试当场变红,
数值与专查报告的 SQL 转录逐字吻合(使能阈值/match 被误判 hard_mismatch/medium);
stash pop 后 37/37 绿。E04 92/92、I01 55/55 全绿;全仓 2447 测通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 07:05:48 +08:00
xcsandClaude Opus 5 67fa226380 fix(w05-e2e): 登录优先级倒过来(服务账号优先)+ 候选缺库案例改两阶段填表 —— 五案例仍未跑成
Round 2 逼出的两处修:
①`login()`/`DIRECT_BEARER` 从「旁路无条件优先」改成「服务账号三变量全齐才是正路,
JCAI_E2E_BEARER 只在缺席时兜底」——实测踩过:.stable/env 留着一枚已过期的真人 token
时,脚本会悄悄绕开新配好的服务账号,五案例全部倒在起单前的 401(未派发,SQL 核过
0 个 testbed 任务被创建)。
②案例 0(候选缺库·新建类型)随 Task 7b 的 parts.chip_type.type_id 从 integer 改
text/auto 重写成两阶段 customFill:先铸自增 type_id、读回来再引用它写 type_param/
profile/parameter,与真人在浏览器里「先建类型保存再选它」同一个顺序。

装包已核实:part-substitution-assessment@2.1.1,六列 ALTER 到 text 全部成功(种子
数据完好)。跑前清理过一批与本次改动无关的旧模型(九步版,08-27)测试残渣
(XL2596S-5.0 在 parts__profile 里的假数据 + 连带 78 行)。

Step 4(五案例)仍未跑成——login() 修好之后卡在新的一处:e2e 服务账号
service-account-task-dispatcher 只有 workbench 端,没有 design 端(试跑起单
挂在设计端),ends=["workbench"],token 里没有 admin 角色。三轮试跑全部倒在
login() 里,没有一个任务被创建。需要人给这个服务账号加能开出 design 端的
realm 角色。

pnpm gates 退出码 0(2422 passed | 34 skipped)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 01:22:19 +08:00
xcsandClaude Opus 5 832c662fdb fix(w05): Task 7b 评审 fix round 2 —— 6 条 DOM 测试是空的,bodyOf/addRow 加 export 后补真测试
复审用变异逮到:把 ViewContainer.tsx:305 的 tr:last-of-type 换成明摆着错的 tr:first-of-type
重跑 view-render-addrow-thead.test.ts 新增的 6 条,6/6 全绿零反应。根因:那 6 条只 import 了
一份手抄的选择器复刻(findAddRowTarget),与真实实现之间没有任何绑定,ViewContainer.tsx 换掉
选择器它不会跟着变 —— 全仓当时没有任何测试真的执行过 bodyOf/addRow,对这两个函数的未来回归
零防护。

「不能 export/import」这条前提是错的:packages-are-leaves 只禁 packages→apps,没有任何规则
禁 apps→packages;bodyOf/addRow 只是没加 export——加 export 是零行为改动。本轮第三次踩同一条
(Task 4 读数函数、Task 6 let 镜像,现在是选择器):禁令属实,但抄多了,能 import 的都该 import。

①ViewContainer.tsx:165/303 给 bodyOf/addRow 加 export,函数体一字未改。
②新增 packages/ui-kit/__tests__/view-container-addrow.test.ts(5 条),照 view-container-
collect.test.ts 的写法直接 import 真实函数,合成最小 fixture(不碰任何活动 HTML)。写的时候
自己踩过两处:addRow 的 appendChild 把新行接到 <table> 下面不是插回原 tbody/thead(断言要查
内容不能查落点);tr:last-of-type 与 tr:first-of-type 在单行行组里永远等价(补一组两行的
动态 tbody + data-marker 才让两个选择器真正分道扬镳)。变异验证:选择器换错后 1/5 真红
(row-2 变 row-1),复原验绿。

③两处测试的头注写清各守各的:新测试守机制(addRow/bodyOf 面对通用形状的真实行为),原 6 条
守 W05 这两张表此刻的具体 renderView() 输出形状是对的(选择器仍是镜像,但选择器本身的正确性
已交给新测试)——头注同时改正了此前「不能 import 是 depcruise 拦着」的错误说法,真实原因是
ViewContainer.tsx 含 JSX、需要 tsconfig.web.json 的 jsx 选项,apps/api 只受 tsconfig.node.json
管,类型系统这一关过不去。

pnpm gates 退出码 0(2422 passed | 34 skipped);E04/I01 包内测试 92/92、55/55 全绿,零回归。
报告同批刷新了上一轮贴错版本的失败输出证据(用当前最终代码重新跑过变异)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 01:00:58 +08:00
xcsandClaude Opus 5 57870329e6 fix(w05): Task 7b 评审 Critical —— data-edit 挂外层 table 后「加一行」克隆到表头,不是数据行
外层 data-edit 是为了让 collect() 同时收到静态种子行与动态行,副作用是 ViewContainer.bodyOf()
的单个 querySelector 从此解析到 <table> 本身,addRow() 接着在这个 host 里找 tr:last-of-type ——
CSS :last-of-type 按各自父元素判定,<thead> 里孤零零的那个 <tr> 在它自己的父元素里必然「最后」,
若 <thead> 排在数据行前面就会被文档序优先命中:克隆出来的是四个 <th> 表头文字、零个
input[data-col],还被 appendChild 到 <table> 底下、不在任何 <tbody> 里。parts.type_param 那张
更要紧——它的加行功能在这次改动之前本来是好的(data-edit 只挂 tbody 时,querySelectorAll 的
搜索范围本就不含 thead),外层 data-edit 一挂就成了真回归。

修法(不改 ViewContainer.tsx/view-render.ts):把两张表的 <thead> 挪到两个 <tbody> 之后。CSS 的
table-header-group 排版规则视觉上永远排最前、不看它在 DOM 里排第几(与 HTML5 允许 tfoot 写在
thead 之后、tbody 之前同一条原理)——呈现不变,而 querySelector/:last-of-type 认的是文档序,
thead 排最后,选择器就再也碰不到表头行。

补 6 条真 DOM 测试(apps/api/.../view-render-addrow-thead.test.ts):真实 renderView() 输出
在 jsdom 里挂起来,逐行复刻 bodyOf()/addRow() 的真实选择器链(它们没 export,双向 depcruise
规则也拦着直接 import),断言克隆样本落在 tbody 里、含 N 个具名 input[data-col],不是表头。
apps/api 只受 tsconfig.node.json 管(零 DOM lib),packages/ui-kit 又不许反向 import apps/*
(packages-are-leaves)——用 TS 标准的 per-file `/// <reference lib="dom" />` 只给这一份文件
挂 DOM 类型,不改 tsconfig 本身。activity.test.ts 另补一条字符串级结构安全网(thead 确实排在
两个 tbody 之后),把源码与镜像测试的假设锁在一起。变异验证两段输出(拿掉修复→3/6 真红,
含 report.ts 真实源码单独验证一次红/绿→改回来全绿)贴在报告里。

Important 两条:①两个平台坑(resolveRowKey 续号步骤不认 auto、coerceToColumn 削前导零)
记入 docs/debt.md,写清仍未修、只是被本活动的声明层绕开;②manifest.ts/activity.test.ts 里
「台账是 JSONB 不存在物理列类型」这句错的论据删掉——master/record 是真实物理列(bigint),
只有草稿层是 JSONB,安全的真正原因是 schema-merge.ts 的单调加宽 lattice 把 integer→text
判定成合法加宽,widen-column 生成的 ALTER TYPE text USING col::text 沿 PG 转型没有失败路径。

pnpm gates 退出码 0(2417 passed | 34 skipped);E04/I01 包内测试 92/92、55/55 全绿。
packageVersion 2.1.0 → 2.1.1。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 00:23:47 +08:00
xcsandClaude Opus 5 d9bcce9516 feat(w05): Task 7b —— 件① 补「新建芯片类型」,零平台改动
类型下拉里没有合适的,从前只有一句 guide 文字,没有可落笔的地方(parts.chip_type 全无
编辑面;parts.type_param 有个「加一行」但零行时 ViewContainer.addRow 找不到 tr:last-of-type
可克隆,按钮到不了岸)。

查证 Step 1 的坎(autoKeys 跨行自增键联动通不通)后确认:同一次保存请求内没有机制把父行
刚铸出来的自增键传给同批的子行,这条联动本身不需要通 —— 改成两步流程绕开(新建类型先保存,
type_id 铸出来后立刻出现在 data-options 现读的下拉里;定义核心参数字段时按类型名称选,
不必照抄编号)。

深挖过程中揪出两个从未被触发过的平台坑,根因都是 parts.chip_type.type_id 是本仓唯一
「auto:true 又是 integer」的主键:① resolveRowKey 的「③ 续号」步骤只认 isSeq(declared
type)不认 auto,会抢在 writeEditedRows 的 autoKeys() 前面把新行按草稿行数续号,撞上种子
数据 type_id=1/2(把「DC-DC 降压稳压器」悄悄改名);② autoKeys() 补零的字符串('03')经
引用列的 coerceToColumn(_,'integer') 会被 Number() 削掉前导零,refersTo/data-by 的字符串
比对全部失配。两处坑统一改声明(type_id 自身 + 五处引用列共 6 处,integer→text)一次性
绕开,desk.ts/ViewContainer.tsx/view-render.ts 零改动。

实现:parts.chip_type 新增可编辑表(动态行 + 一条不受 data-rows 管辖的静态种子行,
data-edit 两处写法保证 collect()/.on 切换与 renderRows 的 editable 检测都够得着);
parts.type_param 的 type_id 列从裸文本输入换成下拉(选项现读 parts.chip_type);两张表
各配一条静态种子行解决零行加不出第一行的问题。9 条新测试 + 3 条既有断言更新,变异验证
逐条附证据(拿掉种子行 / 拿掉 text 声明 / 拿掉下拉分别验证目标测试变红,过程中发现并修正
一条子串包含式断言测不出「只剩一份」的退化)。packageVersion 2.0.0→2.1.0。

pnpm gates 退出码 0(2410 passed | 34 skipped);E04/I01 包内测试同批全绿(92/92、55/55)—
本任务未改共享面,理论上不需要专门回归,仍按纪律核过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 23:19:55 +08:00
xcsandClaude Opus 5 a408542bed feat(w05): 五条样例与 e2e 套件就位,包已装库并经 SQL 核对 —— 五案例尚未跑
Task 11 的 Step 1-3。装包结果用 SQL 核过库里那一行(不看脚本的话):
part-substitution-assessment@2.0.0 · enabled · steps 恰四步
[S1_inputs, S2_assess, S3_review, T_release] · 零处残留已退役的工具名 ·
declarations.tools=[] · 五条样例名与设计一致。

**Step 4(五案例 e2e)没跑,故本提交不声称任何跑通结果。**
卡在登录:KEYCLOAK_E2E_CLIENT_SECRET 与 KEYCLOAK_PROVISIONING_CLIENT_SECRET
指纹完全相同(已核),e2e 服务账号的 secret 从来没被真正配过,取 token 恒 401。
另一条路 JCAI_E2E_BEARER 解出来是真人的 token 且已过期 —— 试跑绝不派到用户
工作台,政策上不能用,技术上也用不了。

pnpm gates 退出码 0(2407 通过 / 34 跳过)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 22:28:42 +08:00
xcsandClaude Opus 5 882f22a01e docs(w05): index.ts 头注失实转给 Task 12 收口(Ruling H25)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 21:41:33 +08:00
xcsandClaude Opus 5 716f17ee2a chore(w05): 两枚工具与两张判定表退役,技能戳删除 —— 规则只许有一处真源
四态与推荐等级现在活在 S2 配方的 when 链里(spec §3.5)。
再留一份 DMN 或一枚工具,就是同一件事两处各存一份,改一处就分叉而没人看得出以哪份为准。
技能戳是给模型看的,而这个活动全程没有 agent。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 21:20:56 +08:00
xcsandClaude Opus 5 77df79d6fb test(w05): 「全只读」连编辑闸的外壳一起说死 —— data-edit= 这个子串照不到它们
2026-08-29 评审自拟变异逮到的盲点:editGate()/addrow() 产出的是 data-edit-open= /
-cancel= / -save= / -for= / -add= / -del=,edit 后面跟的是连字符,永远凑不出
data-edit= 这个连续子串。实测往 landing-confirm 插一个孤立的
<button data-edit-open=…>,原有三条断言原样全绿。

眼下没触发(这份件零处调用那两个 helper),但光有闸没有 data-col 孪生格虽然写不进
数据,屏上会冒出一枚按不出结果的「编辑」钮 —— 那是界面撒谎。

变异验证:注入后两条断言都真红;复原后 report.ts 与 HEAD 逐字节一致,
包内 59 passed、pnpm gates 退出码 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 21:08:20 +08:00
xcsandClaude Opus 5 4bd1a04511 feat(w05): 件③ 落账确认与看板重绑 —— 签之前只核对,硬约束不符逐条点名
Task 9: 新增 landing-confirm(T_release 的终局验收工单,全只读,答「这一
签下去发生什么」——落账去向/结论卡/四态分布/硬约束逐条点名/待验项/发口),
三张件此刻齐了;activity-ledger 看板的 data 声明与 HTML 一并重绑到新九
张表(P-01/P-10 两域随重写撤下),解封 3 条等待三张件齐的 skip。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 20:53:23 +08:00
xcsandClaude Opus 5 04675e814d test(w05): 件② 补八条显式只读断言 —— risk_level 撞车骗过了泛化规则
评审变异实测三次逮到两个活洞:original_value 加一个 data-col 就能在验收
工单上篡改基准事实,63 条既有断言零告警;risk_level 同时是
substitution.relation 与 substitution.difference_item 两张表都声明的
字段名,「data-col 都在台账声明里」那条泛化断言按名字找归属,被这个撞车
巧合放过——它问的是「这个名字合法吗」,不是「这一格该不该可改」。

改成显式点名八个字段(明细表六列 + 收口两枚徽章),不再依赖泛化规则。
八次变异逐一验证:每个字段单独挂上 data-col 都能让新断言真红,
risk_level 那次额外确认了旧的泛化断言在同一次变异下仍然全绿
(复现评审报告的根因)。模版本身未改一字——它一直是干净的。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 20:15:43 +08:00
xcsandClaude Opus 5 59afcbfdbd feat(w05): 件② 差异结论审核 —— 可改的是四态,摘要与等级只读
推荐理由 / 摘要 / 风险 / 验证要求都是规则算出来的:让人直接改它们,
等于让他给自己没做的判断盖章。要改就改上面那一条四态,系统重算。

同批修一条与本次设计矛盾的既有断言:「差异审核与落账确认只读」原把
difference-review 也算只读,而 spec §4.4/§6.2 已把 S3 改成可就地编辑
——改窄成只管 landing-confirm,differerence-review 的只读面改由新增的
「件②」测试组精确覆盖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 19:50:31 +08:00
xcsandClaude Opus 5 10f529d03c docs(w05): Task 8 的示例代码也跟上助手名核正(上一笔只改了 Task 9)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 19:16:40 +08:00
xcsandClaude Opus 5 abe92e0531 docs(w05): Task 8/9 的测试助手名核正 —— activity.test.ts 里没有 viewOf 也不 import ASSESSMENT_STEPS
计划原稿让 Task 8/9 复用「Task 7 已经加进去的 viewOf / ASSESSMENT_STEPS」,
而 Task 7 的实现者核过文件后用的是既有的 viewHtml(:38 本地定义)与
manifest.assets.businessModel.steps —— 照原稿写会去找两个不存在的东西。

这个文件刻意走清单而不是 case-model.ts 的源常量:源常量对得上而清单里漏装,
正是它要拦的那类错。ASSESSMENT_STEPS 是真导出,但那是 steps.test.ts 的用法。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 19:16:14 +08:00
xcsandClaude Opus 5 40daa6c9f7 fix(w05): 评审回来的一条 Critical、一条 Important、两条 Minor
Critical:S1 数据工单的 gateLedgers 带着 parts.chip_type,而这张件没有任何手段
写它(选类型走 data-options 下拉现读,不是编辑表)—— mine:parts.chip_type 永远
是空数组,desk.ts 的 emptyGate 永远判「这一单还一行都没有」,确认钮永久按不下去,
三张真被编辑的表填得再全也一样。删掉 parts.chip_type,只留真被这张件写过的三张;
注释写明这是永久决定,不是等 Task 7b 补了新建类型的编辑表就该还原(主路是从下拉
挑已有类型,那时这张表天然零行,「零行=没干完」这条闸的前提对它压根不成立)。
新增 apps/api 侧回归探针(调用生产 emptyGate,不抄实现):证明旧配置确实会永久
卡死、新配置在同一份数据下真的放行。

Important:BIND_COMMON 的序号计数器规则只认 .tbl-wrap,而三张表的外壳是
.jc-tbl-wrap(继承自已删除的旧 P08_MASTER_CREATE 同一个坑)—— 两个 class 对不上,
「#」列在只读态与编辑态下逐行空白。补上 .jc-tbl-wrap,钉一条结构测试。

Minor:①block③ 的注释「只两栏可改」没说全 —— internal_pn/param_name 也走 cell()
(主键格必挂 data-col 是既定约定,行的钥匙),补全这句话;②data-class-of="gate_reason"
在中文自由文本上永远算不出 v-token,是源码里躺着的一个永远不起作用的属性——删掉,
显隐只留 :has(:empty) 一处,brief 给的那条断言同步改成断真实机制。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 19:11:03 +08:00
xcsandClaude Opus 5 fdcd1f88a7 docs(w05): 立 Task 7b —— 「新建芯片类型」是 spec 与用户都点名的能力,不能只活在一条顾虑里
Task 7 交付的件让人从下拉挑已有类型(主路通),但 spec :37 / :261 与用户原话都
明写还要能「新建一个类型 + 它的核心参数字段」。这一半没做。

做不了的原因已核实属实:ViewContainer.addRow 靠克隆最后一行,零行时返回 false,
而 mine:parts.chip_type 每单开局零行。顺带发现平台自相矛盾 —— desk.ts:843 那条
空表闸在教人「先在件上加一行」,而 UI 做不到。

三条候选路各记了代价:①件上摆字面量空白行(零平台改动,但复合主键的跨行自增键
联动待查)②平台补原型行(小改,但动两个共享面要全量回归)③配方种骨架(已否:
人改挑已有类型时那行空骨架会一直卡着闸)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 18:38:20 +08:00
xcsandClaude Opus 5 c60a2adf43 feat(w05): 件① 评估输入补全 —— 类型下拉从台账现读
芯片类型是人在件上现造的,选项写死等于「只能选出包那天就有的那几种」。
行由系统铺、人不加行:要建哪颗料是启动参数就定死的事,他只填格子。
主键列挂 data-col 当行的钥匙 —— 漏了会把人改的行错配到别行上(2026-08-02 踩过)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 18:33:07 +08:00
xcsandClaude Opus 5 b2159022bc test(w05): 补一条读真文件的引用相等断言 —— 镜像测试证明机制,这条钉住真文件此刻是对的
复审 Important:w05-s3-review-recompute.test.ts 手写镜像、不 import case-model.ts(架构上也
不能),证明的是"少了 let 会怎样"这个机制,却没有任何东西钉住真文件此刻还带着 let: SHARED_LET
——AutoRecipe.let 是可选字段,typecheck 不报;明天有人把 S3_review.auto.let 删掉,原样重犯
这次的 Critical,镜像测试的红绿完全脱钩、不会发现。

在 steps.test.ts 里"S2 与 S3 用的是同一份投影"那条 toBe 断言旁边加同风格的一行:
expect(stepOf('S3_review')?.auto?.let).toBe(stepOf('S2_assess')?.auto?.let) ——
零新增 import,读的是真 case-model.ts。变异验证:把 S3_review.auto 的 let: SHARED_LET, 那一行
删掉(diff 核过只改了那一行),这条断言变红(undefined vs 期望的绑定对象);加回来
(diff 核过与原文件逐字节相同),变绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 17:47:36 +08:00
xcsandClaude Opus 5 392d085f51 fix(w05): S3 复用 S2 投影必须自带 let —— 主键靠它绑,少了就写丢或拼出垃圾行
评审 Critical:S3_review.auto 复用 RELATION_PROJECTION/VALIDATION_PROJECTION 时没有自己的
let,而两笔投影的主键/外键(relationId/evalRef/originalPn/candidatePn/typeId)全靠 let 绑的
名字 —— createAutoRunner.run() 每次都从它收到的那份 recipe.let 现算 lets,不跨步传递
(auto-runner.ts:93-97)。后果:关系表主键算成空串,写被"主键算不出来先不写"悄悄丢掉,人在
审核件上改的四态永远不会反映到推荐等级上;验证记录的主键 cat:['VAL-',{in:'evalRef'}] 反而
拼出非空的 'VAL-',写出一行垃圾,破了"P-06 一单固定一条"的不变量。

brief 让两步共享同一个对象引用(toBe 断言),推翻了 spec"S3 的配方里复制一份 ③④ 两笔……
一步一份配方,不做跨步回跳"的原话。采纳评审方案 (i):把 let 也提成模块级常量 SHARED_LET,
S2 与 S3 的 auto 各自持有 let: SHARED_LET —— 绑定的文本仍只有一处真源,运行时各自完整可求值。
核对过 SHARED_LET 五条绑定只读 sub_eval.case/sub_eval.part(S1 写、S2/S3 都不改),S3 那一拍
与 S2 那一拍算出的值逐字相同,方案 (i) 直接适用。

新增执行级回归测试(w05-s3-review-recompute.test.ts,真 createAutoRunner + 手写假端口,
参照既有 w05-gate-reason-order.test.ts 的搭法):模拟人在审核件上把一条四态改成 hard_mismatch
后重跑 S3,断言关系表真的被重算更新、验证记录仍只有一行。变异验证:去掉 let 复现两种失败
(主键算不出来的行丢失 / 拼出 'VAL-' 垃圾行),加回来两个断言都过。

顺带补 docs/debt.md 一条平台缺口:static-audit.ts 的 walkExpr 对 when/lookup 有校验分支,
唯独没有裸 {in:X} 这一支 —— 未绑定的名字装包时不出声,只在运行期现形。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 17:32:29 +08:00
xcsandClaude Opus 5 725f1ecb0c feat(w05): S3 审核卡与 S4 落账放行 —— 摘要只读,改的是四态
关系与验证记录的投影抽成模块级常量,S2 与 S3 共用同一份:
人改了某一条四态,等级 / 风险 / 摘要 / 验证要求跟着重算。
各写一份早晚分叉,而分叉那一刻屏上会同时摆着两个互相矛盾的等级。
次序是投影在前、卡在后:人交回后卡这一笔只跳过自己,投影仍跑 —— 重算才发生在该发生的那一刻。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 16:47:08 +08:00
xcsandClaude Opus 5 cd88d0e026 docs(w05): Task 11 的共享库处置按用户已给的授权定死 —— 走第①条
用户已定「走查期间先停掉稳定版」,不必让执行者再停在一个已有答案的问题上。
同时写明:停稳定版是控制端的事(本工作树之外的副作用),执行者只负责确认
:8790 没有监听;还在监听就报上来,不自己动手也不绕过去装。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 16:21:29 +08:00
xcsandClaude Opus 5 8c125042ba docs(w05): Task 10 的 Step 3/4 跟上 Step 2 —— 摘注册而非标停用,git add 补齐四个文件
上一笔改了 Step 2 的退役方式(本仓工具没有 enabled 概念),Step 3 还写着
「把注册标成停用」、Step 4 的 git add 也没带上新列进 Files 的四个文件 ——
自己改出来的内部矛盾,一并补齐。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 16:20:49 +08:00
xcsandClaude Opus 5 1c6978818c fix(w05): S2 测试的 stepOf 类型保护改用泛型 Extract,补 {} 兜底的第二个坑
评审判定 PlanStep 标注太松:.fields 落在契约的开放索引签名上,拼写保护回来了但
14 处方括号取值(含 S1 既有三条)全部失去编译期拼写检查。换成泛型 Extract 版
(case-model.ts 四处 item 补 as const 配合),按查的 item 精确narrow 到那一步自己的形状。

Extract 单独应用后 tsc 仍报 13 个错——根因是 brief 给的 7 处测试断言用 `: {}` 兜底,
而"有形对象类型 | {}"在 TS 里恒被吸收成裸 {}(与 stepOf 精不精确无关,5 个隔离复现验证过),
S1 既有断言用的是 `: undefined` 从未踩过。补齐 `: {}` → `: undefined` + `?.` 与两处
for-of 数组补 as const 后,拼错实验复现 TS2551 + "Did you mean" 建议,改回后零错误。

debt.md 补一条:analyze 出参靠 requiredWhen 兜底的字段漏填时,全 auto 步没有就近恢复点
(ADR-014 通用特性,不是 W05 的病)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 16:18:46 +08:00
xcsandClaude Opus 5 89f0be8b81 docs(w05): Task 10 修计划缺陷 —— 退役清单漏四个文件,断言写在不存在的字段上
Files 清单漏了 container.ts(容器里那行 handler 覆盖)、tools-w05.ts(两枚工具的
契约定义整份)、seed-activities.ts 与 seed-mvp.ts(它的两个消费方)。只改点名的
五个文件,工具会在一处退役、在另几处照常注册。

Step 2 的断言 registry.find(t => t.name...).enabled 三处都不成立:该文件里没有
registry;FIRST_PARTY_TOOLS 是 [name, handler] 元组数组,元素上没有 .name,取它
得 undefined 再 .includes() 当场抛;本仓工具也没有 enabled 这个概念。改成断言
「不在注册表里」,同样拦得住手滑重新注册。

Step 1 的预期由「零命中」改成实测的那几处,并写明冒出清单之外的命中要停下来报。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 16:15:10 +08:00
xcsandClaude Opus 5 60955171da feat(w05): S2 配方 —— AI 唯一一次出场,行由字典决定不由它决定
明细按类型字典 each 出来,AI 的出参只是拿参数名去 lookup 的一张查表:
它漏一条,那一行的四态就是空,required 立刻变成 _missing > 0,闸与放行门当场拦住。
从前是 from each(AI 出参) —— 少给两条就少两行,而每一行看着都合规。
值一律 lookup 台账,一个字不让它重述;推荐等级、风险、摘要、理由全由规则与模板拼。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 15:40:07 +08:00
xcsandClaude Opus 5 18d7147ebd docs(w05): S1 配方次序改对 —— 案卷排到骨架两笔之后
gate_reason 引的 parts_profile_missing / parts_parameter_missing 要等
②③ 把骨架行铺出来才有值。排在前面时引擎给它读到 0,卡照样正确发出,
但卡上那句「还差什么」是空白的 —— 恰在人最想要一句解释的时候。

计划与 spec 两处都写着旧次序,不改则下游任务照抄同一个错。
S2/S3/S4 已核,次序都对(投影在前、读它的排在后),病只在 S1 一处。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 15:21:15 +08:00
xcsandClaude Opus 5 02e0d1206d fix(loop-engine): S1 次序回归测试改 import 真读数函数,删掉已经漂移的手抄
上一轮判断"读数层四个函数也躲不掉、只能手抄"是错的:rowReadings/missingReadings/
tallyReadings/distinctReadings 是 apps/api/src/app/wiring/engines.ts 的具名导出,测试文件住在
apps/api/src/modules/loop-engine/__tests__/,import 它们是 apps→apps,activities-isolated 与
apps-not-into-activities 两条隔离规则一条都不沾。真正躲不掉的手抄只有六笔配方常量本身(ACT_*,
因为那条边是 apps/* → activities/* 的反方向,才是两条规则真堵住的)。

而且手抄件已经在同一笔提交里漂了:missingReadings 拼缺项名的真身是
gaps.map((f) => f.label ?? f.name)(engines.ts:378),手抄件漏了 label ?? 那半句,连带镜像的
Declared 类型没有 label 字段,这条分支从结构上就复刻不出来。

删掉约 150 行手抄,改成 import 真身。相对路径要爬三层撞了 eslint 的 no-restricted-imports,
新增 @app/* 路径别名(tsconfig.base.json + vitest.config.ts 各一行,与既有 @modules/* / @shared/*
同一种写法)—— 取名核对过不撞 pnpm 工作区包名 @app/api / @app/web(两者从未被当作 TS import
specifier 用过,且 apps/api 的 package.json exports 只暴露了 "." 一个入口)。

变异复验(删手抄之后这条用例仍能精确复现原 bug):旧次序 → RED,Received 恰是评审报告点名的
那个空字符串,与手抄版本逐字相同;新次序 → GREEN。depcruise 单独确认零违规。

文件头注按"测什么、不测什么"重写:次序不倒退已由 steps.test.ts 用精确数组相等钉在真
ASSESSMENT_STEPS 上;这份镜像守的是引擎逐 action 刷新时机,在形状上有代表性即可,不是逐字
等于真配方的承诺。docs/debt.md 补一条,点名唯一还留着的手抄(ACT_* 六个常量)与谁改了 S1
配方形状该顺手核什么。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 15:15:18 +08:00
xcsandClaude Opus 5 defa048270 fix(w05): S1 配方次序 —— 案卷投影排到主数据/参数骨架之后
gate_reason 最深一层要读 parts_profile_missing + parts_parameter_missing,这两个数由主数据/
参数骨架两笔撑出来。案卷投影若排在它们之前,读到的是这一拍刷新之前的旧数:candidate 的类型刚被
判定、参数骨架行即将铺出但还不存在时,parts_parameter_missing 读到 0,片刻之后变成 1。收步与
发卡的把关本身没坏(card.when 排在铺骨架之后能读到真实缺口,卡照样正确发出),但案卷里存的话、
连同卡面 basis 里「还差什么」那一格,会是一句空话。

六笔重排为 part → profile → parameter → case → card → say;新增引擎级回归测试
(w05-gate-reason-order.test.ts,手写镜像 S1 配方六笔与生产读数层的四个纯函数,因
activities/apps 两侧互相不许 import 而不能直接复用真配方或真引擎的读数逻辑),变异验证过
(旧次序下测试确实变红、且红得精确:Received 恰是评审报告里点名的那个空字符串)。

顺带补一句注释:parts.parameter 的 param_unit 有意直接取字典而不回填现值 —— 同一类型下两颗料
必须按同一个单位比,字典值才是比对基准,S2 判定时用的单位同源,不是漏写了 lookup。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 14:45:57 +08:00
xcsandClaude Opus 5 ab0dfd8b49 feat(w05): S1 配方 —— 校验入参、铺格、缺什么派一张数据工单
在库与否是一次 lookup 的结果,不是一句断言:先取 found_pn_ref(refersTo 查无此值当场拒)再按它判。
参数骨架两颗料都铺、值回填现值 —— 于是「在库但当年参数没录全」也自动被红带拦住。
发卡的条件只用基座读数:gate_reason 为空串时那一列不写、未绑定,而 S-FEEL 里未绑定的名字
`!= ""` 求值为 true —— 拿它当条件恰恰会在一切正常时把卡发出去。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 14:01:15 +08:00
xcsandClaude Opus 5 f22402e155 docs(spec): 拆掉 §4.3 伪码里的反模式 —— 「= 本类型字段数」不是基座数得出来的数
Task 3 的评审指出来的:parts_type_param_rows 数的是本单草稿写下的字段行(人新建类型时加的那几个),
不是整本字典。拿它当分母就是「编一个数不出来的数」—— 本仓栽过六次。
Task 3 的实现者避开了,但下游照抄伪码就会把它带回来。行数由 prune + each 字典结构保证。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 13:44:32 +08:00
xcsandClaude Opus 5 f00ed5c011 feat(w05): 案例模型换成四步 —— 零 guide、零工具、零记账旗
九步砍成四步:校验入参 / 差异分析 / 结论审核 / 落账放行。
三道入场哨兵全读基座数,caseFileModel 里一面记账旗都没有 ——
旗是活动自己写的数,而「机检只读基座数」这条在本仓被违反过六次。
审核与放行触达的是人:标人工激活、不给 doneWhen。
说明书瘦成「整单的事」,示例一律占位形 —— 模型会把示例当成这一单的值。

manualActivation 未照 brief 给的 planItem+definitionRef 写法(与本仓 cmmn-parse.ts
不兼容,会解析出错误的计划项 id):改用本仓既有的单元素写法 ——
<humanTask id="..." manualActivation="true">,与 steps.test.ts 给定的 RED 测试、
E04/I01/旧九步 W05 的既有约定一致。

顺带恢复 Task 2 遗留的两条「Task 3 补回」skip(读写面 ⊆ 台账声明 / 工具 ⊆ 声明工具,
后者按零工具的新立场做了修正而非照抄旧断言);第三条(T_release judge criterion
逐字对应 acceptanceCriteria label)调查后判定为结构性不可恢复 —— 它检查的「guide
散文里嵌 judge criterion 给模型执行」这套机制随零 guide 重写整体撤掉,继续保留
skip 并订正注释说明原因。

四步重写让 activity.test.ts 的「案例模型与清单的对齐」组里 7 条既有(未 skip)断言
硬编码了旧九步结构,不改不可能 pnpm gates 绿:仍然成立的按新模型更新数字/名单
(四计划项 / 两处不给 doneWhen / 三段阶段);检查对象已被撤掉机制的转 skip 并写明
原因(P-08/P-10 双视图已合并进单一 S1_inputs;决策卡 JSON 骨架已从说明书散文挪进
配方的结构化 card 声明)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 13:22:58 +08:00
xcsandClaude Opus 5 d244864317 feat(w05): 清单重写 —— 九张台账、两个启动参数、零工具
台账:两张类型字典 + P-08 主/参数(撤 createOnly、封装工艺归字典)+ 案卷两张 + P-11 主/明细 + P-06。
契约:启动只剩原 PN 与候选 PN 两格且都选填(缺了由第一步派工单补);tools 清空 —— 这是立场不是疏漏。
判据全读基座数;明细加合成列 judgement_class,因为「硬约束不符有几条」不是单列 tally 数得出来的。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 12:39:45 +08:00
xcsandClaude Opus 5 7b8d3b559b feat(seed): 芯片类型字典两张表 —— 约束类型从「每颗料各标一次」搬到「按类型标一次」
两颗料是不是用同一套参数名、同一套软硬约束,从此是结构保证而不是措辞叮嘱:
字段清单挂在类型上,一颗料关联一个类型。此前靠 parts.parameter.param_name 的
自指引用墙硬咬(措辞拦了三次没拦住才砌的),那堵墙可以退休了。
另播一颗 LDO(AMS1117-3.3)—— 它是「两颗料类型不一致 → 硬拦」那条路的唯一素材。

同批修复两条因此变为过时的既有断言(均在 seed-mvp-data.test.ts,非本任务新增):
「样例料号」测试原本枚举 parts.profile 恰好两行,新增 AMS1117-3.3 后改为三行;
「标准化参数集」测试原本断言每行 parts.parameter 都带 constraint_type,该列已
搬到 parts.type_param 字典,断言相应改为 toBeUndefined()。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 11:46:27 +08:00
xcsandClaude Opus 5 94b664bc85 docs(plan): 发车前扫描三条裁定入计划(帮手真名 / found_pn_ref 去重 / assets 处置前移)
G2:Task 1 的测试帮手名是我猜的,已查实(MASTER_OBJECTS / MASTER_SAMPLE_ROWS)—— 直接写对。
G1:spec §3.3 已补 found_pn_ref,Task 2 照 spec 写就有了,Task 4 那一步改成「确认在,不在才补」。
G3:assets(dmn / skills / viewTemplates / distillHooks)的处置前移到 Task 2 ——
    不然 Task 2 到 Task 9 之间清单引着已经不存在的东西,包是不自洽的。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 11:28:57 +08:00
xcsandClaude Opus 5 65a4c7a7dd docs(plan): 第二份实施计划 —— ACT-WORK-05 活动重写(12 个任务)
spec 的 §3 数据模型 / §4 四步案例模型 / §6 三张件 / §7 判据 / §8 退役 / §9 测试 / §10 迁移次序。
自查时同步补了 spec §3.3 的 found_pn_ref —— 「在不在库」要是一次 lookup 的结果,不是一句断言,
计划的 S1 配方靠它,spec 是权威,不能两处不一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 11:15:17 +08:00
xcsandClaude Opus 5 c3fed573ee fix(loop-engine): 收尾复审二审 —— 兜底卡自己会抛 / 清单兜底不看面孔 / 空转写解闸
复审逐条核实上一波(C2/C3/M1/M2)修法本身引入的三处破坏,全部属实并修死:

- I1 二审:配方办不下去时发的那张兜底卡(deps.cards.issue)从前裸调,调人预算
  触顶抛 HumanCallBudgetExceededError 会精确复现 C3 那种无限热循环(推进器每
  2 秒重来、不烧预算、不换状态、界面纹丝不动)。包进 try/catch,失败留痕、
  状态退回 awaiting,不让它抛出去。配了变异验证:拿掉 try/catch 后新用例
  真的变红(runTurn 抛出预算触顶错误),还原后转绿。
- C2 二审:decisionCardFace 的 checklist 兜底从前不看 reviewFace —— 一步只声明
  acceptanceChecklist 而 cardVariant 不是 review(T_release 那种形状)时,配方发
  的普通 decide 卡也会被兜上清单,前端不渲染勾选区、回传门却要求 checked ⊇
  checklist,卡按不下去。改成只在 reviewFace 为真时才退回剧本声明的清单。
- M1/M2 二审:project 动作里 localExisting/localUnkeyed 触发的观察痕与真写
  (localWrote/localDropped)共用 detail.auto + detail.ledger 这两个键,而
  raiseAcceptance 的 revised 判据只认这两个键在不在 —— 一颗料 skipExisting、
  或一行主键算不出来,这条"什么都没动"的痕照样能解开重做闸,人打回重做、
  配方原样重跑一遍,闸却开了。auto-runner 补 detail.changed(真写了/真撤了
  才为真),revised 判据对配方那一路改认这个标记。

另两笔记账:
- save-wake.test.ts 的用例标题写着"验收态就是这一种(没有卡在等人)"是错的,
  pendingCards 把未办结工单也算在内,验收态恰恰有一张开着的验收工单——只改
  标题措辞,不动断言与实现。
- docs/debt.md 补两笔:保存后唤醒的收窄几乎不生效(pendingCards 口径过宽);
  模型路径的写同样会 un-land、只补了配方那一半(独立起一条,排在 W05 重写前)。

pnpm gates 退出码 0(2410 passed / 23 skipped);
git diff --stat b995148...HEAD -- activities/ 为空,九个活动包一字未动。

报告:.superpowers/sdd/2026-08-29-deterministic-steps-auto-recipe/closing-fix-report.md

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 09:21:02 +08:00
xcsandClaude Opus 5 c60c882f06 chore(contracts): loop-engine port 快照与 CONTRACT.md 同步(配方专用两个旗 + caseKey)
契约变更四步:
① depcruise 反查下游 —— `no dependency violations`;`draftWrite` / `writable` 的调用方
   逐个查过(engines 三处、turn-runner 两处、auto-runner 两处),新增字段全是可选,
   老调用方一个字不用改;
② CONTRACT.md 已同步(近变一节写明两个旗各治什么、跳过的行不许被 prune 撤,
   以及同批三条不变量:配方只认 liveStep / submit 与模型同一条处置 / 办不下去发卡交人);
③ 本快照单独说明:`TaskDataGateway.draftWrite` 增 `skipUnchanged`、`skipExisting` 两个
   可选入参与可选出参 `skipped`;`writable` 增可选 `caseKey`。**非破坏性**;
④ 无破坏性变更,不需要三步弃用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:45:36 +08:00
xcsandClaude Opus 5 642bdcef3b fix(loop-engine): 配方少写的行要出声,撤掉的行要点名
**M1**:主键算不出来那一行从前是静默 `continue` —— 既不计数也不留痕。
表里少了行,人和判据都查不出为什么。现在这一笔结束时点明少了几行、主键列是哪几个。

**M2**:`prune` 撤行从前只说「撤掉 M 行」。2026-08-02 为人工撤行立过
「撤要说出来、要点名哪几行」的规矩(不说清,下一拍的读者会把人刚删的写回去),
配方这一侧跟上:点名撤了哪几行,封顶 8 个,**并去掉单号那一段** ——
人正在这一单里看,单号是脚下的地面,不是区分行的信息(2026-08-25 同一条口径,
基座那一侧是 `rowLabel`)。为此 `TaskDataGateway.writable` 补一个可选的 `caseKey`。

3 测(少写出声 / 撤行点名 / 名单不带单号)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:43:53 +08:00
xcsandClaude Opus 5 588d5c2dd4 fix: 配方只认 liveStep;保存不再无端叫醒任务;prune 不在人填的时候撤行
**I6 · 配方只认 `liveStep`**:`current` 有一条兜底(`liveStep ?? 在场的第一个有剧本的项`),
它不过滤 `settledIds`。当初这只影响提示词内容;配方接进来之后它驱动**副作用** ——
再写一遍台账、再 prune 一遍,carded 为假时甚至再发一张卡。兜底继续只服务提示词。
同处 `settledIds` 改取 sweep 之后的 `stateRuntime`(`liveStepOf` 吃的是 sweep 之后的
`stateEnabled`,两边得同一份局面)。
**注**:这一形状(既已收掉又在场)我没能在当前 lifecycle 里构造出可达来路 ——
`publishEnabled` 把 Completed/Terminated 挡在 entries 与 inProgress 之外,
`reenter` 又是就地改状态、不留旧实例。用例用的是 `live-step.ts` 注释描述的那种局面
(同 id 两份实例),守的是不变式;详见最终报告。

**F1 · `afterSave` 只在有未答的卡时才 `wake`**:`wake` 对 awaiting-event / awaiting-human
一律拨成 running,推进器随即推一拍并**记一步预算**。而现有九个活动包一个 auto 都没有:
人每按一次保存就白烧一步、界面闪一次「AI 正在推进这单」;任务本来 awaiting-human
(等人裁决)时被叫醒,那一拍结束还会把它写成 awaiting-event —— 从「等人办事」降级成
「等外部回音」,而等的明明还是人。**这是界面撒谎。**「保存即铺格」的场景恰恰都有卡在场。
用既有的 `humanTool.pendingCards`,不新造。`wake` 失败不再 `.catch(() => false)` 静默 ——
它是「保存即铺格」的唯一触发器,吞掉等于人按了保存、格子没出现、系统一个字不说。

**F3 · `prune` 不在人填的时候撤行**:人在件上「加一行」加出来的行,配方的数据源里当然没有 ——
编辑中途把它悄悄撤掉是最不能接受的一种静默数据丢失。有卡在等人(quiet)那几拍只铺不撤;
同一张表既被 prune 又列在该步 `humanWrites` 里,装包报 warn;规范 §4.6 纪律补两条
(prune 的表不该是人能加行的表 · card 之后那几笔要等人交回之后那一拍才跑)。

I6 2 测(做过变异验证)· F1 5 测 · F3 2+1 测。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:42:06 +08:00
xcsandClaude Opus 5 2d20202b1d fix(memory): 配方留痕恢复 internal,工作日志按 detail.auto 另开例外
`internal` 是 `machinery()`(工作日志)与 `chat-projection`(对话区)**共用的一个旗**。
上一轮为了让配方留痕进工作日志,把 project / analyze 那两笔的 `internal` 整个去掉了 ——
等于同时对两个投影开门:「芯片主数据:写下 2 行」「已把 8 条参数交给模型逐条判定」
这类机械痕一笔一个气泡跑进了对话区,而这个仓花了很大代价立起
「对话区一轮一句,不是一拍一句」「引擎内部观察进日志不进对话」。

治法与 `detail.say` 同款(控制端裁定 F2):配方那几笔照旧标 `internal`(对话区据此挡住),
`machinery()` 这一侧给 `typeof detail.auto === 'string'` 开一道例外 ——
**两件事各有各的开关**。`say` 与「办不下去」那两笔本来就该进对话区,保持不标 internal。

既有那几条「留痕真的走到工作日志」的用例全绿(它们现在喂的正是 internal:true 的形状,
恰好验这条新例外);另补 8 条:auto-runner 侧断言四类痕各自标不标 internal、
chat-projection 侧断言机械痕一个气泡不冒 / say 与失败照进、
task-log 侧断言例外只认结构化的 auto 键(不是字符串就不算)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:36:09 +08:00
xcsandClaude Opus 5 647b86753a fix(activity-registry): 配方静态审补齐 spec §5.7 漏掉的几条
**`when` 从前一条都没审**(§5.7 第三条明写要审)。运行时两种下场都不好:
嵌在表达式里的 `{when}` 出错 → `evalExpr` catch 掉、**静默走 else**;
动作级的 `when` 出错 → 抛 → 整步交人。现在:
  · 所有 `when`(含嵌套在表达式里的)审语法,过 `@sfeel` 的 `staticCheck`;
  · **只有动作级**那一层连名字绑不绑得上一起审 —— 嵌套的 `when` 还看得见行内的
    `col`/`outer`/`computed`,拿 `boundNames` 去卡会误报。
    名字取 AST 的 `references` 而不是本文件那个正则:正则会把 `count(...)`
    这类内建函数名也报成未绑定的台账名。

另三处「装包期判得出、却留到运行时」的:
  · `submit.rationale` 与 `recipe.let` 的表达式从没被 `walkExpr` 走过
    (与这轮已修的 card/say 那个洞同族);
  · `project.fields` 不含目标表的某个主键列 —— 运行时那一行**静默 continue**,
    一行不写、一条痕都没有,表里少了行谁也查不出为什么;
  · `card` 没有 `basis` —— 基座拒发 basis 为空的工单(契约注释自己写着
    「别指望兜底」),运行时那一拒是抛,抛出去就是整步交人。

10 测。既有那条 `toEqual([])` 的用例把主键列补进了 fixture(那份配方本来就是坏的),
断言一个字没弱化。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:33:20 +08:00
xcsandClaude Opus 5 a09acc7504 fix(loop-engine): carded 只跳过这一笔,不收拍 —— 配方尾部的笔从前永远不跑
同一份配方 `[project, card, project]` 在两种局面下行为相反:人还在填(quiet)时
card 被 quiet 过滤 `continue` 掉,**第三笔照跑**;人交回之后(非 quiet、carded)
走到 carded 分支 `stop: true` break 掉整条 do,**第三笔不跑**。
也就是说**唯一一个「人的答案已经在草稿里」的时刻,配方的后半截偏偏不执行**;
而人还没答完时它倒是跑了。写配方的人会很自然地写「铺格 → 发卡 → 收口整理」,
然后发现第三笔从来没生效过 —— 静默,没有痕。

「收拍」留给**真发出去了卡**那一次(两条带 stop 的 return 不动)。

配套一条装包 warn:card 后面还有 N 笔时说清它们什么时候跑 —— 顺序读起来像
「一拍连做」,实际要等人交回之后的那一拍。

auto-runner 3 测(carded 跑完 / 真发卡仍收拍 / quiet 与 carded 现在一致)+
static-audit 2 测。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:29:21 +08:00
xcsandClaude Opus 5 198d12af1b fix(loop-engine): 配方失败给人一条真的路;幂等在「反复进入」下也成立
**I1 · 失败不再伪装成「等外部回音」**:从前配方办不下去只 `status:'awaiting'`
就走了 —— 映射成 `awaiting-event`,而推进器只捡 `running` 的单:卡已答完、
没立监听、没有时限,**再没有任何东西会叫醒它**。人在工作台上看到「等外部回音」,
而外面没有任何回音会来。现照熔断兜底卡的成例:发一张卡把办不下去那一笔的原话
摆给负责人 + 立人事件监听。走到那儿时这一单没有未答的卡(runTurn 开头
`cards.unanswered` 把住),所以不会堆卡。不补 narration —— 那句话 auto-runner
已落成系统观察进了对话区,而且停下来的是系统不是 AI(narration 是 AI 蓝)。

**I2 · 重投不许把已落地的行 un-land**:`DraftStore.put` 的 upsert 尾巴上有
`landed_at = null`,而配方每一拍都把同一张表重投一遍 —— 数据工单确认入库之后
那些行的「已落地」记号被悄悄擦掉,整单放行会再写一遍正式表、再抛一遍 data 事件。
新增两个**配方专用**旗(模型那一路一个字不变):`skipUnchanged`(值没变就不落笔)、
`skipExisting`(「只建不改」的表碰到已在库的键当「不用建」跳过并留痕,不抛 ——
那句拒回是写给模型看的,配方抛出去就是永久停单)。已在库的行也进 `kept`,
prune 不许撤它。

M4 一并做了:auto-runner 的假 `draftWrite` 从**覆盖**改成生产的**合并**语义
(`patch || excluded.patch`),并补上 landed / createOnly 两件生产真有的事 ——
「重投不会擦掉人填的格子」这条结论在覆盖语义下根本不成立,旧替身证明不了它。

I1 2 测、I2 5 测、M4 使既有 20 测在真语义下重跑。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:27:40 +08:00
xcsandClaude Opus 5 6212b34e39 fix(loop-engine): 配方 submit 抛错不再让回合崩,重做闸也认配方的写
两处,缺一不可 —— 它们合起来是一个必然发生的无限热循环:

① 配方那一路的 `raiseAcceptance` 从前是**裸着**调的。而 `engine.advance` 不捕获、
   `task-service.tick` 也不捕获(且 `addConsumption` 排在 advance 之后,一步都不记)、
   `task-advancer` 只写 stderr 就 break,任务留在 `running` —— 下一个 interval 再来一遍:
   不烧预算、不换状态、界面纹丝不动。:1738 模型判齐那处早就包着 try/catch,
   注释白纸黑字写着理由「推进器会一遍遍重来,每遍再撞一次同一道门」。
   现在同款:拒绝 + 留痕 + status running。

② 而它**必然**抛:重做闸(2026-08-23)判「打回之后台账动过没有」只认 `kind:'action'`,
   那是模型 write/drop 的痕;配方的写留痕是 `observation` + `detail.auto` + `detail.ledger`。
   于是配方改了台账闸也看不见 → 抛。软化那一段靠 `refusedBefore`(模型路径 catch 写的痕),
   配方路径从前没有 catch,那条痕永远不写 —— **这堵墙对配方是永远开不了的**。

3 测(拒回 running / 拒过一次再提就放行 / 配方的写解得开闸),两处各做过变异验证。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:21:58 +08:00
xcsandClaude Opus 5 3190392a1d fix(app): 决策卡的人工验收清单也按 itemId 兜底 —— 配方发的审核卡从前一条待确认都没有
装配层从前只给 `variant` 兜底,`checklist` 没有。配方(auto-runner)发审核卡时
只递了 variant —— 那张卡于是穿着验收工单的衣服、allowCustom 也对,**清单是空的**:
回传门 `checkChatCardReply` 的 `checklistMissing` 在 checklist 缺席时**恒过**,
「确认通过」当场可按。2026-08-27 用户当面定的那道人工验收闸就此凭空没了,
而屏上还看不出来 —— 清单区是空的,不是红的。

兜在装配层这一处(不是让每个调用方自己记得):模型路径将来漏传也一并保住。
判断抽成纯函数 `decisionCardFace` / `needsStepLookup`(照本仓 engines.ts 里
createOnlyViolation 那批纯函数的成例),9 测 —— 其中三条把挑出来的清单接到
真回传门上,证明闸真的因此有牙,不只是「函数回了个数组」。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:20:32 +08:00
xcsandClaude Opus 5 fcf1822233 fix(app): 配方的 analyze 走 traced 网关并回账用量 —— 一条问模型不花钱的路
装配层从前传的是裸 `deps.modelGateway`:`createModelGateway` 自己不做
`addConsumption`,计量全靠调用方给的 `onUsage` —— agent 那两条路都接了,
只有 analyze 漏了。后果两条:配方里 analyze 烧的 token 一个数都不计入活动预算
(预算这道闸对它形同虚设);时间轴上也不产生模型 span,「这一单问了几次模型」
看不见它。而 spec §5.2 白纸黑字写着「用量计入预算」。

现 `createAnalyzeGateway(tracedGateway, onAgentUsage)`,网关内**两次** generate
(含重问那一次)的用量各记一笔 —— 失败的调用不是免费的。记账不吞异常:
记不上账等于预算失效,宁可当场停下由配方留痕交人。

debt.md 里那半句「它烧 token、计预算」当时是假的,改老实。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 08:18:10 +08:00
xcsandClaude Opus 5 bd26756da9 docs(adr): ADR-014 §3.2/§4/§5/§11 对齐落地代码 —— 三动词改五动词,拍内次序补全
评审指出 ADR-014 被标「已实现」后,正文 §3.2/§4/§5 仍是实施前的三动词提案,
与真实 schema(auto-recipe.ts)和 turn-runner.ts 的拍内次序对不上,会误导
下一个直接翻开这份 ADR 的读者(W05 重写实现者)。逐条对代码核过后分两档改:
§3.2 动词表与 §5 拍内次序按代码改对(含之前遗漏的「有卡未答就静默重跑配方」
那道前置门);§4 的两个例子不逐行改,改成开头一段按语指出三处已知出入
(workOrder→as、view 挪到 PlanStep、self/joinRows 未实现)。顺带把 §11
待定的前两条标成已定并指回销账位置。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 07:42:27 +08:00