Commit Graph
213 Commits
Author SHA1 Message Date
xcsandClaude Fable 5 0e02bb9411 fix(task): 终态收尾不等蒸馏 —— 终止接口曾被模型调用吊 13-45 秒到报错
CI / ci (push) Failing after 10s
四单法证:finished_at 与蒸馏条目 created_at 差 13.2/36.3/43.4/44.6 秒。
finish 把终态落库并广播之后同步 await memory.distill(一次真模型调用),
终止路由与放行收尾拍(连带推进器租约)都陪着模型等。改后台跑:
封卷仍在前,失败照旧吞掉不掀终态;三测钉死,改前即 30s 超时。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 03:36:53 +08:00
xcsandClaude Fable 5 3c563525f1 fix(engine): 那面「打不回」的旗在容器里被扔了 —— 手写字段清单第三次丢值,改成整包转交
用户:「评审放行与落账确认这一步的验收卡怎么还有重做??你没改上吗?」——
库里的卡确实没有那面旗:装配层算对了、release 转对了、契约也声明了,而**容器那个显式对象**
只转它点到名的那几样,`redoable` 没被点名就在这最后一关静默落地(编译不报、运行时无声)。

同一处已经吃过三次:①fields 写死成 [](验收报告只能看不能改)②blocks 漏一行(卡面块恒空)
③这次。那几行旁边就写着两条「少写一行就静默落地」的警告 —— 措辞治不了的结构治:
context 三样拆出去、**其余整包转交**,以后端口新增可选字段不改这里也到得了。
断言钉在最末端:试跑观测面的卡带上 redoable,e2e 断言「一路到了卡上」。

同批修另一种时序的卡死(同一夜第二次,停掉了一整个 e2e 案例):
**决策卡的回传不发事件、只调 wake**,而 wake 只翻停着的单子 —— 卡刚发出那一刻这一拍还在
running,它空转了一次;随后这一拍把任务 park 回 awaiting,再没有第三样东西会来叫
(实测:卡 02:06:13.250 发出、219ms 后被答,这一拍 .636 才收尾,停到 20 分钟超时)。
修:配方发卡收尾前再看一眼刚发的那张卡还在不在等 —— 已经答了就照实说「还在跑」;
只认这一张卡,别的「等外部回音」照旧是等,不会空转成热循环。
假端口同批改老实:发出去的卡默认就在等(从前发完卡还回「一张待答的都没有」)。

验收:e2e w05 5/5 released、被拒 0,案例 0 四条断言全绿(不摆重做 / 对话区说了话 /
点名要的结论落下去了 / 只动被点名的行);两处变异验证;契约变更 api-report 已刷新。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 02:36:53 +08:00
xcsandClaude Fable 5 cec19adc85 fix(engine): 打不回的验收卡不摆那颗钮 —— 全 auto 的放行步没有模型能接住那句意见
用户:「评审放行与落账确认这一步的验收卡不应该有重做的选项,因为这里都没有接入 ai」。

W05 的 T_release 整个由配方跑完(guide 是空串,不问模型),件又是只读的 ——
打回之后没有任何东西能接住那句意见:配方只会原样再提交一次,撞在「打回之后台账没动过
就不许再提交」那道闸上,连撞两拍落成一张「连续撞在同一条判据上」的兜底卡。
一颗按下去只会把单子推进死胡同的钮,不如不摆(墙要配路;没有路就别立那扇门)。

· 契约:AcceptanceWorkOrder.redoable(不写 = 打得回,老卡老行为);
· 装配层按放行那一步的声明现算 —— releaseRedoable 纯函数(有 auto 且 guide 空 → 打不回;
  半 auto 与模型驱动照旧打得回,那儿真有模型会读意见),与 decisionCardFace /
  executionCardVariant 同一种做法、同一个测试位;
· 卡面收起理由框与那颗钮,换一句真路:「这一步只落账,退不回上一步 —— 有异议就别确认,
  去终止这一单重开」;
· **服务端才是门**:redoable:false 的卡收到非 pass 的回传当场拒(redo-unavailable),
  直调 API 绕不过;拒回把真路指出来。

S3 结论审核那张审核卡不受影响 —— 它的重做真会带着意见重判(昨夜那一轮)。
测:card-rules 3 条 + releaseRedoable 4 条,两处变异验证;e2e w05 5/5 released、被拒 0。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 01:34:16 +08:00
xcsandClaude Fable 5 8314148ebc fix(w05): 重判的行由确定的一侧圈 —— 打回点名工作温度,不许去改静态电流
用户实测:「我打回让修改工作温度的结论,给我改了静态电流的结论」。库里的证据一字不差:
工作温度它没改(还给了理由:候选值 1℃ 远窄于 -40~+85℃,硬约束不满足 —— 这半截是对的),
而没人问过的静态电流被它从 soft_mismatch 翻成 match,evidence_note 里却写着「维持原判」。

根因不是提示词不够严,是**整表交出去、整表写回来**:没有任何东西限制它只动被点名的那一行,
而「我只改了那一条」这句话本身也出自它嘴里。规范 §4.6 纪律一(行由确定的那一侧决定,
不由 AI 决定)—— 昨天刚加的重判轮自己栽在这条上。

S3 复核轮两笔变四笔(全部只在 case_redo_note != "" 那一轮跑):
① 圈范围:按字面点名(contains,与基座给模型摆的「意见点名对上了哪几行」同一种认法)标
   named / kept(明细表新增 redo_scope 列);
