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

26 KiB
Raw Permalink Blame History

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(多行一列拼成串)没有对应写法,真要拼多行, 目前得退回模型或切成独立步骤。

// 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(整步确定,最小的那个)

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 的铸号与存在性核对

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 同款)。