diff --git a/docs/superpowers/plans/2026-11-02-jiancai-dual-contact-ask.md b/docs/superpowers/plans/2026-11-02-jiancai-dual-contact-ask.md new file mode 100644 index 0000000..b5e4ec3 --- /dev/null +++ b/docs/superpowers/plans/2026-11-02-jiancai-dual-contact-ask.md @@ -0,0 +1,311 @@ +# 建材(jiancai)双要话术实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** jiancai 首次索要联系方式的话术改为 WhatsApp + 邮箱一起要(双要),已留资收尾话术渠道中性化,缺失渠道补问通用化——纯话术层(3 个 seed_profiles 文件 + 同包测试锚同步),引擎代码零改动,生产经 admin API 整体对齐出厂。 + +**Architecture:** 词表/提示词全配置化(CLAUDE.md Phase 1),话术分布在 `seed_profiles/jiancai/prompts/system.md`(LLM 索要话术池与原则节)与 `seed_profiles/jiancai/wall.json`(deflections/pivots 注入 $DEFLECTION_POOL、reminders 系统提醒逐字句)。引擎的邮箱轻补注入机制(`_email_asked` 一次性 + `cc_settled` 串行门控)与防骚扰墙(放行"索要缺失渠道")原样保留。spec:`docs/superpowers/specs/2026-11-02-jiancai-dual-contact-ask-design.md`。 + +**Tech Stack:** pytest(静态锚 + 引擎行为测试,Windows 需 `PYTHONUTF8=1`,venv 在 `.venv/Scripts/python`);生产为 admin HTTPS API(`https://chat.ai.hnxinyouhe.com/api/platform`,配置版本管线热生效)。 + +**纪律(全程有效):** +- 临时文件用完即删,验收 = `git status` 干净(CLAUDE.md 硬约束) +- 每个 commit 必须绿(测试锚同步与数据文件改动同 commit) +- 生产凭据**不写入任何仓库文件**,执行时向用户取 + +--- + +## 统一措辞常量(全计划引用,逐字使用) + +- **通用化补问子句(三处 reminder 内嵌)**: + - `lead_given.forbidden` 专用(带"仅"):`缺失渠道(号码或邮箱)仅可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠` + - `contact_ask.app_already` / `lead_given.boundary_tail` 共用(无"仅"):`缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠` + - ("仅可"版与"可"版的前缀差异是测试 :684/:693 的判别依据——`缺失渠道(号码或邮箱)仅可…` 不含子串 `缺失渠道——号码或邮箱——可…`,判别力保留) +- **App 反向留资句(三处一字不差)**:`Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!` + (wall `no_contact.app` = `reminders.contact_ask.app` 逐字句 = system.md L214;现状 "full quotation!" vs "full quote!" 一词分叉,本次统一为上句) +- **已留资确认句(渠道中性)**:`Got it — our manager will reach out to you shortly with the full quote!` + +--- + +### Task 1: wall.json 双要 + 中性化 + 提醒通用化,同包测试锚同步 + +**Files:** +- Modify: `seed_profiles/jiancai/wall.json` +- Create: `tests/test_dual_ask_static.py` +- Modify: `tests/test_lead_email_trigger.py:681,692-693`、`tests/test_cc_integration.py:122` + +- [ ] **Step 1: 新增 wall 侧静态锚测试(先红)** + +创建 `tests/test_dual_ask_static.py`: + +```python +"""双要话术静态锚(spec 2026-11-02-jiancai-dual-contact-ask §5.3)。 + +首次索要联系方式 = WhatsApp+邮箱一起要(双要);已留资后的确认/收尾话术 +渠道中性(邮箱-only 客户不再听到"发到你 WhatsApp")。wall.json 侧锚在 +本文件前半,system.md/company_facts.md 侧锚在后半(Task 2 追加)。 +""" +import json +from pathlib import Path + +SEED = Path(__file__).resolve().parents[1] / "seed_profiles" / "jiancai" + + +def _wall() -> dict: + return json.loads((SEED / "wall.json").read_text(encoding="utf-8")) + + +# ---- wall 侧 ---- + +def test_deflections_dual_ask_all_languages(): + w = _wall() + assert any("and email" in s for s in w["deflections"]["en"]) + assert any("和邮箱" in s for s in w["deflections"]["zh"]) + assert any("y tu correo" in s for s in w["deflections"]["es"]) + + +def test_pivots_dual_ask_all_languages(): + w = _wall() + assert "and email" in w["pivots"]["en"][0] + assert "和邮箱" in w["pivots"]["zh"][0] + assert "y tu correo" in w["pivots"]["es"][0] + + +def test_post_contact_templates_channel_neutral(): + """已留资确认/收尾模板不含渠道词(三语逐一钉死,防只用 en 子串漏检)。""" + w = _wall() + for group in ("deflections_contact", "pivots_contact"): + for lang, items in w[group].items(): + for s in items: + assert "whatsapp" not in s.lower(), (group, lang, s) + assert "whatsapp" not in w["no_contact"]["already"].lower() + + +def test_app_reverse_ask_dual_and_synced(): + """App 反向留资句双要,且 no_contact.app = contact_ask.app 逐字句同源。""" + w = _wall() + app = w["no_contact"]["app"] + assert "and email" in app + assert app in w["reminders"]["contact_ask"]["app"] +``` + +- [ ] **Step 2: 跑新测试确认红** + +Run: `PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py -q` +Expected: 4 failed(deflections 仍为 "or email"、contact 模板仍含 WhatsApp、app 句未双要) + +- [ ] **Step 3: 改 wall.json(逐字段替换值,键结构不动)** + +| 字段 | 新值 | +|---|---| +| `deflections.en`(3 条全换) | ① `Prices vary a lot by config and shipping — our manager will prepare an exact quote for you. What's your WhatsApp and email?` ② `For an exact price our manager needs your config and destination — drop your WhatsApp and email and you'll have the quote today.` ③ `I'd rather give you an exact number than a rough guess — leave your WhatsApp and email and our manager will send it over.` | +| `deflections.zh`(2 条全换) | ① `价格要看配置和目的地,客户经理会给你准确报价——留个WhatsApp和邮箱?` ② `我让客户经理按你的情况出准确报价,留个WhatsApp和邮箱,今天就能发你。` | +| `deflections.es`(3 条全换) | ① `El precio exacto depende de la especificación y el flete — nuestro gerente te lo prepara. ¿Me dejas tu WhatsApp y tu correo?` ② `Para un precio exacto, nuestro gerente necesita tu especificación y destino — déjanos tu WhatsApp y correo y lo tienes hoy.` ③ `Prefiero darte una cifra exacta y no un estimado — déjanos tu WhatsApp y correo y nuestro gerente te lo envía.` | +| `pivots.en[0]` | `Actually, for the exact price — what's your WhatsApp and email? Our manager will send it over.` | +| `pivots.zh[0]` | `具体的报价,客户经理会发给你——留个WhatsApp和邮箱?` | +| `pivots.es[0]` | `Para el precio exacto — ¿me dejas tu WhatsApp y correo? Nuestro gerente te lo envía.` | +| `deflections_contact.en[0]` | `Our manager will send you the exact quote shortly. Anything else about our pipes?` | +| `deflections_contact.zh[0]` | `客户经理稍后会把准确报价发给你。还想了解什么管道产品?` | +| `deflections_contact.es[0]` | `Nuestro gerente te enviará el precio exacto en breve. ¿Algo más sobre nuestras tuberías?` | +| `pivots_contact.en[0]` | `Our manager will send you the exact price shortly.` | +| `pivots_contact.zh[0]` | `客户经理稍后会把准确报价发给你。` | +| `pivots_contact.es[0]` | `Nuestro gerente te enviará el precio exacto en breve.` | +| `no_contact.app` | `Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!` | +| `no_contact.already` | `Got it — our manager will reach out to you shortly with the full quote!` | +| `reminders.contact_ask.app` 逐字句 | 整段改为:`⚠️ 客户正在索要我方联系方式(最高意向信号)。当前为 App 会话,没有页面按钮——禁止使用任何 tap/button 按钮话术!请逐字使用这句:"Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!"(中文同理引导客户留联系方式)。绝对禁止输出任何号码或邮箱。` | +| `reminders.contact_ask.app_already` | 整段改为:`⚠️ 客户正在索要我方联系方式,且客户已留过自己的联系方式。请逐字使用这句:"Our manager will reach out to you shortly with the full quote!"(中文同理)。绝对禁止输出任何号码或邮箱,也绝对禁止再索要客户已提供过的联系方式(缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认——仅系统提供的号码,禁止自编)。` | +| `reminders.lead_given.forbidden` | 整段改为:`⚠️ 客户已提供联系方式({shown})。绝对禁止再索要客户已提供过的联系方式;缺失渠道(号码或邮箱)仅可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认(仅系统提供的号码,禁止自编)。` | +| `reminders.lead_given.boundary_tail` | 整段改为:`客户已留过联系方式——边界请求直接告知'客户经理会把准确报价/安排发给你'即可,禁止再索要联系方式(缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认——仅系统提供的号码,禁止自编)。` | + +**不动**:`reminders.email_light`、`cc_templates`、`cc_guide`、`reask_patterns`、`triggers`、`zero_number`、`no_contact.web`、`reminders.contact_ask.web`、`reminders.lead_given.respond_script/suspect`、`reminders.boundary.*`、`reminders.language_follow.*`。 +**锚串自检(改后必在)**:`。绝对禁止再索要客户已提供过`(forbidden)、`也绝对禁止再索要客户已提供过`(app_already)、`即可,禁止再索要联系方式`(boundary_tail),三处各自 260 字符内都有 `回显补全后的完整号码`。 + +- [ ] **Step 4: 同步既有测试锚(4 处 + 1 处注释)** + +- `tests/test_cc_integration.py:122`:`assert "缺失渠道仅可按轻补指引提一次" in text` → `assert "缺失渠道(号码或邮箱)仅可再礼貌提一次" in text`(行尾注释保留) +- `tests/test_lead_email_trigger.py:454`:`assert "缺失渠道可按轻补指引提一次" in text` → `assert "缺失渠道——号码或邮箱——可再礼貌提一次" in text` +- `tests/test_lead_email_trigger.py:681`:`assert "reach out to you at your WhatsApp" in text` → `assert "reach out to you shortly with the full quote" in text` +- `tests/test_lead_email_trigger.py:692-693`:`:692` 同 :681 替换;`:693` `assert "缺失渠道可按轻补指引提一次" in text` → `assert "缺失渠道——号码或邮箱——可再礼貌提一次" in text`;同步更新 :686-688 注释:内联引文 `缺失渠道可按轻补指引提一次` → `缺失渠道——号码或邮箱——可再礼貌提一次`,括注 `(:651 版是"仅可按",不构成子串)` → `(forbidden 版是"仅可",不构成子串)` +- `tests/test_boundary_filter.py:104`:`assert "WhatsApp?" in pivot_for("how much?")` → `assert "WhatsApp" in pivot_for("how much?")`(新 pivots.en[0] 为 `…WhatsApp and email?`,带问号的 `WhatsApp?` 子串消失,计划审查实证必红;:105 反向断言不受影响) + +- [ ] **Step 5: 跑受影响测试文件全量** + +Run: `PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py tests/test_lead_email_trigger.py tests/test_cc_integration.py tests/test_boundary_filter.py -q` +Expected: 全绿。若有其他硬编码旧句红掉(如 no_contact 墙测试锚旧句),按同一语义同步锚串后重跑至绿。 + +- [ ] **Step 6: Commit** + +```bash +git add seed_profiles/jiancai/wall.json tests/test_dual_ask_static.py tests/test_lead_email_trigger.py tests/test_cc_integration.py +git commit -m "feat(wall): 建材首次索要改双要(WhatsApp+邮箱一起要)+已留资话术渠道中性+缺失渠道补问通用化" +``` + +--- + +### Task 2: system.md + company_facts.md 双要改写 + +**Files:** +- Modify: `seed_profiles/jiancai/prompts/system.md`(16 处点位) +- Modify: `seed_profiles/jiancai/prompts/company_facts.md:30` +- Modify: `tests/test_dual_ask_static.py`(追加 md 侧锚) + +- [ ] **Step 1: 追加 md 侧静态锚(先红)** + +在 `tests/test_dual_ask_static.py` 末尾追加: + +```python +# ---- system.md / company_facts.md 侧 ---- + +def _md(name: str) -> str: + return (SEED / "prompts" / name).read_text(encoding="utf-8") + + +def test_system_md_dual_ask_principle_and_pool(): + src = _md("system.md") + assert "双渠道一次要齐" in src + assert "WhatsApp and email" in src # en 话术池 + assert "WhatsApp和邮箱" in src # zh 话术池 + # 邮箱轻补两条逐字句原文存活(与引擎 email_light 模板逐字一致) + assert ("Got it! And an email address would help too — our manager " + "will send the formal quotation PDF there.") in src + assert "收到!邮箱也留一个呗?客户经理把正式报价单 PDF 发你邮箱。" in src + + +def test_app_reverse_ask_line_matches_wall(): + w = _wall() + assert w["no_contact"]["app"] in _md("system.md") # L214 与 wall 一字不差 + + +def test_company_facts_dual_ask(): + assert "WhatsApp 和邮箱" in _md("company_facts.md") +``` + +Run: `PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py -q` +Expected: 追加的 3 个测试红,Task 1 的 4 个保持绿。 + +- [ ] **Step 2: 改 system.md(逐点位精确替换)** + +改写原则:**保留锚串**——`with the country code`、`formal quotation PDF`、`轻补`、`国家代码`(≥2 处)、`已在手的渠道`、`缺失渠道`、`邮箱轻补句`、`唯一例外`、`回显补全后的完整号码`(≥4 处)、`客户自己的号码也不回显`、`is that right`;**邮箱轻补 en/zh 两条逐字句原文不动**;CC 回显节(L196-199)与好/差例 L157-167 不动。 + +| 点位(现行内容特征) | 改为 | +|---|---| +| L7 模板句族:`转接句 "drop your WhatsApp and they'll reach out directly."、互留句 "Drop yours too..."` | `转接句 "drop your WhatsApp and email and they'll reach out directly."、互留句 "Drop your WhatsApp and email too..."`;同句内 `**缺失渠道**可按【联系方式引导策略】轻补一次` → `**缺失渠道**(号码或邮箱)可再礼貌提一次(见【联系方式引导策略】)`;`邮箱轻补句` 条目与其余句族不动 | +| L25 转接句 | `I'll connect you with our manager to arrange that — drop your WhatsApp and email and they'll reach out directly.` | +| L35 | `Tell me your WhatsApp and email — our manager will work out an accurate quotation and send the PDF over.` | +| L69 | `…That's exactly why I want our manager to send you the full quotation — drop your WhatsApp and email and our manager will reply within 12 hours.` | +| L74 | `Got it — our manager will reach out to you shortly!`(去 `at your WhatsApp`) | +| L140 中文好例 | `…AI用中文回复"价格要看规格、数量和目的地——我让客户经理给你出准确报价,留个WhatsApp和邮箱?"` | +| L152 样品好例 | `I'll connect you with our manager to arrange that — drop your WhatsApp and email and they'll reach out directly.`(与 L25 一字不差) | +| L155 | `Our manager will send you the full quotation.`(去 `to your WhatsApp`) | +| L179-182 en 话术池(4 条全换) | ① `What's your WhatsApp and email? Our manager will reach out on WhatsApp and send the full quotation PDF to your email.` ② `Let me connect you with our manager who handles [country] market — drop your WhatsApp and email and they'll reach out directly.` ③ `I can have our team send you a full quotation PDF — leave your WhatsApp and email here.` ④ `Want an exact quote? Drop your WhatsApp with the country code (like +966 5x xxx xxxx) and your email — our manager will take it from there.`(④保住 `with the country code` 锚) | +| L185-188 zh 话术池(4 条全换) | ① `留个WhatsApp和邮箱呗?客户经理 WhatsApp 联系你,正式报价单 PDF 发你邮箱。` ② `我让负责[国家]市场的同事对接你,留个WhatsApp和邮箱?` ③ `客户经理按你的项目出完整报价单,PDF 发你邮箱——WhatsApp 和邮箱都留一下吧。` ④ `你给我个联系方式(WhatsApp 和邮箱),我让客户经理按你的项目给你出方案。` | +| L190 节标题 | `### 双渠道与国家代码(双要)` | +| L192 原则行 | `- **双渠道一次要齐**:首次索要就把 WhatsApp 和邮箱一起要(天然理由=客户经理 WhatsApp 联系你 + 正式报价单 PDF 发你邮箱);客户只给一个时,另一渠道**轻补一次**(邮箱按系统轻补指引逐字句;号码礼貌补问一句),不成就不再提,绝不纠缠(转化率优先):`(其下两条逐字话术行 L193-194 原文不动) | +| L214 | `Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!`(与 wall `no_contact.app` 一字不差) | +| L216 互留句 | `Drop your WhatsApp and email too — and our manager will send the full quotation!` | +| L221 | `缺失渠道可轻补一次` → `缺失渠道(号码或邮箱)可再礼貌提一次,仅一次不纠缠`;同段确认式例句 `"Our manager will send the full quotation to your WhatsApp."` → `"Our manager will send you the full quotation."` | +| L173、L227 括注 | `(缺失渠道轻补一次、号码缺国家代码时…)` → `(缺失渠道——号码或邮箱——可再礼貌提一次、号码缺国家代码时…)`(两处同构,锚串 `回显补全后的完整号码` 保留) | + +- [ ] **Step 3: 改 company_facts.md L30** + +`留个 WhatsApp,他直接联系你` → `留个 WhatsApp 和邮箱,他直接联系你`(仅插入 ` 和邮箱`,其余标点不动) + +- [ ] **Step 4: 跑 md 侧锚 + 提示词静态测试** + +Run: `PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py tests/test_system_prompt_dual_contact.py tests/test_prompt_static.py -q` +Expected: 全绿(既有锚全部存活)。红则按锚串自检表逐项排查,缺哪个锚补哪个。 + +- [ ] **Step 5: Commit** + +```bash +git add seed_profiles/jiancai/prompts/system.md seed_profiles/jiancai/prompts/company_facts.md tests/test_dual_ask_static.py +git commit -m "feat(prompts): 建材 system.md/company_facts.md 双要改写——话术池/原则节/App反向留资/互留句/提醒配合全量对齐" +``` + +--- + +### Task 3: 全量回归 + 仓库洁净验收 + +- [ ] **Step 1: 根目录全量** + +Run: `PYTHONUTF8=1 .venv/Scripts/python -m pytest -q` +Expected: 现状 collect 819(CLAUDE.md 的 632 基线已被 site-import 等后续工作推高,计划审查实测),改后预期 **826 collected(≈824 passed + 2 skipped,+7 = 新增静态锚)**;若数目对不上,验收标准退为两条:根目录与 tests/ 子目录双跑数字一致 + 相对改前基线差值恰为 +7(执行 Task 1 前先跑一次全量记录改前基线) + +- [ ] **Step 2: tests/ 子目录全量(双跑一致性)** + +Run: `cd tests && PYTHONUTF8=1 ../.venv/Scripts/python -m pytest -q`(跑完回到根目录) +Expected: 与 Step 1 数字一致 + +- [ ] **Step 3: 洁净验收** + +Run: `git status --short` +Expected: 空(无临时文件、无漂移)。有残留即删。 + +--- + +### Task 4: 生产整体对齐出厂 + 三路径验证 + 清理 + +生产信息:admin API `https://chat.ai.hnxinyouhe.com/api/platform`;聊天域名 `chat2.agent.hnxinyouhe.com`(Host 路由 jiancai);凭据执行时向用户取(不写入任何文件)。本次**不涉及镜像重建/SSH**——配置经 admin API 直推租户配置组。 + +- [ ] **Step 1: 登录 + 拉取生产当前配置** + +```bash +TOKEN=$(curl -s -X POST https://chat.ai.hnxinyouhe.com/api/platform/auth/login \ + -H 'Content-Type: application/json' \ + -d '{"username":"<向用户取>","password":"<向用户取>"}' | python -c "import sys,json;print(json.load(sys.stdin)['token'])") +curl -s -H "Authorization: Bearer $TOKEN" https://chat.ai.hnxinyouhe.com/api/platform/tenants/jiancai/config/prompts > /tmp/prod_prompts.json +curl -s -H "Authorization: Bearer $TOKEN" https://chat.ai.hnxinyouhe.com/api/platform/tenants/jiancai/config/wall > /tmp/prod_wall.json +``` + +(登录 409 = must_change_password → 停下找用户;`/tmp/` 为临时文件,任务收尾删除) + +- [ ] **Step 2: 漂移审计(PUT 前置闸)** + +对比 `/tmp/prod_prompts.json`、`/tmp/prod_wall.json` 与仓库出厂(`seed_profiles/jiancai/` 改后版本): +- 预期漂移 = 出厂侧演进未推生产(cd3b58a 翻译规则等)+ 本次双要改动 +- **出现预期外差异(生产侧独有定制)→ 停下报告用户**,不得盲目覆盖 + +- [ ] **Step 3: 验证守卫(按用户全局纪律:测试绝不打扰线上正式收件/推送)** + +PUT 临时配置:`email` 组 `notify_emails` → `["clany@hnxinyouhe.com"]`(测试收件箱);`push` 组 `enabled` → `false`。记录两组当前版本号(验证后恢复用)。 + +- [ ] **Step 4: 整组 PUT 出厂最新版** + +以 Step 1 GET 响应的键结构为准,prompts 组四件(system/extractor/evaluator/company_facts)与 wall 组整组替换为仓库出厂内容后 PUT(读-改-写;PUT = 整组替换语义)。PUT 后 `GET /tenants/jiancai/config/versions` 确认两组版本各 +1。 + +- [ ] **Step 5: 三路径真实验证(REST,与 WS 同 code path)** + +```bash +SID=$(curl -s -X POST https://chat2.agent.hnxinyouhe.com/api/chat/session \ + -H 'Content-Type: application/json' -d '{"context":{}}' | python -c "import sys,json;print(json.load(sys.stdin)['session_id'])") +curl -s -X POST https://chat2.agent.hnxinyouhe.com/api/chat/message \ + -H 'Content-Type: application/json' -d "{\"session_id\":\"$SID\",\"content\":\"...\"}" +``` + +- **路径 a 双要首问**:先 `How much for DN110 PE100 pipe?`(预期先问需求一轮)→ `For water supply project in Saudi Arabia` → 预期回复同时索要 WhatsApp **和** 邮箱 +- **路径 b 号码-only**:新会话走一轮需求后发裸号 `0501234567` → 预期 CC 回显确认(完整号)→ `yes` → 下一轮预期邮箱轻补(`email address would help too`) +- **路径 c 邮箱-only**:新会话走一轮需求后 `My email is test@example.com, please send the quote` → 预期礼貌补问号码一次;回复拒绝(`I'd rather not`)→ 预期不再追要、正常继续 +- 三路径回复存临时文件人工核对后删除;限流 30 条/小时/租户/IP,消息总量控制在其内 + +- [ ] **Step 6: 恢复守卫 + 清理** + +- PUT 恢复 `email`/`push` 两组原值(对照 Step 3 记录),GET 确认内容一致 +- 删除验证会话:`GET /tenants/jiancai/conversations?limit=50` 找当日 app 来源测试会话 → `DELETE /tenants/jiancai/conversations/{conv_id}` 逐个删 +- 删除全部临时文件(`/tmp/prod_*.json`、回复存档等) + +- [ ] **Step 7: 回滚预案(只在异常时执行)** + +`POST /tenants/jiancai/config/rollback`(body 带回 PUT 前版本号,prompts/wall 两组各回退),恢复守卫同 Step 6。 + +- [ ] **Step 8: 观察注记** + +向用户报告:留资率、双渠道齐收率、breach_rate 纳入阶段④日检观察项;若留资率异常下滑,回滚预案同 Step 7(配置版本秒级回退)。 + +--- + +## 验收标准(对齐 spec §7) + +1. 全量 pytest 绿(新增 7 静态锚计入,双跑数字一致) +2. 引擎代码 diff 为零;auto 租户与生成器模板 diff 为零 +3. 生产 jiancai prompts/wall 版本各 +1 = 出厂最新版;三路径真实行为符合预期 +4. 验证会话与临时文件清零;守卫配置恢复原值