② 兜底:一条都没点到(意见说的是整批)→ 整表算被点名,读上一笔刚写下的 tally,不重算;
③ 重判只喂 named 的行 —— 没点名的行 AI 连见都见不到,自然改不着(也省 token);
④ 写回也只写这几行;模型漏答某一条时 else 兜回它上一轮的值。

同批修一条挡了两次路的老毛病(昨夜刚记 debt,今晚又撞停、还卡住了我自己的验证):
人答得比发卡那一拍收尾还快时,回传被判 stale、wake 又被那一拍的 park 覆盖,单子停在
awaiting-event 再没人叫醒。deliver 判了 stale 照旧不跑这一拍,但把停着的单子拨回 running。

验收:e2e w05 5/5 released、被拒 0;案例 0 新断言「打回只动了被点名的那几条,其余行一个字没变」
真跑通过(途中逼出脚本自己的坑:放行会清空草稿层,快照得留在放行之前);
模型调用 案例 0 = 2 次、其余 = 1 次。四处变异验证。W05@2.18.0 已装库。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 01:17:09 +08:00
xcsandClaude Fable 5 4d2c3657f9 feat(w05): 重做的意见交回给 AI 重跑那一步 —— 不再只是把派生列重算一遍
用户:「唯一调用 ai 的这一步,使用重做后是否没有将重做原因带回去重新让 ai 重跑这一步?」
确实没有:S3 那一轮只跑三笔纯投影重算,人写的那句话除了留痕之外无人读 ——
AI 判过的四态一个字不会变,除非他自己在件上逐格改。

平台:引擎把「人这次打回写了什么」注给配方(`case_redo_note`)。
· 下划线不是 case: 前缀 —— S-FEEL 的名字不许带冒号,而它最要紧的用法就是当 when 条件用;
· **没打回也永远绑着空串**:未绑定的名字 `!= ""` 求值为真,不绑就等于那几笔每一轮都跑
  (拿真 @sfeel 钉了这条,桩也照真语义写,否则钉不住);
· 静态审认这个名字 —— 它当场拦下过一次未登记的引用,这道门做对了事。

W05@2.17.0:S3 多两笔,都钉在 `case_redo_note != ""` 上 ——
带意见重判(入参多带上一轮四态/影响/依据/UNKNOWN 去向 + 他那句话;判法与第一轮同一份
ASSESS_RULES,后接复核纪律:没被点到、也确实没判错的行原样带回,别趁重判把人改过的冲掉)
+ 判完落回明细(与第一轮同一笔投影,只多一个 when)。判法、出参形状、落明细抽成三个
共享常量,S2 与 S3 共用 —— 各写一份早晚分叉。

验收:e2e w05 5/5 released、被拒 0;案例 0(先点名驳回、再通过)模型调用恰 2 次,
其余四例仍是 1 次 —— 没被打回就不多花一次模型。auto-runner 3 测(含变异验证)、包内 +1。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 00:33:23 +08:00
xcsandClaude Fable 5 f5435f4c9c fix(engine): 人点「重做」不是「办完了」—— 打回即重开一轮,不再一路推到放行
用户实测:「我重做后怎么直接推进了?不应该是重做刚才那一步吗?」那一单的轨迹逐条对得上:
审核卡发出 → 人带意见交回 →「这一步已经发过卡,不再发第二张」→「自动收步:S3_review」
→ 下一拍发出落账放行的验收卡。意见落进了轨迹(还被 note-anchors 逐行对上了),
而再没有任何东西会去读它。

这条缺陷 2026-08-30 全分支终审已记录成 Important 2、并留着一条坐实病灶的测试,
连「半个修法会把单子卡死」都预判对了。照那个方向修完,两半缺一不可:
① 收步那道门认答复(answerSettlesStep:custom:(人没选任何选项、写了自己的话)与
   约定的 reject 都不算办完;空答复照旧算「照原样确认」);
② 同一句判断也让 carded 让位 —— 幂等挡的是「同一问被重复发出去」,而人点重做正是说
   「这一轮不算,改了再问我一遍」:配方重跑(该重算的重算)再发一张,日志里说一句
   「人把这一步打回重做:<意见>」。

测:坐实那条改写成钉死修复后的行为(历史诊断原样留着);新增「重做但发不出新卡也不收步」
(这一条专管收步门那一半)与「通过照旧收步」;3 条纯函数测。两半各自变异验证。
e2e w05 5/5 released —— 案例 0 现在真的发出两张审核卡(先带意见驳回、再确认通过)。

顺带撞见一条独立的老毛病,已记 debt 未修:人答得比发卡那一拍收尾还快时,回传被判 stale、
wake 又被那一拍的 park 覆盖,单子停在 awaiting-event 再没人叫醒(手工拨回 running 立刻走完)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-09-01 00:03:33 +08:00
xcsandClaude Fable 5 445aa5ef22 fix(w05): 验收工单的面孔在装配层被丢掉 —— 两头都有测,中间那一段把值扔了
用户实测:「你怎么给我发成了执行工单,我需要的是验收工单,然后标题那一块需要是
验收卡的那种绿色」。

