Files
group_fqcd_jr/docs/evidence/20260919-t2k-c10-source-reference-downgrade.json
T
张胜宇 9675df8453 chore(sync): zsy_developcc 全量同步至 qyqy_develop(W26 口径)
- 分支内容对齐 qyqy_develop b6ec3aa,树完全一致(同步后 git diff 为空)
- 覆盖本轮全部交付:客服 Agent 重构(安全路由 / 五出口 / 记忆与画像 / RAG 全链路)
  + 开发文档 62 份编号体系(D1.1 v1.17 索引)
  + 新增 D2.10-客服Agent端到端答辩文档-2026-09-21.html
- 基线:e239eb7(2026-09-17 品牌口径统一快照),本提交为其直接后继
2026-09-21 21:26:30 +08:00

166 lines
8.5 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"artifact": "t2k-c10-source-reference-downgrade",
"generated_at": "2026-09-19T01:41:43.772361+00:00",
"date_local": "2026-09-19",
"purpose": "执行 C-10「知识出口来源引用」(关键路径 C-01 → C-03 → C-09 → C-06 → C-07 → C-10 的收口步);裁定取「乙 · 本期降级」并加双向护栏",
"scope": [
"app/service/agent/implementations/customer_service.py(业务层:_references 降级为死代码并写明依据)",
"tests/unit/service/test_customer_service_agent.py(护栏 A:AST 守无调用点)",
"tests/unit/service/test_agent_governance.py(护栏 B:knowledge 来源必被拒)",
"开发文档对应的 D2.2 / D2.4(口径回写,另见 D2.1 v6.6 / D1.6 §4.19)"
],
"decision": {
"chosen": "乙 · 本期降级(不向客户展示来源引用)",
"rejected": "甲 · 底座方先登记知识来源并放行(需会签)",
"why_b": "甲依赖底座方改造 ToolExecutor 与治理层放行 knowledge 来源,属不可控外部依赖;本模块今天要交付,不能把交付卡在别人会签上。乙的代价只是「不展示出处」,可追溯性并未丢失(见下)。",
"why_not_silent": "降级不是静默丢弃:D2.2 的 FR-CS-010 与验收表、D2.4 的 §5.7 与 G4/AC-06 都写明了「本期降级」及其技术原因与启用前提,避免下一轮误当成已完成功能。",
"reversibility": "完全可逆:代码本体(_references)保留在位,护栏只是禁止调用;底座方放行后删掉护栏断言即可接通。"
},
"technical_reason": {
"gate": "app/service/agent/governance.py:314-316(review_output 的来源白名单只认 memory / tool)",
"failure_mode": "knowledge 来源会被判为「引用未来自本次已授权召回结果」,抛出 ForbiddenAgentError ⇒ **整个 run 失败**,不是降级、也不是少一个字段",
"red_line": "S-8",
"traceability_instead": "可追溯性由审计承接:agent.tool_executed 的工具调用记录含命中 doc_id 与分数;消息表亦留痕"
},
"implementation": {
"docstring": "_references() 的 docstring 重写为「保留待用的死代码 —— 本期(MVP)不向客户展示来源引用」,写明 governance.py:314-316、S-8、审计承接、启用前提须会签",
"helper_state": "函数体原样保留(source_type=\"knowledge\" 的 SourceReference 组装逻辑未改)",
"no_call_sites": "改用其他出口返回正文;零调用点"
},
"guardrails": {
"A_ast_no_call": {
"file": "tests/unit/service/test_customer_service_agent.py",
"test": "test_knowledge_exit_never_calls_the_disabled_reference_helper",
"method": "ast.walk 找 ast.Call 且 func.attr == \"_references\",断言 calls == []",
"why_ast": "该名字会合法出现在注释与文档字符串里,纯文本搜索会把「提一句」误判成「调用」",
"second_assert": "同时断言 _references 函数定义仍在位(清理时不得被连带删除)"
},
"B_governance_rejects": {
"file": "tests/unit/service/test_agent_governance.py",
"test": "test_knowledge_reference_is_rejected_so_it_must_stay_disabled",
"method": "以 source_type=\"knowledge\" 的 SourceReference 调 review_output,断言抛 ForbiddenAgentError",
"why": "把「本期为何必须降级」的技术原因钉成可执行断言:若将来底座放行,这个测试会失败并提醒同步移除护栏 A"
}
},
"html_downgrade_points": [
{
"doc": "D2.2",
"locator": "FR-CS-010 行",
"change": "追加 ⏸ 2026-09-19 本期降级说明 + <span class=\"pill p2\">⏸ 本期降级</span>"
},
{
"doc": "D2.2",
"locator": "US-CS-01 行",
"change": "期望值补「⏸ 本期不展示来源引用:C-10 乙」"
},
{
"doc": "D2.2",
"locator": "验收表 · 一 · 功能正确性",
"change": "「每条须带来源引用」→「⏸ 本期豁免(改由审计承接)」"
},
{
"doc": "D2.4",
"locator": "§5.7 末尾",
"change": "追加降级段:governance.py:314-316、S-8、_references() 死代码 + 护栏、启用前提须会签"
},
{
"doc": "D2.4",
"locator": "G4 行",
"change": "「可追溯」补「⏸ 本期:来源引用降级,可追溯性由审计承接」"
},
{
"doc": "D2.4",
"locator": "§5.7 三条要求 ①",
"change": "「知识类回答必须附来源」补「⏸ 本期降级,见下段」(本轮新发现的口径残留,与降级冲突)"
},
{
"doc": "D2.4",
"locator": "AC-06 行",
"change": "判据补「⏸ 本期豁免:C-10 乙不展示来源引用,改由审计承接」(同上,本轮新发现)"
}
],
"eol_check": {
"D2.2": "CRLF(本轮编辑后复核)",
"D2.4": "LF —— 编辑后实测 bytes=147917, bareLF=1758, CRLF=0,与改前同为纯 LF,EOL 未被改写"
},
"gates": {
"targeted_pytest": {
"files": 4,
"passed": 164,
"failed": 0,
"command": "pytest -q tests/unit/service/test_customer_service_agent.py tests/unit/service/test_agent_governance.py tests/unit/core/test_customer_service_rules.py tests/unit/core/test_compliance_context.py"
},
"ruff": {
"scope": "app tests tools",
"errors": 22,
"note": "等于基线,无新增"
},
"mypy": {
"scope": "app",
"errors": 3,
"note": "等于基线(agent_persistence_service.py:82 / run_query_service.py:79 / promotion_renderer.py:84,均为既有问题)"
}
},
"not_done_in_this_step": [],
"open_questions": {
"C-10_enablement": "若后续要展示出处,需底座方:① ToolExecutor 把工具返回的知识 doc_id 登记为本次可引用来源;② 治理层放行 knowledge 来源。两步都要会签。"
},
"closure_after": {
"document_writeback": "已完成:D2.1 升 v6.6(C-10 行标 [x] + 修订要点)、D1.6 新增 §4.19(第十五轮会话记录)",
"full_gate": "已完成,见 full_gate"
},
"full_gate": {
"pytest": {
"command": "pytest -q -p no:cacheprovider",
"result": "2 failed / 1579 passed / 2 skipped",
"note": "2 failed = T0 基线同两项(profile_snapshot_current_invariant / worker_runtime[False]),不计入本轮回归;passed 1577 → 1579,增量 = 本步新增的 2 条护栏"
},
"ruff": {
"command": "ruff check app tests tools",
"errors": 22,
"note": "= 基线"
},
"mypy": {
"command": "mypy app",
"errors": 3,
"note": "= 基线"
},
"authoritative_docs": {
"command": "python tools/check_authoritative_docs.py",
"result": "checked 50 documents, no number collision"
}
},
"environment_finding": {
"title": "残留 Worker 抢消费 outbox 事件 ⇒ 集成测试偶发失败(环境级,非代码回归)",
"first_symptom": "全量首跑多出第 3 个失败:tests/integration/test_memory_extraction.py::test_memory_is_not_recallable_across_customers",
"diagnosis_steps": [
"① 单文件重跑两次 → 3/3 通过 ⇒ 不稳定,不是确定性缺陷",
"② 按同一链路脚本复现 seed → enqueue → consume_once → 返回 True ⇒ 消费机制本身正常",
"③ 无人消费检测:插入一条 pending 事件后只观察、不消费 → 1 秒内被外部进程改成 published ⇒ 存在外部消费者"
],
"root_cause": "2026-09-18 17:52 启动的残留服务一直存活,其中 python -m app.worker 会抢消费 outbox 事件,与测试自身的 consume_once(aggregate_id=...) 竞争,先到先得 ⇒ 测试偶发拿不到事件而返回 False",
"processes": [
{
"pid": 27204,
"child": 8740,
"cmd": "uvicorn app.main:app --host 127.0.0.1 --port 8000"
},
{
"pid": 27084,
"child": 2204,
"cmd": "python -m app.worker"
}
],
"action": "经用户批准,已 Stop-Process 停掉 4 个 PID;停止后全量 pytest 回到 2 failed 基线",
"reusable_lessons": [
"与 A-02「门禁基线前先停 Worker」完全吻合 —— 该前置不是形式主义,不停就会污染集成测试",
"印证 S-7:知识集合重建后这两个服务早已过期",
"遇到集成测试失败,先做「无人消费检测」排除外部消费者,再怀疑代码"
]
},
"demo_precondition": {
"issue": "API 与 Worker 现已停止(8000 端口无监听)",
"must_do": "演示前按 S-7 重启 API + Worker,否则前端问答直接连不上"
}
}