E04 修到零被拒线,harness 补七刀:跨进程租约与广播桥、原子写、引用墙、对话区按序号排

活动侧(E04 2.1.0 → 2.1.9,I01 1.2.2):
- 覆盖闭合成为机检:基座新增 distinct 读数,「计划盖住的客户数 = 候选行数」钉进
  S3 收步 / S4 哨兵 / 放行 L2;人加的候选客户不再被整单无视
- 「关键人待识别」占位句式全链退役:查无联系人先发数据录入卡收人;契约新增 refersTo,
  计划/回执的 contact_code 只许引用真实存在的联系人(安装期核引用目标)
- 台账更新确认摆全落库面:客户档案/联系人档案两张落库预览(touched: 数据源,
  正式现值 + 草稿叠加),可补改、可加行(自增主键由基座续号);姓名升必填
- 更新明细:update_key 位置键 + 「同一处只留一条」L2;依据写人话不带机器键;
  标题旁不摆单号;判据与文案不夹字段名;行标识去掉单标识那一段
- 回执骨架 = 计划行数钉进 S3 收步与 S4 哨兵;重做分支不许删计划凑数
- 阶段刻度对齐文档 7 步;起单样例预填满

基座侧:
- 两个后端连同一个库:task-claim 跨进程租约(迁移 0030)+ Postgres LISTEN/NOTIFY
  广播桥;对话区按任务内记忆序号排,不按墙上时钟;并卡「刚说过话」按记忆序列判紧邻
- 多行 write 整批先验后写(dryRun),被拒 = 一行都没落
- 决策卡意见与本单草稿字面比对,归属以观察进轨迹(note-anchors)
- 括号栈补「补闭括号」两形(少一个 } / 行对象没关直接跟下一行)
- 件上加行:空着的自增主键直进续号,不从别的行借号;未落下的行点名回给前端
- 人答复落库那一刻就把 runtime 翻 running(task.wake),两块同帧变脸
- 试跑单可落真库(land_master,迁移 0031,默认开);数据域按活动分组;
  JSON 列不再是 [object Object];按钮键感 + 在办进度条 + 防抖
- 报价链 A1–A7 从系统卸载(只卸不删源码),种子只装 E04/I01