一、面孔:engines.ts 发执行工单那一处是一句写死只认 'data' 的三目,配方递下来的
'accept' 静默丢掉 —— 对话区照旧摆「执行工单」。**配方那一头有测(variant 原样递出去)、
前端那一头有测(accept 该穿哪副面孔)、e2e 按标题驱动也全绿,唯独中间没有。**
修:抽成纯函数 executionCardVariant(与旁边 decisionCardFace 同一种做法、同一个测试位),
认 data / accept、拒 review、认不出的当没写;4 测变异验证过。
e2e 同批改**认面孔不认标题**(试跑观测面的 cards 补 variant,契约变更)——
标题是活动自己写的字符串,卡穿哪副面孔由装配层决定,那一次正是两者对不上。

二、颜色:件① 验收工单那一副整套强调色换成验收绿 #0aa294(令牌从 ACCENT_LEDGER
现改选择器得来,与件②③、对话区验收卡一处真源),题头那条写死的浅蓝渐变单独给一条绿的;
数据工单那一副仍是蓝。真渲染核过两副 class 与两套令牌。

验收:e2e w05 5/5 released、被拒 0(面孔断言这一轮真在跑);2563 测、四门禁绿;
2.16.0 装库。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 23:26:09 +08:00
xcsandClaude Fable 5 ce72da77eb feat(w05): 数据齐了也叫人一次 —— S1 改派只读的验收工单,件同一份、编辑钮收起
用户:「就算数据是足的也进入这个数据工单,就派发一个验收工单,界面和数据要和现在的
第一个数据工单一致,只不过把芯片卡片上的编辑按钮去掉,只展示不能编辑,然后把标题旁边的
数据工单改成验收工单」。

病根在引擎拍首那道 doneWhen 扫描:输入本来就齐的单,S1 的 doneWhen 第一拍就为真,
配方一笔都还没跑,这一步当场被收掉 —— 卡永远发不出去,人一眼没看过就进了 S2。
治法:CMMN 给 S1 标 manualActivation,那道扫描多一个门槛(worldTouched),次序变成
「配方发卡 → 人答 → 下一拍 doneWhen 为真才收」;doneWhen 仍是真门,只是不抢在人前面。

平台:执行工单第三副面孔 variant:'accept'(件只读、人只确认「确认无误」)——
与 mode:'acceptance'(submit 抬起来的收口验收,带打回重做)分清;三副面孔的判断
抽成纯函数 order-face.ts(有测),chat-bubbles 只摆。
活动:S1 两笔互斥的 card(carded 幂等保证一步只发一张)+ 案卷粘着的旗 order_kind;
一张件两副面孔 —— 题头/标题/页脚三处名字与卡角编辑钮按 v-data / v-accept 由样式表挑
(藏的是容器 .fabs,逐枚去盖打不完)。

测:引擎测钉死那一对(标了人工激活 → 卡发得出去、步不收;没标 → 步当场被收、卡发不出);
auto-runner variant 透传;order-face 4 条;包内 2 条(两笔卡 + 粘旗)、view 1 条;
两处变异验证。e2e w05 5/5 released、被拒 0、模型调用恰 1 次 —— 都在库那例摆的正是验收工单。
2558 测、四门禁绿;2.15.0 装库核过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 22:44:00 +08:00
xcsandClaude Fable 5 e47ce92702 fix(w05): 两颗料不能是同一颗 —— 起单这一刻立墙,因为路只在这里
用户:「创建活动的启动参数里面不应该能选到同样的两个芯片」。
查下来这不只是没意义,是**把单子做死**:sub_eval.part 的主键是 eval_ref+internal_pn
(role 不在主键里),S1 配方铸的两行撞成同一个主键、后一笔覆盖前一笔,两行收成一行 ——
sub_eval_part_exists_status_found = 2 永不成立,S1 收不了步,而件上那两行人加不出来
(行源 mine:,没有「+加一行」),唯一出路是终止重开。与「两格留空」是同一条链。

平台加互斥声明 x-distinct(契约 distinctGroup)——与 x-link 正相反:那个是引导,这个是墙。
· 前端:别的格占着的候选这一格不列(自己填着的那个永远留着,否则 select 当场变空白),
  并说清是谁占了哪个;走「新增」自己写一个同样的 → 小红字 + 启动按不下去;
· 服务端 validateLaunchParams 独立拦(脚本 / API 绕不过),400 点名后填的那格;
· 「算不算同一个」= 去空白 + 不分大小写(launchDistinctKey,前后端同一把尺)——
  只挡精确相等的话 lm2596s-5.0 照样进去,混一颗垃圾主数据出来。
W05 两格挂 x-distinct: 'part',2.14.0 已装库核过。

测:服务端 6 条 + 前端 8 条(含联动与互斥合起来的真形状)+ 包内 1 条,三处变异验证过;
四道门禁绿(2548 测)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 22:03:56 +08:00
xcsandClaude Fable 5 8c5f3a7736 feat(activity-ops): 启动表单两格按芯片类型联动 —— 候选带分组键,先填的那格是锚
CI / ci (push) Failing after 10s
契约:launchOption 加 group/groupLabel(x-options.group 从字典表现读人读名)、
launchParam 加 linkGroup(x-link 结组);api-report 已随 --update 入库。
前端 launch-link.ts 纯函数管三条口径:锚摆全量、其余按锚的分组键收窄
(未标类型殿后一组,新增照走);锚粘着 —— 清空只解除收窄不换锚,人改得了主意。
服务端不据此立墙:combo 本就收候选之外的值,类型一致归 S1 收步判据
sub_eval_part_distinct_chip_type_id = 1(数得出来的门)。
W05 清单两格挂上 x-link: chip-type;文档同步。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 18:57:29 +08:00
xcsandClaude Fable 5 c4219a7b7d feat(w05+platform): 确认闸条件档 gateWhen —— 类型不一致不许确认;量高改可缩;卡间互换动画
2026-08-31 用户三连:

