docs: IP猜国spec审查修订——出站时序决策(首封不延迟+完结补更正)/状态机补转移与终态键/REST改session注入/白名单逐段比对/存量迁移/推送contact契约

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-08 15:07:43 +08:00
parent b68bd192ca
commit 8d07fc5ec1
@@ -26,23 +26,37 @@
| CC 强制墙骨架 | conversation.py cc_wall 缓冲/替换/Important-2 closing | ✅ 复用(换文案) |
| 出站管道 | email_service + lead_push._build_payload | ✅ 挂补全点 |
## 3. 状态机(核心)
## 3. 状态机(核心,含审查修订)
```
裸号在场且未处理
→ [_cc_pend] ①国家确认轮:"Are you in {英文国名}?"(墙保证必出)
├─ Yes(确定性判定)→ 生成补全号,存 _cc_full
裸号在场且未处理(无 _cc_done)
→ [_cc_pend=ISO] ①国家确认轮:"Are you in {英文国名}?"(墙保证必出)
├─ Yes → 生成补全号(4.1),存 _cc_full
│ → [_cc_conf] ②号码确认轮:"…reach you at {补全号} — is that right?"
│ (墙白名单放行该精确号码;确认句墙保证必出)
│ ├─ Yes → [_cc_done] 完结:出站用补全号(带标注)
│ └─ No → 请给完整号(一次)→ [_cc_done 或超限]
├─ No(猜错)→ 请给带码完整号(一次)→ 得到则 [_cc_done]
└─ 无法判定(长消息/不理睬)→ 下轮再给一次确认机会(计数)
→ 超限(确认机会共 2 次)→ 请完整号一次 → 仍无 → 裸号照发
│ ├─ Yes → [终态 _cc_done] 客户亲口确认
│ └─ No / 无法判定(一次重试后仍无)→ 请给完整号一次[_cc_asked_full]
│ → 给了(带前缀)→ [终态 _cc_done,用客户号无标注]
├─ No(猜错)→ 请给带码完整号一次[_cc_asked_full] → 得到则 [终态 _cc_done]
└─ 无法判定 → ①重试一次 → 仍无 → 请完整号一次 → 仍无 → [超限终态 _cc_done]
旁路(审查补充):流中任意轮 confirmed 号码已带 +/00 前缀(客户主动给完整号)
→ 直接 [终态 _cc_done](用客户号,无标注),①②全退
```
状态键(raw_info,`_` 前缀族):`_cc_pend`(含猜的 ISO code)、`_cc_full`
(补全号字符串)、`_cc_done`(完结标记)、`_cc_tries`(已用确认机会数)。
**终态语义(审查修订)**:`_cc_done` 是唯一终态键(值为 True);区分两种完结:
- 有 `_cc_full`(或客户号带前缀)→ 出站用补全/完整号
- 无 `_cc_full`(超限路径)→ 出站用裸号(现状底线)
**状态键族**:`_cc_pend`(ISO code)、`_cc_full`(补全号)、`_cc_conf`(②进行中)、
`_cc_asked_full`(请完整号已用一次)、`_cc_tries`(①确认机会计数,上限 2)、
`_cc_done`(终态)。写回机制沿用现有(值可为字符串/整数,非仅 True)。
**预算口径(审查修订)**:①确认机会 2 次(_cc_tries);②确认 1 次,不判定或
No 后不再重复②,直接进"请完整号一次";"请完整号"全会话共 1 次。
**存量迁移(审查补充)**:升级部署时存量 `_cc_asked=True` 且号码仍裸的会话,
新状态机视其为**超限终态**(等价 `_cc_done`,无 `_cc_full`)——不违背已向客户
承诺的"仅此一次",直接进入邮箱轻补串行。
## 4. 组件设计
@@ -70,24 +84,38 @@
- ①国家确认轮:reminder 注入逐字句(英文 "Are you in {EN}?" / 中文"你是在
{中文国名}吗?"),CC 墙检测词从 "country code" 改为 **本轮注入的英文国名
在场**(或中文国名),不在则替换——缓冲机制不变
在场**(中文轮检测中文国名),不在则替换——缓冲机制不变
- ②号码确认轮:reminder 注入逐字句含补全号;**墙白名单**:`no_contact_value`
新增参数 `allow_number: str | None`——数字串匹配(digits_only 归一后比对)
该精确号码则放行,其余照拦(安全边界不扩大:只放行系统注入的那一个号)
新增参数 `allow_number: str | None`——**对句中每个 ≥10 位数字匹配段逐一
digits_only 归一比对**(非整句 substring,防"允许号+另一串数字"同句混放),
全部命中白名单才放行该段,其余照拦(安全边界:只放行系统注入的那一个号)
- 确认句墙同款缓冲:检测补全号在场,不在则替换
### 4.4 出站补全(email_service + lead_push_service)
### 4.4 出站补全与时序(审查修订——核心架构决策)
- 消费点:渲染联系方式处(邮件 HTML/主题、push 的 contact.phone/whatsapp)
- 规则:`_cc_done` 且 `_cc_full` 在场 → 用 `_cc_full` 替换裸号展示,**带标注**
(如 `+966501234567(原号 0501234567,据 IP+客户确认为沙特,已补国家码)`)
- ⚠️ 推送侧交互:`_build_payload` 现过滤 `_` 前缀键——补全消费必须在过滤
**之前**(读 raw_info → 生成补全 contact → 过滤画像)
**时序冲突与决策**:裸号 confirmed 当轮即触发 D1 首封 lead 邮件(零阻塞底线),
而①②确认流要到 N+1 轮后才完结。决策:**首封不延迟(裸号照发),确认完结
(_cc_done 且有 _cc_full)时补发一封更正邮件**(复用现有 correction 通道,
占每会话 3 封额度)——客户经理先拿裸号线索(及时性不破),随后收带补全号
+标注的更正(信息升级)。
- **邮件**:更正邮件渲染 `_cc_full` 补全号 + 标注(`+966501234567(原号
0501234567,据 IP+客户确认为沙特,已补国家码)`)
- **推送(UniService,当前关闭)**:contact.phone/whatsapp 给**干净补全号**
(E.164,无标注——对端契约要求"带国际区号且有效",中文标注串会被整条拒收,
见 docs/AI_INQUIRY_PUSH_API.md);标注写 profile 的正常键(如
`号码补全说明`,随画像进 CRM);推送时机=确认完结后的下一轮自然触发(或
correction 推送,与邮件对齐,v1 只做自然轮)
- ⚠️ 消费顺序:`_build_payload` 先读 raw_info 生成补全 contact → 再过滤 `_` 键
- DB 不改客户原文;聊天里仅②确认轮展示补全号(白名单放行)
### 4.5 REST 路由 XFF 前置修复
### 4.5 IP 入口修复(审查修订——位置改 /session)
chat_rest.py:107 补 WS 同款 XFF 第一段逻辑(app 源客户 IP 真实化)
REST 的 client_info 来自 `/session` 请求体(客户端自报),chat_rest.py:107 的
IP 只喂限流——修不到特性数据流。修复:`/session` 路由注入 `Request`,XFF
第一段合并进 conv.client_info(与 WS 路由同款逻辑);另修 ip_service.py 既有
bug:`"AU": "加拿大"` 重复键吞掉了加拿大(应为 CA),并新增
`resolve_iso(ip) -> str`(iptocc 直返 ISO,跳过中文表反查的脆弱性)。
## 5. 防死缠与兜底链