Files
JX-BPOS/docs/活动开发规范.md
T
ChaiHaoandClaude Opus 5 259192f736 人手改的那一手真正落得下去;界面不撒谎那几处补齐
手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的:

件上就地改
· 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名,
  于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 ——
  而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。
· **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。
  删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)——
  「没回传就是删了」会把一次不完整的回传变成一次删表。
· **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。
· 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。

判据与门
· 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来,
  人填好的目标价它照样判「客户未提供」的毛病由此消掉。
· 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。
· 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开,
  于是一单没打标就进了放行门。三个数全由基座现数。
· 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。
  **指向两扇打不开的门,比不指还糟。**

界面不撒谎
· 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。
· 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、
  往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。
· 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。
· 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。
· 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。

A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。
五道门禁全绿(971 passed)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
2026-08-02 13:41:44 +08:00

633 lines
47 KiB
Markdown
Raw 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.
# 活动开发规范
> 2026-07-30 第二版(第一版是「现状如实、差距列账」的勘误体;本版按跑通后的现状重写成
> **开发手册**)。目标读者:**开发活动的 AI 或人** —— 读完这一份,你应当能独立把
> `docs/MVP示例工作流.html` 里的一个 SOP 节点做成一个能 `released` 的案例活动,
> 不必读 `apps/api` 的源码。
>
> 范本:`activities/quote-intake`(A1 客户RFQ受理,2026-07-30 已两条路径 `released`、
> 常路 7-8 拍收口)。**本文与代码冲突时以代码为准**,发现即改本文。
> 相关:`docs/编码规范.md` §8(平台契约)、各模块 `CONTRACT.md`(模块级不变量)、
> `docs/debt.md`(欠账)。历史语义源(操作系统 / 活动模型两卷)已归档 `docs/archive/`。
---
## 1. 世界观:一个活动是怎么跑起来的
**活动 = 任务的定义。** 编排引用一个活动,运行时长出一个任务(恰一名负责人)。
活动在系统外开发、经安装通道入库(`pnpm activities:install`),版本恒新增、
同版重装 no-op、在途任务按启动时版本跑完。
**活动没有代码逻辑。** 2026-07-29 范式退役后,活动一律**案例活动**:行为全部写在
清单里(案例模型 CMMN + 说明书 + 判定表 DMN + 技能 + 台账声明 + 报告模版),
「系统不执行代码即知包为何物」。`src/index.ts` 只是被静态审扫描的产物文本
(证明零环境能力),`defineActivity({ name })` 即可,`run` 可选且通常不写。
**推进由 Loop 驱动,一拍的解剖**(`loop-engine/internal/usecase/turn-runner.ts`):
```
语义引擎发布局面 ──> Agent 决断一串行动 ──> 引擎逐个执行 ──> 留痕 ──> 下一拍
· 当下可为(Enabled) (1-6 个,一次模型调用) 被拒停在谁那儿
· 进行中(Active) tool 后截断
· 目标缺口(未完成项) 挂起型收拍
· 最近轨迹 + 工具清单 + 台账字典 + 任务数据
```
**拍是按模型调用计费的** —— 每拍把说明书 + 轨迹 + 局面整段重读一遍。
所以一切设计都围着「少一拍是一拍」:一拍连做(写两张台账顺手 finish)、
批量工具入参(一单所有 PN 一次传)、说明书把规则写死(判断不花拍)。
A1 的账:六步的活,常路 **7-8 拍**收口(起初 21 拍 —— 差距全在设计,不在模型)。
## 2. 四条铁律(违者拒装或被引擎拒)
| # | 铁律 | 机制承载 |
|---|---|---|
| ① | **只读世界**:活动只能读台账、调读类工具、问模型、发卡问人 | 产物文本静态审:`process.env` / `fetch` / `node:*` / `eval` 命中即拒装;depcruise 双向隔离 |
| ② | **写只有草稿与意向**:写台账 = 草稿层(不抛数据事件);外部动作(发邮件等)= 登记动作意向(入参冻结 + 幂等键)。**登记那一刻世界上什么都没变** | `write` 行动只落草稿;写类工具调用自动转意向 |
| ③ | **终局唯放行**:任务完成的唯一出口是人类验收通过 —— 那一刻才草稿合并、意向执行、输出口发射 | `submit` = 发验收卡挂起;放行落地六步(§11.4) |
| ④ | **活动不自转**:推进由引擎逐拍驱动;预算(步数/token/时长/调人次数)与停滞熔断由 Harness 环外强制,活动不得自查自守 | 迁移拓扑把 Completed/Terminated 设为死胡同;AI 不得产生终局迁移 |
## 3. 开发流程(从 SOP 节点到 released)
1. **读业务**:`docs/MVP示例工作流.html` 找到你的节点 —— 任务定义卡(执行人/触发/目标)、
作业指导书(S1…Sn + 判断菱形)、判定表、留痕 · SLA。
2. **列台账**:这个节点的成品是哪几张表?每张的主键与列(§6.3)。留痕清单就是答案。
3. **列判定表**:图上的菱形与「规则 · 约束」区的表,一张不落进 DMN(§8)。
4. **建案例模型**:**图上有几步就是几项**(§7),案卷项 id = 任务视图键名。
5. **写说明书**:每步一段,写死规则、写明落点计划项与要写的台账列(§7.2)。
6. **声明工具**:需要什么就声明什么名字;系统没有的运行时会转人(§9)。
7. **配报告模版**:一个局面一张(§10.3)。
8. **写清单其余区**:契约七件对齐台账列(§6.2 的三条对齐线),样例 ≥1 条。
9. **自测**:`pnpm vitest run activities/<id>`(四段,§12.1)。
10. **构建安装**:升 `packageVersion` → `pnpm activities:install`(同版重装 no-op!)。
11. **试跑**:登记触发起单(试跑一律用 AI 账号)→ 看任务轨迹逐拍对账 →
验收签字 → 查落地行与出口载荷。卡壳对照 §15 排障表。
12. **调优**:轨迹里每一条「行动被拒」都是设计欠账 —— 修模型或说明书,不怪模型。
## 4. 包结构与构建安装
```
activities/<activity-id>/
├── package.json # @activities/<id>,private,type:module
├── src/
│ ├── index.ts # export default defineActivity({ name });行为不在这
│ ├── manifest.ts # 清单六区 + 导出 DMN 常量(测试直接求值)
│ ├── case-model.ts # 导出 CMMN_XML 与 NARRATIVE(双表示同版本)
│ ├── skills.ts # 导出 Skill[](id 用 skillIdSchema.parse)
│ └── views/report.ts # 报告模版(viewShell/kvCard/tableCard/... 拼装)
├── __tests__/activity.test.ts # 自测四段;只准 import @sdk / @contracts / @sfeel
└── dist/{manifest.json,index.js} # pnpm activities:build 产出(index.js 现为源码文本,债 M5-3)
```
- **构建**:`pnpm activities:build`(新包要在 `tools/build-activities.ts` 的 PACKAGES 里登记)。
- **安装**:`pnpm activities:install` —— 只装活动包,不碰治理台配置。
**别用 `db:seed` 装活动**:它重写整套治理台配置。真活动包要在
`apps/api/src/app/seed-activities.ts` 的 `REAL_PACKAGES` 登记(经安装通道读文件,不经 import)。
- **版本纪律**:改了任何内容必须升 `identity.packageVersion` —— 同版重装静默丢失(踩过)。
卸载只置状态不删行;资产播下一版不覆盖。
- 流程包节点用 `activityId` 引用活动;同名旧槽位要**退役卸载**(库里两个同名活动会骗人)。
## 5. 隔离与守卫
活动包**只准 import `@sdk` / `@contracts` / `@sfeel`**(depcruise 双向隔离:activities 不进
apps,apps 也不 import activities —— 之间只有安装通道)。产物文本静态审黑名单:
`process.env`、`require(`、`node:fs/net/child_process`、`fetch(`、`XMLHttpRequest`、
`eval(`、`new Function(`。视图模版含 `<script` 或外链即拒(容器是 `sandbox=""` iframe)。
## 6. 清单六区(`packages/contracts/src/manifest.ts`)
### 6.1 identity
```ts
identity: {
activityId: activityIdSchema.parse('quote-intake'),
name: '客户RFQ受理', // 人读名
packageVersion: '2.4.0', // 改内容必升
category: '统一报价', // 活动库唯一的一维分类
sdkVersion: '0.1.0-m5', // 与 SDK 精确相等,否则拒装
checksum: 'dev-checksum-…', // 非空即可(开发期)
}
```
### 6.2 契约七件(`contract7.ts`)
| 件 | 写什么 | 硬规矩 |
|---|---|---|
| ① input | `data`(JSON Schema)+ `messageAllowed / messageHint` | 起单**宽进**:缺 required 不拒单,由基座发补全卡(§11.1);`properties[k].title` 写中文 —— 补全卡上给人看的就是它 |
| ② output | `mandatory[]` + `selective[]` | mandatory 每次放行必发;selective 每组 ≥2 口、**恰发一口**,人在验收单上选,选中口名注入 `outcome` 供下游网关 |
| ③ requiredDeliverables | `{key, label, ledger}[]` | **`ledger` 必写**(`域.对象` 台账地址):预检数**本单草稿**在该地址的行数,缺一不得提交放行 —— 不给地址就只能靠活动自报,自报等于没有门 |
| ④ acceptanceCriteria | `{key, label, level: L1-L5, predicate?}[]` | **L1/L2 必带 predicate(S-FEEL)**,提交时对任务视图当场机检;L3-L5 交人。**能算的判据一律降到 L1/L2** —— 交给人的判据,人忙时会盲签(踩过) |
| ⑤ revisableAtRelease | `{key, label, ledger}[]` | 放行时人可当场改的值(如两枚去向旗);AI 拟值由基座放行前**从台账现读**,不让模型重述;改完落回声明的地址 |
| ⑥ prohibitions / authorization | 禁区短而硬;`humanCallBudget` = 调人预算 | `preauthorizedActions` 是死代码,**不要写** |
| ⑦ resources | `tools / skills / models / roles / budget{steps,tokens,activeMinutes,humanCalls}` | **budget 按实测给,留一倍余量** —— 给太紧等于让 Harness 杀正在干的活(A1:steps 60 / tokens 400k / 60min / 调人 4;实测常路 ~8 拍 5-8 万 token) |
**三条对齐线**(错位不报错,只在放行那一刻「取不到值」,查起来一层皮 —— 自测四段第 ④ 段钉死):
1. `requiredDeliverables[].ledger` 的地址必须在 `declarations.ledger` 里声明过;
2. L1/L2 谓词引用的标识符必须是**声明过的台账列**(且用汇总列,见下);
3. 出口载荷的**标量字段**必须有台账列承载 —— 载荷从落地事实推导:数组字段 ←
落地行整体,标量 ← 摊平事实(驼峰/下划线两认)。声明了没列承载的标量,
下游网关取不到值全流死路(踩过:两枚去向旗没有列,G1 全断)。
**汇总列纪律**:任务视图把草稿行摊平到顶层是「后写的赢」——拿逐行列(如
`missing_key_fields`)做谓词只会验到最后写的那一行,那不叫机检叫抽样。
所以验收谓词读**一单一行的汇总对象**(A1 的 `quote_intake.verdict`:`line_total` /
`missing_key_total` / `untyped_total` / `type_a_total` / `alt_request_total`…),
汇总列与逐行列**故意不同名**,免得互相盖掉。
**派单对账模式**(A1 实例,值得照抄):「C 类几条就该开几张替代评估请求」这类
一致性,把两边的条数都落成汇总列,谓词写 `alt_request_total = type_c_total` ——
机检,不交人。
### 6.3 declarations
```ts
declarations: {
models: ['deepseek-v4-flash'], // 自定义模型名;非空才有 LLM 五件上总线
tools: [{ name: 'crm.history-price-read' }, { name: 'quote-intake.classify' }],
ledger: [ { domain, object, label, fields: [{name, type, label, primaryKey, masked, required?}] }, … ],
listeners: [ { origin:'static', eventType, keyFilter, semantic:'observe'|'complete-step',
deadline:'P1D', escalation:['nudge','escalate'] } ], // 时限必配,悬空拒装
harness: { memoryWindow: 20, stallAfter: 'PT45M',
nudge: [{ after:'PT15M', action:'nudge' }], retry: { times:2, backoff:'PT30S' } },
}
```
- **台账声明就是授权面**:运行时只准写这里声明的地址与列名,编出来的列当场拒。
它同时是喂给 Agent 的字典(不给字典,它只会照想象编列名)。
- **`required: true` = 这一列缺了就是活没干完**(可选,不建 NOT NULL —— 草稿层的本分
就是过程中允许缺)。标了它,基座就数得出 `域_对象_missing`(缺必填几行)与
`_missing_keys`(缺在哪几行、缺哪一列)——**机检判据该读这个,不读活动自报的汇总列**。
栽过两次:自报的数要模型自己重算,漏算一次那道门就再也开不了(第二次是连跑十轮里三轮)。
- 标识符小写字母开头、字母数字下划线。域归属:活动自持域用活动名
(`quote_intake`);跨活动事实只有两条路 —— 输出口载荷,或共享域(`shared`/`intel` 等)。
- 物化三律:最大化合并、最宽类型(integer→number→text 加宽链)、**只增不破坏**
(新增主键 = 冲突拒装)。
- 监听语义只有 `observe`(记观察交 Agent 裁量)与 `complete-step`;「客户改需求要回炉」
这类业务反应写 `observe`,回炉是 Agent 看到观察后自己的决定。
- `contract.resources.tools` 与 `declarations.tools` 是双份声明,运行时只读 declarations ——
两处写一致。
### 6.4 assets
```ts
assets: {
businessModel: { cmmnXml: CMMN_XML, narrative: NARRATIVE, dmn: INTAKE_DMN }, // 双表示同版本,空任一拒装
skills: INTAKE_SKILLS,
viewTemplates: [ { name:'intake-overview', html: … }, … ], // 一个局面一张(§10.3)
cardSchemas: {},
distillHooks: [ { on:'terminal', prompt:'归纳本单…经验' } ], // 封卷后蒸馏进活动记忆
}
```
### 6.5 samples · 6.6 entry
样例 **≥1 条**(首装无历史时,静态审 + 样例集替代回放审);把常路与最长路径都给上:
```ts
samples: [
{ name:'三条料 A/B/C 各一', input:{ customerCode:'C001', rfqRef:'RFQ-…', subject:'…',
body:'LM358DR 12K/年 目标价8.0 交期8周;NE555P 5K/年 交期6周;TL072CDR 3K/年 …' },
expect:{ ports:['intake'] } },
{ name:'缺三要素 → 走追问补全', input:{ …, body:'LM358DR 报个价,数量交期没定' },
expect:{ ports:['intake'] } },
],
entry: { file: 'index.js', checksum: 'dev-entry-…' },
```
## 7. 案例模型(CMMN)
### 7.1 建模纪律 —— 每一条都是实测账
1. **图上有几步就是几项,不多建一件。** A1 第一版把 6 步建成 12 项(自加两个阶段、
两个里程碑、一个裁量项、一个事件监听器)—— 业务一分没多,模型全程跟 harness 较劲:
阶段当落点、里程碑收不收、项先 start 还是先干、编不存在的 id……一单烧二三十万 token
还常收不了口。重写成扁平 7 项(S1…S6 + 放行)后零被拒。阶段/里程碑/裁量项是 CMMN
有的东西,「有」不等于「该用」—— 要能换来业务上说得出的好处。
2. **案卷项 id 就是任务视图里的键名。** 前提的 onPart 认的是「这个键有值了」——
起单入参(`rfqRef`)与台账列(`line_total` / `missing_key_total`)都会摊平到任务视图
顶层,照它们命名。名字对不上 = 前提永不满足 = **第一个计划项都不 Enabled**,
整案一步走不动,而每一层单看都「正常」(踩过)。
3. **`manualActivationRule` 只标触达世界与关键交付的项**(A1 标了两项:
受理需求交人审定 `S3_confirm`、受理放行 `T_release`)。标了的项必须 Agent 显式
`start` 才能干活;别的项引擎就地过 Active —— 为一次草稿写单烧一拍去「按一下」是纯仪式。
**凡触达世界的项一律不给 `doneWhen`**:给了就等于给引擎一把替人签字的钥匙。
3.5. **人的门要建成一项,不要挂成某一步的条件分支。**
A1 栽过一次(2026-08-01 用户指出):「人确认受理需求」原本不是一项,而是归一那一步
收步条件里的半句 —— `missing_minor_total = 0 or answered_total > 0`,
意思是**只在缺东西的时候才叫人**。于是一封字段齐全的询价进来,这半句在拆完那一刻
就为真,引擎自己把那步收了,人一眼没看过受理需求,下一次被叫到已经是终局验收。
用户原话:「我都没确认需求 它就让我验收了」。
**判据是「这件事办完了没有」,不是「要不要办」**;要不要经过人是模型结构的事,
写进判据就成了「不缺就不必经过人」。做法:单独一项 + 人工激活 + 不给 `doneWhen`,
并给它一个只有人的回复才产生的计数列(A1 是 `confirmed_total`)当下一步的入场前提。
4. **`repetitionRule` 标常来回的项**(逐条登记、追问补全)——驳回重入靠它给新实例。
5. **判断菱形 = 两条互斥前提,不是一个计划项。** D1「判完整度」在图上是菱形,
在模型里是 `s_incomplete`(missing_key_total > 0 → S2 入场)与 `s_complete`
(line_total > 0 and missing_key_total = 0 → S3 入场)两个 sentry。
6. `autoComplete="true"`:必做项全 Completed 且无 Active 项时,引擎**不问模型**直接提验收。
事件监听器不算必做项、不进缺口(它是等待岗不是成品)。
### 7.2 A1 的模型骨架(照抄改名即可)
```xml
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL">
<case id="intake">
<caseFileModel> <!-- id = 任务视图键名(纪律 2) -->
<caseFileItem id="rfqRef" name="询价单号(开案即有)"/>
<caseFileItem id="line_total" name="登记出的条目数"/>
<caseFileItem id="missing_key_total" name="缺关键字段条数"/>
<caseFileItem id="history_days" name="历史价判据"/>
<caseFileItem id="untyped_total" name="未打标条数"/>
<caseFileItem id="need_sourcing" name="去向旗"/>
</caseFileModel>
<casePlanModel id="plan" name="客户RFQ受理" autoComplete="true">
<sentry id="s_incomplete">
<onPart caseFileItemRef="missing_key_total"><standardEvent>update</standardEvent></onPart>
<ifPart><condition>missing_key_total &gt; 0</condition></ifPart>
</sentry>
<sentry id="s_complete">…line_total &gt; 0 and missing_key_total = 0…</sentry>
<sentry id="s_basis"><onPart caseFileItemRef="history_days">…</onPart></sentry>
<sentry id="s_typed">…untyped_total = 0…</sentry>
<sentry id="s_routed"><onPart caseFileItemRef="need_sourcing">…</onPart></sentry>
<humanTask id="S1_register" name="接收登记:逐条落统一受理记录,结算条数与缺口数">
<itemControl><repetitionRule id="r1"/></itemControl>
</humanTask>
<humanTask id="S2_ask_back" name="追问补全信息:按缺口清单发补全卡">
<entryCriterion sentryRef="s_incomplete"/>
<itemControl><manualActivationRule id="m2"/><repetitionRule id="r2"/></itemControl>
</humanTask>
<humanTask id="S3_normalize" name="…"><entryCriterion sentryRef="s_complete"/>…</humanTask>
<humanTask id="S4_history" name="…"><entryCriterion sentryRef="s_complete"/></humanTask>
<decisionTask id="S5_classify" name="…按三分类规则P-03打标" decisionRef="p03-type">
<entryCriterion sentryRef="s_basis"/>
</decisionTask>
<humanTask id="S6_route" name="定去向与派单…"><entryCriterion sentryRef="s_typed"/></humanTask>
<humanTask id="T_release" name="受理结论验收放行">
<entryCriterion sentryRef="s_routed"/>
<itemControl><manualActivationRule id="mR"/></itemControl> <!-- 触达世界,必标 -->
</humanTask>
</casePlanModel>
</case>
</definitions>
```
CMMN 子集封顶:计划项 8 型(humanTask / decisionTask / processTask / caseTask / stage /
milestone / timerEventListener / userEventListener),表外构件直接拒装。
`ifPart` 前缀 `semantic:` 的条件引擎不机算,标「待裁量评估」交 Agent(judge 行动)。
里程碑**前提达成即达成**(引擎自己收,不需要谁去按;无前提的里程碑不自达成)。
### 7.3 说明书(narrative)—— Agent 的作业指导书,最重要的一份资产
与 CMMN 同版本发布、不允许分叉。它是每一拍叙事面的第一段,**写给 AI 执行看**。
结构照 A1(`activities/quote-intake/src/case-model.ts`):
```
§0 开案:什么事、成品是什么、SLA。
【整条路就 N 步】一步一个计划项,别绕:S1 → D1 →(分支)S2 → S3 → …→ 提交放行。
【S1 … · 计划项 S1_register】干什么、写哪张台账哪些列、几条纪律。
【D1 判完整度 —— 判定表说了算,不是你临场判】…
…每步一段…
【提交放行】机检判据点名(line_total > 0、missing_key_total = 0…),先 start T_release 再 submit。
【怎么把一项干完 · 一拍连做】…(见下)
【一路上要说话】…(见下)
§Z 结案。
```
**八条写作纪律**(每条都对应一次实测翻车):
1. **每步点名落点计划项**(「计划项 S4_history」)—— 模型会把活挂错项,点名了才挂得对。
2. **写哪张台账、哪些列,写死**(`quote_intake.record` 的八字段、`verdict` 的哪几个汇总数)。
汇总列不写,机检判据无米下锅,整案卡在半路。
3. **规则写死,不留给模型判**。Harness 已关模型思考换十倍速度(每拍 1.5-5s),代价是
模型不再细想边界 —— 所以边界规则必须写到不可能读歪:「**关键字段只有三个:PN、EAU、
交期。**目标价/厂牌/包装一律次要 —— 次要缺了不拦、不发补全卡」。凡判定表能表达的,
说明书里只写「照 F-01」,并把表的口径复述一遍。
4. **一拍连做指引**:「写 record、写 verdict、finish S1」是一拍不是三拍;调工具要看出参,
tool 放串尾;发卡/提交会挂起,只能是串尾。**写明常路拍数预期**(「这一单常路 5-7 拍
该收口」)—— 模型会照着省。
5. **finish 语义**:write / tool 不会自动把项收掉(一项常要两三个动作才交得出成果);
都做完了用 finish 宣告,再往下一步走。
6. **补全走 ask 卡**:「用补全卡(ask 行动),不是执行工单」;人答回的值出现在轨迹里,
**看到了要自己 write 进台账并把汇总数改小 —— 系统不会替你写**。
7. **说话指引**:「每一拍都带一句 say,对负责人说人话;局面变了用 view 换画面」+
每个局面对应哪张模版点名(§10.3)。不写这段,对话区从头到尾空白。
8. **禁臆造**:「原文没写的不要补,缺就记缺」「拿不准一律降 B,不许臆断 A」——
受理阶段编出来的数会一路流到报价单上。
### 7.4 拍数账(设计自查)
估算:每个计划项 ≈ 1 拍(写 + finish 同拍),每次工具调用 +1 拍(要看出参),
每次人环 +1 拍(回传后续跑)。A1:S1(1)+ S3(1)+ 工具 ×2(2)+ 打标写+finish(1)+
S6(1)+ start 放行 + submit(1)≈ 7 拍。**估出来超过 10 拍,回头合并计划项。**
## 8. 判定表(DMN)—— 能算的不问 LLM
```ts
export const P03_DMN: DmnModel['tables'][number] = {
id: 'p03-type', label: '三分类规则 P-03', hitPolicy: 'FIRST',
inputs: [ { name:'history_days', expression:'history_days' }, … ],
outputs: [ { name:'type_tag' }, { name:'play' } ],
rules: [
{ id:'p03-c', when:['-','-','"EOL","缺货","要求替代"'], then:['C','转案处理'] },
{ id:'p03-a', when:['<= 90','true','-'], then:['A','直接快答'] },
{ id:'p03-b', when:['-','-','-'], then:['B','重估后报'] },
],
};
```
- 单元格谓词 = S-FEEL:`'-'` 任意、`'"A","B"'` 析取、`'<= 90'` 比较、`'[0..100]'` 区间。
- **FIRST 的行序就是业务优先级**:C(EOL)必须排最前 —— EOL 的料哪怕历史价很新也不该判 A。
- 求值:任一输入缺 → incomplete;命中留痕(inputs / hitRules / outputs),同输入必同结论、零 token。
- **纯工具求值器模式**(推荐):把判定表做成 `pure: true` 的功能工具
(`quote-intake.classify`,`ownerActivityId` 私有作用域),模型只负责取齐判据、
调工具拿结论 —— 「不要绕过决策表自己判:规则表可回放可追责,临场判断不是」。
## 9. 工具
**声明式**:设计时只考虑需要什么(名字进 `declarations.tools`),不关心系统里有没有。
工具本体在系统侧注册(治理台 / `seed-mvp.ts`),契约含面向 AI 的 `description` 与
面向人的 `guide{ask, steps?, standard?}`。
**运行时五分叉**:命中读类直接执行;命中写类**登记动作意向**(放行才执行);
**未命中转人** —— 开一张执行卡(卡面 = guide + 本次入参 + 按出参 Schema 生成的回填表单),
人填回的值就是这次调用的出参(`sys.tool_handoff`,同调用只发一张卡);
未白名单授权报错(越权不转人);已停用走失败路径。
**设计惯例**(拍数与正确性都靠它):
- **批量入参**:一单几十条料,工具收数组(`pns: string[]` / `rows: object[]`),
一次传完 —— 一条一调,每调一次多烧一拍。
- **出参键名与台账列同名**(`type_tag` / `basis` / `history_days`):模型拿回结果直接
write,不必自己改名 —— 改名这一步交给模型,它会漏。
- 判据类出参把「没有」也当判据回(无历史 → `historyDays: 9999, hit: false`),
别回错误 —— 新料号无历史本就该命中 B 类。
**系统内化、零声明即得**(不进目录不进白名单):`write`(写台账草稿,授权面是台账声明)、
`ask`(补全卡)、`human`(执行/验收工单)、`listen`(立监听);声明了模型才有 LLM 五件
(extract/classify/summarize/judge/rewrite,提示词归系统,judge 只作参考不代放行)。
## 10. 人类工具与汇报
### 10.1 四类(前三类已通;决策卡 UI 在、Agent 侧未接)
| 类 | 形态 | 活动怎么用 |
|---|---|---|
| 执行 | 工单:ask / steps / standard / fields(8 种控件,可带 ai 拟值)| `human` 行动,`workOrder:'execution'`;卡面填实(title/ask/basis/fields),裸卡拒发 |
| **补全** | 对话卡:ask + fields(就地填)+「没法填」两条路必走其一 | `ask` 行动 `{kind:'ask', itemId, ask, fields:[{key,label,hint?}], basis}`;fields 空即拒 —— 空表单等于没问 |
| 验收 | 工单:判据表(L1/L2 已机检)+ 落地动作清单 + 可修订值 + 恰选一口 | `submit` 行动;判据/动作/拟值由基座从契约与台账装配,活动不重述 |
| 决策 | 对话卡:选择题 + 推荐 + 后果 + 自定义口径 | 暂不用(Agent 侧未接) |
调人预算(`humanCallBudget`)环外扣账;起单补全卡不占活动预算。
### 10.2 唤醒与挂起
三类同级唤醒:人对话输入、外部事件(数据/时间)、页面交互(答卡/办结工单)。
发卡即挂起(status awaiting),回传即续跑;**在途验收先于一切**(有未裁决验收卡时
回合原地等,不重发卡不问模型)。人答的值出现在下一拍轨迹里(`回填 {...}`)。
### 10.3 报告模版:活动持有,一单一模版贯穿始终
1. **一个局面一张**,局面变了让 Agent 用 `view` 换(A1 四张:overview / gaps /
classified / release),不在一张里堆条件分支;
2. **在办与归档同一张**:办结后指导与判据卸下,报告原样进「工作成果」;
3. 容器是 `sandbox=""` iframe:**无脚本、无外链、样式自带**;数据由基座推送时填
(`data-slot` 取值、`data-rows` 逐行展开、`data-class-of` 按值上色)。
### 10.3.0 三个区各放什么(2026-08-01 用户定,别再混)
| 区 | 放什么 | 不放什么 |
|---|---|---|
| **工作区 · 待处理** | **只放那份件**(活动推的 HTML 模版),整篇铺开、**可就地改** | 题头 / 编号带 / 依据带 / 判据表 / 作业指导书 / 提交钮 |
| **工作区 · 工作成果** | **只放那份件**,不加编辑态 —— 同一份 HTML 就是归档件 | 同上 |
| **对话区的卡** | 作业指导书(ask / steps / standard)· 依据 · 判据逐条 · 去向 · 填报表单 · **提交与裁决** | 件本身(给一个「去工作区看」的入口就够) |
栽过的:工单报告组件自带一整套外壳(题头 / 元信息 / 指标带 / 章节头),再把活动推的那一屏
**包在自己里面** —— 于是人看到的是「一份报告里嵌着另一份报告」,两套版面语言叠在一起。
用户原话:「你用你自己的报告格式嵌套了一层我要的报告格式」。
**就地改怎么做的**(`view-render.ts` 的 `twinInputs` + `ui-kit/ViewContainer`):
可改的格子在模版里挂 `data-col="台账列名"`,所在的行容器挂 `data-edit="域.对象"`;
渲染时每格多渲一个 `<input class="jc-cell">` 孪生件,只读那份与输入那份**由样式表二选一**。
容器是 `sandbox="allow-same-origin"` 的无脚本 iframe —— 外层读得到也改得动里面的 DOM:
加 class、挂事件、读 input 全在父文档这一侧,**iframe 里始终一行脚本都没有**。
收上来的值按台账地址对上工单 `blocks[i]` 的 `ledger`/`fields`,仍走老的 `table:N` 回传,
服务端写回一个字都不用改。
**默认锁着,按「编辑」才开**(2026-08-01 用户定):每张可改的表各带一道闸
(`[data-edit-for="域.对象"]`,三个钮 `data-edit-open` / `-cancel` / `-save`)——
锁着只出「编辑」,开了换「取消 / 保存」;取消还原成按下那一刻的值,保存才交给外层缓冲
(真正落草稿层仍在提交那一下)。**看与改是两件事**:整份件一进屏就是编辑态的话,
人还没决定要改什么,眼前已经是一张待填的表(A1 那份 54 个输入框一起亮着)。
⚠️ **跨 realm 陷阱**:父文档里写 `event.target instanceof Element` 判 iframe 里的元素,
**恒为 false**(`Element` 是父文档那个构造器,原型链不同)。症状极难查:监听挂上了、
事件也收到了,却每次都在第一行 return,而 class / CSS / 选择器逐个查都是对的。
**认能力不认血统**:有 `closest` 就当它是元素。同理,`srcDoc` 换文档的时机抓不准 ——
与其判断「挂过没有」,不如每次有机会就挂一遍(同一个 (type, listener) 重复 add 无副作用)。
为什么两份都渲、不渲成两套 HTML:同一份件既在待处理页给人改、又在工作成果里定稿归档,
**两处是同一份**。渲两套等于存两份,存两份就会分叉。
### 10.3.1 有目标件(设计稿)的时候:**搬,不要照着做**
A1 两份报告是 1:1 平移 `~/coding/JCAI_PRD/docs/活动执行页/` 的目标件 ——
样式表整段搬进 `views/prd-style.ts`(`PRD_CSS` + `PRD_SPRITE`,一个数值都没改),
标记逐块搬进 `views/report.ts`,只把写死的数据换成绑定。**不用 `@sdk` 的拼装件**:
那套外壳有自己的版面语言(编号章节头、摘要段、元信息带),拿它「照着做」出来的是第二种版面。
栽过三轮才认下来:块搬齐了就报「25 块到位」,而那是**结构断言**(某个 class 在不在),
对版面一点约束都没有 —— 人一眼就看出不是那份件。所以:
**视觉目标件只能看,不能用 DOM 断言验收**;搬完必须与目标件并排截图比对。
搬的时候要换掉的四样(原型是带脚本的演示页,这里一行脚本也跑不了):
| 原型怎么做 | 搬过来怎么做 |
|---|---|
| JS 造表、写死的行 | `data-rows` 行模版,一行一条从草稿层展开;序号交 CSS 计数器 |
| JS 算的计数 | 台账里的汇总列,`data-slot` 取 |
| JS 判空插「必填/选填」胶囊 | `data-blank` 让空值真空 + CSS `td:empty::after` |
| JS 拼 class 上色 / 写 `style="flex:N"` | `data-class-of` 把值(或**条数**)投影成 `v-<值>`,样式表按值分档、按数取宽(备一把 `v-1…v-20` 的梯子) |
搬不过来的如实空着 —— A1 空了两处:圆标里的首字母(得算)、「近 6 笔成交」迷你柱
(`market.price_history` 有列没数据、没有写入方)。编六根柱子是撒谎,画一条空轨像组件坏了,
所以那一格不画,把「为什么没有」写在卡脚。
拼装件(`@sdk`,**A2–A7 仍在用**):`viewShell({title,titleRef,kicker,tone,icon,summary,meta,stamp,body,sign})`
外壳,正文用 `section` 编号章节串起 `letter` / `factCards` / `statsBand` / `tableOnly` /
`stagesOnly` / `noteOnly` / `verdict` / `actionList`。
- `letter({from,subject,body,attachments})` —— **来件原文**,`pre-wrap` 照录不摘要。
归一出来的每个字段都要回溯得到出处;原文摆在结果**上面**,人一屏之内核得完。
- `factCards([{label,value,note,icon,tone,mono}])` —— 要素带:摆「谁与什么」,
与指标带(摆「数」)分工。
- `tableOnly(rowsPath, cols, { numbered })` —— 列样式 `mono`/`num`/`pill`/`req`/`opt`
(以及 `req-mono`/`opt-mono`/`req-num`/`opt-num`)。**`req`/`opt` 是分量声明**:
空格子自己出「必填」红胶囊 / 「选填」灰胶囊,缺必填的整行浅红打底、行首红条 ——
哪几列缺了不许放行,是写清单时就该说清的事,不是让人对着一屏「—」自己猜。
序号列由 CSS 计数器排(模版里没有循环变量)。**归档件不摆胶囊**:到了那一屏,
空格子的意思不再是「你还能填」而是「客户没给」。
- `actionList([{title,detail}])` —— 放行那一刻系统替人做了什么,一条一行。
**取数约定**:`mine:域.对象` = 本单草稿行(成果表只用它 —— 别单放行过的记录不混进来);
裸键 = 摊平字段;引擎注入的案情:`case:stages` / `case:enabled` / `case:gaps` ·
`case:openedAt`(本地时刻)/ `case:origin` / `case:owner` ·
**`case:routeKey` / `case:routePort` / `case:routeTo` / `case:routeWhy`** = 这一单算出来走哪一口。
放行那张必须摆出**下游要读的关键值**与**去向**—— 人签字前得看见这一单要往哪走。
**模版不许算,但基座算过的可以摆**。两件事别混:
- 报告**自己算**一遍(把判据抄成静态文字、拿宽度骗人)—— 不行。抄本与判据是两份东西:
改了 `when` 忘了改报告,屏上那一行就一直指着旧规则。
- 摆**基座算好的那一次**—— 该做。去向由契约里各出口的 `when` 按序求值(首个命中即出),
基座把结果注入 `case:route*`,与放行时发口的是同一次判定;
报告只把它摆出来:壳上 `data-class-of="case:routeKey"` → `v-r1/r2/r3`,对应那一行自己上色,
「命中 / 未命中」两句话都写在模版里、由样式表决定哪句现身。
算不出来(有一口没写谓词、或一条都不命中)三个键就是空的 —— 一行都不高亮,不冒充判过。
**剧本的 `view` 决定每一拍推哪张**:引擎按「还没干完的那一步」推,活步都收完就一帧不推 ——
最后摆上去的那张(放行页)就是归档件。所以放行那一步的 `view` 必须指向验收目标件。
## 11. 运行时参考(活动作者要知道的引擎行为)
### 11.1 起单与补全(宽进)
`process.start` 不按 Schema 拒缺项;任务照建,缺 required 顶层键(缺 = `undefined|null|''`)
→ 任务落 `awaiting-human` + 发补全卡(不占预算)。答卡只收还缺的键、只收非空字符串;
「没法填」给理由即**带缺口开跑**,收敛责任归活动。数据按字段名在流程里流动,零强制类型。
### 11.2 Agent 的行动型(一拍一串,封顶 6)
| 行动 | 形状 | 语义 |
|---|---|---|
| `start` | `{itemId}` | 启动标了人工激活的项;已 Active 再按是无事发生 |
| `plan` | `{itemId}` | 裁量项入场 |
| `disable` | `{itemId, reason}` | 此单不走这条 |
| `judge` | `{criterion, satisfied, rationale}` | 评估 `semantic:` 前提,留痕 |
| `write` | `{itemId, address:'域.对象', rows:[{key,patch}]}` | 写草稿。**授权面是台账声明**(未声明地址/列即拒;空转写 —— 一行都没变 —— 即拒);`itemId` 只是记账,挂不上照写不拦;patch **只带有值的列** |
| `drop` | `{itemId, address:'域.对象', keys:[{主键}], reason}` | **撤掉自己写错的那几行**(2026-08-01 加)。只动本单草稿层,正式版碰不到、不抛事件 —— 不是第四种写。授权面与 `write` 同一道;主键要与写进去时**一字不差**,撤不到当场拒。**门拦住一件办错的事却不给改正的手段,那扇门就只是个死胡同** |
| `tool` | `{itemId, tool, input}` | 读类即调(出参进轨迹);写类转意向;未命中转人挂起 |
| `ask` | `{itemId, ask, fields, basis}` | 补全卡,挂起 |
| `human` | `{itemId, card, workOrder}` | 执行/验收工单,挂起 |
| `listen` | `{itemId, eventType, keyFilter, deadline}` | 立监听,挂起;时限必配(缺省兜 P3D) |
| `finish` | `{itemId, rationale}` | 宣告一项完成;非人工激活的项就地过 Active 再收 |
| `submit` | `{rationale, fields?}` | 提交放行 → 预检 → 发验收卡挂起 |
串的纪律(引擎强制):按序执行,谁被拒停在谁那儿(**前缀已生效不回滚**,理由喂回下一拍);
`tool` 之后截断(出参没看见,后面是闭着眼计划的);挂起型收拍;超封顶丢弃留痕。
拍级附带 `say{text, tag, view?}` —— 不占行动位。
### 11.3 局面里有什么(Agent 每拍看到的)
说明书 + 活动记忆选摘 + 最近轨迹(窗口 = memoryWindow;含人答的值与工具出参,旧出参截短;
装配技能戳不进叙事)+ 当下可为(Enabled)+ **进行中**(Active —— 有开着的活就不算断点)+
目标缺口 + 待裁量前提 + 功能工具清单(带入参键名)+ 可写台账字典(地址/主键/列)+
本单草稿(mine:*)+ 任务数据(摊平字段在前、正式版整表殿后截断)。
### 11.4 提交与放行
`submit` → 落地预检(必交付物按 ledger 地址数本单草稿行;写类意向 drafted→prechecked;
数据冲突查草稿基线)→ 预检不过**不发卡**,理由喂回下一拍 → 过了发验收卡
(判据机检结果 + 已验证动作清单 + 可修订值拟值 + 恰选一口)→ 人裁决:
pass → 草稿合并、意向执行(幂等键)、输出口发射、`released`;
redo/revise → 附意见驳回,环内回转。落地失败 → 落地失败卡,任务 `blocked`。
### 11.5 Harness 兜底(活动不用管,但要知道)
预算四项环外强制;停滞熔断:**连续 5 拍行动被拒**即停问模型,发卡把拒绝理由原话
摆给人;`stallAfter` 判停滞、`nudge` 催办;模型隐藏思考已关(`reasoning:'off'`,
每拍 1.5-5s)—— 这就是说明书必须把规则写死的原因(§7.3 纪律 3)。
事件到达痕只记 task 层一条;对话区只投人话(narration / 回执 / 世界事件),
机器踪迹进工作日志不进对话。
## 12. 自测与安装
### 12.1 自测四段(照 `activities/quote-intake/__tests__/activity.test.ts`)
1. **清单合规**:过 `activityManifestSchema`;监听时限已配;L1/L2 有谓词;禁区非空、
调人预算 > 0;**每件必交付物都有 ledger 地址**;
2. **案例模型**:双表示同在;触达世界的项标了人工激活;常来回的项标了可重复;
末尾有收口验收项;说明书把每一步都交代到(逐步关键词断言);
3. **判定表逐行求值**(用 `@sfeel` 的 `evaluateUnaryTest` 写个 6 行的求值器):
每条规则、优先级序、同输入同结论;
4. **声明与契约对齐**(§6.2 三条线)+ 模版纪律(局面数、无脚本、放行页摆关键值)+
技能引用的工具都在声明内。
案例活动没有 `run`,自测**不跑回合**(那是 Loop 的事);mock 宿主对没有 run 的活动
如实报错,不给假 outcome。
### 12.2 安装两审
静态审(任一 error 拒装):signature-invalid / sdk-incompatible / schema-invalid /
model-name-unbound / dangling-listener / template-has-script / environment-capability /
dynamic-eval / ledger-type-conflict / acceptance-level-misreported;
`tool-contract-missing` 是 **warn**(声明了系统没有 → 运行时转人,是系统的缺口不是活动的错)。
回放审:历史成功样本仍须全过;首装以样例集替代。
### 12.3 试跑口径
试跑一律用 AI 账号(绝不派到用户工作台);起单走登记触发;逐拍对账看
`sys.task_memory`(每条「行动被拒」都是设计欠账);测完
`pnpm --filter @app/api reset-runs` 清运行数据(清跑出来的,留配置好的)。
## 13. 记忆与技能
- **任务记忆**只增不减;终态封卷后 `distillHooks` 蒸馏进**活动记忆**(跨任务、四层、
渐进披露、语义引擎不读)。
- **技能** `{id, label, applicability, method, crystallized[], knowledgeRefs[], version}`:
常驻上下文只有目录(`label: applicability`),正文按需加载。**载方法不载权力** ——
结晶片段调工具仍过白名单。A1 的三条(读懂一封询价 / 打标前备齐判据 / 状况标不等于去向)
是好范本:方法里写锚点、纪律、批量惯例,不写成产品文案。
## 14. 排障速查表(症状 → 根因 → 处置;全部实测账)
| 症状 | 根因 | 处置 |
|---|---|---|
| 起单后第一个项都不 Enabled,整案不动 | 案卷项 id 不是任务视图键名 | §7.1 纪律 2:改成入参/台账列名 |
| 连续「行动被拒:…迁移非法/项不存在」,熔断卡出现 | 模型多半没错 —— 计划项建多了/嵌套了 | §7.1 纪律 1:扁平化,图上几步就几项 |
| 一单 20+ 拍才收口 | 说明书没写一拍连做/拍数预期;或计划项过多 | §7.3 纪律 4;§7.4 拍数账 |
| 验收机检判据全 false,可数据明明写了 | 谓词读了逐行列(后写的赢盖掉了) | §6.2 汇总列纪律:一单一行汇总对象 |
| 提交放行被拒「必交付物缺」 | deliverable.ledger 地址没写/写错;或活动确实没写那张表 | §6.2 对齐线 1;说明书点名台账 |
| 下游网关死路(取不到 needXxx) | 出口标量字段没有台账列承载 | §6.2 对齐线 3 |
| 放行后正式表没有某字段 | write 的列名不在声明里(被当场拒过,轨迹里有) | 对照台账字典改列名 |
| 补全卡答了,数据没进台账 | 系统不替 Agent 写 —— 说明书没写「答案要自己 write」 | §7.3 纪律 6 |
| 对话区从头到尾空白 | 说明书没写 say 指引 | §7.3 纪律 7 |
| 工作区画面不换 | view 名不在 viewTemplates 里(轨迹有「换画面失败」留痕) | §10.3 点名局面模版 |
| 该走「受理并追问」的次要缺字段被拦单发卡 | 关思考后模型不细想边界 —— 规则没写死 | §7.3 纪律 3 |
| 明明有工具却发卡问人 | 早期版本症状(工具清单已进提示词);若复现,查该工具是否在 declarations.tools | §9 |
| 收口前一两拍被熔断(预算耗尽) | budget 给太紧 | §6.2 ⑦:按实测留一倍余量 |
| 改了包重装没生效 | 同版重装 no-op | 升 packageVersion |
| 一次人答/工具出参在日志重复多行 | 旧版症状,已修(到达痕只记一条) | 升级后消失 |
| 机检判据全绿,而交付物本身是错的(条数对不上、单没开全) | **等式两边都由活动自报** —— 它数错一次就一起错、一起「对上」 | 一边改读基座数出来的 `域_对象_rows`(本单在那张表写下几行);会数数的交判定器/工具出参,别让模型在脑子里数 |
| 一条 L1/L2 判据**永远绿不了**,而数据本身完全合规 | 它读的是活动自报的汇总列 —— 人补了值、基座写回了草稿层,而那个数要模型自己重算,漏算一次门就再也开不了 | 台账字段标 `required`,判据改读基座现数的 `域_对象_missing`。**机检只许读基座数得出来的数** —— 这一条已经栽过两次,第二次是十轮里三轮 |
| 台账里某个数被「改小」了,判据随之变绿 | 那条机检判据读的正是活动自己写的数 —— **门只接受「是」,模型就去改数**(实测:missing_minor_total 从 1 改回 0) | 机检判据只许读基座数得出来的数;数不出来的,别假装机器判得了 —— 降 L3/L4 交人,并把那个数摆到放行页上让人看见 |
| 裁量判据(L3/L4)裁成「不满足」之后反复提交 | 旧提交门要求裁量项也「为真」—— 而裁量的本义就是可以裁出不满足 | 已修:裁量项**裁过即可**提交,裁断连理由随卡交给人;活动侧把每条 L3/L4 都在 T_release 那一步裁一次,别漏 |
| 同一条理由拒回十几次却不熔断,预算烧到 exhausted | 一拍一串行动,拒回标题带串位(「第 2 个」)—— 整条一比成了多堵墙;叙事窗只有 20 条也装不下几次 | 已修:比对削掉串位前缀 + 熔断另开 60 条窗。活动侧看到这种日志,先查是不是某个数一直不对 |
| 人在卡上填了值,台账里那一格还是空的 | `doneWhen` 写成「问过就算完」(`asked_total > 0`)—— 卡一发它就为真,人一提交这一步当场被自动收掉,模型没轮到把答案写回来 | 收步等的是**答案落了表**:另记一个 `answered_total`,写回台账时才 +1 |
| 「工作成果」里定稿归档的件不是人签的那份 | 收口后引擎还会跑一拍,那一拍推了固定模版名、退到「声明的第一张」,把放行页冲掉 | 已修:按剧本 `view` 推,活步收完不推;活动侧确保放行那一步的 `view` 指向验收目标件 |
| 制表符 BOM 拆错列(空格子被右边的值填上) | 连着的 `\t\t` 是中间那格空着;模型把后面的值往前挪 | 说明书写死:每行格子数必须等于表头列数,数不对整行重数;行尾连续制表符是末尾几列都空 |
| 数值字段被写成 0 而缺口数还记着缺 | 模型拿 0 表示「没有」 | 说明书写死:数值列没有就给 `""`(基座按列型归型,空值=不写这一列);0 是一个真实的数量 |
| 同一颗料在成果表里出现两行 | PN 是主键,补写时换了个写法(补全了后缀)就是新开一行 | 说明书写死:回填按卡上那一列的 PN 对行;发现抄错交给人,别用新 PN 再写一行 |
| **判据拦住了,而模型改不动** —— 同一条理由连拒到熔断 | 门后没有路:办错的那件事没有对应的动作 | §11.2 有 `drop`(撤本单草稿层的一行);拒回的话必须指向**一个做得到的动作**,指错方向比不给指引更糟 |
| 行数判据对不上,模型只会改数字 / 重写 / 再提 | 拒回只给了两个数,「多的是哪一行」它数不出来 | 已修:读数连**主键名单**一起摆(`域_对象_keys`,封顶 12 行)。活动侧照名单撤那一行,别去改数 |
| 「要先 start」而项当下是 Completed,start 又被拒 | 拒回的话没按状态分开说,两堵墙对着撞 | 已修:收掉/停用的项改说「已办完,挂到当下这一步」并列出可为的项;start 已收的项无事发生 |
| **收了步却过不了验收**,回头补写一张已经收掉的表 | 收步判据比验收判据松(读自报的数,验收读基座数的行) | 两处比**同一件事**:收步也读 `域_对象_rows`。松出来的那一截就是返工 |
| **该经过人的一步整个没发生**,人一眼没看过就被叫去验收 | 「经过人」被写成了某一步收步条件里的半句(「缺东西才叫人」)—— 不缺就自动收了 | §7.1 纪律 3.5:人的门单独建成一项 + 人工激活 + 不给 `doneWhen`,下一步的入场前提取一个只有人回复才产生的计数列 |
| 一行没写的那张表,判据 `域_对象_rows = 0` 却过不去,连撞五拍熔断 | 读数从前只数「写过的表」——**没写过 ≠ 写了 0 行**,而 S-FEEL 里未绑定的名字参与比较求值为假 | 已修:按 `declarations.ledger` 把每张声明过的表先铺成 0。活动侧遇到「某类为 0 就卡住」的判据,先查读数在不在 |
## 15. 待实现与已知不一致
| # | 事项 | 现状 | 目标 |
|---|---|---|---|
| ④ | 受理分支误吞验收裁决 | `deliver` 的受理分支判 `awaiting-human && human` 不看 cardId(`task-service.ts`);「没法填」带缺口开跑的单再遇验收驳回会被误判成补全 | 受理分支按补全卡 cardId 匹配。**开发侧规避**:带缺口开跑的单尽早在环内补齐缺项 |
| ⑤ | 治理台白名单授权口 | 只有种子常量表(`seed-mvp.ts`),无 API 无 UI | 治理台按活动配白名单 |
| ⑦ | dist 真编译 | `index.js` 是源码文本拷贝(债 M5-3) | build-activities 真编译 |
| ⑧ | 单拍路径的工具调用缝 | 工具清单已进提示词(只给功能工具);调用仍是自由字符串经 ToolInvoker,未走总线编译 | 开环转正或单拍接总线 |
| ⑨ | `preauthorizedActions` 死代码 | 无调用方 | manifest 里**不要写** |
| ⑩ | `SchemaValidator` 双死依赖 | task/process 声明了不再调 | 清理 |
| ⑪ | 评价卡(rate) | schema 在、链路未接 | 用户定:暂不做 |
小不一致(不阻塞):`declarations.tools[].version` 无消费者;`contract.resources.tools`
双份声明;replay 模式跳过白名单检查;注册路由造不出纯工具;工具 `retries` 声明了不重试;
转人答案不经出参 Schema 校验(债 M6-1);进程重启后动作级 trace 丢失(债 M6-2)。