① 「提示两颗料不是同一个芯片类型…就不应该能写入并继续下一步」——
  缺格闸只数必填空不空,管不了跨行一致性。平台加**确认闸条件档**:配方 card 声明
  gateWhen(S-FEEL,只读基座读数)+ gateWhenSay,受理回传前在 submitGate 现算
  (rows/missing/tally/distinct 全套读数按声明表现读),为假 400、拒回用发卡人那句话;
  装包静态审与动作级 when 同一套审;评估不出的条件不成墙(坏表达式装包就拦)。
  W05 钉 parts_profile_distinct_chip_type_id <= 1 —— 钉 profile 本表(草稿现读零滞后;
  钉 sub_eval.part 那份要等配方 tick,人存完就点确认会被误拦),profile.chip_type_id
  加 distinct 声明。e2e 案例 2 的空手探针从此吃的就是这道闸。

② 「页面底部没东西还能向下滑一截」—— ViewContainer 量高从
  max(documentElement.scrollHeight, body) 改 body 优先:documentElement 以 iframe 自身
  高度为下限,内容缩回后它缩不回去,量高**只涨不缩**(参数编辑态把卡背拉长过一次,
  底部从此留一截空白滚动)。body(margin 0)才是内容自己的高。

③ 「两张卡片中间做出可替换的动画」—— 单点单向信号换成**对向双信号点**(左右互穿,
  走在卡后、只在缝里现身)+ 中央 ⇄ 徽轻呼吸 —— 这界面是判「可否替换」的,动画说的
  就是这件事。reduced-motion 全静。

测:契约 gateWhen 解析 + runner 透传(变异红)+ conditionGateOf 3 例(含坏表达式不成墙)
+ steps/activity 锚;2531 全仓四门绿;2.12.0 装库核过(gateWhen 入库/swapfx);
:8787 已重建重启。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 17:43:08 +08:00
xcsandClaude Fable 5 51914292dc feat(w05): 数据工单第四轮九连改 —— 笔↔软盘卡角编辑、只选不建、浅蓝题头、镜像修数字
2026-08-31 用户一次点齐九件,逐条落:
① 两行编辑闸退役 → 卡右上角笔(开当前这一面的编辑)↔ 软盘(保存)+ 叉(放弃);
  按钮就是平台的 data-edit-open/save/cancel 触发器,快照/取消机制原样,按面显隐全在样式表。
② 保存一瞬间下拉变数字 → ui-kit mirrorTwins:下拉镜像**选项文案**不镜 value,并把
  selected 属性拨到人选的那一项(守卫 CSS 与取消回显都认它)。2 测。
③ 参数输入框占位提示 → 渲染器 twinInputs 认 data-ph 落成 placeholder(平台小扩面,1 测);
  值格「规格书上的值 · 查不到留空」,说明格「补充说明 · 选填」。
④ 参数说明空着不再摆「建议」→ 什么都不摆(连兜底破折号一起收)。
⑤ 区序对调:芯片档案在前,比对基准在后。
⑥ **新建芯片类型整条路移除**(只能选已有类型):抽屉与两张编辑表(种子行/Critical-1
  修法/thead 回归网)退役,S1 配方 ②③ 自投影删除(八笔→六笔),gateLedgers 收窄到
  profile+parameter,writes 同步,gate_reason 与指导书改口「只选不建」;样例改名
  「候选缺库·建档定型」;e2e 案例 0 改走选既有 type_1(type_id=90 那套绕坑法随路退役)。
  退役面的七条钉测按仓例 it.skip 留墓志铭(钉的是已移除的路径,不是放松断言)。
⑦ 题头回浅蓝渐变(原目标件那条 120°),深色夜景只留验收两件。
⑧ 对话区执行卡不再摆「参考信息」板块(basis 数据照旧随卡走,详情/归档仍有)。
⑨ 比对基准降噪:字段收成丸(名+硬软徽+单位),说明藏进悬浮气泡指上即看;
  类型说明一行截断、悬浮展开。

测:ui-kit 33(mirror 2 新)+ 渲染器 61(ph 1 新)+ 包内 104(38 steps 重排)+ 2525 全仓
四门绿;2.10.0 装库 SQL 六探针全中(hd-light/软盘/data-ph/零表格/无 chip_type 编辑面/
gateLedgers 两张);:8787 已重启载入 data-ph 渲染器。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 16:41:14 +08:00
xcsandClaude Fable 5 11375eaf93 feat(w05): 比对基准合一卡 + prune:'live' —— 类型与字段跟手,只显当前那一种
2026-08-31 用户第二轮:「芯片类型列表还是有问题…和核心参数字段不能合并成一个卡片吗…
两个芯片都选一样的就应该只显示一种,现在只会跟着变多不会跟着变少」+
「卡片下面那个容器黑色和页面背景不搭」。

① 比对基准卡(类型 + 字段合一,只读、跟手):不再列 mine: 草稿镜像(镜像里积着改选前的
  旧类型 —— 撤行只在确认那一拍跑,F3 不许在人编辑时静默删行),按案卷锚
  (sub_eval.case.chip_type_id,配方每拍现算)嵌套查字典:卡上改选、按保存,quiet 拍
  project 照跑,这张卡当场换脸 —— 两颗料同类型时恰好只有那一种;没人选时空态指路。
  新建类型收进 details 抽屉(零脚本),两张编辑表原样在抽屉里(种子行/加行机制不动)。

