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
34 KiB
ACT-WORK-05 差异评估 · 整体重写设计
状态:已评审通过(2026-08-29,用户逐节点头)
落点:activities/part-substitution-assessment@2.0.0 + 平台侧「确定性步骤」机制
关联:ADR-014(确定性步骤,本设计是它的第一个落地者与两处改口)、ADR-004(不执行包代码)、
docs/活动开发规范.md、docs/HTML报告生成规范.md、docs/ss02-act-05-new/(业务素材)
1. 为什么推倒重来
现版 @1.7.3 是九步、两枚工具、全程 agent 驱动:每一拍问模型「下一步干什么」,
它自己 write 台账、自己 finish、自己发卡。docs/ACT-WORK-05-端到端测试记录.md 的
A–AE 发现表里,八条事故落在「本来就该是确定的」那几步上 —— 编号抄别单、判在库判错、
数据工单派给在库的料、没写骨架就发卡、库里有值当缺口、抄别的料的规格书。
用户 2026-08-29 定的新立场:
「不是把所有东西都丢给 ai 来处理,不要给 ai 暴露任何的工具,全部由系统确定性的来控制, 一定是系统先确定性的从数据库中取出数据后交给 ai 分析。」
agent 只在真需要判断的地方出现,与「人只在责任点与放行点出现」是同一条原则。 这个活动里,真需要判断的只有一件事:这一条参数,候选跟原厂比算不算事。别的全是确定的。
新版:四步、零工具、零 guide、AI 出场恰好一次。
2. 已定的八条(评审逐条通过)
| # | 决定 |
|---|---|
| 1 | 驱动:落地 ADR-014 声明式配方 + 第四动词「一次结构化 AI 调用」。活动内部不再引第二套引擎(Mastra 仍在流程包编排与模型网关的原位) |
| 2 | 台账:两张新字典表 + P-08 主记录 + 参数子表 + P-11 主/明细 + P-06 + 一张活动自持案卷。P-01 需求主记录 / P-10 原厂资料 / 客户档案本活动不碰 |
| 3 | P-06 芯片验证记录:每条替代关系固定落一条,全推荐的写「本轮无待验项」 |
| 4 | 结论谁定:逐条四态由 AI 判;推荐等级、风险、摘要、推荐理由由规则与模板算 |
| 5 | 补数据的执行工单:保存即铺格 —— 人选定/新建类型后按一次保存,系统按该类型的字段清单把参数值的行铺出来,人只填值,不加行不删行 |
| 6 | 两颗料类型不一致:硬拦,与「PN 不在库」同一条路(派工单让人改),保住「两边字段清单一定是同一套」这个前提 |
| 7 | 结论审核卡:只有「确认通过」一个钮,改全在件上;人改完不重跑 AI,只重算等级与摘要 |
| 8 | 机制规模:只实现本活动真用得到的那几式;没实现的形状装包时静态审当场拒 |
3. 数据模型
3.1 两张新字典表(parts 域,与 P-08 同域)
parts.chip_type · 芯片类型
| 列 | 型 | 档 | 说明 |
|---|---|---|---|
type_id |
integer | 主键 · auto |
人不填,基座续号(现有最大 +1) |
type_name |
text | 必填 | 「DC-DC 降压稳压器」;件上下拉显示它 |
description |
text | 建议 | 这个类型是干什么的 |
parts.type_param · 芯片类型核心参数字段
| 列 | 型 | 档 | 说明 |
|---|---|---|---|
type_id |
integer | 主键 · refersTo parts.chip_type.type_id |
属于哪个类型 |
param_name |
text | 主键 | 「输入电压范围」 |
param_desc |
text | 必填 | 这个参数是什么意思 —— AI 分析时读它 |
constraint_type |
text | 必填 · tally hard/soft |
硬约束 / 软约束 |
param_unit |
text | 建议 | 参考单位 |
关联:一行 P-08 关联一个类型;多行 type_param 关联一个类型。
3.2 P-08 及其子表的改动
parts.profile(声明后为八列,物理表其余列不声明 = 本活动不碰):
internal_pn(主键)· chip_type_id integer 必填 refersTo parts.chip_type.type_id ·
brand_name 必填 · lifecycle_status 必填 tally · specification_description 建议 ·
manufacturer_name 建议 · datasheet_ref 建议 · datasheet_version 建议
createOnly 撤掉(现版标着它,只许建新料、不许改已有行)。它当初是为了拦模型凭空建料;
模型现在不在场,行由配方按本单两颗料铺出来,凭空建在结构上不可能。而撤掉它换来一件必需的事:
给在库的老料补上「芯片类型」这一格 —— 迁移前建的行没有类型,不许改就永远补不了,单子卡死。
不声明:package 封装 · process_technology 工艺 · eol_pcn_status · application_scenarios。
封装和工艺本来就是「核心参数」 —— 它们该以类型字段的身份出现在字典里,不在主记录上另开一列。
parts.parameter:internal_pn(主键,refersTo profile)· param_name(主键)·
param_value 必填 · param_unit 建议 · param_note 建议(值说明,AI 也读它)
parts.parameter 的 createOnly 同样撤掉(理由同上:④那一笔要把在库那颗料的现值回填进草稿层,
只许建新就写不进去)。
两处退休:
constraint_type退休 —— 搬去type_param。同一个参数在两颗料上标法不一致这件事从结构上消失。param_name的自指引用墙退休(refersTo: 'parts.parameter.param_name')。它当初是为了硬逼两颗料 用同一套参数名,措辞拦了三次没拦住才砌的;现在字段清单由类型字典给、行由系统按字典铺, 名字对不上根本不可能发生。distinct一并退休。
3.3 活动自持两张(顶掉现在的 pn_check / input_gap / verdict 三张)
sub_eval.part · 本单两颗料,kind: record,caseKey: eval_ref —— 一单恰两行
eval_ref(主键)· internal_pn(主键)· role 必填 tally original/candidate ·
found_pn_ref(refersTo parts.profile.internal_pn,requiredWhen exists_status = found)·
exists_status 必填 tally found/absent · chip_type_id integer 必填 · distinct: true
(「两颗料是不是同一个类型」于是成了基座数得出来的数:不同取值恰好 1 种)
sub_eval.case · 本单案卷,同上 —— 一单一行
eval_ref(主键)· chip_type_id(本单锁定的类型)· gate_reason(入参未就绪的原因,件上摆的就是它)·
summary(结论一句话)
found_pn_ref 是「在不在库」的依据:判 found 就得拿得出一个基座查得到的料号,
refersTo 让查无此值当场拒 —— 「在库」从此是一次 lookup 的结果,不是一句断言。
exists_status 与两处 chip_type_id 每一拍由 lookup 重算 —— 人把料建好之后自己变 found,
不设 created 档、不靠谁记账。
为什么非拆成两张不可:part 是配方唯一能拿来遍历「哪几颗料要建」的数据源 ——
each: 'mine:sub_eval.part' where exists_status = absent。挤进一行案卷里就没法 where 过滤,
而不能过滤就会给已经在库的那颗料也铺一遍空参数行,_missing 当场报缺、单子死在那儿。
3.4 结论三张
substitution.relation(P-11 主记录,一单一行)
substitution_relation_id(主键)· eval_ref 必填 · original_internal_pn / candidate_internal_pn
必填 refersTo profile · chip_type_id 必填 · recommendation_level 必填 tally
recommended/conditional/not_recommended · recommendation_reason 必填 ·
difference_summary 必填 · risk_level 必填 tally low/medium/high ·
verification_requirement(requiredWhen recommendation_level = conditional)· validation_id ·
analysis_status 必填(固定 fae_confirmed)· rule_version 必填
退休:substitution_type(新流程里没有它的定夺人)· confidence_score · analysis_report_ref ·
recommendation_level 的 alternate 档(原 D02 的 configurable 来路已不存在)。
substitution.difference_item(P-11 明细,一参数一行)
substitution_relation_id(主键 · distinct · refersTo relation)· difference_item_id(主键)·
parameter_name 必填 · parameter_desc 建议(字段说明快照,归档件不必 join)· constraint_type 必填 tally ·
original_value / candidate_value 必填 · parameter_unit 建议 ·
difference_status 必填 tally match/mismatch/unknown/conditional(AI 判) ·
judgement_class 必填 tally match/soft_mismatch/hard_mismatch/unknown/conditional(系统合成) ·
impact_statement 必填(AI)· evidence_note 必填(AI)·
unknown_disposition(requiredWhen difference_status = unknown,AI)· risk_level 必填 tally · rule_version 必填
judgement_class 是这套设计的关键一列:推荐等级要数的是「硬约束不符有几条」,
而基座读数只会数单列 —— 把「约束类型 × 四态」先合成一列,规则才算得出来。
evidence_ref 改名 evidence_note「判断依据」:不写 P-10 了,AI 能给的就是依据不是文件引用,名字得说实话。
validation.record(P-06,一单一行)
validation_id(主键)· eval_ref 必填(顶掉 request_id —— P-01 不在本活动里)·
validation_internal_pn 必填 refersTo profile · validation_type 必填 tally(追加一档 parametric)·
validation_status 必填(固定 not_started)· test_condition 建议 · next_validation_action 必填
3.5 规则(唯一真源在配方里,不再另留 DMN)
judgement_class:match → match;unknown → unknown;conditional → conditional;
mismatch 且硬 → hard_mismatch;mismatch 且软 → soft_mismatch。
明细 risk_level:hard_mismatch → high;unknown/conditional/soft_mismatch → medium;match → low。
recommendation_level(读 judgement_class 的 tally 计数,自上而下第一条命中):
| 条件 | 等级 |
|---|---|
hard_mismatch > 0 |
not_recommended |
unknown > 0 |
conditional |
conditional > 0 |
conditional |
soft_mismatch > 0 |
conditional |
| 其余 | recommended |
关系 risk_level:not_recommended → high;conditional → medium;recommended → low。
模板串:
difference_summary= 「共 N 项核心参数:x 一致 / y 不一致(硬 z)/ u 待确认 / c 有条件」recommendation_reason= 按等级三形:点名硬约束不符的参数 / 点名待确认与有条件的项 / 「N 项全部一致,无待确认项」verification_requirement= 「实测并回填:<UNKNOWN 与 CONDITIONAL 的参数名,顿号连>」- P-06
test_condition= 同上参数名列表(没有就写「无」);next_validation_action= 「实测上述参数并回填 P-08 标准化参数集」/「本轮无待验项」
4. 四步案例模型
进度条四格:校验入参 → 差异分析 → 结论审核 → 落账放行。
全程 guide: ''、tools: []、declarations.tools: []。
4.1 启动参数(运营中心活动详情一键启动)
两格,都选填:
originalInternalPn「原内部料号」——x-control: combo,x-options从parts.profile.internal_pn现读;可选库里的,也可自己写一个新的candidateInternalPn「候选内部料号」—— 同款
4.2 S1 校验入参与补数据 —— 纯系统 +(条件)一张数据工单
let evalRef = cat('W05-', fmt(case:openedAt, digits))
① project sub_eval.part from rows[{pn:入参原PN, role:'original'}, {pn:入参候选PN, role:'candidate'}]
eval_ref = {in:'evalRef'};internal_pn = {col:'pn'};role = {col:'role'}
exists_status = when lookup('parts.profile' by internal_pn={col:'pn'} take internal_pn) != ''
then 'found' else 'absent'
chip_type_id = lookup('parts.profile' by internal_pn={col:'pn'} take chip_type_id, else '')
② project sub_eval.case from once
eval_ref = {in:'evalRef'}
chip_type_id = lookup('mine:sub_eval.part' by role='original' take chip_type_id, else '')
gate_reason = when 链(**全读基座数**):
两颗 PN 有一格为空 → 「原 / 候选料号还没填」
sub_eval_part_exists_status_absent > 0 → 「<PN> 库里没有,请在下面建」
sub_eval_part_distinct_chip_type_id > 1 → 「两颗料类型不一致,换一颗或把类型改对」
parts_profile_missing + parts_parameter_missing > 0 → 「还差 n 格没填」
其余 → ''
③ project parts.profile prune from each('mine:sub_eval.part') ← **两颗料都铺**
internal_pn = {col:'internal_pn'}
chip_type_id / brand_name / lifecycle_status / specification_description /
manufacturer_name / datasheet_ref / datasheet_version
= lookup('parts.profile' by internal_pn={col:'internal_pn'} take 同名列, else '') ← 回填现值
↑ 与④同构。在库那颗回填出它此刻的样子;要建那颗全空等人填。
**「在库的老料还没归类型」于是也被拦住** —— 那一格回填出来是空的,而 chip_type_id 必填。
人不必在件上「加一行」:要建哪颗料是启动参数就定死的事,行由系统摆好,他只填格子。
④ project parts.parameter prune
from each('mine:sub_eval.part') ← **两颗料都铺,不只是缺库那颗**
then each('parts.type_param' where type_id={outer:'chip_type_id'})
internal_pn = {outer:'internal_pn'};param_name / param_unit ← {col:…}
param_value = lookup('parts.parameter' by internal_pn={outer:'internal_pn'},
param_name={col:'param_name'} take param_value, else '') ← **回填现值**
param_note = 同上 take param_note
↑ 三件事一笔办完:
· 人还没挑类型 → {outer:'chip_type_id'} 是空串,where 一行不命中,什么也不铺;
他挑了、按了保存,行就出来 —— **「保存即铺格」不是特判,是这一笔的自然结果**;
· 在库那颗料**也**铺,值回填现值 → 屏上是它此刻的样子,`_missing` 不会误报;
· **「在库但参数没录全」于是自动被拦住** —— 那一格回填出来是空的,红带点名、闸按不下。
⑤ card 执行工单 · variant data · view 'input-supplement' when gate_reason != ''
gateLedgers = [parts.chip_type, parts.type_param, parts.profile, parts.parameter]
⑥ say
doneWhen sub_eval_part_exists_status_found = 2
and sub_eval_part_missing = 0 ← 两行的 chip_type_id 都有值
and sub_eval_part_distinct_chip_type_id = 1 ← 且是同一个类型
and parts_profile_missing = 0
and parts_parameter_missing = 0
doneWhen 里没有「行数 = 字段数×2」这条对账,是故意的:参数行由 prune + each 字典
铺出来,人不加行不删行,少一行在结构上不可能发生(真发生就是配方 bug,归静态审与包内自测)。
而「本类型有几个字段」根本不是基座数得出来的数 —— parts_type_param_rows 数的是本单写下的
那几行(人新建的字段),不是整本字典。给判据编一个数不出来的数,是这个仓踩过六次的同一个坑。
- 两颗都在库、类型一致、参数齐 → 一张卡都不发,首拍
doneWhen即真,直接进 S2。 - 人在件上按「保存」→ 系统 tick → 配方原样重跑:类型这会儿知道了,④把参数行铺出来;
⑤因为卡发过(
worldTouched)不再发;件就地刷新。 幂等靠同 PK 覆盖 + 发过不再发 +prune,不靠谁记得住。 - 人点「提交」= 数据工单确认 →
chip_type/type_param/profile/parameter当场进正式表 (平台 2026-08-27 的范围放行)。它们是主数据,该立刻可用,不等整单放行。
4.3 S2 差异分析 —— 纯系统 + AI 唯一一次出场
① analyze model: deepseek-v4-flash into 'result:items'
input from each('parts.type_param' where type_id=本单类型):
{ 字段名, 字段说明, 约束(硬/软), 单位,
原PN值 = lookup('parts.parameter' by internal_pn=原PN, param_name=col),
原PN值说明 = 同上 take param_note,
候选值 = lookup('parts.parameter' by internal_pn=候选PN, param_name=col),
候选值说明 = 同上 take param_note }
output schema { items: [{ param_name, difference_status, impact_statement,
evidence_note, unknown_disposition? }] }
② project substitution.difference_item prune
from each('parts.type_param' where type_id=本单类型) ← **行由字典决定,不由 AI 决定**
parameter_name / parameter_desc / constraint_type / parameter_unit ← {col:…}
original_value / candidate_value ← lookup('parts.parameter' …)
difference_status / impact_statement / evidence_note / unknown_disposition
← lookup('result:items' by param_name={col:'param_name'}, else '')
judgement_class = when 链(见 §3.5);risk_level 由它映射
③ project substitution.relation from once
recommendation_level / risk_level / difference_summary /
recommendation_reason / verification_requirement ← §3.5 规则与模板
④ project validation.record from once
⑤ say
doneWhen substitution_difference_item_rows > 0
and substitution_difference_item_missing = 0
and substitution_relation_rows = 1
and validation_record_rows = 1
为什么不写
= 本类型字段数(这份 spec 早先的伪码里写过,是错的): 「本类型有几个字段」不是基座数得出来的数 ——parts_type_param_rows数的是本单草稿写下的 字段行(人新建类型时加的那几个),不是整本字典。拿它当分母就是「编一个数不出来的数」, 本仓在这上面栽过六次。行数由prune+each 字典结构保证;判据只查「写了」与「不缺」。
这一步一张卡都不发,人不出现。 三处刻意的克制:
- AI 决定不了「有几行」。 明细的行由类型字典
each出来,AI 的出参只是拿param_name去lookup的一张查表;它漏了哪一条,那一行的四态就是空 ——required立刻把它变成_missing > 0,提交闸与放行门当场拦住。从前是from each(AI 出参),AI 少给两条就少两行, 而每一行看着都合规。 - AI 只答「这一条算什么、为什么」,值是多少一个字都不重述 —— 基座读得到的事实不让模型转述。
- 推荐等级、风险、摘要、推荐理由全由规则与模板拼。永远不会出现「有硬约束不符却写着推荐」。
- 出参不合 schema → 就地重问一次;第二次仍不成 → 留痕交人,不退回模型自由发挥。
4.4 S3 结论审核 —— 一张验收工单,人就地改
配方只有一笔:card 审核卡(variant: 'review',件 difference-review,单一选项「确认通过」,
带人工验收清单四条)。
人在件上改四态 / 影响 / 依据 → 保存 → tick → 配方重跑 → S2 的第③笔把推荐等级、摘要、风险 重算一遍(同 PK 覆盖)→ 件刷新。改几次都行,不重跑 AI。
重算发生在 S2 那一步的配方里,而 S2 已收步 —— 所以 S3 的配方里复制一份 ③④ 两笔 (纯投影、幂等),不去回退步骤。这是刻意的:一步一份配方,不做跨步回跳。
这一步不给 doneWhen(它触达世界),人点了「确认通过」即收步。
4.5 S4 落账放行 —— 纯系统派单
① project sub_eval.case.summary from once ← 模板串
② submit view 'landing-confirm' + 人工验收清单五条
人签 → 合并 substitution.relation / difference_item / validation.record / sub_eval.case
进正式表 → 发口 assessment-released。
出口载荷:evalRef · relationId · originalPn · candidatePn · recommendationLevel ·
matchTotal / mismatchTotal / unknownTotal / conditionalTotal · validationId。
4.6 预算
调人 3 次(补数据 / 审核 / 放行),cap 8;模型调用 1 次,tokens 30 万;
步数 40(保存一次 = 一拍,给人反复保存留余量);activeMinutes 60。
对照现版:90 步 / 200 万 token / 16 次调人。
5. 平台要补的能力
全是加法。E04 / I01 一个字不用改 —— 有 guide 的步照常问模型。
5.1 PlanStep.auto 配方 + 求值器
契约照 ADR-014 §4 的形状,只实现这几式:
- 动词:
project·card·analyze·submit·say - 表达式:常量 ·
{in:入参|基座读数|let}·{col:本行列}/{outer:外层行列}·{lookup:地址, by, take, else}·{when:S-FEEL, then, else}·{cat:[…]}·{fmt:…, as:'digits'}——lookup的地址除了域.对象(任务视图)/mine:域.对象,还能是拍内作用域的数组result:xxx(analyze的出参当查表用,S2 的明细就靠这一条) - 数据源:
{once}·{rows:[…]}·{each:…, where?, then?{each:…}}(两层封顶) - 地址只有四种,配方里出现的每一处都得是其中之一:
域.对象—— 任务视图 = 正式表 + 本单草稿叠加。这是配方默认该用的那一种。 (它不是新东西:engines.ts的taskData.view早就把ledger.taskView的结果铺在这个键上。 这一点是这套设计的命门 —— 人在件上新建的料与字段,按保存那一刻只在草稿层; 拿纯正式表去看永远看不见它们,参数格就永远铺不出来。)mine:域.对象—— 只有本单草稿写下的行。「哪几颗料是这一单要办的」只有它答得出in:入参数组—— 起单入参里的数组result:xxx—— 本拍analyze的出参 没有「纯正式表」这一种,配方不需要它:要区分「本单写的」与「别人放行的」,用mine:就够了。
project加prune: true:这一笔投影出来的行就是这张表在本单草稿里的全部,多出来的撤掉。 只动本单草稿层,正式表一个字不碰。(换类型时上一个类型铺的参数行必须消失。)
求值器是纯函数(域层、零 IO);执行器在 usecase,拿 ledger / humanTool / llm 端口。
必须写死的一条:每一笔 do 执行之前刷新基座读数。S2 的第③笔要数第②笔刚写下的行 ——
不刷新就恒为 0,等级永远算成「推荐」。
5.2 analyze 动词 —— 声明式 AI 调用
入参由声明组装、出参给 JSON schema、结果落进拍内作用域 result:* 供后面的 project 消费。
走现有模型网关(四元绑定、凭证隔离、录制回放、用量计入预算),不给工具、不给记忆、不多轮:
一次 generate,结构化出参。出参不合 schema → 就地重问一次 → 仍不成 → 留痕交人。
5.3 视图绑定加一件:data-options
data-options="parts.chip_type" + data-option-value="type_id" + data-option-label="type_name"
→ 渲染时把 <option> 铺进那个 <select>,与 data-rows 同一层机制:只取值,不判断。
地址写法与 data-rows 一致(域.对象 = 任务视图,mine:域.对象 = 本单草稿)。
只读那一份不用新东西 —— 走已有的 data-by 查表
(<span data-rows="parts.chip_type" data-by="type_id=chip_type_id"> 摆 type_name)。
「枚举列上下两态得说同一个字」那条账因此不会重演:上面是 label,下面也是 label。
5.4 件上「保存」之后 tick 一次
保存路由落草稿后调 TaskService.wake(现在只有两条回传路由调它)。
护栏:有未答的卡在场时,配方只跑 project / say,不发新卡、不 submit、不 analyze。
—— 只算、只写。
5.5 审核卡放宽到单选项 + 卡答复即收步
- 决策卡「少于两个选项拒发」对
variant: 'review'放宽到 1。 - auto 步发过的卡被答复、且这一步没有
doneWhen→ 收步。 人已经签了,收步是照实记录,不是替人签字 ——「人工激活项不给doneWhen」那条纪律 防的是引擎抢在人前面,这里人在前面。
5.6 submit 动词
ADR-014 §8 原写「不 submit、不发决策卡」。那是假设还有 agent 在场时的保守话; 这个活动全程无 agent,没有别人来干这两件事,不开口最后两步就没人做。
护栏:submit 只许出现在声明了 acceptanceChecklist 的那一步;提交门(判据全绿)照旧把关 ——
系统提交不比模型提交宽松。
5.7 静态审在装包时把住
与 refersTo 目标检查同款:
into/each/lookup的台账地址必须在declarations.ledger里(与写的授权面同一份名单)fields的列名必须在该对象的列声明里when的 S-FEEL 必须能解析、引用的名字绑得上(boundNames,已有)card/submit的view必须在viewTemplates里analyze的出参 schema 必须是对象,into指向的键必须被后面某一笔消费- 没实现的表达式形式当场拒 —— 说好「只做真用得到的那几式」,静态审就是这句话的执行人
5.8 引擎拍内次序
tick
├─ ① 拍首自动收步(doneWhen 为真的项;人工激活项需 worldTouched)
├─ ② 算 current
├─ ③ current.auto → 跑配方(每一笔之前刷新基座读数)
│ · 有未答卡在场 → 只跑 project / say
│ · 抛错 → 留痕 + 交人(不重试、不退回模型)
├─ ④ 发了卡 / submit → 收拍(零 token)
├─ ⑤ 没发卡 → 重求 doneWhen;真了收步,回 ②(封顶 8 轮)
├─ ⑥ 这一步有 guide → 照常问模型(老活动走这条)
└─ ⑦ 无 guide、doneWhen 不成立、也没有卡在等 → 交人(配方没做到位,是包的 bug)
5.9 观测
配方每一笔进 sys.task_memory,detail.auto = '<步名>#<第几笔>',工作日志归系统灰;
analyze 那一笔也归系统灰(2026-08-29 改口)—— 那一行说的是「系统把 N 条参数交给模型、
拿回 M 条结论」,发起与落表都是系统做的,模型只提供了其中的内容;内容的归属在它落下的
台账行上看得见,不必让日志行冒充。归属规则保持单一(detail.auto → 系统),不必为一个动词开例外。
(实测踩过的病根:kind: 'result' 不在工作日志的骨架集合里,带 internal 的又会被
machinery() 再滤一道 —— 配方留痕能不能被看见,靠的是「kind 选对 + 不标 internal」,
没有机制保证。)治理台的活动详情能把「这一步系统会做什么」直接摆出来 ——
配方是数据,渲染得出来。
6. 三张件 + 一张看板
6.1 input-supplement · 评估输入补全(数据工单)
三块竖排,一张贯穿(在办可编辑 ↔ 办结 finalHtml 静态归档)。
- 要评估什么 —— 原 PN / 候选 PN 两行(行由系统铺,人不加行)。在库的挂品牌与类型徽;
不在库的那一行摊开成可填的主数据格(类型下拉 / 品牌 / 生命周期 / 规格描述…)。
顶上一条红带说清
gate_reason。 - 芯片类型 —— 一个下拉(
data-options从parts.chip_type现读)+ 下面 「这个类型的核心参数字段」表。选已有类型 → 表只读回显;+新建类型 → 类型表加一行(type_id空着由基座续号),字段表加行(字段名 / 说明 / 硬软 / 单位)。 - 参数值 —— 左右两栏(原 PN / 候选 PN),一字段一行,两栏都可填:在库那颗回填现值 (它的参数当年没录全的话,那一格就是空的,照样点名要填),要建那颗全是空格。 行由系统铺,人不加行不删行。
底下编辑闸 + 缺项红带,与提交闸、S1 的 doneWhen 同一份数。
6.2 difference-review · 差异评估结论审核(验收工单)
- 顶:对决头(原 PN → 候选 PN、类型徽)+ 四态分布带 + 推荐等级大徽章 (规则算的,人改四态它当场跟着变)。
- 中:逐条差异明细表,一行一参数 —— 字段名 · 说明 · 硬/软徽 · 原值 · 候选值 · 单位 · 四态(下拉,可改) · 影响说明(可改) · 判断依据(可改) · UNKNOWN 去向(判 UNKNOWN 才必填)。
- 下:推荐理由 / 差异摘要 / 风险 / 验证要求 —— 只读。 它们是系统算出来的,要改就去改上面那一条四态 —— 让人直接改摘要, 等于让他给自己没做的判断盖章。
6.3 landing-confirm · 落账确认(终局验收工单)
全只读,答「这一签下去发生什么」:进哪几张表各几行(摆人读名)· 结论卡(等级 + 一句话)· 四态分布,硬约束不符的逐条点名 · 待验项清单(P-06 的测试条件与后续动作)· 发哪一口。
6.4 运营中心看板 activity-ledger
重绑到新表(最近一次已放行实例:案卷 → 两颗料 → 类型与字段 → 参数对照 → 明细 → 关系 → P-06)。
7. 判据与人工验收清单
机检(全部基座数)
| key | level | 谓词大意 |
|---|---|---|
input-ready |
L1 | 两颗都在库 · 类型一致 · 参数行数 = 字段数×2 · 参数值不缺 |
every-param-judged |
L2 | substitution_difference_item_missing = 0 且四态四档计数之和 = _rows(AI 漏一条 → 那行四态为空 → 两边都不成立) |
level-by-rule |
L2 | 推荐等级与 judgement_class 计数一致(硬不符>0 必须是「不推荐」…) |
unknown-has-disposition |
L2 | substitution_difference_item_missing = 0 |
validation-landed |
L2 | P-06 一行且 _missing = 0 |
evidence-credible |
L4 | 影响说明与判断依据站得住(裁量) |
values-traceable |
L4 | 参数值与原厂资料对得上(裁量) |
gate03-release |
L5 | 责任关,不上卡 |
人工验收清单
- S3 审核卡四条:逐条对过原值与候选值 / 硬约束项逐条看过 / UNKNOWN 都写了去向 / 影响说明能站住
- S4 放行卡五条:等级与四态一致 / 待验项清单认可 / 落账内容核对过 / 参数值来源可追 / 签了就发给下游
8. 包与退役
原地重写 part-substitution-assessment@2.0.0。跟着退役的:
- 两枚工具
compare-parameters/recommend(全仓只有 W05 用)—— 注册表停用,留墓志铭; - 判定表 D01 退役(四态改 AI 判);D02 的规则搬进配方的
when链后 DMN 也退役 —— 规则只许有一处真源,再留一份 DMN 就是同一件事两处各存一份; - 说明书
NARRATIVE瘦成「这个活动是干什么的」(全程无guide,它不再进提示词;analyze的提示词是那个动词自己的字段); - 现有五张视图模版全部作废,重做三张 + 看板重绑;
sub_eval.pn_check/input_gap/verdict三张单据退役,由sub_eval.case一张顶掉。
样例集五条(名 ≤14 字,与 e2e 案例一一对应): 「都在库·零卡直分析」「候选缺库·类型已有」「候选缺库·新建类型」「类型不一致·硬拦」「两颗都缺库」
9. 测试与验收线
- 包内自测:配方每一笔的口径钉死(字段齐全 /
when链 / 模板串 / 静态审能过 / 说明书不出现具体料号与参数名)。 - 平台侧:求值器纯函数测(七式表达式 × 三种数据源 × 两层嵌套 ×
prune)·analyze出参不合 schema 就地重问 · 卡答复即收步 · 保存后 tick ·data-options渲染 · 未答卡在场时不发第二张卡 ·submit只许在有验收清单的步 · 静态审逐条拒。 - e2e
w05五案例,验收线:5/5 released · 配方零出错 · 提交闸每单真拦 · 模型调用恰 1 次。 (「行动被拒 0」这条对无 agent 的活动自动成立,改数模型调用次数。) - 浏览器走查:「候选缺库·新建类型」那条最长的路走完(新建类型 → 加字段 → 保存铺格 → 填值 → 提交 → 结论审核就地改 → 确认 → 落账放行 → released)。
- 五道门禁以退出码验:
pnpm gates。契约变更走四步(depcruise 反查 → CONTRACT.md →pnpm api:check --update && git add api-report)。
10. 迁移次序
- 契约与求值器:
PlanStep.auto、五动词、七式表达式、prune—— 纯函数先行,单测钉死。 - 引擎接线:拍内次序 §5.8、留痕、观测;拿一个最小假活动(两步:project + card)验证机制。
analyze动词:走模型网关,schema 重问,用量计预算。- 平台三件小的:
data-options渲染、保存后 tick、审核卡单选项 + 卡答复即收步、submit动词。 - 静态审:扩到配方,把没实现的形状拒死。
- 数据模型:两张新表 +
chip_type_id由声明合并物化;种子给库里两颗料建 「DC-DC 降压稳压器」+ 核心参数字段,并把parts.parameter现有行的constraint_type迁进字段表;再加一颗 LDO 料(如AMS1117-3.3)+「LDO 线性稳压器」类型, 供「类型不一致·硬拦」那条 e2e 用。 - 活动包 2.0.0:四步配方 + 三张件 + 判据 + 样例。
- e2e 五案例 + 浏览器走查;旧 W05 跑出来的运行数据
reset-runs清掉。
11. 风险与代价
- 代码错是必然的错,模型错是概率的错。 配方写错 = 每一单都错。 对策:静态审 + 包内自测 + 五案例 e2e。但必然的错比概率的错好查 —— 这正是把它从模型手里拿走的理由。
- AI 那一步是唯一的自由面。 提示词与 schema 是它的全部约束;出参不合 schema 就地重问一次, 仍不成交人。值一律 lookup 台账,所以它就算胡说也污染不了数据,只会污染判断 —— 而判断正是人在 S3 要核的东西。
- 类型字典会被建脏(同一类型建两遍、字段名不一致)。件上下拉优先选已有、新建是次选; MVP 不做去重合并,记 debt。
prune会撤本单草稿层的行 —— 只在 S1 参数骨架这一笔用,别处不许用。静态审可以只放行这一处。- 保存一次 = 一拍。人反复保存十几次会吃步数,预算给到 40。
- 表达力有上限。上限之外的不要硬塞 —— 退回模型,或者把那一段切成独立的 agent 步。
12. 不做什么
- 不给 AI 任何工具、不多轮、不给它写台账的口 —— 它只出一份结构化判断
- 不做类型的去重与合并(记 debt)
- 不迁 E04 / I01 —— 机制先在一个活动上跑通
- 不碰 P-01 需求主记录 / P-10 原厂技术资料 / 客户档案
- 不在治理台上编辑配方 —— 那就成了低代码平台;配方随包走,改配方 = 升版本重装
- 不做三层以上的嵌套 —— 两层(类型字段 × 料)是本活动的上限
- 不在活动内部再引一套 Mastra —— 它在流程包编排与模型网关的原位不动