契约变更(api-report 已刷新):FieldDeclaration.distinct / refersTo,
objectDeclaration.file,landMaster,unkeyed,assetFiles,ledgerByActivityRoute。
1700 测 / typecheck / lint / depcruise / api:check 绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
This commit is contained in:
xcs
2026-08-25 01:23:30 +08:00
co-authored by Claude Fable 5
parent 92dd729c36
commit f3397a60ef
91 changed files with 3919 additions and 350 deletions
@@ -35,11 +35,13 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<caseFileItem id="scope_confirmed_total" name="覆盖范围已核对确认(人环走完的凭据)"/>
<caseFileItem id="crm_coverage_plan_rows" name="经营计划行数(基座现数)"/>
<caseFileItem id="crm_coverage_plan_missing" name="经营计划缺必填的行数(基座现数)"/>
<caseFileItem id="crm_coverage_plan_distinct_customer_code" name="经营计划盖住了几家客户(基座现数不同值)"/>
<caseFileItem id="plan_approved_total" name="经营计划已审批通过(仅通过时置一)"/>
<caseFileItem id="crm_coverage_receipt_rows" name="执行回执行数(基座现数)"/>
<caseFileItem id="crm_coverage_receipt_missing" name="执行回执缺必填的行数(基座现数)"/>
<caseFileItem id="crm_coverage_ledger_update_rows" name="台账更新明细行数(基座现数)"/>
<caseFileItem id="crm_coverage_ledger_update_missing" name="台账更新明细缺必填的行数(基座现数)"/>
<caseFileItem id="crm_coverage_ledger_update_distinct_update_key" name="台账更新明细覆盖了几处不同位置(基座现数不同值)"/>
<caseFileItem id="customer_profile_rows" name="本单写下的客户档案行数(基座现数)"/>
<caseFileItem id="customer_profile_missing" name="客户档案缺必填的行数(基座现数)"/>
<caseFileItem id="customer_contact_rows" name="本单写下的客户联系人行数(基座现数)"/>
@@ -60,12 +62,12 @@ export const CMMN_XML = `<?xml version="1.0" encoding="UTF-8"?>
<!-- ③ 经营计划已起草且齐 —— 审批那一步的入场前提 -->
<sentry id="s_plan_drafted">
<onPart id="op_pd" caseFileItemRef="crm_coverage_plan_rows"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pd"><condition>crm_coverage_plan_rows &gt; 0 and crm_coverage_plan_missing = 0</condition></ifPart>
<ifPart id="if_pd"><condition>crm_coverage_plan_rows &gt; 0 and crm_coverage_plan_missing = 0 and crm_coverage_plan_distinct_customer_code = crm_coverage_candidate_rows</condition></ifPart>
</sentry>
<!-- ④ 经营计划已审批通过 —— 交人执行那一步的入场前提 -->
<sentry id="s_plan_approved">
<onPart id="op_pa" caseFileItemRef="plan_approved_total"><standardEvent>update</standardEvent></onPart>
<ifPart id="if_pa"><condition>plan_approved_total &gt; 0</condition></ifPart>
<ifPart id="if_pa"><condition>plan_approved_total &gt; 0 and crm_coverage_receipt_rows = crm_coverage_plan_rows</condition></ifPart>
</sentry>
<!--
⑤ 执行回执已交回且齐 —— 收口写台账那一步的入场前提。
@@ -180,6 +182,17 @@ export const NARRATIVE = `§0 开案:**这一轮要经营哪些客户的关系,
**六步走**:识别候选 → 人核对范围 → 制定计划 → 人审批(可能来回改一轮)→
人执行填回执 → 收口写台账 → 人签字放行。
**人核对过的候选清单是后面每一步的输入契约**:覆盖范围确认那一步人可能增删行
(轨迹里「就地修订成果」那条痕点名了增删的是谁),**他加的行与你识别的同等作数、
他删的行别再写回去** —— 缺口分析、经营计划、执行回执、收口写台账,每一步都以
台账现读的 \`crm_coverage.candidate\` 全表为准,不以你 S1 识别时的那份记忆为准。
经营计划必须一家不落(收步判据数这个闭合:计划盖住的客户数 = 候选清单行数)。
**计划与回执只指向真实存在的关键人**:\`crm_coverage.plan\` / \`receipt\` 的
contact_code 必须是 \`customer.contact\` 里已有的编码,contact_name 必须是真人姓名 ——
任何一步都不许自造联系人编码、不许拿占位称呼顶在人名格里。查无联系人的候选客户,
先在 S3 发数据录入卡把第一位关键人收进来(骨架见 S3 剧本),再排它的计划。
【本单可能用到的另一种卡 —— 新客户建档,长这样,只在 S1_identify_candidates 这一项里
发生,\`customers\` 里出现对象形状的元素才发】新客户这一刻没有编码,**不能自己编一个**
(客户编码是主数据的主键,编错一次后面每一单都跟着错——这条纪律与 A1 quote-intake
@@ -383,7 +396,20 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
reads: ['crm_coverage.candidate', 'customer.profile', 'customer.contact'],
view: 'coverage-plan',
tools: ['crm-relationship-coverage.recompute-score', 'crm-relationship-coverage.rank-key-persons'],
doneWhen: 'crm_coverage_plan_rows > 0 and crm_coverage_plan_missing = 0',
/*
* 覆盖闭合(2026-08-24 柴浩实测):只数「计划有没有行」的话,人在覆盖范围确认卡上
* 加的那家客户被漏掉后这一步照样自动收步 —— 计划盖住的客户数(distinct,基座数)
* 必须等于候选清单行数(人增删后的现数),一家不落这一步才算办完。
*/
doneWhen:
'crm_coverage_plan_rows > 0 and crm_coverage_plan_missing = 0 and ' +
'crm_coverage_plan_distinct_customer_code = crm_coverage_candidate_rows and ' +
/*
* 回执骨架与计划一一对应(2026-08-24 柴浩实测:计划 7 行回执 6 行,人填完 6 条就能交卷,
* 到放行门才撞墙)—— 少一行骨架人就少一格可填,而提交闸只数已有行缺不缺必填,
* 数不出「少了一行」。这里与 S4 入场哨兵同一句,派回执单之前就得齐。
*/
'crm_coverage_receipt_rows = crm_coverage_plan_rows',
guide: `候选范围定了,**现在读现状、找缺口、排计划** —— 固定三拍,一拍一件事,别在同一拍里
把「调工具」和「写台账」串在一起(**工具调用后同拍其余动作作废,结果要下一拍才看得见**;
串了也是白串,只会多烧一拍)。这一步只需要调工具**各一次**,拿到结果后不要再调第二次。
@@ -396,6 +422,11 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
候选范围毫无关系)——动手前先把候选清单的 \`customer_code\` 抄一遍,写计划时逐条核对
是不是都在这张抄出来的清单里,不在的一律不许写。
**也一家都不能少** —— 抄名单抄的是台账现读的 \`crm_coverage.candidate\` **全表**,
不是你 S1 识别时记住的那几家:人核对时可能加了行(轨迹里那条「就地修订成果:新增」
点名了加的是谁),**人加的行与你识别的同等作数**。收步判据现在数这个闭合
(计划盖住的客户数 = 候选清单行数),漏一家这一步就收不了。
【第一拍:缺口分析,只调工具,别的什么都不写】把本轮全部候选客户(读
\`crm_coverage.candidate\`)一次性打包,调一次
\`crm-relationship-coverage.recompute-score\`,入参:
@@ -421,6 +452,9 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
任何节点、白算了这一趟。空缺字段(如 \`influence\` 还没定过)照现有值填,是空字符串
或 0 都行,工具认得。
多家候选客户就都塞进同一个 \`customers\` 数组里,**一次调完,不要一家调一次**。
某家候选客户在 \`customer.contact\` 里一个联系人都没有(人核对时额外纳入的客户常是
这种)—— \`contacts\` 传空数组 \`[]\`,工具会如实给出「三个节点全是缺口」,
这正是要的答案,不是异常。
调完这一拍就结束(工具出参要下一拍才看得见)。
【第二拍:排序,只调工具,别的什么都不写】把上一拍 \`results\` 里出现过的**所有联系人**
@@ -436,6 +470,14 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
离这个节点最近的联系人**(按 \`decisionNode\` 匹配,匹配不上就挑影响力最高的那位)
写一条「去覆盖这个节点」的动作。**\`gapNodes\` 全员缺口是正常结果,不是异常,
不需要回去重调工具确认。**
**计划表里的 contact_code 只许写 \`customer.contact\` 里真实存在的编码,
contact_name 只许写真人姓名** —— 不许为缺口节点自造顺延的引用码、不许拿占位称呼
顶在关键人那一格(实测走岔过:模型给两个缺口节点各编了一个顺延号、关键人一栏
填了占位称呼 —— 那不是计划,是把「还没认识人」伪装成「有人」;审批的人一眼就退)。
缺口节点的动作挂在**已知联系人**名下,「识别/引荐」写进动作内容,不写进人名。
计划表**一位联系人一行**(主键就是联系人编码):同一位联系人罩几个缺口节点,
几个动作并进**他那一行**的 action(分号连写),不为节点另开行。
引擎会核 contact_code 真的在 \`customer.contact\` 里 —— 编出来的号写不进去。
② **写 \`crm_coverage.plan\`,按第二拍的 rank_score 降序、一个关键人一行**(rows 收数组;
模版不排序,你必须已经按序写)。round_ref、contact_code(**必填**)、customer_code
@@ -447,6 +489,25 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
\`"low"\`)、relationship_gap(**必填**,第二拍算出的数)、rank_score(**必填**,第二拍
算出的数)、target_date(**必填**,具体日期,没有时间点的建议不算数)。
**查无任何联系人的候选客户,先把人收进来,再排它的计划**(人核对时额外纳入的客户
常是这种 —— 该经营谁,加行的人自己知道;你不许替他编一个人):在调工具**之前**
给这样的客户各发一张数据录入卡,照这个骨架(一家一张、一单只发一次;
人确认那一下基座直接落草稿,你不经手):
{"kind":"entry","itemId":"S3_plan",
"ask":"新纳入的客户 <客户名> 还没有联系人档案,请录入这一轮要经营的第一位关键人",
"ledger":"customer.contact","key":{"customer_code":"<候选行的客户编码>"},
"fields":[
{"key":"name","label":"姓名","required":true},
{"key":"title","label":"职务"},
{"key":"decision_node","label":"决策链节点(bt-01 采购 / bt-02 技术 / bt-03 高层)"}
]}
联系人编码由基座续号,不进卡面。发完这一拍就结束(发卡挂起);人答回之后这家客户
就有真实联系人了(在 \`customer.contact\` 里看得到),照上面三拍走,计划行引用
**这位真人**的编码与姓名,动作写首访与关系建立(influence/relationship 还没首评,
排序入参按默认:influence 按职务估、没法估写 \`"low"\`,relationship 写 1)。
**不许因为查无联系人就把这家客户跳过** —— 跳过它,收步判据
(计划客户数 = 候选行数)就差一家,这一步永远收不了。
③ **同一拍,给每条计划配一行 \`crm_coverage.receipt\` 骨架**(这一步只写骨架,完成状态那几列
留空,S5 那一步才由人填):round_ref、contact_code(与 plan 同一个,**必填**)、
customer_code(选填,照抄)、customer_name(选填,照抄 —— 执行回执工单的「对象」列长
@@ -455,8 +516,10 @@ cust_type(选填,\`customer.profile.cust_type\` 有值就直接抄它——新
completion_status / result_summary / customer_feedback / key_person_change
**这一步一格都不写**。
④ 若某候选客户的 \`cust_type\`/\`bt_level\` 在 S1 那步没判出来,顺手补写
\`crm_coverage.candidate\` 的这两列(同 PK 覆盖写)。
④ 若某候选客户的 \`cust_type\`/\`bt_level\` **还空着**,顺手补写
\`crm_coverage.candidate\` 的这两列(同 PK 覆盖写)。**两列都已有值的行一个字都不写**
—— 原值重写会被当空转拒(实测:这一句从前没写「还空着」,模型把有值的行
照抄了一遍,白吃一次拒)。
写完这一拍就结束(不必你 finish)——\`crm_coverage_plan_rows > 0 and
crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
@@ -494,20 +557,34 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
发完这一拍就结束了(发卡挂起)。
【驳回之后 —— 不管有没有具体意见,都只处理这一轮,不许再追问一遍】
① \`value\` 以 \`"custom:"\` 开头 —— \`note\` 就是他写的具体意见,**按这条意见**修订
\`crm_coverage.plan\`(同 PK 覆盖写对应的行,不开新计划项、不重新进入 sentry)。
① \`value\` 以 \`"custom:"\` 开头 —— \`note\` 就是他写的具体意见。**先认归属,再动笔**:
意见里点了名(客户名 / 关键人名 / 编码)就拿那个名字与计划表逐行比对
(customer_name / contact_name / customer_code / contact_code 四列;轨迹里若有一条
「意见按字面点名对上了…」的观察,那是基座比对好的答案,**照它认,不自己另猜**)。
**改的必须是被点名的那一行** —— 点名的客户没有意见里说的那种联系人
(比如让去见某个职位的人,而这家的关键人还待识别),改的是这一行自己的动作内容
(把首访对象换成那个职位),**不是把改动挪到别家碰巧有这种联系人的行头上**;
名字对不上任何一行、或对上不止一行认不出唯一,**不许猜**:重新发卡,把
「你理解要改哪一行」写进 ask 请人确认(这算修订重提交,不算追问理由)。
认准之后**按这条意见**修订 \`crm_coverage.plan\`
(同 PK 覆盖写对应的行,不开新计划项、不重新进入 sentry)。
② \`value\` 是 \`"reject"\`(点了固定按钮,没写具体意见)—— **不许再发卡追问理由**
(那两个按钮点出来的回复永远没有 note,再问一遍是把人指向一扇打不开的门,
会被同一句话拒回,连问出真话的机会都没有)。改成你自己复核:排序有没有明显不合理、
有没有遗漏更紧要的缺口节点、理由那一句站不站得住,尽量改得更有说服力;复核后**至少要有
一处实质性变化**(改排序、换动作、补理由,不能原样重发)。
③ **改了 \`action\` 这一列的话,\`crm_coverage.receipt\` 同一行的 \`action\` 也要跟着改**
③ **计划与回执骨架一一对应,改哪边都得同步**:加了一条计划就同拍给它加一行
\`crm_coverage.receipt\` 骨架(round_ref + contact_code,其余照抄计划,完成状态那几列留空);
撤了一条计划就 \`drop\` 它的回执骨架。引擎数「回执行数 = 计划行数」,差一行,执行单派不出去。
**改了 \`action\` 这一列的话,\`crm_coverage.receipt\` 同一行的 \`action\` 也要跟着改**
(同 PK——\`round_ref\`+\`contact_code\`——覆盖写)。receipt.action 是 S3 那一拍从
plan.action **抄**过来的展示副本(执行回执工单直接读它、只读展示),这里只改了
plan 不改 receipt,S5 那一步人看到的还是驳回前的旧动作——已经实测过:审批来回改了
三稿,receipt 里摆的还是第一稿,推进到执行回执时对不上人刚通过的那一版。
④ **重新发卡的 \`ask\` 必须换一种说法**,带上修订轮次与改了什么,例如
"已复核并修订(第2稿:调整了 CU-0142 的优先级,补充了理由)请再次审批"——
④ **重新发卡的 \`ask\` 必须换一种说法**,带上修订轮次与**改了哪一家哪位、改成了什么**
(用人读名:客户名 · 关键人名,别只给编码 —— 这句话是给人核对归属的,
他要一眼看出你改的是不是他点名的那一行),例如
"已复核并修订(第2稿:把 客户甲·采购经理 的动作改为下月带认证报告拜访)请再次审批"——
措辞不换,系统会把它当成同一张卡:前两次静默吞掉(人看不到新卡),第三次直接拒。
**每一轮的 \`ask\` 都要与上一轮明显不同**,不止改个数字。
⑤ 再走一遍【发一张决策卡】那一段(用新措辞,\`allowCustom\` 仍然要给),然后这一拍结束
@@ -608,9 +685,13 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
【第二拍:只写这两张,别的表这一拍不碰】
· **写 \`customer.contact\`**(对应联系人行,同 PK 覆盖写):**碰过的每一行,
\`influence\` / \`relationship\` / \`key_person\` 三列一列都不许缺**(基座数缺必填
只看本单写下的行 —— 你写了这一行却漏了其中一列,\`customer_contact_missing\`
就大于零,放行门永远打不开):\`influence\` 写推导后的评级(没变化就照现值抄一遍)、
\`name\` / \`influence\` / \`relationship\` / \`key_person\` 四列一列都不许缺**
(基座数缺必填只看本单写下的行 —— 你写了这一行却漏了其中一列,
\`customer_contact_missing\` 就大于零,放行门永远打不开;name 照现值抄一遍即可)。
**没有真实姓名的人不许建联系人行** —— 「计划要经营的岗位」不是人,节点缺口
留在 decision_chain 里如实标 covered:false,不落 customer.contact
(实测炸过:两条人名职务全空的"联系人"进了正式表,还标着关键人、亲密度3 ——
那是把计划冒充成了档案,后来只能人工删掉):\`influence\` 写推导后的评级(没变化就照现值抄一遍)、
\`relationship\` 写推导后的新值(没变化照现值抄)、\`key_person\` 照这轮判断写;
**本单新建档的联系人**(录入卡只落了姓名/职务/邮箱)这三列由你首评:
\`influence\` 按职务估(总监/VP 级 high、经理级 medium、其余 low)、
@@ -618,10 +699,25 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
\`role\`/\`decision_node\`/\`comm_pref\` 若这轮有新信息一并补;
\`change_log\` 追加一句本轮变动(离职/调岗从回执的 \`key_person_change\` 抓,
没有变动不必写这一列)。
· **写 \`customer.profile\`**(对应客户行,同 PK 覆盖写):\`coverage_score\` = 工具
· **写 \`customer.profile\`**(对应客户行,同 PK 覆盖写):**碰过的每一行把
\`name\` 照现值带上(必填,漏了放行门不开)**;\`coverage_score\` = 工具
算出的新分数(**必填**);\`decision_chain\` = 更新后的节点覆盖状态(**必填**,
整个数组重写,不是增量合并);\`interaction_timeline\` 追加本轮的互动条目(选填,
没有新增互动的客户不必写这一列)。
**本轮新建档的客户(候选清单里人加的那家)要把档案填实,不许只落两个数**:
\`name\` / \`cust_type\` 照候选行抄(candidate 里人核对过的就是权威);
\`industry\` / \`region\` 从**已知信息**里分析 —— 来件正文、背景说明(note)、
客户类型、回执里人写的内容,推得出就填、推不出留空(空格会以「建议填」摆在
台账更新确认屏上,人补得了)—— **不许编造一个查无出处的行业或地区**。
· 回执若带回了**新认识的关键人**(姓名/职务在 \`result_summary\`/\`key_person_change\` 里),
给这家客户**新建**联系人行 —— \`contact_code\` 照声明的续号规则铸:
\`<customer_code>-两位序号\`,序号接在该客户名下现有联系人(查 \`customer.contact\`
全表)最大号之后,一位都没有就从 \`-01\` 起;姓名/职务照回执写,首评三列
(influence/relationship/key_person)按上面「本单新建档」那条规则给。
回执没带回人,就什么都不建,如实留缺口。
**人工新增的候选客户在 \`customer.profile\` 里还没有档案的,这一拍补一行**
(customer_code/name 照候选行抄,coverage_score 与 decision_chain 照工具出参写)——
没有档案行,这家客户的覆盖度就没有落点。
· **把办完的候选行状态置 \`covered\`**(写 \`crm_coverage.candidate\`,同 PK 覆盖写,
只带 status 一列):回执里有 \`done\` 动作的客户置 ASCII \`covered\`;
全部 \`skipped\` 的留 \`pending\` 如实 —— 清单上那枚状态徽是给人看的,
@@ -638,7 +734,9 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
view: 'coverage-ledger-update',
tools: [],
doneWhen:
'crm_coverage_ledger_update_rows > 0 and crm_coverage_ledger_update_missing = 0 and customer_profile_missing = 0 and customer_contact_missing = 0',
'crm_coverage_ledger_update_rows > 0 and crm_coverage_ledger_update_missing = 0 and ' +
'crm_coverage_ledger_update_distinct_update_key = crm_coverage_ledger_update_rows and ' +
'customer_profile_missing = 0 and customer_contact_missing = 0',
guide: `主数据已在上一步写回,**这一步只写两张表、一拍连做**:先写更新明细
(它最费篇幅,放在串首),再写结论。**上一步那三张表(contact/profile/candidate)
这一拍不许再写一遍** —— 再写一遍原值只会被当空转拒掉。
@@ -649,8 +747,12 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
如"客户主台账·关系覆盖度")、ledger_address(**必填**,机器地址,如
"customer.profile.coverage_score")、customer_code(**必填**)、contact_code
(选填,客户级字段留空)、field_label(**必填**,人读列名)、before_value(**必填**,
无值写字面"(无)")、after_value(**必填**)、evidence_ref(**必填**,引用具体回执
事实,如"回执 contact_code=CU-0142-01:已完成拜访,亲密度由3提升至4")。
无值写字面"(无)")、after_value(**必填**)、evidence_ref(**必填**,**用人话**引用具体
回执事实,如"Itelma·Ekaterina 的回执:已完成拜访,亲密度由3提升至4" —— 写客户名与
人名,**不写联系人编码、不带任何机器键前缀**:这一列是摆给签字的人看的)、
update_key(**必填**,更新位置键,按「台账地址|客户编码|联系人编码|字段」四段用竖线
拼,客户级字段第三段留空,如"customer.profile.coverage_score|CU-0142||关系覆盖度"
—— 同一处位置本轮只能有一行,引擎数这个)。
**没有真实变化的字段不必开一行** —— 明细表只记「真改了什么」。
每一处真实变化的依据都在回执与上一步写下的新值里,对照着写,别凭记忆。
· **写 \`crm_coverage.verdict\`**(同一拍接着写):ledger_update_confirmed_total = 1、
@@ -669,7 +771,10 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
view: 'coverage-ledger-update',
tools: [],
/* 放行触达世界,必须人按一下 —— 引擎不自动收它 */
guide: `台账更新明细齐了、主数据缺项也补齐了,**可以走放行了**
guide: `台账更新确认那一屏摆着三样:更新明细(你的摘要)+ **两张落库预览**
(将写入的客户档案 / 联系人档案,人在上面补字段、加联系人都是合法的兜底 ——
他补的值、他加的行已在草稿层,**你不许再 write 覆盖**;判齐与哨兵读的是现值,
人补完门自己开)。台账更新明细齐了、主数据缺项也补齐了,**可以走放行了**
(T_release 人工激活:动它就算启动,不必单发 start)。
【**先看一眼轨迹里有没有「验收被打回重做」**】有的话,这一拍**不是**再提交一遍 ——
@@ -682,10 +787,17 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
· 他说**台账更新没依据 / 数不对** → 改 \`crm_coverage.ledger_update\` 的
\`evidence_ref\`(点名是哪条回执支持它)或 \`before_value\`/\`after_value\`。
· 他说**结论没说清** → 改 \`crm_coverage.verdict.summary\`。
· 放行门说**「每条计划都收到执行回执」不成立**(回执比计划少行)→ **不许删计划来凑数**
(删了「每家候选都进了计划」立刻不成立,两堵墙对着撞);补上缺的那行回执骨架
(round_ref + contact_code,照计划抄动作),再发一张执行工单让人把那条回执填回来 ——
回执是人办事的凭据,只有人能填。
· 他**没写具体意见**(只点了重做)→ 自己把成果复核一遍,挑最站不住的那处改实
(理由最空的那条计划、依据最含糊的那条更新),**至少改对一处**;
实在判断不出他要什么,发一张零输入执行工单问清楚,别原样重提。
**改的是值,不是数**:同一行覆盖写,别新开行、别把行数改了 —— 那会把收步判据弄假。
**明细里过期的旧记录要撤掉,不留旧账**(2026-08-24 用户当场指出):重做后某条更新
不再成立(数变了、结论变了),就用 \`drop\` 撤掉那一行再写新的,或直接同 seq 覆盖写;
同一处位置(update_key 相同)出现两条,判据「同一处字段只留一条更新」当场拦。
【没被打回(或已按意见改过了)】先把裁量判据裁掉,然后 submit,同一拍连做:
@@ -708,10 +820,28 @@ crm_coverage_plan_missing = 0\` 一旦为真,引擎自己收这一步。`,
/**
* 阶段 —— 进度条的刻度,只给人看。除放行外每个计划项恰属一个阶段;
* 放行那一格由基座统一加,人看到的是 x/4。
* 放行那一格由基座统一加(`CLOSING_PHASE`),所以声明 6 段、人看到的是 **x/7**。
*
* **刻度要与业务流程的步数对得上**(2026-08-24 用户:「E04 应该是有 7 步、
* 给人确认是 4 步,这里要是 x/7 才对」)。从前这里按「归口」粗分成三段,
* 于是屏上是 x/4 —— 而 `E04-活动流程与工单说明.md` 开篇写的是「整体流程(7 步)」:
* 人对着文档数是 7,对着进度条数是 4,**同一件事两个数**。
* 现在一段对一步,与文档那张表逐行对齐:
*
* ① 启动与覆盖范围确认 → 识别候选客户(AI 汇总)+ 覆盖范围确认(**人**,执行工单1)
* ②③④ 读现状 / 缺口分析 / 制定计划 → 制定关系经营计划(AI 算)
* ④ 计划审批 → 经营计划审批(**人**,验收工单1)
* ⑤ 执行经营动作 → 执行与回执(**人**,执行工单2)
* ⑥ 更新关系台账 → 写回主数据 + 台账明细留痕
* ⑦ 复盘与完结(门禁) → 放行收口(**人**,验收工单2,基座那一格)
*
* 四张卡就是那 4 个「给人确认」的刻度 —— 其余三格是 AI 在办事。
*/
export const COVERAGE_PHASES = [
{ name: '确认覆盖范围', items: ['S1_identify_candidates', 'S2_scope_confirm'] },
{ name: '制定与审批计划', items: ['S3_plan', 'S4_plan_approval'] },
{ name: '执行与更新台账', items: ['S5_execute_receipt', 'S6_writeback', 'S6_ledger_update'] },
{ name: '识别候选客户', items: ['S1_identify_candidates'] },
{ name: '覆盖范围确认', items: ['S2_scope_confirm'] },
{ name: '缺口分析与制定计划', items: ['S3_plan'] },
{ name: '经营计划审批', items: ['S4_plan_approval'] },
{ name: '执行经营动作与回执', items: ['S5_execute_receipt'] },
{ name: '更新关系台账', items: ['S6_writeback', 'S6_ledger_update'] },
];