② 契约新增 prune:'live'(quiet 拍也撤):只给**人不会在件上加行**的表用 —— F3 防的是
  「人刚加的行被撤」,人加不了行的表没有这个风险,而「改了类型,旧类型的参数行还摆在
  屏上」是另一种撒谎。W05 参数骨架 ⑤ 改 'live':改选类型,参数行跟手换。
  static-audit 的 prune×humanWrites 审计盖住 'live';规范 §4.6 纪律 5 补这条反面特例;
  两档都跳过已落地的行并出声(skipLanded)。

③ 板底改浅色蓝图:深色容器与纸面色阶断裂(用户点名),换强调色极浅晕染 + 淡格线,
  走线与信号点用强调色 —— 电路语汇留着,整页只剩 hero 一处深色。

测:契约 'live' 解析 + runner quiet-live 对齐(变异验证)+ 基准卡锚(变异验证)+
steps ⑤ 钉 'live';108 包内 + 全仓四门绿;2.8.1 装库 SQL 核过;:8787 已跑新构建
(watcher 又断,按应急程序手起 node dist/main.js)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 14:47:25 +08:00
xcsandClaude Fable 5 5a7bb9b58f fix(w05): 数据工单三病一改 —— 档案卡上桌、类型守卫、prune 跳过已落地
2026-08-31 用户实测三连 + 「设计只是头上加了动画,操作感没变」:

① 类型「自己给上了」:data-by 行缺键落回外层同名字段(debt I3),查名 pill 摆出
  案卷锚(原 PN)的类型 —— 这一行根本没有的值。守卫认 selectCurrent 按行作用域写的
  option[selected]:真选上才摆名,没选摆「待选择」。守卫必须单层 :has ——
  :has() 里嵌 :has() 是非法选择器,整条被浏览器静默丢弃(第一版栽过,预览截图逮回)。

② 「品牌…点保存根本不会保存」:块① 行源是 sub_eval.part,那张案卷表没有主数据列,
  renderTemplate 只认行作用域 —— 保存真落草稿,显示与孪生件永远空白(四步重写后的
  潜伏缺陷:e2e 走 API、浏览器走查在 swap 版上,两条验证线都照不到,真人一填才现形)。
  行源改 mine:parts.profile 本身(S1 ④ prune:true 恒有两行),行里天生有全部列。

③ 「改回同一类型,下面还是两个类型」:②③ 自投影为绕「已落地行撤不掉」的死锁
  (2026-08-30 e2e 案例 2)特意不带 prune,没落地的陈旧行也跟着积在屏上。
  治在平台:draftDrop 加配方专用旗 skipLanded(跳过并出声「N 行已随数据工单入库、
  这一笔不清它」,不炸;模型那一路的硬拒一个字不变),②③ 恢复 prune:true ——
  死锁与积行两头都收,debt「prune 与确认即入库的架构性冲突」销账。
  quiet 拍仍不撤行(F3),清理落在每次确认工单那一拍。

④ 设计:块① 从 11 列横滑表推翻成电路板工作台 —— 深色板底上两张可就地编辑的
  芯片档案卡(SOIC 封装 SVG + 角色/在库徽 + 类型行 + 双栏主数据格),编辑态整卡
  变表单;三级占位(必填红/建议橘)上卡与参数值表;参数值表所属料号列加角色徽。
  嵌套的角色/在库查表全只读(嵌套行里有 data-col 会被 collect 并成一条,测钉死)。

契约:loop-engine port draftDrop +skipLanded/+landed(纯新增,CONTRACT.md 已记,
api-report 已刷)。测:107 包内(三锚变异验证)+ auto-runner skipLanded(变异验证)
+ 2520 全仓;2.7.0 装库 SQL 核过(档案卡宿主/守卫/胶囊/S1 四笔 prune)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 14:12:10 +08:00
xcsandClaude Opus 5 e5ca9caaa6 fix(ledger): 草稿层按诞生序读 —— 保存过的行不再跳到列表末尾
用户:「点击保存后,列表里面的元素每次排列顺序都不一致,需要保持顺序的一致性」

病根在读:`select * from ledger.__draft ... order by updated_at`,而 `put` 的 upsert
分支每次都写 `updated_at = now()` —— **人保存了哪几行,哪几行就跳到列表末尾**;
同一批写下的行(配方一次 project 铺 7 行)`updated_at` 又完全相同,并列之间没有次级键,
PostgreSQL 返回什么顺序都算数(更新一行 = 旧版本作废 + 新版本插到堆尾,物理顺序本来
就会变),于是每保存一次屏上的表就重排一次。

加 `seq bigserial`:insert 那一刻取号、upsert 的 update 分支不碰它 —— 记的正是「这一行
是第几个被写下的」,而配方铺行的顺序就是人期望在件上看到的顺序(原 PN 的参数在前、
候选的在后,组内按字典序 —— 配方那么遍历的)。单键、严格全序、没有并列。

**列加在 DRAFT_DDL 里,不写迁移**:这张表由启动时的 DDL 自建(`container.ts` 调
`DRAFT_DDL`),`landed_at` 立的就是这个先例 —— 头一版我没读到那行注释,写了迁移 0036
又撤掉(journal 已回退;开发库那条孤儿记录无害,drizzle 只按 when 比大小)。

