{ "artifact": "t2m-h04-graded-fallback-and-transfer-whitelist", "generated_at": "2026-09-19T02:04:04.969488+00:00", "date_local": "2026-09-19", "purpose": "执行批次 H 的 H-04「分级回退 E5 + 转人工白名单收口」(答辩反馈「很多问题都强制转人工」的最正面修复)", "scope": [ "app/core/customer_service_rules.py(P1 口径显式化)", "tests/unit/core/test_customer_service_rules.py(P1 口径用例)", "tests/unit/service/test_customer_service_agent.py(白名单结构性守卫 + 零残留守卫)", "客服agent/D2.2-客服Agent需求文档.html(US-CS-08 残留口径修正)", "开发文档/D3.1-客服Agent需求开发文档与设计方案.html(TK-CS-011 残留口径修正)" ], "pre_existing_state_取证": { "① 回退链 E5a/E5b/E5c": "已在位(E5a 见 H-01;E5b `_exit_partial` 不建单;E5c `_exit_transfer` 为唯一置位出口)", "② 白名单 4 类码": "已在位:`TRANSFER_REASONS = {safety_risk, account_data, write_or_dispute, explicit_request}`", "③ E5c 上下文摘要": "已在位:`app/service/customer_service_handover_context.py`(转接原因 / 澄清轮次 / 知识来源数 / 脱敏会话摘要),由 `agent_persistence_service` 写入工单与 Outbox", "④ 回退不得跨档位": "**结构性成立**:`tiers` 是 `search()` 必填参数、由 `tiers_for_roles(context.roles)` 从鉴权推导一次;三条召回路径(向量 / 产品名词法 / 父块)共用同一 `expression = visibility_expression(tiers)`,不存在放宽档位的回退分支", "low_score_repeat 残留": "**代码零残留**(`rg` 全仓无命中)" }, "real_gaps_found": { "gap_A_第四条白名单码未发出": "`TRANSFER_REASON_ACCOUNT`(P1 账户数据)声明在白名单里,但 `route_message` 的 P1 分支 `transfer_required=False` ⇒ 白名单 4 类实际只发出 3 类", "gap_B_无结构性守卫": "「白名单外不得转人工」只靠 `_exit_transfer` 的运行时校验;`route_message` 是**直接**置 `transfer_required=True` 的,新增分支可以绕过,跑一遍看不出明天的分支", "gap_C_文档残留": "`D2.2` `US-CS-08` 与 `D3.1` `TK-CS-011` 仍写「连续 2 轮答不上来 → 转人工(low_score_repeat)」,与 v2.5 `FR-CS-023`(已删除该触发)**自相矛盾**,且 `low_score_repeat` 不在白名单内" }, "implementation": { "gap_A": { "decision": "P1 账户数据**有意不建单**(不改行为,只把口径显式化)", "rationale": "账户数据 Agent 读不到、人工接线同样读不到;建单只会把客户从自助路径推到排队,与 H-04 的目标相反。白名单是「允许上限」而非「必须转」。", "code": "P1 分支补注释写明「有意不建单、`TRANSFER_REASON_ACCOUNT` 保留但不发出、若启用须同步更新 DoD」", "test": "tests/unit/core/test_customer_service_rules.py::test_p1_account_data_never_opens_a_ticket_by_design", "flagged_for_user": "若你希望对**已登录客户**的账户数据问题也建单,改动是一行(P1 分支置 `transfer_required=True` + `TRANSFER_REASON_ACCOUNT`),但转人工率会上升,且与 D3.7 的 ≤15% 目标相抵。我的建议是维持不建单。" }, "gap_B": { "guard": "AST 扫源码:所有 `transfer_required=True` 的落点,必须 ① 落在客服 Agent 的 `_exit_transfer` 内(该函数有 `reason_code not in TRANSFER_REASONS` 运行时校验),或 ② 在规则模块里带可解析的 `TRANSFER_REASON_*` 常量且值在白名单内", "test": "tests/unit/service/test_customer_service_agent.py::test_transfer_requires_a_whitelisted_reason_code", "extra": "同时断言白名单里**实际会发出**的三类必须在场、`_exit_transfer` 守卫入口存在" }, "gap_B2": { "guard": "`low_score_repeat` / 「连续 2 轮兜底」在规则模块与客服 Agent 源码中零残留", "test": "tests/unit/service/test_customer_service_agent.py::test_consecutive_fallback_transfer_rule_has_no_residue" }, "gap_C": { "D2.2": "`US-CS-08` 改为「**不转人工**(v2.5 已删除 low_score_repeat):第 2 轮起走 E5b 部分答 + 引导;转人工仅限 FR-CS-023 白名单 4 类」", "D3.1": "`TK-CS-011` 改为「连续兜底**不**触发转人工」并写明白名单外一律判不合格", "eol": "D2.2 保持 CRLF(1085);D3.1 保持 LF —— 编辑后逐项核对" } }, "dod_mapping": { "① 回退链 E5a→E5b→E5c": "已在位;本步取证确认(E5a 见 H-01 证据)", "② 白名单 4 类 + 删除连续 2 轮兜底": "白名单 4 码已在位;连续 2 轮兜底代码零残留(守卫用例);P1 第 4 类口径显式化为「允许但不发出」", "③ E5c 带上下文摘要": "已在位并经取证(`customer_service_handover_context.py`)", "④ 回退不得跨档位": "**结构性成立**并经取证:`tiers` 必填 + 单点推导 + 三路召回共用同一 expression", "⑤ 白名单外转人工 = 不合格": "新增结构性守卫用例(AST 扫全仓 `transfer_required=True` 落点)" }, "gates": { "targeted_pytest": { "files": 2, "passed": 128, "failed": 0, "note": "test_customer_service_agent.py + test_customer_service_rules.py" }, "full_pytest": { "result": "2 failed / 1594 passed / 2 skipped", "note": "2 failed = T0 基线同两项;passed 1591 → 1594(+3 = 本轮新单测)" }, "ruff": { "scope": "app tests tools", "errors": 22, "note": "= 基线" }, "mypy": { "scope": "app", "errors": 3, "note": "= 基线" } }, "honest_notes": [ "本轮**没有**把 P1 改成建单 —— 这是**有意的口径确认**,不是漏改;已写成注释 + 单测 + 文档", "`TRANSFER_REASON_ACCOUNT` 因此是「白名单内的保留码」,代码里没有任何发出点;这与「白名单外不得转人工」不冲突(白名单是允许上限)", "`客服agent/_build/_body_requirements.html` 仍是**旧版**(US-CS-08 与 FR-CS-023 都是删除前的文本)—— 属构建中间产物,本轮未动,需你裁定是否重生成或删除", "`开发文档/D3.5` 第 125 行仍写「FR-CS-023 的『连续 2 轮兜底』规则不变」—— 那是备选方案池文档的历史记录,本轮未动(它不是需求来源)", "DoD 的 `D3.7` `B-01`~`B-06` 金标回归与 `M-6` 转人工率属 `H-06`,本轮未跑" ], "open_follow_ups": [ "H-02(E2 计算型)、H-03(E4 证据约束生成)、H-06(金标评测 46 条 + M-6 转人工率)", "H-05(档位分区隔离)与 `_build/` 旧件的处置" ] }