From 85466e815fc5bc579ae2547d9b84f224dadc48ba Mon Sep 17 00:00:00 2001 From: 18628386721 <18628386721@163.com> Date: Sun, 2 Aug 2026 15:30:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E5=BC=80=E5=8F=91=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E7=AC=AC=E4=B8=89=E7=89=88(=E6=8C=89=E5=9B=9B?= =?UTF-8?q?=E8=BD=B4=E9=87=8D=E7=BB=84)+=20HTML=E6=8A=A5=E5=91=8A=E7=94=9F?= =?UTF-8?q?=E6=88=90=E8=A7=84=E8=8C=83=E5=8D=95=E7=8B=AC=E6=88=90=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 给「任务活动开发 agent」沉淀的两份操作手册,基准是用户验收过的 A1(quote-intake@42, 三条形状不同的案例 released、被拒 0)。 活动开发规范.md 第三版,按四条轴重组: · 解耦机制 —— 活动与系统之间只有安装通道;三份声明=三个授权面; 边界纪律「别替下游产出」(随令牌流向下游的只有验收过的成果); · Harness 契约 —— 台账三写一撤与字段四标记(required/expect/tally/auto)、 基座读数全表与机检纪律(三次违反史逐条记账)、一路的门与闸 (起单宽进/提交闸两端/提交门/放行门哨兵/熔断)、拒回话术三分、五类卡、 phases/三色日志/一轮一句、放行落地; · Loop —— 局面组成、行动十四型全表、串纪律、人工激活与 start (「话说的范围要盖住门拦的范围」)、doneWhen 自动收步; · 活动怎么写 —— 清单六区(ledgers/editableAtRelease/判据措辞/内部一致性不上卡)、 建模十纪律(哨兵 ifPart、阶段全覆盖补进)、剧本 steps 六字段与 doneWhen 三坑、 guide 写作十条(一单一卡、卡骨架照抄、纯文本)、三常备案例与验收口径。 排障速查表并入这一轮全部新账(14 行新增)。 HTML报告生成规范.md 初版(用户点名单独成册,并强调报告与表单的关系): · 一份 HTML 三个身份:件(看)/表单(改)/归档件(存)—— 同一份,分开必分叉; · 报告与卡的关系立成硬规矩:逐行数据归件、整单级归卡;编辑在件、提交在卡; 同一个数管三处(件上红带/卡上闸/放行门 L1 同源);保存链路与三条实测教训; · 绑定语言全表、不算不判(按值上色/按条数分宽/按数开关)、编辑态四件套 (闸/孪生件/增删行/缺项红带)、PRD 平移法、浏览器走查清单、陷阱表。 CLAUDE.md 文档权威地图指向两份新版。五道门禁全绿(975 passed)。 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp --- CLAUDE.md | 3 +- docs/HTML报告生成规范.md | 218 +++++++++ docs/活动开发规范.md | 968 +++++++++++++++++---------------------- 3 files changed, 630 insertions(+), 559 deletions(-) create mode 100644 docs/HTML报告生成规范.md diff --git a/CLAUDE.md b/CLAUDE.md index 76974b8..b8b1c7e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -423,7 +423,8 @@ |---|---|---| | **活文档**(冲突时以代码现状为准) | `docs/编码规范.md` | 工程结构权威:模块层级 L1-L8、依赖门禁、AI 协作协议 §12 | | | `docs/产品设计.md` | 三面一体的产品形态与决策记录 | -| | `docs/活动开发规范.md` | **开发活动照这份**(2026-07-30 重写为开发手册):世界观/四铁律/开发流程/清单六区/案例建模八纪律/判定表/工具/人类工具/运行时参考/自测/**排障速查表**;§15 = 待实现 | +| | `docs/活动开发规范.md` | **开发活动照这份**(2026-08-02 第三版,按四轴重组:解耦机制/Harness 契约/Loop/活动怎么写):基座读数与机检纪律、门与闸、五类卡、剧本 steps、建模十纪律、三常备案例与验收口径(released+被拒0)、**排障速查表**;§8 = 待实现 | +| | `docs/HTML报告生成规范.md` | **报告模版照这份**(2026-08-02 从开发规范拆出):一份 HTML 三个身份(件/表单/归档件)、**报告与卡的关系与同源纪律**、绑定语言全表、编辑态(闸/孪生件/增删行/红带)、PRD 平移法、陷阱表 | | | `docs/debt.md` | 欠账台账(只增,销账划线) | | | `docs/adr/` · 各模块 `CONTRACT.md` | 架构决策与模块级不变量 | | 业务样例(活) | `docs/MVP示例工作流.html` | 七节点 SOP + 判定表 + 台账域 —— 开发活动的业务素材 | diff --git a/docs/HTML报告生成规范.md b/docs/HTML报告生成规范.md new file mode 100644 index 0000000..e6c8adc --- /dev/null +++ b/docs/HTML报告生成规范.md @@ -0,0 +1,218 @@ +# HTML 报告生成规范(活动视图模版) + +> 2026-08-02 初版,从《活动开发规范》拆出成册。基准:`activities/quote-intake@42` 的三张模版 +> (`intake-overview` / `intake-classified` / `intake-release`)—— 用户验收通过的那套。 +> 渲染引擎:`apps/api/src/modules/interaction/internal/domain/view-render.ts`; +> 容器:`packages/ui-kit/src/ViewContainer.tsx`;保存链路:`apps/api/src/app/routes/desk.ts`。 +> **本文与代码冲突时以代码为准。** + +--- + +## 1. 报告是什么:同一份 HTML 的三个身份 + +| 身份 | 在哪 | 状态 | +|---|---|---| +| **件**(给人看) | 工作区 · 待处理 | 锁定态 —— 进屏先是一份能读的报告,不是一张待填的表 | +| **表单**(给人改) | 工作区 · 待处理,按「编辑」后 | 可改格子现出输入件,能增删行 | +| **归档件**(存档) | 工作区 · 工作成果 | 同一份 HTML 原样定稿,零输入框零外壳 | + +**为什么必须是同一份**:渲成两三套就是存两三份,存多份就会分叉,分叉之后没人看得出 +以哪份为准。**看与改是两件事**(整份件一进屏就是编辑态,人还没决定改什么, +眼前已经是 54 个亮着的输入框 —— 踩过),但**看的与改的必须是同一份东西**。 + +三条硬规矩: + +1. **一单一模版贯穿始终**:同一个局面,在办与归档用同一张;局面变了换张 + (A1 三张:受理需求 → 料号分类 → 受理记录),不在一张里堆条件分支。 +2. **剧本每步的 `view` 定推哪张**,写清单时定死,不归模型临场挑;放行那一步的 + `view` 必须指向验收目标件 —— 最后摆上去的那张就是归档件。 +3. **数据变了基座重渲**(repush):人在件上保存、在卡上回填,落草稿层后服务端拿 + 新数据重渲同一张 —— 由数据算出来的东西(类徽、分布条、去向)跟着一起变。 + **手改的全部意义就是纠正 AI 的错,改了数而画面不动,这个兜底功能就是假的**(用户原话级教训)。 + +## 2. 报告与卡(表单)的关系 —— 这套里最容易做错的一章 + +三个区各归各位(2026-08-01 用户定,别再混): + +| 区 | 放什么 | 不放什么 | +|---|---|---| +| **工作区 · 待处理** | **只放那份件**(活动的 HTML 模版),整篇铺满工作区宽度、可就地改 | 题头 / 编号带 / 依据带 / 判据表 / 指导书 / 提交钮 —— 一样都不要 | +| **工作区 · 工作成果** | 同一份件,定稿归档 | 同上 | +| **对话区的卡** | 作业指导书(steps/standard)· 依据 · 判据 · **整单级表单字段** · **提交与裁决** · 落地结果 | 件本身(给一枚「执行工单/验收工单」四字钮跳过去就够) | + +分工的原则:**逐行数据归件,整单级归卡;编辑在件,提交在卡。** + +- 一张 9 行的清单,人要逐格核、逐格改 —— 那是件的事(报告即表单); +- 「结果说明」这种整单一格的字段、「先受理还是退回」这种裁决 —— 那是卡的事; +- 卡上那枚提交钮是**唯一的交卷动作**;件上的保存只落草稿层,**工单不办结、 + 放行门的位置不动**。 + +**同一个数管三处(同源纪律)**:台账声明 `required` 产出的 `域_对象_missing`—— + +``` +件上的缺项红带(gapBar) ←┐ +卡上的提交闸(gate) ←┼── 同一个基座读数、同一套算法 +放行门的 L1 机检 ←┘ +``` + +三处各算各的,就会出现「件上说齐了、卡上说不能交、放行门又说行」—— +那种矛盾人是判不出对错的。所以**模版与卡都不算这个数,只摆基座算好的**。 + +**保存链路(件 → 库 → 屏)**: + +``` +人按「保存」→ 前端按件自己说的话收值(data-edit 地址 + data-col 列名 + drops) + → POST /tasks/:id/table {tables, drops} + → 服务端按【活动清单的台账声明】过授权门,归型,只写真改了的行, + 自增主键补号,draftDrop 撤显式点名的行 + → 留一条人痕(改了几行/新增几行/撤掉哪几行 —— 那条痕是下一拍模型的行动指南) + → repush 重渲 → 闸、判据、类徽、分布条、去向全部跟着变 +``` + +三条实测教训,每条都别再犯: +- **授权面是台账声明,不是「当下那张卡长什么样」** —— 从前保存借道工单 `blocks[i]` + 取列名,熔断卡(没有 blocks)开着时,人补的那一格一个字都送不出去, + 而他要补的恰恰是把这一单卡住的必填格; +- **删要说出来**(载荷 `drops` 点名主键),不靠「没回传就是删了」—— 那种口径会把 + 一次不完整的回传变成一次删表; +- **只写真改了的行** —— 件上按一次保存整张表都回传,照单全写会留「修订 4 行」的痕, + 而人只改了一格;日志是给人看的,那句话就成了假的。 + +**卡侧还有一条老路**(与件侧并存,别混):卡面 `blocks` 里绑了 `ledger`+`fields` 的表, +由**基座铺行**(发卡那一刻从草稿层铺,活动别自己抄行 —— 回复有长度上限,铺长了截断 +整拍作废)、人提交时由**基座写回**(`table:N` 载荷)。验收卡的 `editableAtRelease` +成果表走的就是它。两条路的授权面是同一份台账声明。 + +## 3. 沙箱与安全 + +- 容器:`sandbox="allow-same-origin"` 的 iframe,**永不给 `allow-scripts`** + (两者同给才是破沙箱)。父文档读得到也改得动里面的 DOM:开编辑 = 加 class、 + 收值 = 读 input,全在父文档侧;**iframe 里始终一行脚本都没有**。 +- 模版含 ``)。 +- ⚠️ **跨 realm 陷阱**:父文档里 `event.target instanceof Element` 判 iframe 元素 + **恒为 false**(Element 是父文档的构造器)。症状:点「编辑」毫无反应而 class/CSS/ + 选择器逐个查都对。**认能力不认血统**(有 `closest` 就当元素);监听挂载写成幂等的, + 每次有机会就挂一遍(srcDoc 换文档的时机抓不准)。 +- 内容更新**就地换 `body.innerHTML`,不换 srcDoc** —— srcDoc 一变整个文档重载, + 高度掉回兜底再涨回来,人刚按下保存件就「闪黑」一下。 + +## 4. 模版语言参考(绑定全表) + +渲染 = 纯取值绑定。基座三遍扫描:槽位填值 → 输入件填 value → 独立 class 投影。 + +| 属性 | 写在哪 | 语义 | +|---|---|---| +| `data-slot="键"` | 任意元素 | 取值填为文本。数值整数原样、小数截 4 位;数组给长度 | +| `data-rows="mine:域.对象"` | 行容器(tbody 等) | 按本单草稿行逐行复制内部结构;行内 `data-slot` 相对该行取值;序号交 CSS 计数器 | +| `data-blank` | 与 `data-slot` 同元素 | 空值**真空**(不写「无」)—— 空格子的胶囊、占位交给 CSS `:empty::after` | +| `data-class-of="键"` | 任意元素 | 把值消毒成 `v-<小写值>` 追加进 class(数值就是 `v-0`/`v-1`/…)。**判断留在样式表** | +| `data-col="台账列名"` | 可改格子(与 `data-slot` 同元素) | 渲染时多渲一个孪生 ``;写的是**台账列名**不是表头文字(表头改说法不该让写回错位) | +| `data-edit="域.对象"` | 可改表的行容器 | 这组行可就地改;保存按它认地址 | +| `data-value` | `` | 值进 value 属性(孪生件用,模版一般不手写) | + +取数约定:`mine:域.对象` = 本单草稿行(成果表**只用它** —— 别单放行过的记录不混进来); +裸键 = 摊平字段 + **基座读数**(`域_对象_rows` / `_missing` / `_missing_keys` / +`_<列>_<值>`…);`case:openedAt / case:origin / case:owner / case:routeKey / routePort / +routeTo / routeWhy` = 引擎注入的案情与算出的去向。 + +**语言纪律**:报告上不摆系统内英文名 —— 状态名、表名、列名、口名一律人读名; +数值列**与表头同侧左对齐**(右对齐是给要竖着相加的数用的;一行一个料、彼此不相加的表, +右对齐只让数和自己的标题分家),等宽字体留着。 + +## 5. 模版不许算,但基座算过的可以摆 + +两件事别混: + +- 报告**自己算**(把判据抄成静态文字、拿宽度骗人、JS 拼 class)—— 不行。 + 抄本与判据是两份东西,改了一处另一处就一直指着旧规则。 +- 摆**基座算好的那一次** —— 该做,而且要做足。三个惯用法: + - **按值上色**:`data-class-of="type_tag"` → `.v-a/.v-b/.v-c` 三色徽; + - **按条数分宽**(分布条):`data-class-of="quote_intake_record_type_tag_a"` → + 样式表备一把 `v-1…v-20` 的宽度梯子,`flex` 按档取;零条那段自己消失; + - **按数开关**(缺项红带):`data-class-of="quote_intake_record_missing"` → + `v-0` 那档 `display:none` ——「一格不缺就别出这条带」这个判断在样式表里, + 模版仍只做取值。 +- 去向那行:基座按契约各出口 `when` 序求值后注入 `case:route*`(与放行发口是 + **同一次判定**),报告只摆;算不出来键就是空的,一行都不高亮,不冒充判过。 +- 搬不过来的如实空着:编数据是撒谎,画空轨像组件坏了 —— 那一格不画, + 把「为什么没有」写在卡脚。 + +## 6. 编辑态(报告即表单) + +**默认锁着,按「编辑」才开。** 每张可改的表配三样(A1 `report.ts` 有现成实现): + +1. **闸**(`editGate`):`[data-edit-for="域.对象"]` + 三钮 + `data-edit-open / -cancel / -save`。锁着只出「编辑」;开了换「取消/保存」; + 闸只在这一屏可改时现身(根节点 `editable`),归档件里一个字都没有。 +2. **孪生输入件**:挂了 `data-col` 的格子渲染时自动配一个 ``, + 只读那份与输入那份由样式表按表上的 `on` 二选一 —— 同一份 HTML 两态复用。 + 编辑态下必填空格红框(`:placeholder-shown`)、整行浅红打底。 +3. **增删行**:序号格编辑态换成 ×(一列两用);表尾一条「加一行」 + (照末行克隆、值清空,主键不填不落库)。**删是挂记号不当场抹掉** + (`jc-row-gone` 划线摆着 —— 看得见、可后悔);「取消」把加的行撤掉、 + 把删记号摘掉、把值放回快照,**三样一起才叫还原**。 + + 为什么必须有增删:手改是**兜 AI 的错**的那一手,而错不止「这一格填错了」—— + 「这一行根本不该在」(客户点名跳过的料被写进有效清单)与「漏了一行」是同一类错, + 只给改格子就没有出口(实测那一单 13 次拒回、234k token 才熔断)。 + +4. **缺项红带**(`gapBar`):表上方一条红带点名「还差 N 格必填,分别是哪一行哪一列」 + (`_missing` + `_missing_keys`,与提交闸、放行门同源),并告诉人怎么办 + (「按右上角编辑开表,红框那几格就是要填的」)。只在可改屏出、缺口为 0 自动收 + (§5 的按数开关)。小胶囊淹在九行表里,人得逐行扫 —— 而他打开这一屏就是来填的。 + +## 7. 有目标件(设计稿)时:搬,不要照着做 + +A1 两份报告是 1:1 平移 PRD 目标件:样式表整段搬(`prd-style.ts` 的 `PRD_CSS` + +`PRD_SPRITE`,一个数值都没改),标记逐块搬,只把写死的数据换成绑定。 +**不用拼装件复述** —— 那套外壳有自己的版面语言,「照着做」出来的是第二种版面。 + +搬的时候四样替换(原型是带脚本的演示页,这里一行脚本跑不了): + +| 原型怎么做 | 搬过来怎么做 | +|---|---| +| JS 造表、写死的行 | `data-rows` 行模版 + CSS 计数器序号 | +| JS 算的计数 | 台账汇总列或**基座读数**,`data-slot` 取 | +| JS 判空插胶囊 | `data-blank` 真空 + CSS `:empty::after` | +| JS 拼 class / `style="flex:N"` | `data-class-of` 投影 + 样式表分档(§5) | + +**验收只认并排截图比对** —— 「25 块到位」是结构断言,对版面零约束(栽过三轮)。 + +## 8. 版式纪律 + +- **铺满工作区**:去掉外层宽度上限与横向留白、卸掉外框圆角边 —— 件就是这一屏的正文, + 不是嵌在里面的另一个页面(`jc-stage { max-width:none }`)。 +- 必填/选填胶囊只在**在办态**摆;归档件一律 `dimempty`(那时空格的意思不再是 + 「你还能填」而是「客户没给」,摆红胶囊是在催一件办完的事)。 +- 别摆恒常说「没有」的横幅(「本轮不报 0 行:—」),别把同一个数摆两处 + (人得停下来对一遍两处一不一样);已在别处说过的结论不再复述一遍。 +- 标记里不写 `**` 星号这类记号 —— 卡面与件都是纯文本/纯 HTML,记号不会变粗体。 + +## 9. 自测与浏览器走查 + +包内自测(结构层):模版无 ` 2026-07-30 第二版(第一版是「现状如实、差距列账」的勘误体;本版按跑通后的现状重写成 -> **开发手册**)。目标读者:**开发活动的 AI 或人** —— 读完这一份,你应当能独立把 -> `docs/MVP示例工作流.html` 里的一个 SOP 节点做成一个能 `released` 的案例活动, -> 不必读 `apps/api` 的源码。 +> 2026-08-02 第三版。基准:`activities/quote-intake`(A1 询价受理,`@42`,**用户验收通过** —— +> 三条形状不同的案例端到端 `released`、行动被拒 0)。目标读者:**开发活动的 AI(任务活动 +> 开发 agent)或人** —— 读完这一份,你应当能独立把一个 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/`。 +> 本版按四条轴重组:**解耦机制**(§2)· **Harness 契约**(§3)· **Loop**(§4)· +> **活动怎么写**(§5)。报告模版单独成册:`docs/HTML报告生成规范.md`(与本文互为姊妹篇, +> 报告与表单的关系在那份里立成硬规矩)。 +> +> **本文与代码冲突时以代码为准**,发现即改本文。相关:`docs/编码规范.md` §8、 +> 各模块 `CONTRACT.md`、`docs/debt.md`(欠账)。历史语义源已归档 `docs/archive/`。 --- ## 1. 世界观:一个活动是怎么跑起来的 **活动 = 任务的定义。** 编排引用一个活动,运行时长出一个任务(恰一名负责人)。 -活动在系统外开发、经安装通道入库(`pnpm activities:install`),版本恒新增、 -同版重装 no-op、在途任务按启动时版本跑完。 +活动在系统外开发、经安装通道入库,版本恒新增、同版重装 no-op、在途任务按启动时版本跑完。 -**活动没有代码逻辑。** 2026-07-29 范式退役后,活动一律**案例活动**:行为全部写在 -清单里(案例模型 CMMN + 说明书 + 判定表 DMN + 技能 + 台账声明 + 报告模版), -「系统不执行代码即知包为何物」。`src/index.ts` 只是被静态审扫描的产物文本 -(证明零环境能力),`defineActivity({ name })` 即可,`run` 可选且通常不写。 +**活动没有代码逻辑。** 行为全部写在清单里:案例模型(CMMN)+ 逐步剧本(steps)+ +说明书(narrative)+ 判定表(DMN)+ 技能 + 台账声明 + 报告模版。 +「系统不执行代码即知包为何物」。`src/index.ts` 只是被静态审扫描的产物文本, +`defineActivity({ name })` 即可。 -**推进由 Loop 驱动,一拍的解剖**(`loop-engine/internal/usecase/turn-runner.ts`): +**推进由 Loop 逐拍驱动**(一拍 = 一次模型调用): ``` 语义引擎发布局面 ──> Agent 决断一串行动 ──> 引擎逐个执行 ──> 留痕 ──> 下一拍 - · 当下可为(Enabled) (1-6 个,一次模型调用) 被拒停在谁那儿 - · 进行中(Active) tool 后截断 - · 目标缺口(未完成项) 挂起型收拍 - · 最近轨迹 + 工具清单 + 台账字典 + 任务数据 + · 当下可为(Enabled,标「须你启动」) (1-6 个) 被拒停在谁那儿 + · 进行中(Active) tool 后截断 + · 当前这一步的剧本(guide 摊开,其余步各留一行) 挂起型收拍 + · 目标缺口 + 轨迹 + 工具清单 + 台账字典 + 基座读数 ``` -**拍是按模型调用计费的** —— 每拍把说明书 + 轨迹 + 局面整段重读一遍。 -所以一切设计都围着「少一拍是一拍」:一拍连做(写两张台账顺手 finish)、 -批量工具入参(一单所有 PN 一次传)、说明书把规则写死(判断不花拍)。 -A1 的账:六步的活,常路 **7-8 拍**收口(起初 21 拍 —— 差距全在设计,不在模型)。 +**拍是按模型调用计费的** —— 一切设计围着「少一拍是一拍」:一拍连做、批量工具入参、 +规则写死不留给模型判。A1 的账:六步的活,常路 **9-11 拍**收口(第一版 21 拍 —— +差距全在设计,不在模型)。 -## 2. 四条铁律(违者拒装或被引擎拒) +**人只在责任点与放行点出现**:确定交系统(判定表/基座读数),临场交 AI(编排、拆解、 +落表),落地交人(审定、放行)。整套机制的反复主题只有一句: +**该由人亲手落下的,不许引擎或 AI 悄悄替它发生;能由数据算出来的,不许活动自报。** + +### 1.1 四条铁律(违者拒装或被引擎拒) | # | 铁律 | 机制承载 | |---|---|---| -| ① | **只读世界**:活动只能读台账、调读类工具、问模型、发卡问人 | 产物文本静态审:`process.env` / `fetch` / `node:*` / `eval` 命中即拒装;depcruise 双向隔离 | -| ② | **写只有草稿与意向**:写台账 = 草稿层(不抛数据事件);外部动作(发邮件等)= 登记动作意向(入参冻结 + 幂等键)。**登记那一刻世界上什么都没变** | `write` 行动只落草稿;写类工具调用自动转意向 | -| ③ | **终局唯放行**:任务完成的唯一出口是人类验收通过 —— 那一刻才草稿合并、意向执行、输出口发射 | `submit` = 发验收卡挂起;放行落地六步(§11.4) | -| ④ | **活动不自转**:推进由引擎逐拍驱动;预算(步数/token/时长/调人次数)与停滞熔断由 Harness 环外强制,活动不得自查自守 | 迁移拓扑把 Completed/Terminated 设为死胡同;AI 不得产生终局迁移 | +| ① | **只读世界**:活动只能读台账、调读类工具、问模型、发卡问人 | 产物文本静态审(`process.env`/`fetch`/`node:*`/`eval` 命中即拒装);depcruise 双向隔离 | +| ② | **写只有草稿与意向**:写台账 = 草稿层(不抛事件);外部动作 = 登记动作意向(入参冻结 + 幂等键)。登记那一刻世界上什么都没变 | `write` 只落草稿;写类工具自动转意向 | +| ③ | **终局唯放行**:任务完成的唯一出口是人验收通过 —— 那一刻才草稿合并、意向执行、输出口发射 | `submit` = 发验收卡挂起;放行落地见 §3.7 | +| ④ | **活动不自转**:推进由引擎逐拍驱动;预算与熔断由 Harness 环外强制 | 迁移拓扑把 Completed/Terminated 设为死胡同 | -## 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/`(四段,§12.1)。 -10. **构建安装**:升 `packageVersion` → `pnpm activities:install`(同版重装 no-op!)。 -11. **试跑**:登记触发起单(试跑一律用 AI 账号)→ 看任务轨迹逐拍对账 → - 验收签字 → 查落地行与出口载荷。卡壳对照 §15 排障表。 -12. **调优**:轨迹里每一条「行动被拒」都是设计欠账 —— 修模型或说明书,不怪模型。 +## 2. 解耦机制:活动与系统之间只有一条通道 -## 4. 包结构与构建安装 +### 2.1 安装通道(唯一入口) + +``` +activities//src/ ──(pnpm activities:build)──> dist/{manifest.json,index.js} + ──(pnpm activities:install)──> 库(经安装通道读文件,不经 import) +``` + +- 新包在 `tools/build-activities.ts` 的 PACKAGES 与 + `apps/api/src/app/seed-activities.ts` 的 `REAL_PACKAGES` 两处登记。 +- **别用 `db:seed` 装活动**:它重写整套治理台配置。 +- **版本纪律**:改了任何内容必须升 `identity.packageVersion` —— 同版重装静默丢失(踩过)。 + 卸载只置状态不删行;**同名旧槽位要退役卸载**(库里两个同名活动会骗人)。 +- 撤掉一张台账声明**不会 drop 物理表**(物化只增不破坏)—— 历史行还在库里,要清得单独迁移。 + +### 2.2 import 白名单与静态审 + +活动包**只准 import `@sdk` / `@contracts` / `@sfeel`**;depcruise 双向隔离 +(activities 不进 apps,apps 不 import activities)。产物文本黑名单: +`process.env`、`require(`、`node:fs/net/child_process`、`fetch(`、`XMLHttpRequest`、 +`eval(`、`new Function(`。视图模版含 `_<值>` 这些基座读数在审的白名单里)。 + +### 2.4 边界纪律:别替下游产出 + +**随令牌流向下一个节点的,只有工作成果验收里的内容**(出口载荷 = 已落地的事实)。 +下游活动拿到之后按自己的口径归一需要的数据。 + +实测账(2026-08-02,用户当场定):A1 从前逐条开「替代评估请求 / 询价请求」交给 A2/A3 —— +那是**替下游把它的输入先摆好**,而下游本来就要归一一遍:同一份事实两处各存一份, +改一处就分叉,而分叉之后没人看得出以哪份为准。两张表连同派单判据、报告明细表一并退休。 + +跨活动共享事实只有两条路:**输出口载荷**,或**共享域**(`shared`/`customer`/`market` 等 +主数据域)。活动自持域用活动名(`quote_intake`)。 + +--- + +## 3. Harness 契约:基座替你做什么 + +这一章列的是**你能依赖、也必须依赖**的基座行为 —— 活动侧不要重做它们, +更不要绕开它们。 + +### 3.1 台账:三写一撤 + 归型 + 字段四标记 + +- **写三分**:`systemWrite`(世界已发生的事实,即时抛事件)/ `draftWrite`(任务草稿层, + 读己之写,不抛事件)/ `releaseWrite`(放行合并,此刻抛事件)。三个方法不是一个带 flag 的方法。 +- **`draftDrop`**:撤掉本单草稿层的一行 —— 不是第四种写(正式版碰不到、不抛事件)。 + 它存在的理由:门拦住一件办错的事却不给改正的手段,那扇门就只是个死胡同。 +- **归型前移**:`draftWrite` 按声明列型归(空值 = 不写这一列、数值/布尔字符串转真类型、 + 归不了当场拒)。**人手改的值与 AI 写的值过同一道门**。 +- **字段四标记**(`fieldDeclarationSchema`): + +| 标记 | 语义 | 基座据此产出 | +|---|---|---| +| `required` | 这一列缺了就是活没干完(验收标准;不建 NOT NULL) | `域_对象_missing` / `_missing_keys`;执行卡提交闸;件上缺项红带 | +| `expect` | 缺了不拦单,但那一问不能省(客户该给而没给的) | `域_对象_gaps` / `_gaps_keys`;L4「选填缺口」谓词读它 | +| `tally: ['A','B','C']` | 分类列,给出取值域 | `域_对象_<列>_<值>` 按值现数(没出现的值也铺 0) | +| `auto` | 内部流水号,**不问人、不让模型编** | 写入那一刻按「同组其余主键值-两位序号」生成(`CU-9001-01`);录入卡上那一格不出现,另摆一句「由系统按顺序生成」 | + + 注意 `expect` 的边界:**我方写的注(如备注列)不算** —— 标了它 `_gaps` 恒大于 0, + 那条判据永远绿不了,门就废了。 + +### 3.2 基座读数(机检的唯一合法数源) + +任务视图顶层永远铺着这些数,**全部由基座现算,模型碰不着**: + +| 键 | 含义 | 备注 | +|---|---|---| +| `域_对象_rows` | 本单在这张表写了几行 | 声明过的表**铺 0**(没写过 ≠ 写了 0 行 —— S-FEEL 未绑定名字参与比较为假,踩过) | +| `域_对象_keys` | 本单写的行的主键名单(JSON) | 封顶 12 行;拒回时摆出来,「多的是哪一行」一比便知 | +| `域_对象_missing` / `_missing_keys` | 缺必填几行 / 缺在哪几行哪一列 | 名单用中文列名 | +| `域_对象_gaps` / `_gaps_keys` | 缺选填几行 / 哪几行 | 同上 | +| `域_对象_<列>_<值>` | 该分类值现有几行 | 取值域声明给,数得全 | + +**机检纪律(最重要的一条,已被违反三次)**: +**L1/L2 判据、收步判据、放行门哨兵,一律只许读基座数得出来的数。** +活动自报的汇总列(`line_total` 这类)只能出现在等式的**一边**,或只做提示。 + +三次违反史,每次都是同一个死法 ——「那个数要模型自己重算,漏算/改小一次,门就废了」: +① `missing_key_total` 自报 → 人补了值门不开,十轮里三轮熔断; +② `missing_minor_total` 自报 → 门只接受「是」,模型把 1 改回 0,台账从此写着假数; +③ `untyped_total` 自报 → 模型跳过打标写个数就想过门(判据现读 +`_type_tag_a + _b + _c = _rows`,两边都是基座出的数)。 + +### 3.3 门与闸(从起单到放行,一路的拦截点) + +| 门 | 在哪 | 规则 | +|---|---|---| +| **起单宽进** | `process.start` | 不按 Schema 拒缺项;缺 required 顶层键 → 发补全卡(不占预算)。「没法填」给理由即带缺口开跑 | +| **执行卡提交闸** | 卡面 `gate` + 服务端 | 卡上绑了台账表的执行单,必填缺着按不下提交(`{ready,why,blanks}`,与放行门 L1 **同一个数同一套算法**);**服务端在收下回传之前也把一遍**(前端灰按钮只是给人看的)。算不出来(卡没绑表)不给闸 —— 没有依据的时候不拦人 | +| **提交门**(`submit` 预检) | engines | 机检(L1/L2)**必须为真**;裁量(L3/L4)**裁过即可**(裁成「不满足」照样能提交,裁断连理由随卡交给人 —— 一条只接受「是」的判据是一句教模型说谎的提示);**L4 带谓词时谓词说了算**(谓词真 = 已达标,模型那句相反的理由一并丢掉;谓词假才轮到裁量);必交付物按 ledger 地址数本单草稿行 | +| **放行门入场哨兵** | CMMN sentry | **必须有 ifPart,且读基座数、认「办完了没有」** —— 只有 onPart 的哨兵等于「有人写过一笔就开门」,模型写个 `false` 就推开了(实测:跳过三步直达放行门) | +| **熔断** | Harness | 连续 5 拍行动被拒 → 停问模型,发熔断卡把拒绝理由摆给人(比对削掉串位前缀、另开 60 条窗) | + +**拒回话术纪律**(拒回的话是模型下一拍的行动指南,指错方向比不给指引更糟): +- 机检差数,先分清**数不对**(改汇总列)还是**行不对**(照主键名单 drop/补写); +- 判据点名的表**这一单一行都没写**(`_rows` = 0)→ 直说「那一步还没办,回去办了再来」—— + 这时数与行都是对的,指「改数/改行」是指向两扇打不开的门(实测五拍撞死); +- 已收掉的项按状态分开说(「挂到当下这一步」+ 列出可为的项),别说「先 start」—— + start 一个 Completed 项同样被拒,两堵墙对着撞。 + +### 3.4 人类工具:五类卡 + +| 类 | 行动 | 形态与硬规矩 | +|---|---|---| +| **执行** | `human` + `workOrder:'execution'` | 卡 = 查看工单钮(四字粗体,与验收卡同形)+ ask + **作业指导书**(`steps`+`standard`,与判据同一套版面、不画对勾)+ 依据 + 表单 + 提交闸。指导书写「哪几格必须有、哪几格值得追」,不写「怎么核表」。**`steps`/`standard` 是纯文本** —— 别写 `**` 这类记号,它们不会变粗体,只会原样摆在人眼前 | +| **验收** | `submit` | 判据表(读时现算 —— 人在件上补一格判据跟着变;**L5 不上卡**:那张卡本身就是那道关,列进去等于摆一条恒红的项)+ 落地动作 + 可修订值 + 出口(算得出摆 `routedPort` 告知,算不出退 `portChoice` 让人选)。**办结之后换成落地结果**(landing):按交付物点名落了几行、转出哪一口、带过去哪些字段;一个口都没发直说「到此为止」 | +| **补全** | `ask` | 对话卡就地填;`fields` 空即拒;能预填的带 `value` 预填 | +| **决策** | `decide` | 选择题(≥2 个选项,少了拒发);人点哪个进轨迹 | +| **录入** | `entry` | **人按下确认那一刻基座直接写库,AI 不经手** —— 建档这类主数据用它。可多段(`also`):建客户 = 公司档 + 联系人档,一张卡两段、确认一次落两行(值按 `段号.列名` 隔开)。卡面摆表的**人读名**(不摆 `customer.profile`);`auto` 列不进表单、另摆一句说明 | + +通用规矩: +- **一单同一张卡只发一次**。人交回来之后不论还缺什么都不许再发 —— + 必填有提交闸兜着(卡回来时必填一定齐);**人留空交回来 = 对方给不了,那也是一个答案**: + 如实记账、缺口照实留着、把这一步收掉往下走。 +- 发卡即挂起,回传即续跑;在途验收先于一切;人答的值出现在下一拍轨迹里, + **看到了要自己 write 落台账**(绑台账的表除外 —— 基座直写)。 +- 调人预算(`humanCallBudget`)环外扣账。 + +### 3.5 视图渲染(概要,详见《HTML报告生成规范》) + +- 模版**活动持有**,一单一模版贯穿始终(硬规矩);剧本每步的 `view` 定推哪张。 +- 渲染 = 纯取值绑定(`data-slot`/`data-rows`/`data-blank`/`data-class-of`/`data-col`); + **模版不算不判**,基座算好的可以摆。 +- 待处理那一屏**报告即表单**:可改格子渲孪生 ``,默认锁定、按「编辑」才开, + 能增删行;保存按地址落草稿层(授权面 = 台账声明),**基座随即重渲**(repush)—— + 类徽、分布条、去向这些算出来的东西跟着变。 +- 报告与卡的分工、同源读数、保存链路 —— 全在姊妹篇 §2,那是这套里最容易做错的一章。 + +### 3.6 进度条、日志、对话区 + +- **phases 是给人看的刻度**(引擎一眼不看):`{name, items[]}[]`, + **除放行外每个计划项恰属一个阶段**(漏一项,人办那件事时进度条已经走过去了 —— + 实测:S3 漏在名单外,人对着审定卡而条上写着「判定去向」)。收口那一格由基座统一加 + (「放行收口」),总格数 = 声明数 + 1。 +- **日志三色**:负责人绿 / 智能体蓝 / 系统灰;日志与卡面**不摆系统内英文名** + (状态名、表名、列名、口名一律换人读名)。 +- **对话区一轮一句**:`say` 只在交出控制权那一拍(ask/human/decide/entry/listen/submit) + 才摆到人面前,别的拍进工作日志。到了要找人的那一拍,say 写「这一轮办完了什么、 + 为什么现在需要他」。顺利收尾对话区**不再出一条**(落地结果那一屏已说全); + 没顺利收尾的出一条人话(「这一单受阻待人」)。 + +### 3.7 放行落地(人按「通过」之后发生什么) + +草稿合并进正式版(逐表计数留结构化痕)→ 动作意向执行(幂等键;失败发落地失败卡, +任务 `blocked`)→ 输出口发射:**载荷 = 已落地的事实**(`landedFacts`,放行后草稿层已清, +不从作用域猜)→ `released`。selective 出口按各口 `when` 谓词序求值,首个命中即出 —— +**去向是算出来的,不是人选的**;人签的字确认的是算出来的那一口。 + +--- + +## 4. Loop:一拍怎么跑(Agent 视角) + +### 4.1 局面里有什么 + +当前这一步的剧本(guide 整段摊开,其余步各留一行)+ 说明书(整单的事)+ +活动记忆选摘 + 最近轨迹(含人答的值与工具出参)+ 当下可为(标「须你启动」/「裁量项」)+ +**进行中**(有开着的活就不算断点)+ 目标缺口 + 待裁量前提 + 工具清单(带入参键名)+ +可写台账字典(地址/主键/列/列型)+ 本单草稿(`mine:*`)+ **基座读数** + 任务数据 +(摊平字段在前、整表殿后截断 —— 但剧本 `reads` 声明的表**置前不截断**)。 + +### 4.2 行动十四型(一拍一串,封顶 6) + +| 行动 | 形状 | 语义 | +|---|---|---| +| `start` | `{itemId}` | 启动标了人工激活的项;**已 Active / 已收掉再按 = 无事发生** | +| `plan` | `{itemId}` | 裁量项入场 | +| `disable` | `{itemId, reason}` | 此单不走这条 | +| `judge` | `{criterion, satisfied, rationale}` | 裁语义性前提 / L3-L4 判据;criterion 照判据名**整句原样抄** | +| `write` | `{itemId, address, rows:[{key,patch}]}` | 写草稿。授权面是台账声明;`itemId` 只是记账,挂不上照写不拦;patch 只带有值的列;空转写(一行没变)即拒 | +| `drop` | `{itemId, address, keys:[{主键}], reason}` | 撤本单草稿层的行;主键与写进去时一字不差 | +| `tool` | `{itemId, tool, input}` | 读类即调;写类转意向;未命中转人挂起 | +| `ask` | `{itemId, ask, fields:[{key,label,hint?,value?}], basis}` | 补全卡,挂起 | +| `entry` | `{itemId, ask, ledger, entryKey, fields, also?}` | 数据录入卡,挂起;确认那一下基座直写 | +| `decide` | `{itemId, ask, options(≥2), basis}` | 决策卡,挂起 | +| `human` | `{itemId, workOrder, card{title,ask,steps?,standard?,basis,fields?,blocks?}}` | 执行/验收工单,挂起。`blocks` 里绑 `ledger`+`fields` 的表**由基座铺行、由基座写回**,别自己抄行(回复有长度上限,铺长了截断整拍作废) | +| `listen` | `{itemId, eventType, keyFilter, deadline}` | 立监听,挂起;时限必配 | +| `finish` | `{itemId, rationale}` | 宣告一项完成;非人工激活的项就地过 Active 再收 | +| `submit` | `{rationale, fields?}` | 提交放行 → 预检 → 发验收卡挂起 | + +**串的纪律**(引擎强制):按序执行,谁被拒停在谁那儿(前缀已生效不回滚,理由喂回下一拍); +`tool` 之后截断(出参没看见,后面是闭着眼计划的);挂起型收拍;超封顶丢弃留痕。 +拍级附带 `say{text, tag, view?}`,不占行动位。 + +### 4.3 人工激活与 start(话与门同宽) + +标了 `manualActivationRule` 的项,**动它之前必须先 `start`** —— 调工具、发卡 +(ask/human/decide/entry)、宣告完成(finish)都算,一拍里 start 排在那个动作前面即可。 +只有 `write` 例外(itemId 只是记账)。 + +实测账(2026-08-02):这条规矩从前在提示词里写成「先 start **再调工具**」—— +只说了调工具,而门拦的是一整片;模型那一拍发的是 `human`,照字面读规矩管不着它, +于是漏了 start 白烧一拍。**话说的范围必须盖住门拦的范围,话窄一格,模型就正好漏在那一格上。** +写全之后三案例复跑全部被拒 0。 + +### 4.4 自动收步(doneWhen) + +引擎每拍开头对任务视图求每一步的 `doneWhen`,**真了就自己收那一项,不问模型** +(与模型同拍撞车时:引擎收过了,模型这拍的 finish 当已完成,不抛非法迁移)。 +人工激活的项**不给 doneWhen** —— 给了等于给引擎一把替人签字的钥匙。 + +--- + +## 5. 活动包怎么写 + +### 5.1 包结构 ``` activities// ├── package.json # @activities/,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) +│ ├── index.ts # export default defineActivity({ name }) +│ ├── manifest.ts # 清单六区 + 导出 DMN 常量 +│ ├── case-model.ts # CMMN_XML + NARRATIVE + INTAKE_STEPS + INTAKE_PHASES +│ ├── skills.ts # Skill[] +│ └── views/{report.ts, prd-style.ts} # 报告模版(见姊妹篇) +├── __tests__/ # 自测;只准 import @sdk/@contracts/@sfeel +└── dist/ # pnpm activities:build 产出 ``` -- **构建**:`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.2 清单六区要点(全量 schema 见 `packages/contracts/src/manifest.ts`) -## 5. 隔离与守卫 +**identity**:`activityId / name / packageVersion(改内容必升)/ category / sdkVersion(精确相等)/ checksum`。 -活动包**只准 import `@sdk` / `@contracts` / `@sfeel`**(depcruise 双向隔离:activities 不进 -apps,apps 也不 import activities —— 之间只有安装通道)。产物文本静态审黑名单: -`process.env`、`require(`、`node:fs/net/child_process`、`fetch(`、`XMLHttpRequest`、 -`eval(`、`new Function(`。视图模版含 ` 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 - - - - - - - - - - - - - update - missing_key_total > 0 - - …line_total > 0 and missing_key_total = 0… - … - …untyped_total = 0… - … - - - - - - - - - … - - - - - - - - - - - - -``` - -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="域.对象"`; -渲染时每格多渲一个 `` 孪生件,只读那份与输入那份**由样式表二选一**。 -容器是 `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` 的梯子) | +| ① input | 起单宽进;`properties[k].title` 写中文(补全卡上给人看的);**`inboundComm`** 声明哪几个入参字段合起来是那封来件 —— 基座起单那一刻归档进沟通记录 | +| ② output | mandatory 必发;selective 每组 ≥2 口、**每口写 `when` 谓词**(读台账列/基座读数)—— 放行时按序求值首个命中即出,算不出才退人选。载荷 = 已落地事实,别在出口另编载荷 | +| ③ requiredDeliverables | `ledger` 必写(预检数行);**一份成果由几张表承载就写 `ledgers`**(A1 的受理记录 = record + type_table + verdict 三张表、**一份**成果 —— 从前各占一条,人在成果面上看到三样东西);`editableAtRelease` 列出验收页上人可就地改的列(**AI 判的结论列也要在其列**,如 `type_tag` —— 人改它就是在推翻一次判定,那是他的职权) | +| ④ acceptanceCriteria | L1/L2 必带 predicate 且**只读基座读数**(§3.2);L4 可带谓词(谓词说了算,§3.3);**措辞写全**(「客户已确认:发件邮箱认出为老客户,或已为新客户建好档」——「(认出或已建档)」这种缩写人读不懂);**内部一致性不上卡**(打标依据对不对、两张表行数齐不齐 —— 该由收步判据看住,放行卡是给签字的人看的,一屏九条真要他过目的反而淹了);**判据得能指向一个可做的动作**(「去向合乎常理」这种让模型给自己没做的决定盖章的,删) | +| ⑤ revisableAtRelease | 放行时人可当场改的整单级值;拟值基座从台账现读,不让模型重述 | +| ⑥ prohibitions / authorization | 禁区短而硬;`humanCallBudget` 调人预算 | +| ⑦ resources | budget 按实测留一倍余量(A1:steps 60 / tokens 400k / 60min / 调人 4;实测常路 9-11 拍 8-13 万 token) | -搬不过来的如实空着 —— A1 空了两处:圆标里的首字母(得算)、「近 6 笔成交」迷你柱 -(`market.price_history` 有列没数据、没有写入方)。编六根柱子是撒谎,画一条空轨像组件坏了, -所以那一格不画,把「为什么没有」写在卡脚。 +**declarations**:台账(字段四标记,§3.1)、tools、models、listeners(时限必配)、harness。 -拼装件(`@sdk`,**A2–A7 仍在用**):`viewShell({title,titleRef,kicker,tone,icon,summary,meta,stamp,body,sign})` -外壳,正文用 `section` 编号章节串起 `letter` / `factCards` / `statsBand` / `tableOnly` / -`stagesOnly` / `noteOnly` / `verdict` / `actionList`。 +**assets**:`businessModel{cmmnXml, narrative, dmn, steps, phases}` + skills + viewTemplates + +cardSchemas + distillHooks。**steps 与 phases 都在这**(见 §5.4、§3.6)。 -- `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}])` —— 放行那一刻系统替人做了什么,一条一行。 +**samples ≥1 条**(常路 + 最长路径都给上;input 键与 contract.input 对齐 —— +A1 是 `customerEmail / subject / body / attachments`)。 -**取数约定**:`mine:域.对象` = 本单草稿行(成果表只用它 —— 别单放行过的记录不混进来); -裸键 = 摊平字段;引擎注入的案情:`case:stages` / `case:enabled` / `case:gaps` · -`case:openedAt`(本地时刻)/ `case:origin` / `case:owner` · -**`case:routeKey` / `case:routePort` / `case:routeTo` / `case:routeWhy`** = 这一单算出来走哪一口。 -放行那张必须摆出**下游要读的关键值**与**去向**—— 人签字前得看见这一单要往哪走。 +### 5.3 案例模型(CMMN)建模纪律 —— 每条都是实测账 -**模版不许算,但基座算过的可以摆**。两件事别混: -- 报告**自己算**一遍(把判据抄成静态文字、拿宽度骗人)—— 不行。抄本与判据是两份东西: - 改了 `when` 忘了改报告,屏上那一行就一直指着旧规则。 -- 摆**基座算好的那一次**—— 该做。去向由契约里各出口的 `when` 按序求值(首个命中即出), - 基座把结果注入 `case:route*`,与放行时发口的是同一次判定; - 报告只把它摆出来:壳上 `data-class-of="case:routeKey"` → `v-r1/r2/r3`,对应那一行自己上色, - 「命中 / 未命中」两句话都写在模版里、由样式表决定哪句现身。 - 算不出来(有一口没写谓词、或一条都不命中)三个键就是空的 —— 一行都不高亮,不冒充判过。 +1. **图上有几步就是几项,不多建一件。** A1 第一版 6 步建成 12 项(自加阶段/里程碑/ + 裁量项),模型全程跟 harness 较劲,一单二三十万 token 收不了口;扁平 7 项后零被拒。 +2. **案卷项 id 就是任务视图键名**(入参与台账列摊平后的名字)。对不上 = 前提永不满足 = + 第一个项都不 Enabled,而每一层单看都「正常」。 +3. **`manualActivationRule` 只标触达世界与关键交付的项**,且**不给 doneWhen**。 + A1 标两项:S3_confirm(要打断真人)、T_release(签下去触达世界)。 +4. **人的门要建成一项,不要挂成某一步收步条件里的半句。** 「缺东西才叫人」的写法, + 一封齐整的询价进来那半句当场为真,引擎自己收步,人一眼没看过就被叫去验收 + (用户原话:「我都没确认需求 它就让我验收了」)。做法:独立一项 + 人工激活 + + 不给 doneWhen + 下一步的入场前提取**只有人的回复才产生的计数列**(`confirmed_total`)。 +5. **哨兵必须有 ifPart,且读基座数、认「办完了没有」**(§3.3 放行门那行的教训)。 +6. `repetitionRule` 标常来回的项;判断菱形 = 两条互斥前提的 sentry,不是一个计划项。 +7. `autoComplete="true"`;里程碑前提达成即达成;`ifPart` 前缀 `semantic:` 交 judge。 +8. **每个计划项(除放行)恰属一个 phase**;放行收口那格基座加,活动别自己起名。 -**剧本的 `view` 决定每一拍推哪张**:引擎按「还没干完的那一步」推,活步都收完就一帧不推 —— -最后摆上去的那张(放行页)就是归档件。所以放行那一步的 `view` 必须指向验收目标件。 +### 5.4 剧本(steps)—— 每步一条,引擎按它组织上下文 -## 11. 运行时参考(活动作者要知道的引擎行为) +```ts +{ item: 'S2_normalize', // CMMN 计划项 id,对不上安装拒 + intent: '一句话:这一步交出什么', + guide: `这一步的做法(只在轮到它时进上下文)`, + writes: ['quote_intake.record'], // 可写哪几张表 + reads: ['customer.contact'], // 要读哪几张表 —— 声明了置前不截断(不声明会被盲截, + // 实测:客户联系人表落在刀口外,老客户被当成新客户) + tools: ['crm.history-price-read'], + doneWhen: 'line_total > 0 and quote_intake_record_rows = line_total', + view: 'intake-overview', // 这一步人看哪张件 —— 写清单时定死,不归模型临场挑 +} +``` -### 11.1 起单与补全(宽进) +**doneWhen 三坑**: +- `X != null` —— S-FEEL 未绑定名字 `!= null` 为 **true**,那一步开局即收。判「写过没有」 + 用正向式(`X >= 0`)或计数列; +- **布尔列不当判据** —— 合法值含 false,分不清「没写」与「写了 false」。 + 取同步显式写下的计数列(`route_decided_total`); +- **收步判据与验收判据同源** —— 都读基座读数,比同一件事。松出来的那一截就是返工 + (收了步却过不了验收,回头补写一张已收掉的表);同源还把发现点提前 + (「多写一行」从第 10 拍提前到第 2 拍)。 -`process.start` 不按 Schema 拒缺项;任务照建,缺 required 顶层键(缺 = `undefined|null|''`) -→ 任务落 `awaiting-human` + 发补全卡(不占预算)。答卡只收还缺的键、只收非空字符串; -「没法填」给理由即**带缺口开跑**,收敛责任归活动。数据按字段名在流程里流动,零强制类型。 +### 5.5 说明书与 guide 写作纪律 -### 11.2 Agent 的行动型(一拍一串,封顶 6) +说明书(narrative)只讲**整单**的事(开案、成品、SLA、口径);**逐步做法归剧本 guide** +(两处各写一份会分叉出模型里不存在的项名,有测试钉死)。guide 写作十条: -| 行动 | 形状 | 语义 | -|---|---|---| -| `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?}` | 提交放行 → 预检 → 发验收卡挂起 | +1. **规则写死,不留给模型判**(隐藏思考已关换十倍速度,边界必须写到不可能读歪: + 「关键字段只有 PN/EAU/交期」)。 +2. **写哪张表、哪些列,点名写死**;人在哪一步答的东西谁负责落表,写清。 +3. **卡的骨架照抄给它**(整段 JSON 例子,含 `steps`/`standard`/`also`/绑表的 `blocks`)—— + 散文描述不管用;系统提示词写死的形状盖过剧本,骨架里少一个键那个键就不存在。 +4. **一拍连做指引** + 常路拍数预期;工具批量入参,一次传完。 +5. **一单同一张卡只发一次**;人留空 = 给不了(§3.4)。别写「还缺就再发一张卡」—— + 提交闸上线后那条路不存在,模型照它办每次都撞「刚问过,人也答过了」(实测连烧四拍)。 +6. 绑台账的表**别自己抄行**;人提交后**别再 write 一遍**(值没变会被「写了等于没写」拒)。 +7. 回填按主键对行 —— 主键换个写法就是新开一行(实测同一颗料成了两行); + 发现主键抄错交给人,别用新主键再写一行。 +8. **别替下游产出**(§2.4);去向不用你选也不用人选,你只负责把判据数写对。 +9. 汇总列是你的活的,写明重算时机(人补完值你要把数改对);**机检读数你改不动, + 别为过判据去改自报数**。 +10. **卡面是纯文本**:`steps`/`standard`/say 里别写 `**` 星号记号。 -串的纪律(引擎强制):按序执行,谁被拒停在谁那儿(**前缀已生效不回滚**,理由喂回下一拍); -`tool` 之后截断(出参没看见,后面是闭着眼计划的);挂起型收拍;超封顶丢弃留痕。 -拍级附带 `say{text, tag, view?}` —— 不占行动位。 +### 5.6 判定表(DMN)—— 能算的不问 LLM -### 11.3 局面里有什么(Agent 每拍看到的) +单元格谓词 = S-FEEL(`'-'` 任意、`'"A","B"'` 析取、`'<= 90'`、`'[0..100]'`); +**FIRST 的行序就是业务优先级**(C 类 EOL 必须排最前);任一输入缺 → incomplete; +命中留痕、同输入必同结论、零 token。推荐做成 `pure: true` 的私有功能工具 +(`quote-intake.classify`),模型只取齐判据、调工具拿结论; +**出参键名与台账列同名**(拿回直接 write)、连 `counts` 一起给(供照抄); +「没有」也当判据回(无历史 → `historyDays: 9999`),别回错误。 -说明书 + 活动记忆选摘 + 最近轨迹(窗口 = memoryWindow;含人答的值与工具出参,旧出参截短; -装配技能戳不进叙事)+ 当下可为(Enabled)+ **进行中**(Active —— 有开着的活就不算断点)+ -目标缺口 + 待裁量前提 + 功能工具清单(带入参键名)+ 可写台账字典(地址/主键/列)+ -本单草稿(mine:*)+ 任务数据(摊平字段在前、正式版整表殿后截断)。 +### 5.7 工具 -### 11.4 提交与放行 +声明式(名字进 `declarations.tools`,系统没有的运行时转人 —— 执行卡代办, +人填的值就是出参);批量入参;读类即调、写类转意向、未授权报错。 +系统内化零声明即得:write/drop/ask/entry/decide/human/listen。 -`submit` → 落地预检(必交付物按 ledger 地址数本单草稿行;写类意向 drafted→prechecked; -数据冲突查草稿基线)→ 预检不过**不发卡**,理由喂回下一拍 → 过了发验收卡 -(判据机检结果 + 已验证动作清单 + 可修订值拟值 + 恰选一口)→ 人裁决: -pass → 草稿合并、意向执行(幂等键)、输出口发射、`released`; -redo/revise → 附意见驳回,环内回转。落地失败 → 落地失败卡,任务 `blocked`。 +### 5.8 报告模版 -### 11.5 Harness 兜底(活动不用管,但要知道) +**见《HTML报告生成规范.md》。** 那份是硬规矩:一单一模版贯穿始终、三个区各放什么、 +报告与表单的关系、模版语言、编辑态、PRD 目标件平移法、陷阱表。 -预算四项环外强制;停滞熔断:**连续 5 拍行动被拒**即停问模型,发卡把拒绝理由原话 -摆给人;`stallAfter` 判停滞、`nudge` 催办;模型隐藏思考已关(`reasoning:'off'`, -每拍 1.5-5s)—— 这就是说明书必须把规则写死的原因(§7.3 纪律 3)。 -事件到达痕只记 task 层一条;对话区只投人话(narration / 回执 / 世界事件), -机器踪迹进工作日志不进对话。 +### 5.9 技能与记忆 -## 12. 自测与安装 +技能 `{id, label, applicability, method[], version}`:常驻只有目录,正文按需加载, +载方法不载权力。方法里写锚点、纪律、批量惯例,不写产品文案。**剧本改了口径, +技能里同一句也要跟着改**(A1 定去向那条技能与 S6 同步删过开单指引)。 +任务记忆只增;终态封卷后 `distillHooks` 蒸馏进活动记忆。 -### 12.1 自测四段(照 `activities/quote-intake/__tests__/activity.test.ts`) +--- -1. **清单合规**:过 `activityManifestSchema`;监听时限已配;L1/L2 有谓词;禁区非空、 - 调人预算 > 0;**每件必交付物都有 ledger 地址**; -2. **案例模型**:双表示同在;触达世界的项标了人工激活;常来回的项标了可重复; - 末尾有收口验收项;说明书把每一步都交代到(逐步关键词断言); -3. **判定表逐行求值**(用 `@sfeel` 的 `evaluateUnaryTest` 写个 6 行的求值器): - 每条规则、优先级序、同输入同结论; -4. **声明与契约对齐**(§6.2 三条线)+ 模版纪律(局面数、无脚本、放行页摆关键值)+ - 技能引用的工具都在声明内。 +## 6. 自测与验收口径 -案例活动没有 `run`,自测**不跑回合**(那是 Loop 的事);mock 宿主对没有 run 的活动 -如实报错,不给假 outcome。 +### 6.1 包内自测(照 `activities/quote-intake/__tests__/`) -### 12.2 安装两审 +1. **清单合规**:过 schema;监听时限;L1/L2 有谓词且**谓词名都在台账列或基座读数里**; + 必交付物都有 ledger; +2. **案例模型**:双表示同在;触达世界的项标人工激活且无 doneWhen; + **每个计划项恰属一个 phase(除放行)、一项不进两个阶段**; + 剧本 item 与 CMMN 对得上;**doneWhen 开局不为真**(空局面求值必须 false); +3. **判定表逐行求值**; +4. **对齐线**:deliverable.ledger 在声明里;出口载荷标量有列承载; + 剧本 view 都在 viewTemplates 里; +5. **模版纪律**(见姊妹篇 §9)。 -静态审(任一 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**(声明了系统没有 → 运行时转人,是系统的缺口不是活动的错)。 -回放审:历史成功样本仍须全过;首装以样例集替代。 +### 6.2 五道门禁与安装两审 -### 12.3 试跑口径 +`pnpm gates`(typecheck / lint / depcruise / api:check / test),以退出码验。 +安装静态审 error 即拒(黑名单见 §2.2;`acceptance-level-misreported` = L1/L2 没谓词)。 -试跑一律用 AI 账号(绝不派到用户工作台);起单走登记触发;逐拍对账看 -`sys.task_memory`(每条「行动被拒」都是设计欠账);测完 -`pnpm --filter @app/api reset-runs` 清运行数据(清跑出来的,留配置好的)。 +### 6.3 试跑口径与验收标准 -## 13. 记忆与技能 +- **试跑一律用 AI 账号**(密码 `jcai-dev-2026`),绝不派到用户工作台; + 测完 `pnpm --filter @app/api reset-runs`。 +- **三条常备案例**(形状真不同,不是同一封信改个号): + ① 老客户 · BOM 附件齐整 · 全 A 类直通;② 老客户 · 正文点名一条本轮不报(测人手撤行); + ③ 新客户 · 中文正文列料 · 无附件 · 缺必填(测建档 + 人补格子)。 +- **验收标准:三案例全部 `released`、行动被拒 0。** + 每一条「行动被拒」都是设计欠账 —— 修清单或剧本,不怪模型; + 被拒理由指向死路(模型改不动的那种)是 harness 欠账,报上去。 -- **任务记忆**只增不减;终态封卷后 `distillHooks` 蒸馏进**活动记忆**(跨任务、四层、 - 渐进披露、语义引擎不读)。 -- **技能** `{id, label, applicability, method, crystallized[], knowledgeRefs[], version}`: - 常驻上下文只有目录(`label: applicability`),正文按需加载。**载方法不载权力** —— - 结晶片段调工具仍过白名单。A1 的三条(读懂一封询价 / 打标前备齐判据 / 状况标不等于去向) - 是好范本:方法里写锚点、纪律、批量惯例,不写成产品文案。 +--- -## 14. 排障速查表(症状 → 根因 → 处置;全部实测账) +## 7. 排障速查表(症状 → 根因 → 处置;全部实测账) | 症状 | 根因 | 处置 | |---|---|---| -| 起单后第一个项都不 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 ⑦:按实测留一倍余量 | +| 起单后第一个项都不 Enabled | 案卷项 id 不是任务视图键名 | §5.3-2 | +| 连续「迁移非法/项不存在」熔断 | 计划项建多了/嵌套了 | §5.3-1 扁平化 | +| 一单 20+ 拍才收口 | 没写一拍连做/拍数预期;或项过多 | §5.5-4 | +| 机检判据全绿而交付物是错的 | 等式两边都由活动自报 | 一边改读基座读数(§3.2) | +| 一条 L1/L2 永远绿不了而数据合规 | 读了自报汇总列 | 标 `required`,读 `_missing`(§3.2 违反史①) | +| 台账里某个数被改小、判据变绿 | 门只接受「是」,模型去改自报数 | 机检只读基座数;数不出的降 L3/L4(违反史②) | +| 没打标就进了放行门 | 哨兵只有 onPart(写过一笔就开门) | ifPart 读基座数认「办完没有」(§3.3) | +| 同一条理由连拒到熔断而数与行都对 | 拒回把「活没干完」说成「数/行不对」 | 已修:`_rows`=0 直说那一步没办 | +| 行数对不上,模型只会改数字 | 拒回没摆主键名单 | 已修:`_keys` 名单;照名单 drop | +| 「要先 start」而 start 也被拒 | 拒回没按状态分开说 | 已修;start 已收的项无事发生 | +| 模型漏 start 白烧一拍 | 提示词的话比门窄(只说了调工具) | 已修:话与门同宽(§4.3) | +| 人答了卡,模型又发一张一样的 | 剧本残留「还缺就再发一张卡」死路 | §5.5-5:一单一卡;必填有闸兜着 | +| 人在卡上填了值,台账还是空的 | doneWhen 写成「问过就算完」 | 等「答案落了表」的计数(`answered_total`) | +| 该经过人的一步整个没发生 | 「经过人」写成了收步条件里的半句 | §5.3-4:独立一项+人工激活 | +| 人改了表,分布条/去向不动 | 汇总数没人重算;或保存没落库 | 分类列标 `tally` 由基座数;保存走件上按地址那条路(姊妹篇 §6) | +| 人在件上保存,一格都没落 | 保存借道当下那张卡的 blocks(熔断卡没有 blocks) | 已修:按 `data-edit` 地址收,授权=台账声明 | +| L4 判错(人填了它说没填) | 整条只读裁断,模型凭记忆写理由 | L4 带谓词,谓词说了算(§3.3);`expect` 别标我方写的注 | +| 判据卡面与提交门说的不一样 | 两处各算各的 | 已修:读时现算,同一段 `criterionCheck` | +| 进度条在人办事时已走过那一格 | phases 漏了那一项 | §3.6:每项恰属一格,自测钉死 | +| 收了步却过不了验收 | 收步判据比验收松 | §5.4:同源 | +| 全 0 判据过不去连撞熔断 | 没写过 ≠ 写了 0 行 | 已修:声明过的表铺 0 | +| 补全卡答了数据没进台账 | 系统不替 Agent 写 | §5.5:答案要自己 write(绑表除外) | +| 同一颗料成果表里两行 | 主键换写法 = 新开一行 | §5.5-7 | +| 数值列被写成 0 表示「没有」 | 模型拿 0 当空 | 写死:没有给 `""`(归型不写这一列) | +| 制表符 BOM 拆错列 | 连续 `\t` 是中间格空 | 附件 `rows` 有值照它拆,别自己数制表符 | +| 工作区画面不换 / 推错张 | view 名不在模版里 / 剧本没写 view | §5.4 view 定死 | +| 归档件被冲成别的屏 | 收口后按固定名推退到第一张 | 已修:按剧本推,活步收完不推 | +| 消息被「无主入站待认领」刷屏 | 台账 data 事件进了兜底 | 已修:只有 `source.kind==='channel'` 才兜 | +| 下游网关取不到值 | 出口标量没有台账列承载 | §5.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 就卡住」的判据,先查读数在不在 | +| 收口前一两拍预算耗尽 | budget 给太紧 | §5.2-⑦ 留一倍余量 | -## 15. 待实现与已知不一致 +## 8. 待实现与已知不一致 | # | 事项 | 现状 | 目标 | |---|---|---|---| -| ④ | 受理分支误吞验收裁决 | `deliver` 的受理分支判 `awaiting-human && human` 不看 cardId(`task-service.ts`);「没法填」带缺口开跑的单再遇验收驳回会被误判成补全 | 受理分支按补全卡 cardId 匹配。**开发侧规避**:带缺口开跑的单尽早在环内补齐缺项 | -| ⑤ | 治理台白名单授权口 | 只有种子常量表(`seed-mvp.ts`),无 API 无 UI | 治理台按活动配白名单 | +| ④ | 受理分支误吞验收裁决 | `deliver` 的受理分支判 `awaiting-human && human` 不看 cardId | 按补全卡 cardId 匹配;开发侧规避:带缺口开跑的单尽早补齐 | +| ⑤ | 治理台白名单授权口 | 只有种子常量表 | 治理台按活动配白名单 | | ⑦ | dist 真编译 | `index.js` 是源码文本拷贝(债 M5-3) | build-activities 真编译 | -| ⑧ | 单拍路径的工具调用缝 | 工具清单已进提示词(只给功能工具);调用仍是自由字符串经 ToolInvoker,未走总线编译 | 开环转正或单拍接总线 | -| ⑨ | `preauthorizedActions` 死代码 | 无调用方 | manifest 里**不要写** | -| ⑩ | `SchemaValidator` 双死依赖 | task/process 声明了不再调 | 清理 | +| ⑧ | 单拍工具调用缝 | 调用经 ToolInvoker 未走总线编译 | 开环转正或接总线 | +| ⑨ | `preauthorizedActions` 死代码 | 无调用方 | manifest 里不要写 | +| ⑩ | `SchemaValidator` 双死依赖 | 声明了不再调 | 清理 | | ⑪ | 评价卡(rate) | schema 在、链路未接 | 用户定:暂不做 | +| ⑫ | `judge 还没裁`各花一拍 | 待裁量判据不在单拍局面里,撞拒回才知道 | 把「还没裁的判据」进局面(结构改动,未做) | 小不一致(不阻塞):`declarations.tools[].version` 无消费者;`contract.resources.tools` -双份声明;replay 模式跳过白名单检查;注册路由造不出纯工具;工具 `retries` 声明了不重试; -转人答案不经出参 Schema 校验(债 M6-1);进程重启后动作级 trace 丢失(债 M6-2)。 +双份声明;replay 跳过白名单;工具 `retries` 不重试;转人答案不经出参 Schema 校验(债 M6-1)。