测:ledger 集成测试补一条(对真库),同批写下五行 → 顺序原样;改中间那行、再改另外两行
→ 顺序一个字不变;值确实写进去了(不是靠没写成功换来的稳定);新写的行排末尾。
变异验证:读序退回 updated_at,这一条真红(也证明它连的是真库,不是被 skip 掉)。
pnpm gates 退出码 0(2495 测)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 11:35:03 +08:00
xcsandClaude Opus 5 2265ab7833 fix(desk): 保存那一拍对话区也别出声 —— 「在办事」不亮、say 落工作日志不上对话区
用户:「每次保存的时候右边也会出现 ai 推进中,然后会说一句话,能不能把这个也取消掉」

两处,同一个根:件上按保存会 wake 这一单(「保存即铺格」),runtime 被拨成 running。

① 右边那条办事条:`TaskChat` 的 `working` 只看 runtime === 'running'。它自己的注释
早写着「等人、等外部、已收尾都不算 —— 那时亮『工作中』就是撒谎:环没在转,是在等」——
save-wake 恰恰把「在等人」的单拨成了 running,把这条原则架空了。加 `!awaitingHuman(...)`
是**恢复**那句注释说的语义:有未办结工单 / 未回的卡就是在等人,哪怕 runtime 写着 running。
判断与工作区那一侧同一个函数。

② 那句话:quiet 拍只跑 project 与 say,于是人按几次保存,同一句交代往对话区说几遍。
对话区的纪律是**一轮一句,不是一拍一句**(2026-08-01):只有交出控制权那一拍的交代
摆到人面前;而 quiet 拍压根没交出控制权(人正对着卡填),算不上新一轮。
改:quiet 拍的 say 标 `internal`。压下去不是丢掉 —— `internal` 是两个投影共用的一个旗
(终审 I5/F2):`chat-projection` 据此挡住对话区,`task-log.machinery()` 对 `detail.auto`
开了例外,这一笔在工作日志里照旧看得见(话有插值时值的变化也查得到)。

测:quiet 的 say 标 internal 且工作日志仍在(3 条变异各红一条 —— 退回原样 / 反过来连
非 quiet 也标 / 连 auto 一起去掉;其中「反过来标」红的是既有那条「非 quiet 不标」,
两条互为护栏)。后一半(标了 internal 就进不了对话区)由 chat-projection 既有测钉着,
projectChat 没出模块边界,两半各钉在自己模块里、注释互指。
pnpm gates 退出码 0(2494 测)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-31 11:16:09 +08:00
xcsandClaude Opus 5 e38631224a test(w05): 数据工单嵌套 data-rows 探针 —— 钉死 collect() 的并行行为,平坦形状是唯一能用的路
W05「核心参数值」想做成外层按候选料分组(<section>)、内层铺参数瓷贴的嵌套 data-rows 版面。
用真实渲染管线(renderView)产出 DOM 后喂给真实 collect() 实测:rowOf() 爬到 host 的直接
子元素时只能爬到候选料的 <section>(瓷贴与 section 之间还隔着内层 data-rows 留下的一层
<div>),于是一颗料的多个参数瓷贴被并成一条记录、后写的列覆盖先写的,悄悄丢数据 ——
证实预读判断(嵌套 = 并行),用两条断言(记录数 2 非 6、input_voltage/output_voltage 两条
参数在输出里完全消失)钉死,并用四条变异验证(候选数变 3→段数跟 / 摘一整段→记录数跟 /
摘主键 data-col→字段消失)确认断言真的在读结构。

同时钉平坦退路形状(瓷贴直接是 data-edit 宿主的子元素,data-rows 不分料铺):collect()
按瓷贴数返回记录、互不覆盖,行容器换成 <div>/<li> 均成立(现有测试全是 <tr>,没人钉过
非表格行容器)。

renderView(apps/api,tsconfig.node.json)与 collect()(packages/ui-kit,tsconfig.web.json:
jsx react-jsx + DOM lib)不能进同一个测试文件——depcruise 放行,但 typecheck 真红
(TS6142: ViewContainer.tsx 用了 JSX,tsconfig.node.json 没设 --jsx),两侧拆开、
中间用同一段渲染输出的 HTML 字符串接力(两处注释互相点名,防副本漂移)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 20:22:38 +08:00
xcsandClaude Opus 5 45cec82984 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
2026-08-30 16:09:18 +08:00
xcsandClaude Opus 5 9f5b1c99d3 fix(w05): S3 补一笔只重算派生列 —— 人改了四态,结论才跟得上
浏览器走查实测:件上承诺「你改动下表里任何一条四态,这五项当场重算」,
而把使能阈值由 mismatch 改成 match 保存后,同一行的 judgement_class 仍是
hard_mismatch、risk_level 仍 high —— 行内自相矛盾,结论纹丝不动
(仍「不推荐 / 高风险 / 1 项硬约束不符」)。

断链在:S3 复用的 RELATION_PROJECTION 算结论读的是 judgement_class 的 tally,
而 judgement_class 由 difference_status 在差异明细那一笔的 when 链推出来 ——
那一笔只在 S2。人改的 difference_status 没有任何东西把它转成 judgement_class。

当初不把那一笔放进 S3 是有真理由的:它的 difference_status 走 lookup:'result:items',
而 result: 只在 analyze 那一拍存在,在 S3 重跑会把人刚改的值抹成空。

