This repository has been archived on 2026-09-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
BPOS-TMP-REPO/docs/adr/ADR-014-deterministic-steps.md
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

399 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-014 确定性步骤:确定的活由引擎按声明做,不问模型
状态:**已实现**(2026-08-29;设计见 `docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md` §5)
背景出处:ACT-WORK-05 差异评估九步流程盘点(§2)、`docs/ACT-WORK-05-端到端测试记录.md` 发现表 A–AE。
关联:ADR-004(活动包 ABI:**系统不执行代码即知包为何物**)、`docs/活动开发规范.md` §4.4(自动收步)、
`PlanStep.humanWrites`(2026-08-28 立的墙)、ADR-005(工具契约与落地预检)。
---
## 1. 问题
用户 2026-08-28 的原话:
> 「这个不是一个确定的流程吗?根本不需要交给 ai,你直接查库不就知道有没有了吗?」
指的是 W05 的「逐个料号查库 → 不在库就铺骨架、发数据工单」。这一段没有任何判断余地,却写成了模型的 guide。
不是活动作者偷懒 —— **平台里没有别的落点**:
| 现有机制 | 能做 | 不能做 |
|---|---|---|
| 纯工具(`pure`) | 算、读台账(1.2.0 起) | 写台账、发卡 |
| 判定表(DMN) | 出结论 | 写台账、发卡 |
| `doneWhen` | 判「办完没有」,引擎自己收步 | **做事** |
| 哨兵(`entryCriteria`) | 判「能不能进这一步」 | **做事** |
| 视图绑定语言 | 把台账取出来**摆到屏上** | 写回去 |
「读台账 + 写草稿 + 发人卡」跨了三样,没有一个机制能整段承接。而引擎这三样能力**全有**
(`data.view` / `data.draftWrite` / `humanTool.issueExecution`)—— 缺的只是**声明面**。
`packages/activity-sdk/src/define.ts` 里 `run?` 的注释早就写着:
> 留着它是为**确定性子序列(结晶片段)**将来有个落点;不写就是不写,不是缺件。
这份 ADR 就是给那个落点定形。
**代价已经付过。** W05 的事故里,至少八条落在「本来就该是确定的」那几步上(§7 逐条验算)。
---
## 2. 盘点:W05 九步里哪些是确定的
| 步 | 确定的部分 | 需判断的部分 |
|---|---|---|
| **S0_bind** 建上下文 | 铸案号 · 查客户在不在库 · 写需求单(入参搬运+常量)· 逐料号查库写核对表 · 写结论行 | **零** |
| **S0d_p08** 派主数据单 | 定名单 · 铺三张表骨架 · 发卡 · 人交回后改旗 | 有 datasheet 附件时从原文抽参数值 |
| **S0d_p10** 派资料单 | 定名单 · 发卡 · 人交回后改旗 | **零** |
| **S1** 评估输入清单 | 清单九行(四行绑定投影 + 两行固定缺口 + 三行常量默认)· 发执行单 · 把 supplement 抄进需求单 | 人补的自由文本**归纳**成技术约束 |
| **S2** 标准化对照表 | 关系骨架 · 逐参数对照行(原 PN 参数集 × 候选同名行的 join)· 补封装/生命周期两行 | 单位换算;`constraint_type` 未标时归类(**应进 DMN,不进配方**) |
| **S3** 逐项差异评估 | 工具串接 · 四态照抄 · 计数 · `difference_summary` · 置信取最小 | **客户原话 → 约束式** · 影响陈述 · UNKNOWN 去向与验证条件 · 推荐理由扩写 |
| **S4** 结论审核 | 发决策卡(内容是计数投影)· approve 收步 | **读人的自由文本意见 → 定位到具体行 → 改实** |
| **S5** 拟落账 | 分析状态推 FAE 确认 · 回写需求单五项(风险取最高档、VAL 号拼接、编号、常量三项) | 只有 summary 一句人话(可模板化) |
| **T_release** 放行 | submit · 无分支去向 | 两条裁量判据 · 打回后修订 |
**agent 真正不可替代的只有四类**:①自然语言 → 结构化;②读人的意见并落到具体行;③写给人看的话;
④非结构化来源抽取(datasheet 抽值、单位换算)。第四类在实跑中一次都没发生过(没传附件)。
**结论:九步里三步整个确定(S0_bind / S0d_p10 / S5),四步八成确定,只有 S3 是 agent 的主场。**
按拍数算,一单十来拍里有七八拍在做确定性的活 —— 而每一拍都是一次采样,都有编错号、抄错行、漏一列的机会。
---
## 3. 决策
### 3.1 声明式配方,**不执行活动包代码**
`sys.activity_package.artifact`(包的 `index.js`)存了库,但**至今一次都没被执行过**。这份设计**不开这条路**:
- ADR-004 的立场是「系统不执行代码即知包为何物」;配方是**数据**,装进来仍然只是数据,
可静态审、可回放、可在治理台上摆出「这一步系统会做什么」。执行代码则相反。
- 执行第三方包要配沙箱、超时、内存墙,而 `scanArtifact` 只是文本扫描,拦不住混淆。
- 配方要表达的形状**很窄**(§3.2 五个动词),不值得为它开一个能跑任意代码的面。
**代价**:表达力有上限。上限之外的**不要硬塞** —— 退回模型,或者把那一段切成独立的 agent 步(§3.4)。
### 3.2 五个动词,复用已有的两套求值
**改口**(2026-08-29,落地时定):设计之初只列了三个,实现时补了 `analyze` 与 `submit`
(§11 待定①②随之销账,见该节按语)。配方有五个动词:
| 动词 | 做什么 |
|---|---|
| `project` | 从「某个来源的每一行」投影出台账行,`draftWrite` 落草稿层 |
| `analyze` | 一次结构化 AI 调用:系统组装入参、声明出参 JSON Schema,出参落拍内作用域供后续 `project` 消费。不给工具、不给记忆、不多轮 |
| `card` | 发一张人类工具的卡(执行工单 / 数据工单 / 决策卡),**至多一张** |
| `submit` | 提交放行,发验收卡挂起(走与模型 `submit` 同一条 `raiseAcceptance`) |
| `say` | 一句给人看的交代(模板串,不是模型生成) |
条件与聚合**不发明第二套求值**:
- 条件用 **S-FEEL**(`when`,对任务视图求值)—— 与哨兵、`doneWhen` 同一个求值器;
- 计数/聚合用**基座读数**(`_rows` / `_missing` / `_distinct_x` / tally 计数)—— 本来就在作用域里。
### 3.3 `auto` 是「问模型之前先把确定的事做了」,不是「整步替代」
`PlanStep.auto` 是一个属性。引擎在这一步的**每一拍拍首**先跑配方,然后:
- 配方发了卡 → 这一拍到此为止(**零 token**,不问模型);
- 没发卡且这一步**没有 `guide`** → 求 `doneWhen`,真了收步、进下一步(下一步若也是 auto 就接着跑);
- 没发卡且这一步**有 `guide`** → 照常问模型(半 auto:确定的已经做完,模型只面对剩下的判断)。
这样老步骤一个字不改,可以一步一步搬。
### 3.4 作用域:**一个计划项内,到交出控制权为止**
配方跑到「发卡」就结束 —— 与现有 `handsOff` 语义(ask/human/decide/listen/submit 是交出控制权)对齐。
人答回来之后,那一步若还有活,归 `guide`。
**推论(重要)**:一步要么确定,要么不是。**不做「一半 auto」的复杂机制** —— 判断点要先从那一步里切出去。
例:S0d_p08 的「datasheet 抽值」应当移出这一步(归到起单读来件),这一步才变成纯 auto。
**这条逼着活动作者把判断点想清楚,是特性不是限制。**
### 3.5 幂等
配方会被反复进入(人答卡后再 tick),必须幂等:
- **写**:`draftWrite` 本来就是同 PK 覆盖。值没变 → **静默跳过,不留痕、不算空转被拒**
(「写了等于没写」那条判据是拦模型空转的,配方不是模型);
- **发卡**:先看 `worldTouched` —— 发过就不再发。人工激活的 auto 步,**配方发卡就是启动**。
### 3.6 出错 = 交人,不重试、不退回模型
配方抛错(地址不存在、S-FEEL 算不出、卡骨架不合契约)是**活动包的 bug**:如实留痕 + 交人。
**不重试五遍,也不退回模型** —— 退回模型就是让它去凑,那正是这份 ADR 要消灭的东西。
### 3.7 静态审在装的时候把住
`staticAudit` 扩到配方(与 `refersTo` 目标检查同款,写错地址 = 每一单都错):
- `into` / `each` / `lookup` / `joinRows` 的台账地址必须在 `declarations.ledger` 里(与写的授权面同一份名单);
- `fields` 的列名必须在该对象的列声明里;
- `when` 的 S-FEEL 必须能解析,引用的名字要绑得上(`boundNames`,已有);
- 卡骨架的 `view` 必须在 `viewTemplates` 里;
- 同一张表既在 `auto.project` 里又在 `humanWrites` 里 → **警告**(铺骨架合法,但要作者确认列不重叠)。
### 3.8 观测与度量
- 配方的每一笔照常进 `sys.task_memory`,`detail.auto = '<步名>#<第几笔>'`;
工作日志三色里归**系统灰**(已有档,不必新增);
- 治理台的活动详情能摆出「这一步系统会做什么」(配方是数据,渲染得出来)—— 这是相对提示词的一大好处;
- **自主率的分法要认第三档**:现在两档(AI / 人),配方做的事既不是 AI 判的也不是人办的,是系统做的。
**改口**(2026-08-29,实现时踩出来的):`docs/superpowers/specs/2026-08-29-act-work-05-rewrite-design.md`
§5.9 写的是「`analyze` 那一笔归 AI 蓝」—— 那句要改口,**它也归系统灰**。理由:那一行记的是
「系统把 N 条参数交给模型、拿回 M 条结论」,发起与落表都是系统做的,模型只提供内容;
内容本身的归属在它落下的台账行上看得见,不必让日志行冒充成「AI 判的」。
连带修了一处会让整条留痕**静默消失**的坑:配方留痕最初用的是 `kind: 'result'` —— 那不在工作日志
的骨架集合里(`result` 是进模型叙事轨迹那一类,不是给人看的那一类);`project` / `analyze` /
「自动收步」那几笔当时又都标了 `internal: true`,会被 `machinery()` 再滤掉一道 ——
**三段各自「正确」,合起来一笔都进不了工作日志**,人打开日志想知道「系统刚做了什么」,
看到的是空的。现在统一改成 `kind: 'observation'` 且不标 `internal`(「已发过卡,不再发第二张」
这一笔仍保留 `internal` —— 它每拍都打一遍,是排障痕,不是给人看的交代)。
---
## 4. 声明形状
> **这一节是 2026-08-28 的原始提案,落地时有出入。精确语法以
> `packages/contracts/src/auto-recipe.ts` 与《活动开发规范》§4.6 为准。**
> 已知的三处差异(下面 §4.1/§4.2 的例子按原始提案写,未逐行改过,读的时候按这三条心里默认翻译):
> `workOrder` → `as`(且没有 `entry`,只有 `execution` / `decide`);**卡上不写 `view`** ——
> 画面归 `PlanStep.view`(已有机制,一处声明,§3 没提是因为原提案漏写了这条,不是后来删的);
> `{ self: true }` 与 `{ joinRows: … }` **未实现** ——`each: 'in:candidateInternalPns'` 这种
> **扁平数组**(元素不是对象、是原始值)的本项,实现里是 `rowsAt` 把每个元素包成 `{ value: 原值 }`
> 再取 `{ col: 'value' }`,不是 `self`;`joinRows`(多行一列拼成串)没有对应写法,真要拼多行,
> 目前得退回模型或切成独立步骤。
```ts
// PlanStep 新增(可选,老活动一个字不改)
auto?: AutoRecipe;
type AutoRecipe = {
/** 配方级局部值:先算一遍,后面所有表达式都能引用({in:'evalRef'})—— 整单只有一个案号,靠它 */
readonly let?: Record<string, AutoExpr>;
/** 按序执行;每一笔可带 when(S-FEEL),假就跳过这一笔 */
readonly do: ReadonlyArray<AutoAction>;
};
type AutoAction =
| { kind: 'project'; when?: string; into: string; from: AutoSource; fields: Record<string, AutoExpr> }
| { kind: 'card'; when?: string; workOrder: 'execution' | 'entry'; variant?: 'data';
title: AutoExpr; gateLedgers?: string[]; view?: string; /* 指导书等静态文本直接写 */ }
| { kind: 'say'; when?: string; text: AutoExpr };
type AutoSource =
| { once: true } // 一行
| { rows: ReadonlyArray<Record<string, AutoExpr>> } // 固定几行(S1 的默认三项)
| { each: string; where?: Record<string, AutoExpr>; // 'in:candidateInternalPns' | 'mine:域.对象' | 'ledger:域.对象'
then?: { each: string; where?: Record<string, AutoExpr> } }; // 第二层,**最多两层**
type AutoExpr =
| string | number | boolean // 常量
| { in: string } // 入参 / case:* / 基座读数 / let
| { self: true } // each 是数组时的本项
| { col: string } | { outer: string } // 本行的列 / 外层那一行的列
| { cat: AutoExpr[] } // 拼接
| { fmt: AutoExpr; as: 'digits' } // 只留数字(铸号)
| { lookup: string; by: Record<string, AutoExpr>; take: string; else?: AutoExpr } // 查另一张表的一列
| { joinRows: string; where?: Record<string, AutoExpr>; take: string; sep?: string } // 多行一列拼成串
| { when: string; then: AutoExpr; else: AutoExpr }; // S-FEEL 条件(可嵌套)
```
**字段按依赖拓扑序求值,不按声明/存储顺序**(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(整步确定,最小的那个)
```ts
auto: {
do: [
{ kind: 'card',
when: 'sub_eval_pn_check_exists_status_absent > 0',
workOrder: 'execution', variant: 'data', view: 'p10-document-create',
gateLedgers: ['parts.document'],
title: { cat: ['数据工单 · 原厂技术资料创建(P-10):',
{ joinRows: 'mine:sub_eval.pn_check', where: { exists_status: 'absent' },
take: 'internal_pn', sep: '、' }] } },
{ kind: 'say', text: '这几颗料的原厂技术资料库里查无记录,已派数据工单请你登记。' },
],
}
// guide 不写。doneWhen 照旧:parts_document_rows >= parts_profile_rows and parts_document_missing = 0
// 人在件上填完确认 → 数据即入库 → 下一拍 doneWhen 成立 → 引擎收步。**模型在这一步一次都不出现。**
```
### 4.2 例:S0_bind 的铸号与存在性核对
```ts
auto: {
let: {
digits: { fmt: { in: 'case:openedAt' }, as: 'digits' },
evalRef: { cat: ['W05-', { in: 'digits' }] },
requestId:{ cat: ['REQ-', { in: 'digits' }] },
},
do: [
// 客户不在库 → 发数据录入卡,这一拍到此为止
{ kind: 'card', when: 'customer_profile_rows = 0', workOrder: 'entry', /* … */ },
{ kind: 'project', into: 'demand.request', from: { once: true },
fields: { request_id: { in: 'requestId' }, customer_code: { in: 'customerCode' },
requirement_version: 'V1', request_type: '技术需求',
request_description: { in: 'requestDescription' },
input_snapshot_ref: { cat: ['SNAP-', { in: 'evalRef' }] },
original_internal_pn: { in: 'originalInternalPn' },
request_status: 'pending', current_activity: 'ACT-WORK-05', current_stage: 'assessing' } },
{ kind: 'project', into: 'sub_eval.pn_check', from: { each: 'in:candidateInternalPns' },
fields: { eval_ref: { in: 'evalRef' }, internal_pn: { self: true }, role: 'candidate',
found_pn_ref: { lookup: 'ledger:parts.profile', by: { internal_pn: { self: true } },
take: 'internal_pn', else: '' },
exists_status: { when: 'found_pn_ref != ""', then: 'found', else: 'absent' } } },
// 原 PN 那一行同理(from: { once: true },role: 'original')
{ kind: 'project', into: 'sub_eval.verdict', from: { once: true }, fields: { /* … */ } },
],
}
```
「在不在库」从此是一次 `lookup`,不是模型的一句断言 —— 08-27 那条「原 PN 不在库却判在库」结构性消失。
---
## 5. 引擎行为(拍内次序)
**改口**(2026-08-29,与 `turn-runner.ts` 实际次序对齐 —— 落地时多了 `analyze`/`submit`
两个动词,分支也比原提案细):
```
tick 开始
├─ ⓪ 这一单还有任何卡没交回(不论哪一步发的)→ 这一拍不问模型;
│ 若当下这一步声明了 auto → 静默跑一遍它的配方(quiet:只跑 project / say,
│ 「保存即铺格」——人在件上按了保存,系统按最新草稿把该铺的行铺出来)→
│ 记痕「等人交回」→ status awaiting,这一拍到此为止,不进入下面任何一步
├─ ① 拍首自动收步(现有:doneWhen 为真的项,人工激活项需 worldTouched)
├─ ② 算 current(当下在办的那一步)
├─ ③ current.auto 存在 → 跑配方(走到这里已经过了 ⓪ 那道门,`quiet` 恒为 false;
│ `do` 数组按声明顺序逐笔执行,五个动词可任意穿插)
│ ├─ 求 let
│ ├─ 按序执行每一笔(when 假则跳过;project 写/撤过东西后,下一笔重新取一次读数)
│ │ · project → 逐行 draftWrite(同 PK 覆盖;值没变静默跳过)
│ │ · analyze → 一次结构化 AI 调用,出参落拍内作用域 `result:<into>`,供后续 project 消费
│ │ · card → 先看 worldTouched,发过就当这一笔已完成、跳过(不当被拒);
│ │ 发了标 worldTouched、烧 humanCalls,这一笔之后停(交出控制权)
│ │ · submit → 记下 rationale,这一笔之后停(交出控制权)
│ │ · say → 进对话区
│ ├─ 每一笔留痕(by=系统,detail.auto)
│ └─ 抛错 → 留痕 + 交人(不重试、不退回模型),这一拍到此为止
├─ ④ 配方发了卡 → 标 worldTouched、按声明推画面,status awaiting,收拍(零 token)
├─ ⑤ 配方 submit → 发验收卡挂起(走与模型 `submit` 同一条 `raiseAcceptance`),status awaiting,收拍
├─ ⑥ 没发卡也没 submit,但配方写过东西(project 落了行/撤了行)→ 重求 doneWhen;
│ 真了收步,下一拍重新导航(封顶 8 轮,与现有自动收步同款)
├─ ⑦ 卡答过了就收步(近道):doneWhen 未声明 · 卡已发过(worldTouched)· 配方会发卡
│ · 这一步**没有** `guide`(全 auto)→ 引擎直接把这一项收掉,不问模型
│ (**半 auto 的步 —— 声明了非空 `guide` —— 不走这条**,R9,见 §3.3)
├─ ⑧ 这一步有 guide → 照常问模型(半 auto)
└─ ⑨ 无 guide、doneWhen 不成立、也没有卡在等 → **交人**(配方没做到位,是包的 bug)
```
**预算**:配方不烧 token(`analyze` 是例外 —— 它是一次真实的模型调用,烧 token、计入预算,
但不计入 `stalledTurns` 熔断计数,见 debt.md);发卡烧 `humanCalls`;配方段在同一 tick 内,不额外计 step。
---
## 6. 与现有机制的衔接
| 机制 | 处置 |
|---|---|
| `doneWhen` | 不变。auto 步几乎都该有 —— 它做的事是可验的 |
| 哨兵 `entryCriteria` | 不变。auto 步照样要满足入场前提才在场 |
| `worldTouched` | 配方发的卡同样标 —— 它是真发了 |
| `humanWrites`(2026-08-28) | **配方豁免**:那道墙防的是模型即兴,配方是审过的声明。铺骨架合法,人填内容不冲突(静态审提示列不要重叠) |
| 写三分 | 不变:配方只写**草稿层**,落正式表仍靠放行 / 数据工单确认入库 |
| 「一单一卡」 | 不变:配方至多一张卡 |
| 熔断 | 配方不会「被拒」,不进 `stalledTurns` |
| 试跑 / 回放 | 配方是纯数据求值,同输入同结果 —— 可回放。**配方里没有时钟与随机**(时刻取 `case:openedAt`) |
| 记账旗 | **该删的删,不是该 auto** —— `input_confirmed_total` 这类基座本就数得出来 |
---
## 7. 回溯验算:历史事故有几条会结构性消失
| # | 事故 | 落在哪 | auto 后 |
|---|---|---|---|
| 1 | 编号从正式表里**别单的案号**里挑 | S0_bind 铸号 | 消失(模板铸号) |
| 2 | 原 PN 不在库却判「在库」(发现 I) | S0_bind 查库 | 消失(lookup 现查) |
| 3 | 数据工单派给了**在库**的那颗(发现 J) | S0d_p08 名单 | 消失(where absent) |
| 4 | 压拍没写骨架就发卡,人打开是空表(发现 Q) | S0d_p08/p10 | 消失(配方按序) |
| 5 | S1 清单 11 行全漏 `input_item`,人无处可填 | S1 前半 | 消失(字段声明齐全) |
| 6 | 库里有值的参数被 S1 当缺口再问一遍(发现 AB,三次复现) | S1 前半 | 消失(清单是投影) |
| 7 | P-10 抄**原 PN 的规格书**安在候选头上(发现 AE) | S0d_p10 | 消失(模型不出现) |
| 8 | `finish` 撞 worldTouched 墙五拍熔断(发现 AE 同单) | S0d_p10 | 消失(那一步不问模型) |
**八条落在会被 auto 掉的三步半上。** 剩下的事故(工具没调、比对入参抄错、验收卡面孔、重做循环)
不在这个范围内 —— 它们该由别的机制治,这份 ADR 不承诺。
---
## 8. 不做什么
- **不调工具**:工具有副作用面与预算面,先不给(S3 的 `compare → recommend` 串接留作待定;
实现时换了个更窄的口子解掉同一个需求,见下方「两处改口」与 §11 待定②的销账)
- ~~**不 `finish`、不 `submit`、不发决策卡**:收步归 `doneWhen`,终局归 submit,岔路口本就是判断~~
(**`submit` 与发决策卡两条已撤**,见下方「两处改口」;`finish` 这条不变 —— 配方从不
显式收步,收步仍旧只有「引擎按 `doneWhen` 自动收」与「半 auto 步问模型、模型 finish」两条路)
- **不写正式表**:仍走草稿层
- **不做三层以上的嵌套**:两层(关系 × 参数)是 W05 的上限;超出就别 auto
- **不在治理台上编辑配方**:那就成了低代码平台;配方随包走,改配方 = 升版本重装
- **不承诺消灭 agent**:§2 的四类判断照旧归它
> **两处改口**(2026-08-29,ACT-WORK-05 重写时用户定):`submit` 与**发决策卡**这两条禁令撤掉。
> 它们原本是**假设还有 agent 在场**时的保守话;而全程无 agent 的活动**没有别人来干这两件事**,
> 不开口最后两步就没人做。护栏没松:`submit` 只许出现在声明了人工验收清单的那一步,
> 提交门(判据全绿)照旧把关 —— **系统提交不比模型提交宽松**;决策卡只在 `variant:'review'` 时
> 放宽到单选项(它不是岔路口,是一道签字关)。
---
## 9. 代价与风险
1. **活动作者要写结构化声明,不能再靠散文。** 但那些散文本来就在(而且更长、还不能测);
配方**能单测**(纯数据求值,包内自测钉死)。
2. **代码错是必然的错,模型错是概率的错。** 配方写错 = 每一单都错。
对策:静态审(§3.7)+ 包内自测 + 安装两审的回放审拿样例入参跑一遍配方。
**但必然的错比概率的错好查** —— 这正是把它从模型手里拿走的理由。
3. **立场要改口。** 「组织的工作定义为可编排的工作流……活动是完成一项任务的 Agent 机制」——
加一句:**agent 只在真需要判断的地方出现,与「人只在责任点与放行点出现」是同一条原则。**
确定的事交系统,判断交 AI,责任交人。
4. **把有判断的地方硬写成规则的风险。** `constraint_type` 的 hard/soft 归类按品类会变 ——
这类**进 DMN 判定表,不进配方**;配方里只许出现「取值、拼装、投影、发卡」。
5. **现在的「确定」有一半是靠强制命名一致换来的。** S2 能纯 join,前提是「候选与原 PN 参数名一模一样」
(P-08 建骨架时用中文名硬压 + 门上的行数对账)。**一旦料号来自外部导入**,同义归一就回来了 ——
那是判断,是 agent 的活。**auto 化不等于永远不需要 agent。**
---
## 10. 迁移次序(每搬一步都要五案例 e2e:released + 被拒 0 才算数)
1. **`S0d_p10_document`** —— 最小、纯粹、刚出过事;拿它验证机制(配方 + 静态审 + 留痕 + 观测)
2. **`S5_prepare_landing`** —— 整步确定、不发卡;验证「不发卡时接着走 doneWhen 收步」那条路
3. **`S0_bind`** —— 整步确定、含一张录入卡的分岔;验证 `let` 铸号与 `lookup` 存在性
4. **`S1_input_confirm` 前半** —— 清单投影 + 发卡;半 auto(收口仍归 guide)
5. **`S0d_p08_master`** —— **先把 datasheet 抽值切出去**,再 auto
6. **`S2_normalize`** —— 两层嵌套,最难;搬之前先确认它与比对工具的 join 是否重复(§11)
---
## 11. 待定(要用户定的)
1. ~~**决策卡要不要给配方**~~:S4 第一张卡的 ask 内容是计数投影,确定;但决策卡是岔路口,给了会不会滑坡?
**已定:给了**——`card.as: 'decide'`(`variant:'review'` 时放宽到单选项),见 §8「两处改口」。
2. ~~**工具串接要不要做第四个动词**~~:S3 的 `compare → recommend` 之间模型只是转手 counts,却占掉整整一拍。
**已定:没照原样给**——没有做「按序调用多个工具」那个第四动词,换成了更窄的 `analyze`
(一次结构化 AI 调用,不给工具不给记忆不多轮),见 §3.2 与 `docs/debt.md`「平台缺『确定性步骤』」条。
3. **`humanWrites` 该不该细到列**:现在是表级。auto 铺骨架 + 人填内容之后,列级更准。
4. **S2 与比对工具的 join 是否重复**:`compare-parameters` 1.2.0 起自己从台账取参数对,
而 S2 让模型抄的 `difference_item` 对照表是**同一份 join**。一份事实做了两遍,一遍还是手抄的 ——
要么 S2 auto 化,要么让工具的出参直接落 `difference_item`(那是另一个机制:工具出参绑台账)。
5. **`T_release` 第三条裁量判据**「推荐等级与差异明细一致」就是工具的定级规则本身,
让模型再裁一次是给自己盖章 —— 该删(与 08-01 在 A1 上拆掉 L4 同款)。