修法:加 DIFFERENCE_ITEM_RECOMPUTE 作 S3 的第一笔 —— each(mine:…) 不 prune
(避开「已落地行不许 prune」),fields 只有两个主键列 + JUDGEMENT_DERIVED_FIELDS
(judgement_class / risk_level),**一列都不多写**,稀疏 patch 绝不碰
difference_status 与人可改的叙述列;排在 RELATION_PROJECTION 之前。
when 链提成 JUDGEMENT_DERIVED_FIELDS 一处真源,S2 与 S3 spread 同一个,
新测试用 toBe 断言引用相等(不是形状相同)。

**旧测试为什么漏掉**:w05-s3-review-recompute.test.ts 的 HUMAN_EDIT mock 在同一个
patch 里同时写了 difference_status 和 judgement_class —— **把系统本该做的翻译自己
先算好了**,那座缺失的桥就此被掩盖。现改为只写人真能碰的列(difference_status +
evidence_note),并加修前/修后成对断言。

变异验证两路都真红:①拆掉配方里那一笔 → 2/3 红,复原 → 3/3 绿;
②git stash 真 case-model.ts → steps.test.ts 5 条结构测试红,pop → 38/38 绿。

包版本 2.1.8(已装,SQL 核过)。pnpm gates 退出码 0(2454 测)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 13:27:33 +08:00
xcsandClaude Opus 5 764dab342d fix(w05): 三份件的机械抽样式补齐缺失 class —— 落账件同款会炸,提前拦下
浏览器走查实测:差异评估结论审核件里三个 <svg class="zic"> 撑成 946×946,
整份文档拉到 4625px 高,正文全被挤走 —— 人打开只见灰色巨块,没法逐条核四态。

根因:.zic{width:14px;height:14px} 只在 P08_CSS(数据工单那一段),而
difference-review 用的是 ACC1_CSS。ACC1_CSS / ACC2_CSS 是从目标件机械抽出来的,
report.ts:538 那句既有注释早已承认漏过一批(.jc-vcell/.jc-cell/…),补了几个,
这几个漏了。

按「一次查干净」逐个核三份件用到的每个 class,补齐:
· .zic —— ACC1_CSS **与 ACC2_CSS**(后者即 landing-confirm,同款会炸,走查下一步就会撞上)
· .note .done / .note .need —— ACC1_CSS 与 ACC2_CSS
· .kv-box —— BIND_LANDING
· .risk-badge 缺基座形状(只有配色没有胶囊盒子)—— 人工核对徽标家族一致性时发现,机械扫描漏网

新增自测:三份件用到的 class 必须都能在它自己那段 CSS 里找到定义 ——
将来谁再加一个没定义的 class 就变红。变异验证:删掉 .zic 那条真红,补回真绿。

另:嵌套 data-by 的「只读态显示了并不存在的值」已坐实为渲染器侧机制
(递归合并时行内没有该字段会落回外层同名字段),加 it.fails 钉住,平台修法另议。

包版本 2.1.7。pnpm gates 退出码 0(164 文件 / 2450 测)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-30 12:51:14 +08:00
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 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 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 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 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 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 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 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 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 2bb974a10d fix(loop-engine): 「配方跑完了还没算办完」交人消息改 kind: observation —— R10 第六处
这条是五(六)处里最不能隐身的:它是配方没做到位时交人的那一句,人打开工作日志
想知道的第一件事就是「为什么停了」,而从前 kind: 'result' 不在 SKELETON 里,
这条消息永远进不了工作日志,单子看着像凭空卡住。只改 kind,detail 不动。

card 动作里「这一步已经发过卡,不再发第二张」那条保持 internal: true 不改 ——
它每次重入(卡开着的每一拍)都会打一遍,是典型的排障痕,machinery() 的过滤
存在的意义就是滤掉这类;摆到人面前只会把真正要看的东西淹掉。

新增回归测试:跑真实的 turn-runner.ts,让案例落到「没 guide、没发卡、doneWhen
不成立」这条分支,把捕获到的留痕喂给 structuredLog 全程,断言可见且归系统。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 07:09:10 +08:00
xcsandClaude Opus 5 fc48920c32 fix(loop-engine): 「自动收步」留痕去掉 internal —— machinery() 挡下的第五处同病
R10:评审挖出第五个同病的 —— turn-runner.ts 那条「自动收步:…卡已交回」用的是
SKELETON 里就有的 kind: 'engine-transition',但 detail.internal: true 让它照样
被 machinery() 的第一刀滤掉,永远进不了工作日志。保留 item / auto 两个键,只删 internal。

新增回归测试:跑真实的 turn-runner.ts,捕获这一笔留痕喂给 structuredLog 全程,
断言可见且归系统。

顺带修实一条名不副实的既有用例:task-memory.test.ts:453 那条看着像在覆盖
「自动收步」这件事,但 entry() 工厂不设默认 detail,原入参是 {},根本没走到
machinery() 的 internal 过滤 —— 补上生产环境真实的 detail 形状(item / auto 两键)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 07:02:38 +08:00
xcsandClaude Opus 5 154b222860 fix(loop-engine): 配方留痕改 kind: observation —— result 不在骨架里,永远进不了工作日志
R10:评审查实 Task 12 只测了 actorOf 这个纯函数,而 auto-runner.ts 四类留痕
(say / project / analyze / 出错兜底)一直用 kind: 'result',它不在 task-log.ts 的
SKELETON 里,structuredLog 在骨架过滤这一关就把它们全部滤掉,actorOf 判得再对也没用。
project 与 analyze 两笔还带 detail.internal: true,即使 kind 改对也会被 machinery() 挡下,
一并去掉。

新增回归测试:直接跑真实的 auto-runner.ts,把捕获到的 {kind, title, detail} 喂给
structuredLog 全程,断言它出现在结果里且 who 是 system —— 不是照 spec 手写理想形状。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 07:02:26 +08:00
xcsandClaude Opus 5 09885c3dbd fix(memory): 工作日志把配方做的事归系统 —— 它不是 AI 判的,也不是人办的
不加这一条,配方的留痕会掉进 default 分支记在「任务助手」名下,日志上看着像 AI 干的。
自主率的分法要认第三档:确定的事交系统,判断交 AI,责任交人。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 06:32:52 +08:00
xcsandClaude Opus 5 35ef0f1790 feat(desk): 件上保存之后叫醒任务 —— auto 步的配方才有机会按最新草稿铺格
从前只有两条回传路由调 wake;保存不是回传,于是「保存即铺格」永远等不到那一拍。
叫不醒不抬手:保存本身是成功的,不该因为叫不醒把 200 变成 500。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 06:32:45 +08:00
xcsandClaude Opus 5 6f721ffc6d fix(interaction): data-options 收尾防重改认显式标记,不再靠「有没有 selected 这个词」
评审复现了一个真泄漏:selectCurrent 用 /\bselected\b/.test(block) 当「行内那一遍
处理过没有」的代理 —— 而一行被正确处理过、且合法地落在「没有选中项」上(值不在
选项里,或这一格还没填),跟「压根没被处理过」在字面上是同一个样子。renderView
收尾那一遍拿外层作用域重新处理这块,若外层恰好有个同名裸键命中某个选项,就会把
它错误地渗进这一行 —— 界面撒谎:显示一个这一行数据里根本不存在的选中态。

改记一个跟内容无关的标记属性 data-sel-scoped:处理完(不管有没有真标出 selected)
都打上,下一遍见到标记就跳过;renderView 收尾前把它整个剥掉,不留进交给沙箱的成品。
顺带治了评审点的 Minor #1:旧写法还会被 <option> 显示文字里恰好出现的 "selected"
误判(枚举标签写成 "Selected Value" 那种)。

三条新用例:行内下拉值不在选项里 / 新加的空行,均验证不再被外层同名值渗透
(RED 证实了泄漏确实发生过);行外独立下拉仍正确标好 selected(防重归防重,
不能把本该工作的那条也防没了)。

docs/HTML报告生成规范.md 补一句:data-options 与 data-slot 不能共存,后者会被
fillOptions 摘掉不起作用(评审 Minor #2)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 06:21:26 +08:00
xcsandClaude Opus 5 81fb8179c2 feat(interaction): data-options —— 下拉的选项从台账现读
芯片类型是人在件上现造的,写死在模版里等于「只能选出包那天就有的那几种」。
与 data-rows 同一层机制:只取值不判断,选中哪一个仍由 selectCurrent 按行内的值定。
空选项一律留着 ——「还没选」得说得出来。

brief 给的实现有两处会让测试落空,已按 TDD 修正并留痕在代码注释里:
①产出的 <select> 若留着 data-slot,renderTemplate 的 SLOT 规则会把整段 <option>
  当文本槽冲掉(<select>2</select>)—— fillOptions 需要把 data-slot 一并摘掉;
②selectCurrent 只在 renderRows 的逐行流程里被调用,不在任何 data-rows 里的下拉
  永远等不到人给它标 selected —— renderView 收尾再兜底调一遍(用外层作用域),
  已经被行内定过的整块原样放过(靠判断块内是否已有 selected,不靠字符串层面
  分不清的「是不是在行里」)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 06:03:45 +08:00
xcsandClaude Opus 5 9ab2f4b695 fix(activity-registry): 配方静态审补 card/say 的表达式扫描(评审 Important)
card.title / card.ask / card.standard / card.basis[].v 与 say.text 全是 AutoExpr,
合法地可以含 lookup —— 原实现只核了 gateLedgers,这几处从没被 walkExpr 走过,
卡面上的一处笔误因此能一声不响地装进来,只在这一步第一次真跑起来时才现形。

补齐四处 walkExpr 调用 + 新增 say 分支;顺带核实 walkExpr 对 when/then/else 这类
嵌套结构确实递归到底(title 的测试用例特意嵌了一层 when/then 验证)。

同批把名不副实的「each / lookup 的地址同样要在声明里」拆开:该用例改名只测 each,
另加一条真的在 project.fields 里放 lookup 节点的用例(评审 Minor)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
2026-08-29 05:43:53 +08:00
xcsandClaude Opus 5 3636496774 feat(activity-registry): 配方静态审 —— 写错地址在装包那一刻就拒
与 refersTo 目标检查同款:代码错是必然的错,而必然的错只有在装的时候被拦下才算好查。
台账地址 / 列名 / 模型名 / 视图模版 / submit 的验收清单逐条核;
analyze 的出参没人消费报 warning —— 那是白烧一次模型调用。

顺带(R8,Task 7 评审挖出):全 auto 的步没有 guide 是正常形态,
guide.min(1) 放宽为 z.string() + superRefine —— 没 auto 又不写 guide 照旧拒装。

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