feat(ops)+docs: 密钥轮换工具 + 两份文档目录审计收口(D1.1 §23 / D2.1 v6.26)

一、密钥轮换(新增工具 + 操作手册)
- 新增 tools/rotate_api_keys.py:--check 体检 + 交互式轮换;getpass 不回显、
  自动备份 .env.bak-<时间戳>(已被 ignore 命中)、校验不过整体不写入、
  三个 Qwen 变量写同一值 / 两个 DeepSeek 变量写同一值。
  实测 --check:Qwen 三变量同值且非空、DeepSeek 两变量同值且非空。
- 新增 开发文档/D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md(CS-OPS-2026-023):
  .env 5 个变量与读取方取证、五步流程、3 个坑、复核清单、回退方式、能力边界。
- 口径确认:model_endpoint_config.secret_ref 存变量名 ⇒ 轮换只改 .env,不动 DB;
  但必须重启 API + Worker。

二、门禁修复:docs/ 编号撞车
- tools/check_authoritative_docs.py(D3.4 N-14 登记的验收命令集之一)实测 FAIL:
  我方 docs/46 docs/47(2026-09-20 建)与投顾组 docs/46-投顾Agent需求文档.md
  docs/47-投顾Agent功能架构文档.md(2026-09-16 建)同号。
- 按「后到者让位」改名:docs/48-可改文件白名单.md / docs/49-底座会签申请单-2026-09-19.md,
  同步 8 处引用。修复后:checked 54 documents, no number collision,exit 0。

三、前端品牌残留(W12 合并静默回退)
- employee-advisor/dashboard/index.html 与 customer/advisor-plans/index.html 的
  <title> 仍是 南方财富(投顾组分支带回)→ 按 DEC-27 改为 南方基金。
- HTTP 实测两页标题已正确;全仓 app/ 复查 南方财富 = 0。

四、文档口径校准(12 处事实漂移)
- D1.1:D2.1 版本 v5.3 → v6.26(§4.0 / §4.1 / §1 / §2 四处长期错误);
  §8 四行遗留项闭合(D-5 / D-6 / D-7 / 仓库副本同步)+ 新增 §22 §23 留痕;
  §10.2「本区不在任何 git 仓库内」更正为已入库;新增两编号 ⇒ 计数 56 → 58 全量同步。
- D2.2:顶栏徽标 v2.4 与元数据 v2.5 自相矛盾 → 统一;「投顾已清除」→ 状态更新
  (模块 2026-09-20 已恢复,但客服范围裁定 §1.7 / RK-10 不变)。
- D2.3:徽标 v1.0 · 7 批次 51 项 → v1.1 · 8 批次 57 项;投顾清除后果 + §7.1 头号风险
  + 风险表 + 不触碰行全部加恢复口径。
- D2.4:v1.3 变更说明 ⑦ / §1.4 Out of scope / Q-09 加投顾恢复口径。
- D2.5:advisor_t 自相矛盾口径改写为账号表一行 + 口径更正;五项自检首选改为
  一键脚本 启动演示.bat / demo.ps1;补 D3.8 与未发布 advisor:* 白名单登记。
- D2.6:门禁数字 1856/2 → 1909/3 skipped、ruff 19 → 20、补 portal_api_check 行;
  §10 两项已闭环(密钥轮换已工具化、A-10 组 3/4 已补签);头部加 W12/W13 状态更新。
- D4.5:顶部状态更新补指向 D4.7。
- 新增 开发文档/D4.7-投顾模块恢复记录-2026-09-20.md(CS-PURGE-2026-014):
  时间线、8 项恢复动作、客服线不变的结论、DEC-19 理由更正、遗留 1 项、失误登记。
- _consistency.py(维护侧):§三 改为「投顾状态口径检查」,合法语境扩为
  清除史 / 恢复史 / 不属本 Agent 范围。

五、回归实测(全绿)
- pytest -q:1909 passed / 3 skipped / 0 failed
- ruff check app tools tests:20(与 W12 持平,未引入新债)
- mypy app:2(= 既有基线)
- tools/check_authoritative_docs.py:54 文档无编号冲突(exit 0)
- tools/e2e_smoke_test.py --read-only:31/31
- tools/portal_api_check.py:40 项 通过 35 / 失败 0 / 跳过 5
- _eval_harness/http_probe.py:11/11 succeeded
- _consistency.py:GATE PASS
- demo.ps1 -SkipStart -NoBrowser:五项自检全过、退出码 0
- 权威副本 ↔ 仓库:逐字节一致(客服agent 24 / 开发文档 52)

六、未做(如实登记)
- 投顾 config_release 工具白名单(advisor:*)仍未发布 ⇒ 投顾 Agent 工具调用 fail closed
  (实测 active_agent_tools 仅 customer_service:* 4 项 + risk:* 4 项)。与客服线无关;
  要演投顾线先跑 tools/publish_advisor_demo_config.py --apply。
- 两把 key 的实际轮换需你在控制台建新 key(无法代做),流程见 D3.8。
This commit is contained in:
张胜宇
2026-09-20 15:27:07 +08:00
parent 4306326a56
commit c91bbcbdc1
16 changed files with 623 additions and 55 deletions
@@ -1 +1 @@
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>我的投顾方案 · 南方财富</title><link rel="stylesheet" href="/static/portal/common/base.css"><link rel="stylesheet" href="/static/portal/common/advisor-plan.css?v=20260916-plan3"><link rel="stylesheet" href="/static/portal/customer/advisor-plans/advisor-plans.css?v=20260916-plan3"></head><body><main id="main-content" class="page-shell"><div class="page-heading"><div><h1 class="page-heading__title">我的投顾方案</h1><p class="page-heading__description">可在下方申报投顾方案(需已完成且在有效期内的风险测评);投顾受理并发送后,方案会出现在「已收到的方案」里。</p></div><div class="page-heading__actions"><button class="button" type="button" data-reload>刷新</button></div></div><section class="panel"><div class="panel__header"><h2 class="panel__title">申报投顾方案</h2></div><div class="panel__body"><form class="advisor-request-form" data-request-form><label class="form-field"><span class="form-field__label">投资金额(万元)</span><input class="form-field__input" data-request-amount type="number" min="1" step="1" value="500" required></label><label class="form-field"><span class="form-field__label">投资期限</span><select class="form-field__input" data-request-horizon><option>&lt;1年</option><option>1-3年</option><option selected>3-5年</option><option>&gt;5年</option></select></label><label class="form-field"><span class="form-field__label">风险偏好</span><select class="form-field__input" data-request-risk><option>稳健</option><option selected>平衡</option><option>进取</option></select></label><label class="form-field advisor-request-form__wide"><span class="form-field__label">补充说明(可选)</span><input class="form-field__input" data-request-note maxlength="200" placeholder="例如:希望偏债、不想承受太大波动"></label><button class="button button--primary" type="submit" data-request-submit>提交申报</button><p class="form-alert" data-request-alert></p></form></div></section><section class="panel panel--flush"><div class="panel__header"><h2 class="panel__title">我的申报</h2></div><div class="panel__body" data-request-list></div></section><section class="panel panel--flush"><div class="panel__header"><h2 class="panel__title">已收到的方案</h2></div><div class="panel__body" data-list-content></div></section></main><script type="module" src="/static/portal/customer/advisor-plans/advisor-plans.js?v=20260916-plan3"></script></body></html>
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>我的投顾方案 · 南方基金</title><link rel="stylesheet" href="/static/portal/common/base.css"><link rel="stylesheet" href="/static/portal/common/advisor-plan.css?v=20260916-plan3"><link rel="stylesheet" href="/static/portal/customer/advisor-plans/advisor-plans.css?v=20260916-plan3"></head><body><main id="main-content" class="page-shell"><div class="page-heading"><div><h1 class="page-heading__title">我的投顾方案</h1><p class="page-heading__description">可在下方申报投顾方案(需已完成且在有效期内的风险测评);投顾受理并发送后,方案会出现在「已收到的方案」里。</p></div><div class="page-heading__actions"><button class="button" type="button" data-reload>刷新</button></div></div><section class="panel"><div class="panel__header"><h2 class="panel__title">申报投顾方案</h2></div><div class="panel__body"><form class="advisor-request-form" data-request-form><label class="form-field"><span class="form-field__label">投资金额(万元)</span><input class="form-field__input" data-request-amount type="number" min="1" step="1" value="500" required></label><label class="form-field"><span class="form-field__label">投资期限</span><select class="form-field__input" data-request-horizon><option>&lt;1年</option><option>1-3年</option><option selected>3-5年</option><option>&gt;5年</option></select></label><label class="form-field"><span class="form-field__label">风险偏好</span><select class="form-field__input" data-request-risk><option>稳健</option><option selected>平衡</option><option>进取</option></select></label><label class="form-field advisor-request-form__wide"><span class="form-field__label">补充说明(可选)</span><input class="form-field__input" data-request-note maxlength="200" placeholder="例如:希望偏债、不想承受太大波动"></label><button class="button button--primary" type="submit" data-request-submit>提交申报</button><p class="form-alert" data-request-alert></p></form></div></section><section class="panel panel--flush"><div class="panel__header"><h2 class="panel__title">我的申报</h2></div><div class="panel__body" data-request-list></div></section><section class="panel panel--flush"><div class="panel__header"><h2 class="panel__title">已收到的方案</h2></div><div class="panel__body" data-list-content></div></section></main><script type="module" src="/static/portal/customer/advisor-plans/advisor-plans.js?v=20260916-plan3"></script></body></html>
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>投顾工作台 · 南方财富</title>
<title>投顾工作台 · 南方基金</title>
<link rel="stylesheet" href="/static/portal/common/base.css">
<link rel="stylesheet" href="/static/portal/common/operations.css">
<link rel="stylesheet" href="/static/portal/common/advisor-plan.css?v=20260916-plan3">
@@ -33,7 +33,7 @@
### 类 3 · 提案后由底座方修改(**须会签**)
即 `D2.1` **§1.1 六文件八处** + **§1.3 四文件**。逐项会签申请见 `A-10`(`docs/47-底座会签申请单-2026-09-19.md`)。
即 `D2.1` **§1.1 六文件八处** + **§1.3 四文件**。逐项会签申请见 `A-10`(`docs/49-底座会签申请单-2026-09-19.md`)。
| 组 | 文件 | 触碰项 |
|---|---|---|
+172
View File
@@ -0,0 +1,172 @@
"""轮换 `.env` 里的模型密钥(DashScope/Qwen 与 DeepSeek)。
## 为什么需要它
这两把 key **在聊天/文档里出现过明文**,必须轮换。手工改 `.env` 有三个坑:
1. **三个 Qwen 变量必须同一个值** —— `QWEN_API_KEY` / `QWEN_EMBEDDING_API_KEY` /
`DASHSCOPE_API_KEY`。漏改一个,入库与检索就用了两把不同的 key,
症状是「检索能跑、入库失败」或反过来(很难一眼看出是 key 的问题)。
2. **两个 DeepSeek 变量也必须同一个值** —— `DEEPSEEK_API_KEY` 与
`OFFSITE_DEEPSEEK_API_KEY`(场外文档识别复用同一把)。
3. 写文件时若**带上 BOM**或**改变编码/换行**,`.env` 第一项就可能读不出来。
## 用法
.venv\\Scripts\\python.exe tools\\rotate_api_keys.py # 交互输入(不回显)
.venv\\Scripts\\python.exe tools\\rotate_api_keys.py --check # 只体检当前 5 个变量的取值关系
## 安全约定
- 输入**不回显**(`getpass`),**不写入任何日志**,**不回显 key 值**(只打印掩码与长度)。
- 改前自动备份为 `.env.bak-<时间戳>`(该模式已被 `.gitignore` 覆盖)。
- 任一校验不通过就**整体不写入**(先全量校验、后一次性落盘)。
"""
from __future__ import annotations
import argparse
import getpass
import shutil
import sys
from datetime import datetime
from pathlib import Path
ENV_PATH = Path(__file__).resolve().parent.parent / ".env"
#: 变量名 -> 归属的 key(同一个 key 必须写成同一个值)
QWEN_VARS = ("QWEN_API_KEY", "QWEN_EMBEDDING_API_KEY", "DASHSCOPE_API_KEY")
DEEPSEEK_VARS = ("DEEPSEEK_API_KEY", "OFFSITE_DEEPSEEK_API_KEY")
TARGETS = {name: "qwen" for name in QWEN_VARS} | {name: "deepseek" for name in DEEPSEEK_VARS}
MIN_LEN = 20
def mask(value: str) -> str:
"""只暴露前 6 位,其余按长度打码 —— 够判断"换没换",不够还原。"""
if not value:
return "(空)"
return f"{value[:6]}…({len(value)} 位)"
def parse_env(text: str) -> list[tuple[str, str]]:
rows: list[tuple[str, str]] = []
for line in text.split("\n"):
if "=" in line and not line.lstrip().startswith("#"):
key, _, value = line.partition("=")
rows.append((key.strip(), value))
return rows
def check(rows: list[tuple[str, str]]) -> int:
"""体检:三个 Qwen 是否同值、两个 DeepSeek 是否同值、是否为空/像占位符。"""
values = dict(rows)
problems = 0
for group, names in (("Qwen(DashScope)", QWEN_VARS), ("DeepSeek", DEEPSEEK_VARS)):
present = [values.get(n, "") for n in names]
print(f"· {group}:")
for name in names:
print(f" {name:<26} {mask(values.get(name, ''))}")
uniq = {v for v in present if v}
if not uniq:
print(" ❌ 全部为空")
problems += 1
elif len(uniq) > 1:
print(" ❌ **取值不一致** —— 这会让「入库/检索」或「主链路/场外链路」用到不同的 key")
problems += 1
elif len(present) != len([v for v in present if v]):
print(" ❌ 有变量为空(必须同一个值)")
problems += 1
else:
print(" ✅ 同值且非空")
return problems
def rewrite(text: str, values: dict[str, str]) -> tuple[str, int]:
"""只替换目标行的值,其它行(含缩进、注释、顺序)原样保留。"""
out: list[str] = []
replaced = 0
for line in text.split("\n"):
stripped = line.lstrip()
if stripped.startswith("#") or "=" not in line:
out.append(line)
continue
key, sep, _old = line.partition("=")
name = key.strip()
if name in values and name in TARGETS:
out.append(f"{name}{sep}{values[name]}")
replaced += 1
else:
out.append(line)
return "\n".join(out), replaced
def main() -> int:
parser = argparse.ArgumentParser(description="轮换 .env 里的 Qwen / DeepSeek 密钥")
parser.add_argument("--check", action="store_true", help="只体检,不修改")
args = parser.parse_args()
if not ENV_PATH.exists():
print(f"❌ 找不到 {ENV_PATH}")
return 2
raw = ENV_PATH.read_bytes()
if raw.startswith(b"\xef\xbb\xbf"):
print("⚠️ .env 带 UTF-8 BOM:本次会去掉它(BOM 会让第一个变量名读不出来)")
newline = "\r\n" if b"\r\n" in raw else "\n"
text = raw.decode("utf-8-sig").replace("\r\n", "\n")
rows = parse_env(text)
print("=== .env 密钥体检(只显示掩码)===")
problems = check(rows)
if args.check:
print()
print("体检完成。" + ("存在不一致,建议先修。" if problems else "一致。"))
return 1 if problems else 0
missing = [n for n in TARGETS if n not in dict(rows)]
if missing:
print(f"❌ .env 里缺少这些变量:{missing}")
return 2
print()
print("=== 开始轮换 ===")
print("直接回车 = 跳过该项(保留旧值)。输入不会回显,也不会被记录。")
print()
new_values: dict[str, str] = {}
for group, names in (("DashScope / Qwen", QWEN_VARS), ("DeepSeek", DEEPSEEK_VARS)):
entered = getpass.getpass(f"{group} 的新 key(回车跳过):").strip()
if not entered:
print(f" · {group}:跳过,保留旧值")
continue
if not entered.startswith("sk-") or len(entered) < MIN_LEN:
print(
f" ❌ {group}:格式不像 key(应以 'sk-' 开头且长度 ≥ {MIN_LEN})—— **本次不做任何写入**"
)
return 3
for name in names:
new_values[name] = entered
print(f" · {group}:将更新 {len(names)} 个变量({mask(entered)})")
if not new_values:
print()
print("没有输入任何 key,未做修改。")
return 0
backup = ENV_PATH.with_name(f".env.bak-{datetime.now():%Y%m%d-%H%M%S}")
shutil.copy2(ENV_PATH, backup)
updated, replaced = rewrite(text, new_values)
if replaced != len(new_values):
print(f"❌ 只匹配到 {replaced}/{len(new_values)} 行,已放弃写入(备份在 {backup.name})")
return 3
ENV_PATH.write_bytes(updated.replace("\n", newline).encode("utf-8"))
print()
print(f"✅ 已更新 {replaced} 行;备份:{backup.name}")
print(
" 下一步:重启 API 与 Worker 让新 key 生效,然后跑一次链路复核(见仓库根 demo.ps1 的自检)。"
)
return 0
if __name__ == "__main__":
sys.exit(main())
+36 -2
View File
@@ -1,4 +1,4 @@
# 客服 Agent 执行 Todolist(执行看板 · v6.25)
# 客服 Agent 执行 Todolist(执行看板 · v6.26)
> **体系编号**:`D2.1` · 域:二、对外交付 · 编号体系见 `D1.1` §4.0
@@ -46,6 +46,40 @@
**看板状态更新**:`F-3` → **✅ 已落地**(新增 4 条单测)。批次 H 剩余:`H-05`。新增待办:**三项安全路由缺口收口**(`G-01` 优先,建议排在 `H-05` 前)。
## v6.26 本轮修订要点(2026-09-20 · `W13`:**密钥轮换工具** + 两份目录的**文档审计与口径校准**)
> **本轮决议**(用户 2026-09-20「**现在就带你走一遍,然后你再检查一遍我这两个文件夹里的文档 看看还有什么需要完善和补充的**」)。
> 完整会话记录见 `开发文档\D1.6` §4.39。
**一、新增密钥轮换工具(消除一个必须人工、易错、且已在会话中泄露过一次的操作)**
| # | 修订 | 依据 |
|---|---|---|
| 1 | ✅ **新增 `tools/rotate_api_keys.py`**(`--check` 体检 + 交互式轮换):`getpass` **不回显**、自动备份 `.env.bak-<时间戳>`(已被 ignore 命中)、**三个 Qwen 变量写同一值 / 两个 DeepSeek 变量写同一值**、校验不过则**整体不写入**、不落日志、不落任何文档 | 本轮实测 |
| 2 | ✅ **`--check` 实测通过**:`QWEN_API_KEY` / `QWEN_EMBEDDING_API_KEY` / `DASHSCOPE_API_KEY` 三变量 **✅ 同值且非空**;`DEEPSEEK_API_KEY` / `OFFSITE_DEEPSEEK_API_KEY` 两变量 **✅ 同值且非空** | 本步实测 |
| 3 | 📌 **边写边修(如实登记)**:脚本初稿有两处硬伤 —— ① 第 75 行的提示语用了 **ASCII 双引号包中文**(`"入库/检索"`),直接 `SyntaxError`;② 文档字符串里 22 处「反斜杠 + 反引号」触发 `SyntaxWarning: invalid escape sequence`。均已修正并跑 `ruff check` + `ruff format` 通过 | 本步实测 |
| 4 | 📌 **口径确认**:`model_endpoint_config.secret_ref` 存的是**变量名**(`env:QWEN_API_KEY` / `env:DEEPSEEK_API_KEY`)而非值 ⇒ **轮换只改 `.env`,不需要动 DB**;但**必须重启 API + Worker**,否则进程里仍是旧 key | 连库实测 + 源码 |
| 5 | ✅ **新增操作手册 `开发文档\D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md`**(5 步 + 3 个坑 + 复核清单) | 本轮用户要求「带一遍」 |
**二、两份文档目录审计(用户要我「仔细推理还缺什么」)**
| # | 发现 | 处置 |
|---|---|---|
| 1 | 🔴 **`docs\` 编号撞车**:我方 `docs\46` / `docs\47`(2026-09-20 建)与组员 `docs\46-投顾Agent需求文档.md` / `docs\47-投顾Agent功能架构文档.md`(2026-09-16 建)**同号**,`tools/check_authoritative_docs.py`(`D3.4` `N-14` 登记的门禁)**实测 FAIL** | **后到者让位**:`git mv` 我方两份 → `docs\48-可改文件白名单.md` / `docs\49-底座会签申请单-2026-09-19.md`,并同步 8 处引用(含仓库内自引用) |
| 2 | 🔴 **前端品牌残留 2 处**:`employee-advisor/dashboard/index.html` 与 `customer/advisor-plans/index.html` 的 `<title>` 仍是 **`南方财富`**(投顾组分支带回) | 按 `DEC-27`(已批「改,并升 P1」)改为 **`南方基金`**;全仓 `app\` 复查 `南方财富` = **0** |
| 3 | 🔴 **`D1.1` 索引版本号严重过期**:§4.0 总表与 §4.1 明细把 `D2.1` 记为 **v5.3**(实际已 v6.25) | 同步为现行版本,并新增 §22(第十八、十九轮)留痕 |
| 4 | 🔴 **三份 HTML 交付文档口径过期**:`D2.2`/`D2.3`/`D2.4` 仍写「**投顾已清除**」(W12 已恢复);`D2.2` 顶栏徽标 `v2.4` 与文档元数据 `v2.5` **自相矛盾**;`D2.3` 徽标 `v1.0 · 7 批次 51 项` 也过期 | 全部加「2026-09-20 状态更新」口径;徽标更正为 `v2.5` / `v1.1 · 8 批次 57 项`。**关键区分**:投顾模块恢复 **≠** 客服可承接投顾能力 ⇒ `§1.7` 范围与 `RK-10` **裁定不变** |
| 5 | 🟡 **`D2.5`/`D2.6` 内部矛盾**:`D2.5` §2.1 先写「**不要念** `advisor_t`,库里不存在」,同段又写「已重建、重新有效」 | 改写为**一张表的行 + 一段口径更正**(可登录,但本演示仍只演游客线 + 客服线) |
| 6 | 🟡 **`D2.6` 门禁数字落后一轮**:仍写 1856 passed / 2 skipped、`ruff` 19 | 更新为 **1909 / 3 skipped**、`ruff` 20,并补 `portal_api_check` 行 |
| 7 | 🟡 **`D2.6` §10 两项已闭环却仍列在「未做项」**:密钥轮换(已工具化)、`A-10` 组 3/4 签字(2026-09-20 已补签) | 两项改为「已闭环」,并新增 `D3.8` / `D4.7` 交叉引用 |
| 8 | 🟡 **`D1.1` §8「遗留与待决」三行已过期**:`D-5` 前端品牌面、`D-7` 语料入库门禁、`开发文档\` 仓库副本未同步 | 逐行标注**已执行 / 已实现 / 已同步**,并保留 2 处真实残留 |
| 9 | 🟡 **`D1.1` §10.2 表述过期**:「本区不在任何 git 仓库内」 | 更正为**已入库**(`W12`),并说明「移动/改名必须先整包备份」的理由仍成立 |
| 10 | 🟢 **新增 `开发文档\D4.7-投顾模块恢复记录-2026-09-20.md`**:`D4.4`/`D4.5` 是「清除」视角,**恢复视角**此前只在 `D4.5` 顶部状态更新里 | 独立成文,给「删了又恢复」这个答辩必问点一份**唯一现状态**文档 |
| 11 | 🟢 **`_consistency.py` §三 口径同步**:原文假设「投顾命中必须出现在『已清除』语境」,恢复后会**误报** | 改为「投顾状态口径检查」,合法语境扩为 **清除史 / 恢复史 / 不属本 Agent 范围**,并在表头写明判读规则 |
| 12 | 📌 **一处未做(如实登记)**:投顾 `config_release` 工具白名单(`advisor:*`)**当前未发布** ⇒ 投顾 Agent 工具调用 fail closed(实测 `active_agent_tools` 只有 `customer_service:*` 与 `risk:*`)。**与客服线无关**;要演投顾线先跑 `tools/publish_advisor_demo_config.py --apply` | 连库实测 |
**看板状态更新**:客服线 **57 项全部有终态**(55 项落地 / 2 项按裁定挂起);本轮为**交付面收口**(工具 + 手册 + 索引校准),未新增批次任务。剩余为**答辩后当轮**的 1 项动作:跑 `tools/rotate_api_keys.py` 轮换两把 key(见 `D3.8`)。
## v6.25 本轮修订要点(2026-09-20 · 演示一键启动脚本 + **权威文档目录入库** + 回归复跑)
> **本轮决议**(用户 2026-09-20「**你现在帮我做一个演示的前端一键启动的一个脚本 然后再跑一遍回归测试,把 `客服agent/` + `开发文档/` 一并入库并再推一次**」)。
@@ -130,7 +164,7 @@
| 6 | ✅ **`F-07` 实测失效锚点 = 0**(8 份交付 HTML;`D7.1` 63 个 `href` / 24 个目录锚点全部命中)⇒ **零工作量,非「未做」** | 脚本实测 |
| 7 | ✅ **`E-08` 三条红线对照**:**11 passed**,逐条映射(红线 1 = 5 例 / 红线 2 = 2 例 / 红线 3 = 4 例),**未发现缺口 ⇒ 无新增拦截代码** | `docs/evidence/20260919-t10-e08-…json` |
| 8 | ✅ **`H-05` 由「未通过」改判达标**:四集合 `visibility` 分区键(`max_length=16` / `num_partitions=16`)+ **fail-closed 写入实测**(缺档位即拒写)+ **双向可见性实测** + `over-fetch` 全仓零命中 + **双 schema 收敛**(`build_schema` 全仓仅 1 处、灌库脚本反向 import) | `docs/evidence/20260919-t10-h05-…json` |
| 9 | ✅ **`A-09` / `A-10` 落档**:新增 `docs/46-可改文件白名单.md`(四类 + **零 DDL 声明** + 实际改动对照表)、`docs/47-底座会签申请单-2026-09-19.md`(组 1 / 组 2 + 🆕 **组 3 组外扩张 2 文件须补签**) | 用户批准(按附录B 模板) |
| 9 | ✅ **`A-09` / `A-10` 落档**:新增 `docs/48-可改文件白名单.md`(四类 + **零 DDL 声明** + 实际改动对照表)、`docs/49-底座会签申请单-2026-09-19.md`(组 1 / 组 2 + 🆕 **组 3 组外扩张 2 文件须补签**) | 用户批准(按附录B 模板) |
| 10 | ✅ **全量回归**:`pytest` **1810 passed / 2 skipped / 0 failed**|`ruff` **19**(=基线)|`mypy` **2**(优于基线 3)|`_consistency` **GATE PASS**|冒烟 **31/31**|HTTP **11/11**|46 条金标 **11 项全达标**(与 `score_w9c` 逐项相同) | 本步实测 |
**看板状态更新**:`A-09` / `A-10` / `D-04` / `E-08` / `F-01` / `F-05` / `F-07` / `G-01` / `H-05` / `H-06` → **✅ 已落地**(§5 勾选框已同步刷新)。**仍未落地且非挂起的只剩 `G-03`**(`app\core\knowledge_tier.py` 未新增):`D-04` 的「档位由身份推导」已由 `knowledge_tool.py` + `knowledge_contracts.tiers_for_roles()` 达标(fail-closed 收敛为 `public`),`G-03` 只是把推导**并入单文件**以便将来 `G-02` 落地,属**可延后**。
@@ -306,7 +306,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<header id="topbar">
<div id="topbar-title">智能服务系统 — 智能客服 Agent 需求文档</div>
<span id="topbar-badge">v2.4 · 双主体 · 投顾已清除</span>
<span id="topbar-badge">v2.5 · 双主体 · 五出口 · 投顾已恢复</span>
</header>
<main id="main">
@@ -322,7 +322,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">文档版本</span>
<span class="doc-meta-value">v2.5(双主体 · 身份与角色分离 · 投顾已清除 · 五出口智能增强对接)</span>
<span class="doc-meta-value">v2.5(双主体 · 身份与角色分离 · 五出口智能增强对接 · 投顾模块 2026-09-20 随合并恢复)</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">文档定位</span>
@@ -671,7 +671,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<table>
<thead><tr><th>In Scope</th><th>Out of Scope(及归属)</th></tr></thead>
<tbody>
<tr><td>客服对话(访客 + 客户两类主体)</td><td>投顾能力——<strong>已于 2026-09-17 整体清除</strong>(见 §0.4 v2.4)</td></tr>
<tr><td>客服对话(访客 + 客户两类主体)</td><td>投顾能力——<strong>不属本 Agent 范围</strong>(2026-09-17 曾整体清除;2026-09-20 随合并恢复,但仍由投顾 Agent 承接,客服不越界。见 §0.4 v2.4 与上方状态更新)</td></tr>
<tr><td>5 类业务意图 + 4 类运行时意图(<code>clarify</code> / <code>compliance_block</code> / <code>auth_required</code> / <code>fallback</code>)</td><td>风控预警的生成与处置(风控 Agent)</td></tr>
<tr><td>RAG 检索、跨集合回退、来源引用(检索模块设计见《知识库设计方案》)</td><td>图谱关系检索(Neo4j 派生视图)</td></tr>
<tr><td>短期会话记忆(<strong>读 / 写</strong>);<strong>长期记忆召回关闭</strong>;<strong>画像仅字段级只读</strong>(<code>risk_level</code> / <code>customer_level</code>,供确定性规则;访客硬禁)</td><td>长期记忆的抽取与投影(记忆 Worker)</td></tr>
@@ -760,7 +760,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
</tbody>
</table>
<blockquote class="callout-warn"><p><strong>⚠️ 投顾线已移除</strong>:投顾模块(Agent、4 个 controller、<code>/api/v1/advisor</code> 全部端点、<code>employee-advisor/</code> 前端、相关权限码与 5 个工具)<strong>已于 2026-09-17 整体清除</strong>。原「三条业务线」「九步主干」「客服发工单 → 投顾侧可见」等表述<strong>全部作废</strong>。详见 <code>开发文档/D4.5-投顾模块清除执行报告-2026-09-17.md</code>。</p></blockquote>
<blockquote class="callout-warn"><p><strong>⚠️ 投顾线状态更新(2026-09-20 · <code>W12</code>)</strong>:投顾模块(Agent、4 个 controller、<code>/api/v1/advisor</code> 全部端点、<code>employee-advisor/</code> 前端、相关权限码与 5 个工具)<strong>已于 2026-09-17 整体清除,又于 2026-09-20 随合并恢复</strong>(远端组员 3 个提交的反向依赖,裁定「组员新功能 &gt; 本地清除」;见 <code>D1.6</code> §4.37 与 <code>D4.5</code> 顶部状态更新与 <code>D4.7</code>)。原「三条业务线」「九步主干」等表述仍<strong>不适用于本 Agent</strong>:<strong>投顾模块恢复 ≠ 客服可承接投顾能力</strong>,本需求文档的范围裁定(§1.7 / RK-10)不变。</p></blockquote>
<h2 id="1.10-业务红线与-MVP-必守项">1.10 业务红线与 MVP 必守项</h2>
@@ -877,7 +877,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<tr><td>RK-18</td><td>体验/成本</td><td>访客限流阈值不当:过严误伤共享出口、过松失去防刷</td><td><span class="intent-tag mid">中</span></td><td>正常用户被 429 / 被刷量拖垮</td><td>IP 从严 + 令牌从宽 + 人机校验夹层;监控误伤率 &lt; 1%</td><td>后端架构师</td></tr>
<tr><td>RK-19</td><td>数据</td><td>访客身份被误当客户处理,污染客户指标或画像</td><td><span class="intent-tag high">高</span></td><td>披露口径失真 + 画像体系被绕过</td><td>不写 <code>sys_user</code>、不生成 <code>customer_id</code>;<strong>仓库层类型隔离</strong></td><td>AI 工程师</td></tr>
<tr><td>RK-20</td><td>一致性</td><td><strong>访客三元组两处定义不一致</strong>,改一处不生效</td><td><span class="intent-tag high">高</span></td><td>受理放行但执行失败(run 卡 <code>queued</code> 且无报错)</td><td>方案乙单点化(§1.8.4)+ 接缝单测</td><td>后端架构师</td></tr>
<tr><td>RK-21</td><td>范围</td><td><strong>投顾清除后失去第二条回归业务线</strong></td><td><span class="intent-tag mid">中</span></td><td>底座改动的回归验证只剩客服一条线</td><td>上调「测试环境就位」优先级;<strong>底座两组改动必须全量跑门禁</strong></td><td>全员</td></tr>
<tr><td>RK-21</td><td>范围</td><td><strong>投顾清除后失去第二条回归业务线</strong>(2026-09-20 投顾已恢复 ⇒ <strong>对照组回归</strong>,本条降级为「历史上曾存在的风险」)</td><td><span class="intent-tag mid">中</span></td><td>底座改动的回归验证只剩客服一条线</td><td>上调「测试环境就位」优先级;<strong>底座两组改动必须全量跑门禁</strong></td><td>全员</td></tr>
</tbody>
</table>
@@ -316,7 +316,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<header id="topbar">
<div id="topbar-title">智能服务系统 — 智能客服 Agent 开发计划</div>
<span id="topbar-badge">v1.0 · 7 批次 51 项 · 12 步关键路径</span>
<span id="topbar-badge">v1.1 · 8 批次 57 项 · 12 步关键路径</span>
</header>
<main id="main">
@@ -394,13 +394,13 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<thead><tr><th>项</th><th>状态</th></tr></thead>
<tbody>
<tr><td>客服 Agent 模块</td><td><strong>已按形态 A 整体清除</strong>(2026-09-16,删 34 文件 + 摘 17 个共享文件引用,回退副本 <code>_cs_purge_backup/</code>)——下一步是<strong>重建</strong></td></tr>
<tr><td><strong>投顾模块</strong></td><td><strong>已于 2026-09-17 整体清除</strong>(后端 21 文件 + 前端 <code>employee-advisor/</code> 9 文件 + <code>/api/v1/advisor</code> 全部端点 + 5 个脚本 + 14 个测试 + 相关权限码)。<strong>验证:0 语法错误、0 悬空 import</strong>;备份 <code>_advisor_purge_backup/</code></td></tr>
<tr><td><strong>投顾模块</strong></td><td><strong>已于 2026-09-17 整体清除</strong>(后端 21 文件 + 前端 <code>employee-advisor/</code> 9 文件 + <code>/api/v1/advisor</code> 全部端点 + 5 个脚本 + 14 个测试 + 相关权限码)。<strong>验证:0 语法错误、0 悬空 import</strong>;备份 <code>_advisor_purge_backup/</code><br>⚠️ <strong>2026-09-20 状态更新</strong>:投顾模块<strong>已随合并恢复</strong>(<code>W12</code>;远端组员 3 个提交反向依赖被清除的模块,裁定「组员新功能 &gt; 本地清除」)。本轮客服侧工作<strong>不受影响</strong>:本计划的批次、接触面与门禁均针对客服 Agent,投顾恢复只是把「第二条回归业务线」还了回来。见 <code>D1.6</code> §4.37 / <code>D4.7</code>。</td></tr>
<tr><td><strong>身份与鉴权方向</strong></td><td>已拍板:<strong>「方案乙(收敛)现在做 → 方案甲(拆轴)MVP 后」</strong></td></tr>
<tr><td>工程纪律</td><td>19 项决策已全部定案;底座接触面已逐文件取证</td></tr>
</tbody>
</table>
<blockquote class="callout-warn"><p><strong>⚠️ 投顾清除带来一个必须正视的后果</strong>:投顾原是<strong>第二条回归业务线</strong>,用于验证 6 个底座公共件的改动。清除后,底座改动的回归验证<strong>只剩客服一条线</strong>。因此本计划把「<strong>测试环境就位(G-00)</strong>」列为<strong>最优先的前置</strong>——没有可运行的门禁时改底座,等于盲改。</p></blockquote>
<blockquote class="callout-warn"><p><strong>⚠️ 投顾清除带来一个必须正视的后果</strong>:投顾原是<strong>第二条回归业务线</strong>,用于验证 6 个底座公共件的改动。清除后,底座改动的回归验证<strong>只剩客服一条线</strong>。因此本计划把「<strong>测试环境就位(G-00)</strong>」列为<strong>最优先的前置</strong>——没有可运行的门禁时改底座,等于盲改。<br>⚠️ <strong>2026-09-20 状态更新</strong>:投顾<strong>已随合并恢复</strong> ⇒ <strong>对照组回归</strong>,本条降级为「历史上曾存在的风险」;<code>G-00</code> 的优先级结论<strong>不变</strong>(本轮实测全量回归 1909 passed,已由事实替代推断)。</p></blockquote>
<hr>
@@ -461,7 +461,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<tr><td><strong>原位改造</strong></td><td>客服 Agent 主体、会话记忆、转人工三件套</td><td>不改文件名、不改对外签名、不迁移目录;改造前先确认既有行为的验证方式</td></tr>
<tr><td><strong>最小新增</strong></td><td>档位过滤、档位标注器、主体判定、访客令牌、访客限流、合规词库、<code>actor.py</code>、<code>knowledge_tier.py</code></td><td>不新增架构层;文件名与既有命名风格一致</td></tr>
<tr><td><strong>最小挂载</strong></td><td>路由注册、Agent 工厂分支、配置项追加</td><td>只追加不改既有逻辑;每处追加在评审中单独说明理由</td></tr>
<tr><td><strong>不触碰</strong></td><td>投顾已清除(无对象);其余公共基座<strong>只调用不改写</strong></td><td>例外见 §4.2(组 2 的本轮扩张)</td></tr>
<tr><td><strong>不触碰</strong></td><td>投顾模块(2026-09-17 曾清除、<strong>2026-09-20 已恢复</strong>,但<strong>本轮仍不触碰</strong>);其余公共基座<strong>只调用不改写</strong></td><td>例外见 §4.2(组 2 的本轮扩张)</td></tr>
</tbody>
</table>
@@ -759,7 +759,7 @@ A-02 → A-07 → G-01 → G-01b → D-01 → D-02 → G-03 → D-04
<!-- ==================== 7 ==================== -->
<h1 id="7.-风险与分工">7. 风险与分工</h1>
<h2 id="7.1-头号风险">7.1 头号风险:投顾清除后失去第二条回归业务线</h2>
<h2 id="7.1-头号风险">7.1 头号风险:投顾清除后失去第二条回归业务线(⚠️ 2026-09-20 已随合并恢复 ⇒ 本条降级)</h2>
<blockquote class="callout-warn"><p><strong>⚠️ 本条必须置顶</strong>:投顾原本是 <strong>6 个底座公共件改动时的对照组</strong>——它是唯一完好的业务线,能在改底座时暴露跨模块回归。它在 2026-09-17 被整体清除后:<strong>底座改动的回归验证只剩客服一条线</strong>。<br><strong>应对三条</strong>:① <strong>G-00(测试环境就位)优先级上调为最前置</strong>;② 底座两组改动<strong>必须全量跑 §6.1 门禁</strong>,不得只跑单测;③ 组 2 单独 PR,便于<strong>独立回滚</strong>。</p></blockquote>
@@ -768,7 +768,7 @@ A-02 → A-07 → G-01 → G-01b → D-01 → D-02 → G-03 → D-04
<table>
<thead><tr><th>排名</th><th>风险</th><th>最高风险点</th><th>对策</th></tr></thead>
<tbody>
<tr><td>1</td><td><strong>失去对照组</strong>(投顾已清除)</td><td>底座改动无第二条线兜底</td><td>G-00 前置 + 全量门禁 + 组 2 单独 PR</td></tr>
<tr><td>1</td><td><strong>失去对照组</strong>(投顾已清除 → <strong>2026-09-20 已恢复,对照组回归</strong>)</td><td>底座改动无第二条线兜底</td><td>G-00 前置 + 全量门禁 + 组 2 单独 PR</td></tr>
<tr><td>2</td><td><strong>D-02</strong> 过滤器按集合逐个拼</td><td>改错会让客服<strong>全员转人工且不报错</strong></td><td>先补两类替身测试(部分集合有/部分没有、字段名不同)再改实现;上线后立刻验召回量不变;保留独立计数</td></tr>
<tr><td>3</td><td><strong>G-01/G-01b</strong> 身份单点化</td><td>改鉴权入口与 Worker 执行路径,<strong>无测试环境时是盲改</strong></td><td>G-00 必须先完成;新增<strong>接缝单测</strong>(两侧产出必须相等);对外行为零变化的验收方式</td></tr>
<tr><td>4</td><td><strong>C-06</strong> 四向验证 / <strong>C-03</strong> 词表取回</td><td>词表缩水不报错;补偿式修改会放过真实承诺</td><td>从留痕文档<strong>逐条抄</strong>;四个方向用例都必须有;反例守卫</td></tr>
@@ -487,7 +487,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<tbody>
<tr><td>v1.0</td><td>2026-09-16</td><td>首次发布:三集合 + 三档可见性 + 八项技术选型决策</td></tr>
<tr><td>v1.1</td><td>2026-09-16</td><td>基线对齐:新增 §1.5 业务基线、§12 待确认;术语补「游客/访客」对照;落位修正为 <code>app/core/config.py</code></td></tr>
<tr><td><strong>v1.2</strong></td><td><strong>2026-09-17</strong></td><td><strong>与身份模型对齐 + 自洽性修正</strong>:① <strong>检索签名由布尔改为档位集合</strong>(原 <code>include_internal: bool</code> 表达不了三档);② 上游依据统一指向《客服 Agent 需求文档》v2.4(原 doc 内混用 v2.1/v2.2);③ <strong>补「客户档位降级」的配置承载</strong>(原降级态在映射表与配置里均无表达方式);④ 明确「降级原因提示」的边界(可提示账号状态、<strong>不可提示内容存在性</strong>);⑤ 配置项全集补全 4 项;⑥ <code>metadata.type</code> 取值统一小写;⑦ <strong>删除 <code>registered</code> 档中的「基金投顾策略详情」示例</strong>(投顾模块已于 2026-09-17 整体清除);⑧ 明确「事件名」与「指标名」两套命名;⑨ 补档位报告产物规格与签收机制;⑩ 补 <code>fin_knowledge_meta</code> 字段级建议;⑪ 修正备选方案数(8 项决策共 <strong>26</strong> 个备选,原写 24);⑫ 新增 §1.5 与身份轴的接口</td></tr>
<tr><td><strong>v1.2</strong></td><td><strong>2026-09-17</strong></td><td><strong>与身份模型对齐 + 自洽性修正</strong>:① <strong>检索签名由布尔改为档位集合</strong>(原 <code>include_internal: bool</code> 表达不了三档);② 上游依据统一指向《客服 Agent 需求文档》v2.4(原 doc 内混用 v2.1/v2.2);③ <strong>补「客户档位降级」的配置承载</strong>(原降级态在映射表与配置里均无表达方式);④ 明确「降级原因提示」的边界(可提示账号状态、<strong>不可提示内容存在性</strong>);⑤ 配置项全集补全 4 项;⑥ <code>metadata.type</code> 取值统一小写;⑦ <strong>删除 <code>registered</code> 档中的「基金投顾策略详情」示例</strong>(投顾模块已于 2026-09-17 整体清除;⚠️ 该模块 <strong>2026-09-20 已随合并恢复</strong>,但本设计方案<strong>不对投顾知识引入任何档位</strong> —— 删除该示例的决定继续有效,见 <code>D4.7</code>);⑧ 明确「事件名」与「指标名」两套命名;⑨ 补档位报告产物规格与签收机制;⑩ 补 <code>fin_knowledge_meta</code> 字段级建议;⑪ 修正备选方案数(8 项决策共 <strong>26</strong> 个备选,原写 24);⑫ 新增 §1.5 与身份轴的接口</td></tr>
<tr><td><strong>v1.2.1</strong></td><td><strong>2026-09-17</strong></td><td><strong>品牌全量口径统一</strong>:旧值(<code>XX科技</code> / <code>400-XXX-XXXX</code> / <code>南方财富</code> / <code>nanfangwm.com</code>)统一为<strong>南方基金</strong>(热线 <code>400-889-8899</code> · 官网 <code>nffund.com</code>),系统名统一为「智能服务系统」。</td></tr>
<tr><td><strong>v1.3</strong></td><td><strong>2026-09-17</strong></td><td><strong>档位隔离增强 + 检索能力对接五出口架构</strong>:① <strong>§7.2 决策 2 补强</strong>——新增「集合内分区」备选并采纳:<code>visibility</code> 声明为 <strong>partition key</strong>,<strong>隔离由分区承担、标量字段保留用于词法回退与审计</strong>(写入侧对空值拒绝 = fail-closed);② <strong>取消 <code>over-fetch ×3</code></strong>(分区裁剪后「TopK 被不可见条目占满」的场景不再存在,精度净提升);③ 附录A 增加 <code>visibility NOT NULL</code>、<code>family_id</code>、<code>param_class</code> 三个字段;④ 附录B 补<strong>机器可判的档位判据</strong>并裁定「服务分层门槛属 <code>public</code>」;⑤ 新增 <strong>附录F</strong>(术语字典层 / 同族合并 / 证据包 / 计算型参数位 / 评测门禁);⑥ §5.5 补「回退不得跨档位」。</td></tr>
<tr><td><strong>v1.4</strong></td><td><strong>2026-09-18</strong></td><td><strong>落地回写 · 三集合已重建重灌(628 块)</strong>:① <strong>§7.2.1 修正</strong>——实测 partition key 模式下<strong>禁止手工 <code>create_partition</code></strong>,「档位值变更 = 建分区」<strong>作废</strong>,改为<strong>「无需动作,引擎按哈希自动路由」</strong>(<code>num_partitions = 16</code>,创建后不可改);② <strong>附录A 补「实库 vs 设计态」对照</strong>——<code>doc_id</code> 业务主键 + 14 个 VARCHAR + <code>embedding</code>,<code>family_id</code> / <code>param_class</code> / <code>intent</code> <strong>本轮未落地</strong>;③ 语料口径由 <strong>617 块</strong>(2026-09-16 陈旧件)更新为 <strong>628 块</strong>(<code>faq 149 / policy 288 / product 191</code>;<code>public 603 / registered 25</code>);④ 附录F 块长实测按新语料复测(平均 101.5 字 / 528 块 &lt; 200 字 / 最长 2828 字)。</td></tr>
@@ -547,7 +547,7 @@ hr { border: none; height: 1px; background: linear-gradient(90deg, transparent,
<tr><td>检索、过滤、回退、兜底、来源引用</td><td>意图分类与路由(需求文档 §1.4 域 A)</td></tr>
<tr><td>知识库管理接口规格</td><td>管理端前端页面</td></tr>
<tr><td>词法回退(关系型库 LIKE)作为降级手段</td><td>图谱检索(依赖图数据库)</td></tr>
<tr><td>性能、安全、部署、监控、验收</td><td>投顾相关一切能力——<strong>已于 2026-09-17 整体清除</strong></td></tr>
<tr><td>性能、安全、部署、监控、验收</td><td>投顾相关一切能力——<strong>不属本知识库范围</strong>(2026-09-17 曾随模块整体清除;2026-09-20 模块已恢复,但投顾知识仍<strong>不进入本设计的三集合与三档</strong>)</td></tr>
</tbody>
</table>
@@ -1434,7 +1434,7 @@ RAG_QUERY_CACHE_TTL=300 # 秒;缓存键必须含主体标识</code
<tr><td>Q-06</td><td>未说明与既有 1535 行代码的关系</td><td>可能重复实现已有能力</td><td>✅ 已补(§3.2 / §10.1)</td></tr>
<tr><td><strong>Q-07</strong></td><td><strong>检索签名用布尔表达不了三档</strong>(原设计 <code>include_internal: bool</code>)</td><td>布尔只有「含/不含」,无法表达「访客只看 public、客户看 public+registered」,且默认值即 fail-open</td><td>✅ <strong>v1.2 已修正</strong>(改为档位集合,§5.4)</td></tr>
<tr><td><strong>Q-08</strong></td><td><strong>客户档位降级态无配置承载</strong>,实现时必然落回硬编码</td><td>降级行为不可配置、不可审计</td><td>✅ <strong>v1.2 已修正</strong>(新增降级档位配置)</td></tr>
<tr><td><strong>Q-09</strong></td><td><strong><code>registered</code> 档示例含「基金投顾策略详情」</strong>,而投顾已整体清除</td><td>指向已不存在的业务</td><td>✅ <strong>v1.2 已删除该示例</strong></td></tr>
<tr><td><strong>Q-09</strong></td><td><strong><code>registered</code> 档示例含「基金投顾策略详情」</strong>,而投顾已整体清除(该模块 2026-09-20 已恢复,但「投顾知识不进客服知识库」的裁定不变)</td><td>指向已不存在的业务</td><td>✅ <strong>v1.2 已删除该示例</strong></td></tr>
<tr><td><strong>Q-10</strong></td><td><strong>未命中规则默认放行依赖「应该会有人复核」</strong>,缺兜底约束</td><td>实践中会退化为「默认放行且无人复核」,与 P2 冲突</td><td>✅ <strong>v1.2 已补</strong>:报告未签收即视为入库无效(§4.4 / §5.2)</td></tr>
<tr><td><strong>Q-11</strong></td><td>档位报告格式与签收机制未定义</td><td>复核环节无产物规格,无法留痕</td><td>✅ <strong>v1.2 已补</strong>(§5.2)</td></tr>
<tr><td><strong>Q-12</strong></td><td>文档内上游版本号混用(v2.1 / v2.2)</td><td>追溯困难</td><td>✅ <strong>v1.2 已统一</strong>为《需求文档》v2.4</td></tr>
@@ -9,6 +9,10 @@
## 1. 演示前五项自检(`A-05` 清单 · 本文即产物)
> ✅ **首选做法(2026-09-20 起):双击仓库根目录的 `启动演示.bat`**(等价于 `powershell -ExecutionPolicy Bypass -File .\demo.ps1`)。它一次做完:拉起服务(幂等)→ **刷新行情** → **自动跑下面五项自检** → 打开访客页与客户登录页。
> 常用参数:`-SkipStart`(服务已起,只自检 + 开页面)、`-NoBrowser`、`-Port 8100`、`-KeepPrices`(不刷新行情)。
> 下面的手工命令表是**同口径的备用路径**(一键脚本不可用 / 想逐项看细节时用)。
> 五项**全过**才开讲。第 4 项最容易翻车(行情有效期只有 15 分钟)。
| # | 检查项 | 命令(在 `group_fqcd_jr` 下) | 2026-09-19 实测 |
@@ -43,9 +47,11 @@
| 运营专员 | `offsite_t` | `offsite123` | 同上(→ 运营工作台) | **200** `roles=['operator']` |
| **边界账号** | `review_t` | —— | —— | **401** ✅**设计如此**:账号存在但**不绑角色、不设口令**(`seed_test_rbac.py` 的 `USERS` 注释) |
| 访客 | 无需账号 | —— | `POST /api/v1/visitor-tokens` | **201**,令牌 `sub` 是**数字串**(`D-06` 的前提成立) |
| 投顾顾问 | `advisor_t` | `abc12345` | `/portal/employee-advisor/login/` | **可用**(2026-09-20 `W12` 随投顾模块恢复而重建;账号 `9020` + `advisor` 角色 34 权限) |
| 画像演示客户 | `profile_c5` / `profile_c3` / `profile_c1` / `profile_c2` / `profile_expired` | —— | —— | **仅数据层**(无口令);`profile_expired` 专门覆盖「测评过期 = 失败关闭」 |
> ⚠️ **不要念 `advisor_t / abc12345`**:投顾模块已整体清除,该账号在库里**不存在**(`D4.5`)。该账号已于 2026-09-20 重建(`tools/grant_advisor_role.py` + `tools/create_test_user.py --id 9020`)⇒ `docs/44-演示流程.md` 与 `docs/40` 里的那一行**重新有效**。
> 📌 **`advisor_t` 口径更正(2026-09-20)**:投顾模块**已于 2026-09-20 随合并恢复**(`W12`;「整体清除」被组员新功能取代,见 `D4.5` 顶部状态更新与 `D4.7`),该账号已重建、**可以登录**。但**本演示脚本仍只演「游客线 + 客服线」** —— 投顾线是组员的地盘,讲客服时不必替它背书;被问到时说明「投顾线已恢复,可单独演示」即可。
> ⚠️ **注意一处未做**:投顾的 `config_release` 工具白名单(`advisor:*`)**当前未发布** ⇒ 投顾 Agent 的工具调用会 fail closed(2026-09-20 实测 `active_agent_tools` 只有 `customer_service:*` 与 `risk:*`)。要演投顾线,先跑 `tools/publish_advisor_demo_config.py --apply`。**与客服线无关。**
### 2.2 冷启动(换机器/重启后重建演示数据)
@@ -180,4 +186,6 @@ cd D:\桌面\金融\group_fqcd_jr
| 本文对应的 Todolist 项 | `D2.1` 批次 F:`F-03` / `F-04` / `A-05` |
| 台词证据(真 HTTP 原始答复) | `group_fqcd_jr/docs/evidence/20260919-t8-demo-lines.json`(17 条)、`20260919-t8-demo-lines2.json`(5 条) |
| 出口定义与判分规则 | `D3.7-客服Agent评测金标集与判分规则`、`D3.6-客服Agent智能增强架构建议` |
| **演示一键启动** | `group_fqcd_jr\demo.ps1` + `group_fqcd_jr\启动演示.bat`(五项目检口径与本文 §1 **完全一致**) |
| **答辩后立刻做**:模型密钥轮换 | `D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md`(工具 `tools/rotate_api_keys.py`) |
| 会话记录 | `D1.6-对话上下文提取与开工前补充决策` 对应轮次 |
@@ -4,7 +4,8 @@
> **读者**:答辩评委 + 答辩当天操作演示的人 + 三个月后的自己。
> **性质**:本报告回答**一个**问题 —— 「答辩老师批评这个客服 Agent **不智能、动不动就转人工**,凭什么说现在改好了?」
> **口径**:本文所有数字均为**真机实测**(`_eval_harness` 46 条金标 + `http_probe` 全链路 + `e2e_smoke_test` 宽链路冒烟 + 12 条真机边界用例),**修复前基线如实并列**,不修饰。
> **配套**:架构依据 `D3.6`(五出口 + 安全不变量);判分规则 `D3.7`(46 条金标 / 10 项指标 / 4 项零容忍);演示脚本与账号 `D2.5`;执行看板 `D2.1`;会话留痕 `D1.6` §4.36。
> **配套**:架构依据 `D3.6`(五出口 + 安全不变量);判分规则 `D3.7`(46 条金标 / 10 项指标 / 4 项零容忍);演示脚本与账号 `D2.5`;执行看板 `D2.1`;密钥轮换 `D3.8`;会话留痕 `D1.6` §4.36 与 §4.37—§4.39。
> **状态更新(2026-09-20 · `W12`/`W13`)**:本文的**结论与金标数字全部不变**(金标仍是同一套用例、同一台机器)。但有三处外部事实需要并读:① **投顾模块已随合并恢复**(组员新功能取代「整体清除」,见 `D4.5` 顶部状态更新与 `D4.7`)—— 对本客服 Agent 的能力与门禁**无影响**;② 权威文档目录(`客服agent\` 24 份 / `开发文档\` 52 份)**已入库**,全量回归在其后复跑(**1909 passed / 3 skipped**,见 §6.3);③ 增加了**模型密钥轮换工具** `tools/rotate_api_keys.py`(操作手册 `D3.8`),§10 第 4 项由「手工 4 步」升级为「跑一个脚本」。
---
@@ -181,11 +182,12 @@
| 门禁 | 结果 |
|---|---|
| 全量单测 + 契约测 + 集成测 | **1856 passed / 2 skipped / 0 failed** |
| 全量单测 + 契约测 + 集成测(`W12` 权威文档入库后复跑) | **1909 passed / 3 skipped / 0 failed**(`W11` 时为 1856 / 2 skipped —— 差额为本轮新增用例) |
| 宽链路 HTTP 冒烟(`e2e_smoke_test --read-only`) | **31/31 通过** |
| HTTP 全链路探针(`http_probe.py`,含访客线 + 客户线 + 反诈/账户/代办/画像/费率/多轮) | **11/11 succeeded** |
| 静态检查 / 类型检查 | `ruff` **19**(持平基线)、`mypy` **2**(持平基线) |
| 静态检查 / 类型检查 | `ruff` **20**(远端分支本身为 22;本地客服线基线 19,**未引入新债**)、`mypy` **2**(= 既有基线) |
| 跨文档一致性 | `_consistency.py` **GATE PASS** |
| 门户接口清单 | `tools/portal_api_check.py` 40 项:**通过 35 / 失败 0 / 跳过 5**(空集按 `SKIP` 而非 `FAIL`) |
| 真机入参边界(12 条) | **12/12 符合预期**(超限一律 `422` + 字段级错误,不再落库 500) |
---
@@ -204,11 +206,11 @@
| 项 | 落点 |
|---|---|
| 可改文件白名单(四类) | `group_fqcd_jr\docs\46-可改文件白名单.md`(`A-09`) |
| 底座会签申请单(4 组,逐项最小化边界 + 降级方案) | `group_fqcd_jr\docs\47-底座会签申请单-2026-09-19.md`(`A-10`) |
| 可改文件白名单(四类) | `group_fqcd_jr\docs\48-可改文件白名单.md`(`A-09`) |
| 底座会签申请单(4 组,逐项最小化边界 + 降级方案) | `group_fqcd_jr\docs\49-底座会签申请单-2026-09-19.md`(`A-10`) |
| 零 DDL 声明 | 不新增/不修改任何表结构;`audit_schema.py` = 89 张业务表与基线一致 |
| 跨文档一致性 | 需求 ↔ 计划 ↔ Todolist ↔ 知识库 四份交叉引用无冲突 |
| 会话留痕 | `D1.6` §4.1—§4.36(每轮"你说了什么 → 我做了什么 → 实测是什么") |
| 会话留痕 | `D1.6` §4.1—§4.39(每轮"你说了什么 → 我做了什么 → 实测是什么";§4.37—§4.38 = `W12` 合并与文档入库,§4.39 = `W13` 密钥轮换工具 + 文档审计) |
---
@@ -231,8 +233,8 @@
| 1 | `G-02` 身份轴(方案甲) | **按裁定挂起**,等 MVP 演示通过后再做 | 访客角色仍走 RBAC 的 `visitor` 角色(`actor.py` 单点构造),当前行为正确,只是"身份轴"这一层抽象未引入 |
| 2 | `E-07` | 按裁定挂起 | — |
| 3 | `M-2` / `M-2b` 剩余近分(28/31、15/18) | 按裁定**不做家族加权** | 剩余未命中是"同族多块打平"类,属检索区分度问题,不影响出口与事实正确率(均 100%) |
| 4 | 两把 API key 轮换 | 需登服务商控制台操作,且已在会话中出现过明文 | **答辩后当轮立刻轮换**(4 步见 `D1.6` §4.35 第五节第 2 项);任何文档不落 key 值 |
| 5 | `A-10` 组 3 / 组 4 的**签字** | 需你本人签字(已一次性授权,本单为逐项留痕) | 不签字则"两组每一处都有会签记录"这条完工判据不完整 |
| 4 | 两把 API key 轮换 | 需登服务商控制台操作(**控制台建新 key 这一步无法自动化**),且已在会话中出现过明文 | 🆕 **已就绪:跑一个脚本** —— `tools/rotate_api_keys.py`(先 `--check` 体检,再交互式输入;自动备份 + 三个 Qwen 变量同值 + 两个 DeepSeek 变量同值)。完整 5 步见 **`D3.8`** 操作手册;任何文档不落 key 值 |
| 5 | `A-10` 组 3 / 组 4 的**签字** | ~~需你本人签字~~ → ✅ **2026-09-20 已补签受理**(`甲-3` 本人会签 + 逐项留痕;组 1—组 4 全部 ☑ 受理,见 `D2.1` v6.23 第 8 条与 `docs\49-底座会签申请单-2026-09-19.md`) | 本项**已闭环**,不再是未做项 |
| 6 | 长期记忆召回 / 画像写入 | `DEC-19` 合规裁定:短期会话记忆=开、**长期记忆召回=关**、画像=客户侧字段级只读 | 这是**合规要求不是缺陷**;被问到时按 `DEC-19` 口径回答 |
| 7 | `dependency_health_check.py` 把 neo4j 当硬前置 | 属底座工具,改动需另立会签 | 演示前五项自检用替代判据(`D2.5` §1),**不用它**判"环境没准备好" |
@@ -262,5 +264,5 @@
| 方案 | 出口 **2 → 5**(`E1` 澄清 / `E2` 计算 / `E3` 直返 / `E4` 证据约束生成 / `E5` 分级回退) |
| 安全 | 5 条不变量(`INV-1`—`INV-5`)+ 转人工白名单 4 类 + 档位物理隔离;**安全只增不减** |
| 效果 | 转人工率 **43.5% → 10.9%**;出口准确率 **45.7% → 100%**;事实正确率 **69.6% → 100%**;4 项零容忍**全 0** |
| 验证 | 46 条金标 11 项全达标 + 1856 单测 0 failed + 31/31 宽链路冒烟 + 11/11 HTTP 探针 + 12/12 真机边界 |
| 验证 | 46 条金标 11 项全达标 + **1909** 单测 0 failed + 31/31 宽链路冒烟 + 11/11 HTTP 探针 + 12/12 真机边界 + 门户接口 35 通过 / 0 失败 |
| 纪律 | 白名单(`A-09`)+ 会签单(`A-10`,4 组)+ 零 DDL + 跨文档一致性 GATE PASS |
@@ -2,22 +2,22 @@
> **体系编号**:`D1.1` · 域:一、治理与索引 · 编号体系见 `D1.1` §4.0
> **编号**:CS-DOC-2026-017 | **版本**:v1.4 | **日期**:2026-09-17 | **状态**:**现行(活文档,随文档区变动同步更新)**
> **编号**:CS-DOC-2026-017 | **版本**:v1.5 | **日期**:2026-09-17 | **状态**:**现行(活文档,随文档区变动同步更新)**
> **性质**:本文件是 `开发文档\` 的**唯一入口**。任何人(含三个月后的自己)打开这一份,就应知道:先读什么、哪份为准、每份什么状态。
> **盘点范围**:`开发文档\`(**50 个文件** = 49 份编号文档 + 1 份入口存根 `CLAUDE.md`,无归档子目录)+ `客服agent\`(**6 份**对外交付文档)。
> **盘点范围**:`开发文档\`(**52 个文件** = 51 份编号文档 + 1 份入口存根 `CLAUDE.md`,无归档子目录)+ `客服agent\`(**6 份**对外交付文档)。
---
## 0. 一句话结论
**56 份文档(55 份编号 + 1 份不占编号的入口存根 `CLAUDE.md`)已按 8 个域统一编号为 `D<域>.<序>`(规则见 §4.0,层级见 §3)。开工只读 5 份 = `D2.1` / `D2.2` / `D2.3` / `D2.4` / `D3.3`(见 §2)。**
**58 份文档(57 份编号 + 1 份不占编号的入口存根 `CLAUDE.md`)已按 8 个域统一编号为 `D<域>.<序>`(规则见 §4.0,层级见 §3)。开工只读 5 份 = `D2.1` / `D2.2` / `D2.3` / `D2.4` / `D3.3`(见 §2)。**
| 域 | 名称 | 份数 | 定位 |
|---|---|---|---|
| **D1** | 一、治理与索引 | 6 | 先读 `D1.1`(本文件)——编号体系、权威链、开工只读集 |
| **D2** | 二、对外交付 | 6 | 🔴 **开工必读**(A1—A4;另含 `D2.5` 演示脚本、`D2.6` 答辩报告) |
| **D3** | 三、现行权威·完整版与专项 | 7 | 查证据、查 FR 推导过程(含 A5 鉴权专项 `D3.3`;检索升级 `D3.5`;架构 `D3.6`;**评测金标 `D3.7`**) |
| **D4** | 四、清除与重建留痕 | 6 | 追溯「删了什么、怎么恢复」;`D4.1` 即**重建指南**,`D4.6` 为验收基线留痕 |
| **D3** | 三、现行权威·完整版与专项 | 8 | 查证据、查 FR 推导过程(含 A5 鉴权专项 `D3.3`;检索升级 `D3.5`;架构 `D3.6`;**评测金标 `D3.7`**;**密钥轮换 `D3.8`**) |
| **D4** | 四、清除与重建留痕 | 7 | 追溯「删了什么、怎么恢复」;`D4.1` 即**重建指南**,`D4.6` 为验收基线留痕,`D4.7` 为**投顾恢复现状** |
| **D5** | 五、业务流程基线 | 1 | 两条业务线 / 三条红线 / 演示跑通验收 |
| **D6** | 六、公司事实与知识源 | 17 | 🔴 改写知识库、核对数据口径 |
| **D7** | 七、早期系统文档 | 5 | 状态待确认;仅在追查历史口径时读(**不可删**,见 §10) |
@@ -36,7 +36,7 @@
```
对外交付(客服agent\) 配套完整版 / 前身(开发文档\)
────────────────────────────────── ─────────────────────────────────────
A1 [D2.1] D2.1-客服Agent执行Todolist.md v5.3 ←→ [D3.4] D3.4-客服Agent重构Todolist.md v5.1
A1 [D2.1] D2.1-客服Agent执行Todolist.md v6.26 ←→ [D3.4] D3.4-客服Agent重构Todolist.md v5.1
A2 [D2.2] D2.2-客服Agent需求文档.html v2.5 ←→ [D3.1] D3.1-客服Agent需求开发文档与设计方案.html v2.4
A3 [D2.3] D2.3-客服Agent开发计划.html v1.1 ←→ (无旧版)
A4 [D2.4] D2.4-客服Agent知识库设计方案.html v1.3 ←→ [D3.2] D3.2-知识库设计方案.html v1.2
@@ -59,7 +59,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| # | 体系编号 | 文档 | 版本 | 作用 |
|---|---|---|---|---|
| **A1** | **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v5.3** | **唯一开工入口**。**57 项 / 8 批次(A—H)** / 12 步关键路径 / 2 组会签 / **完工判据 13 条**。**新增批次 H · 智能增强**(`H-01`~`H-06`) |
| **A1** | **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.26** | **唯一开工入口**。**57 项 / 8 批次(A—H)** / 12 步关键路径 / 2 组会签 / **完工判据 13 条**。**新增批次 H · 智能增强**(`H-01`~`H-06`) |
| **A2** | **D2.2** | `客服agent\D2.2-客服Agent需求文档.html` | **v2.5** | 对外需求:**FR-CS-001~052**(52 条,新增域 H)+ NFR-CS-001~021 全量、身份与鉴权模型、验收标准(**新增 AC-13 金标门禁**) |
| **A3** | **D2.3** | `客服agent\D2.3-客服Agent开发计划.html` | **v1.1** | 前置条件、测试环境就位(G-00)、会签流程、门禁、交付物、**批次 H(§3.4b)** |
| **A4** | **D2.4** | `客服agent\D2.4-客服Agent知识库设计方案.html` | **v1.3** | 三集合 / 三档可见性 / **集合内分区隔离(§7.2.1)** / 8 模块 / 7 步入库 8 步检索 / 8 项决策 / **附录F 五出口对接** |
@@ -75,7 +75,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
第 0 层 唯一入口 D1.1 D1.1-文档索引与权威声明.md
第 1 层 域(8 个) D1 ─ D8
第 2 层 子域(仅域 6 有) D6.1 ─ D6.5
第 3 层 文档(56 份) D<域>.<序> / D<域>.<子域>.<序>
第 3 层 文档(58 份) D<域>.<序> / D<域>.<子域>.<序>
```
### 3.2 八个域(=逻辑顺序=阅读优先级)
@@ -84,22 +84,22 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
|---|---|---|---|---|
| **D1** | 一、治理与索引 | 6 | 现行 | **先读 D1.1**(唯一入口,本文件) |
| **D2** | 二、对外交付 | 6 | 现行 | 🔴 **开工必读**(含「开工只读 5 份」的 4 份) |
| **D3** | 三、现行权威·完整版与专项 | 7 | 现行 | 查证据、查 FR 推导过程时读 |
| **D4** | 四、清除与重建留痕 | 6 | 已完成 | 追溯「删了什么、怎么恢复」时读(D4.1 是重建指南) |
| **D3** | 三、现行权威·完整版与专项 | 8 | 现行 | 查证据、查 FR 推导过程时读 |
| **D4** | 四、清除与重建留痕 | 7 | 已完成 | 追溯「删了什么、怎么恢复」时读(D4.1 是重建指南;D4.7 是投顾恢复现状) |
| **D5** | 五、业务流程基线 | 1 | 现行 | 核对业务范围与三条红线时读(**冲突时以它为准**) |
| **D6** | 六、公司事实与知识源 | 17 | 现行 | 🔴 改写知识库、核对数据口径时读 |
| **D7** | 七、早期系统文档 | 5 | **待确认** | 只在追查历史口径时读;已被上游依据表引用,**不可删**(见 §10) |
| **D8** | 八、AI 协作规则 | 8 | 现行 | 让 AI 接手时的规则文件(`D8.1`=语言规范正文;`CLAUDE.md`=入口存根,不占编号) |
> 合计:6 + 6 + 7 + 6 + 1 + 17 + 5 + **8** = **56 份**(其中 `客服agent\` 的 6 份不计入 `开发文档\` 的 50 个文件;域 D8 的 8 份含 1 份**不占编号**的入口存根 `CLAUDE.md`)。
> 合计:6 + 6 + 8 + 7 + 1 + 17 + 5 + **8** = **58 份**(其中 `客服agent\` 的 6 份不计入 `开发文档\` 的 52 个文件;域 D8 的 8 份含 1 份**不占编号**的入口存根 `CLAUDE.md`)。
---
## 4. 全量文档清单
> **本节结构**:**§4.0 = 编号规则 + 全量编号总表(56 份,按编号顺序)——查找入口**;§4.1—§4.8 = 按类别展开的明细表(编号见 §4.0 总表,同一逻辑顺序)。
> **本节结构**:**§4.0 = 编号规则 + 全量编号总表(58 份,按编号顺序)——查找入口**;§4.1—§4.8 = 按类别展开的明细表(编号见 §4.0 总表,同一逻辑顺序)。
### 4.0 编号规则与全量编号总表(56 份)
### 4.0 编号规则与全量编号总表(58 份)
**编号规则**
@@ -123,7 +123,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| **D1.4** | `开发文档\D1.4-知识源与品牌整改变更说明-2026-09-17.md` | CS-CONTENT-2026-016 | 现行 | 逐份变更说明(§3.1—§3.9 改写映射 + G-01~G-09) |
| **D1.5** | `开发文档\D1.5-开发前决策清单与阻塞项-2026-09-17.md` | CS-DOC-2026-018 v1.0 | 现行 | 🔴 **开工前唯一决策登记册**:28 项待你拍板 + 阻塞分级(P0 12 / P1 10 / P2 6)+ 需你提供的 7 项输入;§7 为回填表(增补项见 `D1.6` §4.3) |
| **D1.6** | `开发文档\D1.6-对话上下文提取与开工前补充决策-2026-09-17.md` | CS-DOC-2026-019 v1.0 | 现行 | 🔴 **本轮会话上下文提取件**:已读清单与权威链校正 / 可复用事实(含实测)/ 旧实现 **7 条转人工通路** / 文档缺陷 `Q-1.1`~`Q-1.6` / 前提风险 `K-01`~`K-08` / 待拍板 `N-01`~`N-09` |
| **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v5.3** | 现行 | 🔴 **唯一开工入口**:**57 项 / 8 批次** / 12 步关键路径 / **批次 H 智能增强** |
| **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.26** | 现行 | 🔴 **唯一开工入口**:**57 项 / 8 批次** / 12 步关键路径 / **批次 H 智能增强** |
| **D2.2** | `客服agent\D2.2-客服Agent需求文档.html` | **v2.5** | 现行 | 🔴 对外需求:**FR-CS-001~052** + NFR-CS-001~021 |
| **D2.3** | `客服agent\D2.3-客服Agent开发计划.html` | **v1.1** | 现行 | 🔴 前置条件 / 批次 / 会签 / 门禁 / 交付物 |
| **D2.4** | `客服agent\D2.4-客服Agent知识库设计方案.html` | **v1.3** | 现行 | 🔴 三集合 / 三档可见性 / **分区隔离** / 7 步入库 8 步检索 / **附录F** |
@@ -136,12 +136,14 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| **D3.5** | `开发文档\D3.5-知识库检索升级备选方案建议-2026-09-17.md` | CS-KB-2026-020 v1.0 | 现行 | 知识库检索升级**备选方案池**(**不是**任务来源):`K-01`~`K-08` 前提风险 / `§3-A`~`§3-H` 八个升级方向 / 与 `DEC-11` 耦合的推荐组合 / 对 `D2.4`·`D2.1` 的 10 条修订建议 / 可证伪验收判据 |
| **D3.6** | `开发文档\D3.6-客服Agent智能增强架构建议-2026-09-17.md` | CS-ARCH-2026-021 **v1.1** | 现行 | 🔴 **智能增强架构(**已裁定件**)**:诊断(10 条转人工通路 / 设计有澄清与生成但未实现)/ 「智能」7 条可验收定义 / **五出口决策链** E1—E5 / 安全不变量 `INV-1`~`INV-5` / 转人工白名单 4 类 / **§9 八项决策已拍板** |
| **D3.7** | `开发文档\D3.7-客服Agent评测金标集与判分规则-2026-09-17.md` | CS-EVAL-2026-022 v1.0 | 现行 | 🔴 **验收依据**:46 条金标(含四要素:期望出口 / 期望证据 / 期望关键事实 / 禁止出现)+ 10 项指标 + **4 项零容忍**(禁忌·越权·无出处数字·误拒)+ 前置阻塞 `B-1`~`B-4` + 问法分级(难例 32 条) |
| **D3.8** | `开发文档\D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md` | CS-OPS-2026-023 v1.0 | 现行 | 🔴 **答辩后当轮执行**:为什么轮换 / `.env` 5 个变量与读取方取证 / **三个 Qwen 同值 + 两个 DeepSeek 同值** / 五步流程 / 3 个坑 / 复核清单(工具 `tools/rotate_api_keys.py`) |
| **D4.1** | `开发文档\D4.1-客服Agent重构报告-2026-09-16.md` | CS-REFACTOR-2026-010 | 底稿 | 🔴 **重建指南**:清除了什么 / 缺什么 / 按什么顺序装回去 |
| **D4.2** | `开发文档\D4.2-客服模块清除影响面清单.md` | CS-PURGE-2026-007 | 已完成 | 客服形态A 清除的影响面 |
| **D4.3** | `开发文档\D4.3-客服模块清除执行报告-2026-09-16.md` | CS-PURGE-2026-008 | 已完成 | 客服清除验证数据 + **安全能力损失清单**(重建须补回) |
| **D4.4** | `开发文档\D4.4-投顾模块清除范围与影响面清单-2026-09-17.md` | CS-PURGE-2026-012 | 已完成 | 投顾清除范围 |
| **D4.5** | `开发文档\D4.5-投顾模块清除执行报告-2026-09-17.md` | CS-PURGE-2026-013 | 已完成 | 投顾清除验证数据 + 恢复方式 |
| **D4.6** | `开发文档\D4.6-客服Agent一期合规红队与业务评测集-留痕-2026-09-18.md` | CS-DOC-2026-020 v1.0 | 留痕 | 🔴 **验收基线**:一期红队 `RT-001`~`018` 原文 + `C-06` 实测回填;`A-01`/`C-06`/`C-07` 的唯一对比基准 |
| **D4.7** | `开发文档\D4.7-投顾模块恢复记录-2026-09-20.md` | CS-PURGE-2026-014 v1.0 | 现行 | 🔴 **投顾现状**:2026-09-17 清除 → 2026-09-20 随合并恢复的时间线 / 恢复动作清单 / 客服线不变的结论 / **一处必须更正的理由表述**(`DEC-19`)/ 遗留 1 项 |
| **D5.1** | `开发文档\D5.1-业务流程-MVP版-最终交付-2026-09-15.md` | — | 现行 | 🔴 MVP 业务基线:两条业务线 / 三条红线 / 演示跑通验收(**冲突时以它为准**) |
| **D6.1.1** | `开发文档\公司信息\D6.1.1-南方基金-企业信息.md` | **V2.0** | 现行 | 🔴 **母本**:品牌 / 工商 / 资质 / 组织 / 财务的唯一权威 |
| **D6.1.2** | `开发文档\公司信息\D6.1.2-南方基金-高频问答对.md` | NF-FAQ-2026-001 **V2.0** | 现行 | 64 组 FAQ(档位 public 54 / registered 10) |
@@ -174,20 +176,20 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| **D8.6** | `开发文档\ai\D8.6-04_OUTPUT_RULES.md` | — | 现行 | 产出规则(§5 高风险变更须先确认) |
| **D8.7** | `开发文档\ai\D8.7-05_PROJECT_CONTEXT.md` | — | 现行 | 项目背景速览 |
> **注入校验**:**52 份**可注入文档(**42** `开发文档\*.md` + 5 `开发文档\*.html` + 3 `客服agent\*.html` + `客服agent\D2.5-…md` + `客服agent\D2.6-…md`)**已全部带「体系编号」行**;3 份 `.txt` 按上表例外处理。(原表述的 49 份**未计入** `客服agent\D2.1` 的 `.md` —— 该漏计是历史口径,本轮**只补新增件、不追改历史**。)「开工只读 5 份」对应 **D2.1 / D2.2 / D2.3 / D2.4 / D3.3**。
> **注入校验**:**54 份**可注入文档(**44** `开发文档\*.md` + 5 `开发文档\*.html` + 3 `客服agent\*.html` + `客服agent\D2.5-…md` + `客服agent\D2.6-…md`)**已全部带「体系编号」行**;3 份 `.txt` 按上表例外处理。(原表述的 49 份**未计入** `客服agent\D2.1` 的 `.md` —— 该漏计是历史口径,本轮**只补新增件、不追改历史**。)「开工只读 5 份」对应 **D2.1 / D2.2 / D2.3 / D2.4 / D3.3**。
### 4.1 Ⅰ 对外交付 / 现行权威(`客服agent\`,6 份)
| 文件名 | 版本 | 日期 | 定位 | 关联 |
|---|---|---|---|---|
| `D2.1-客服Agent执行Todolist.md` | **v5.3** | 2026-09-17 | 唯一开工入口 | 收敛自 `开发文档\D3.4-客服Agent重构Todolist.md` v5.1 |
| `D2.1-客服Agent执行Todolist.md` | **v6.26** | 2026-09-17 | 唯一开工入口 | 收敛自 `开发文档\D3.4-客服Agent重构Todolist.md` v5.1 |
| `D2.2-客服Agent需求文档.html` | **v2.5** | 2026-09-17 | 对外需求(FR **52** / NFR 21) | 完整版见 §4.2 |
| `D2.3-客服Agent开发计划.html` | **v1.1** | 2026-09-17 | 批次 / 会签 / 门禁 | 与 A1 批次号一一对应 |
| `D2.4-客服Agent知识库设计方案.html` | **v1.3** | 2026-09-17 | 三集合 / 三档 / 入库检索流程 | 完整版见 §4.2 |
| `D2.5-客服Agent演示脚本与账号速查-2026-09-19.md` | — | 2026-09-19 | 演示脚本(`F-03`/`F-04`/`A-05` 三合一) | 台词证据:`group_fqcd_jr\docs\evidence\20260919-t8-demo-lines*.json` |
| `D2.6-客服Agent答辩报告-2026-09-19.md` | — | 2026-09-19 | 答辩报告(问题定义 / 根因 / 五出口 / 安全不变量 / 前后对比 / 现场速答) | 数字来源:46 条金标 `score_before` vs `score_w11b` + `e2e_smoke_test` + `http_probe` + 12 条真机边界 |
### 4.2 Ⅱ 开发文档区内的现行权威(7 份)
### 4.2 Ⅱ 开发文档区内的现行权威(8 份)
| 文件名 | 版本 | 定位 |
|---|---|---|
@@ -198,6 +200,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| `D3.5-知识库检索升级备选方案建议-2026-09-17.md` | CS-KB-2026-020 | 知识库检索升级**备选方案池**(**建议**,非需求/任务来源) |
| `D3.6-客服Agent智能增强架构建议-2026-09-17.md` | CS-ARCH-2026-021 **v1.1** | 智能增强架构(**§9 八项已拍板**):五出口决策链 / 安全不变量 / 转人工白名单 4 类 / 访客档计算型分项开放口径 |
| `D3.7-客服Agent评测金标集与判分规则-2026-09-17.md` | CS-EVAL-2026-022 | **评测输入件**:46 条金标 + 判分规则 + 门槛(**验收依据**,非需求/任务来源) |
| `D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md` | CS-OPS-2026-023 | **操作手册**:模型密钥轮换(工具 `tools/rotate_api_keys.py`)+ 复核 + 回退;**不落任何 key 值** |
### 4.3 Ⅲ 内部复核底稿(2 份)
@@ -206,7 +209,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| `D4.1-客服Agent重构报告-2026-09-16.md` | CS-REFACTOR-2026-010 | 清除了什么 / 缺什么 / 按什么顺序装回;§9 决策记录 |
| `D5.1-业务流程-MVP版-最终交付-2026-09-15.md` | — | MVP 业务流程基线 |
### 4.4 Ⅳ 清除执行记录与重建留痕(5 份)
### 4.4 Ⅳ 清除执行记录与重建留痕(6 份)
| 文件名 | 编号 | 定位 |
|---|---|---|
@@ -215,6 +218,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| `D4.4-投顾模块清除范围与影响面清单-2026-09-17.md` | CS-PURGE-2026-012 | 投顾清除范围 |
| `D4.5-投顾模块清除执行报告-2026-09-17.md` | CS-PURGE-2026-013 | 投顾清除验证数据 + 恢复方式 |
| `D4.6-客服Agent一期合规红队与业务评测集-留痕-2026-09-18.md` | CS-DOC-2026-020 | 🔴 一期红队 `RT-001`~`018` 原文留痕 + `C-06` 实测回填(验收基线) |
| `D4.7-投顾模块恢复记录-2026-09-20.md` | CS-PURGE-2026-014 | 🔴 **投顾的「现状」单据**:清除 → 恢复的时间线与动作清单;`D4.4`/`D4.5` 降级为历史留痕 |
### 4.5 Ⅴ 本次整改工作文档(5 份)
@@ -442,10 +446,10 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| # | 事项 | 我的建议 | 影响 |
|---|---|---|---|
| **D-5** | 前端 24 份 + 风控 Agent 5 处仍用 `南方财富`(G-02 / G-05) | 改(**评审唯一肉眼可见的品牌面**:文档再干净,打开页面仍显示「南方财富」) | 🔴 高 |
| **D-6** 🆕 | **系统名不统一**:`开发文档\公司信息\D6.1.1-南方基金-企业信息.md`(母本)等用「**智能财富管家系统**」,而 `knowledge\` 镜像与四份交付文档用「**南方基金·智能服务系统**」。**同源两版再次打架**(约 13 处:母本 4 / 早期 HTML 8 / 开发引导 1),另有 `D6.1.4-公司新人指南.md` 4 处、`docs\` 3 处、`_flows\` 3 处 | **统一为「南方基金·智能服务系统」**(与四份交付文档、`knowledge\` 镜像一致);同时核对 `BRAND_SYSTEM_NAME` 配置位 | 🟡 中(命名不统一,且母本与镜像不一致) |
| **D-7** 🆕 | **B-01 语料入库门禁(四查)并未实现** | 属 Todolist 批次 B 的代码工作;**实现时品牌白名单必须用 `南方基金` + `nffund.com`**(旧文档里的 `南方财富` + `nanfangwm.com` 会放行错误品牌) | 🔴 高(不改会误杀新知识源 / 或放行旧品牌) |
| — | `_chunks.jsonl` 未重灌库;`开发文档\` 的仓库副本(`group_fqcd_jr\开发文档\`)未同步 | 属派生件与推送动作 | 中 |
| **D-5** | 前端 24 份 + 风控 Agent 5 处曾用 `南方财富`(G-02 / G-05) | ✅ **2026-09-18 已执行**(`C-05` 品牌面清零);🔴 **2026-09-20 复查发现 2 处残留** —— 投顾组分支带回的 `employee-advisor/dashboard/index.html` 与 `customer/advisor-plans/index.html` 的 `<title>`(`W12` 合并引入),**已于 `W13` 按 `DEC-27` 修正**;全仓 `app\` 复查 = 0 处 | ✅ 已闭环 |
| **D-6** 🆕 | **系统名不统一**(母本曾用「智能财富管家系统」) | ✅ **2026-09-19 已执行**(`乙-25`):母本 `D6.1.1` / `D6.1.4` 与 `D6.5.x` / `D6.4.3` 已统一为「**南方基金·智能服务系统**」;本轮复查 `开发文档\公司信息|公司业务` = **0 处**。剩余命中全部为**合法语境**:① 变更说明(`D1.2`/`D1.4`/`D1.5`);② **早期文档**(`D7.1`/`D7.2`/`D7.4`)—— 按 §4.7「只标注不改品牌」裁定**有意保留**;③ 仓库 `docs\` / `_flows\`(另一套编号空间) | ✅ 已闭环 |
| **D-7** 🆕 | **B-01 语料入库门禁(四查)并未实现** | ✅ **已实现**:`tools\knowledge_corpus_gate.py`(品牌白名单为 `南方基金` + `nffund.com`,旧值进了**黑名单**;单测 `tests\unit\tools\test_knowledge_corpus_gate.py`) | ✅ 已闭环 |
| — | ~~`_chunks.jsonl` 未重灌库;`开发文档\` 的仓库副本未同步~~ | ✅ **均已闭环**:三集合已于 2026-09-18 `drop` 重建重灌(`DEC-29`「不用旧数据,全部用新数据」);`开发文档\`(50 份)+ `客服agent\`(24 份)已于 2026-09-20 随 `bc61d5c` **入库并与权威副本逐字节一致**(`D1.6` §4.38) | ✅ 已闭环 |
> ⚠️ **D-5 / D-7 属前端与代码改动**;本轮已按指令完成 D-2/D-3/D-4(其中 D-4 含 3 处代码改动)。
@@ -487,7 +491,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
**本轮不做删除。** 「只留协助开发的文档」这个目标由 **分类(§3)+ 状态标注(§4.7)+ 开工只读 5 份(§2)** 达成,而不是靠删文件——本区文档按「**文件名 + 编号**」互引(§9),删任何一份都会打折别人的依据链。
若仍要物理瘦身,唯一不破坏引用的方式是**移动而非删除**(移入 `开发文档\_archive\`),但代价有三:① 路径型引用(如 §7.5 的 `开发文档\D4.5-投顾模块清除执行报告-2026-09-17.md`)失效;② `_archive\` 目录本身会被后续盘点再认作「待归档」;③ **本区不在任何 git 仓库内**,移动前必须先整包备份。
若仍要物理瘦身,唯一不破坏引用的方式是**移动而非删除**(移入 `开发文档\_archive\`),但代价有三:① 路径型引用(如 §7.5 的 `开发文档\D4.5-投顾模块清除执行报告-2026-09-17.md`)失效;② `_archive\` 目录本身会被后续盘点再认作「待归档」;③ **本区**已于 2026-09-20 入库**(`group_fqcd_jr\开发文档\` / `group_fqcd_jr\客服agent\`,见 `D1.6` §4.38)
> 🔴 **本轮最重要的教训**:判断一份文档「有没有用」**不能凭体裁**("像通用教材"「像过程记录」),**必须按文件名反查引用**。本轮因此发现了 `D7.3-记忆架构设计.html` 被误归档(§7.1 纠正)、以及 5 份「看起来没用」的文档其实全部被上游依据表引用。
@@ -683,7 +687,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
|---|---|
| 新增文档 | `开发文档\D8.1-项目语言规范.md`(域 8「AI 协作规则」,**语言规范正文**:四条硬规则 + AI 入口协议 + 编号落位) |
| 改造文档 | `开发文档\CLAUDE.md` → **三行入口存根**(保留文件名以维持 AI 工具约定与既有引用锚点) |
| 新增纪律凭据(写在仓库 `docs\`,**不占** `开发文档\` 编号) | `group_fqcd_jr\docs\46-可改文件白名单.md`(`A-09`:类 1 纯新增 / 类 2 客服业务层 / 类 3 须会签 / 类 4 禁止修改 + **零 DDL 声明** + 实际改动对照表);`group_fqcd_jr\docs\47-底座会签申请单-2026-09-19.md`(`A-10`:组 1 六文件八处 + 组 2 四文件 + 🆕 **组 3 组外扩张 2 文件(须补签)**) |
| 新增纪律凭据(写在仓库 `docs\`,**不占** `开发文档\` 编号) | `group_fqcd_jr\docs\48-可改文件白名单.md`(`A-09`:类 1 纯新增 / 类 2 客服业务层 / 类 3 须会签 / 类 4 禁止修改 + **零 DDL 声明** + 实际改动对照表);`group_fqcd_jr\docs\49-底座会签申请单-2026-09-19.md`(`A-10`:组 1 六文件八处 + 组 2 四文件 + 🆕 **组 3 组外扩张 2 文件(须补签)**) |
| 计数同步 | §0 结论 54 → **55**;§0 盘点范围 49 → **50 个文件**;§0 域表与 §3.2 表 D8 **7 → 8**;§3.1 第 3 层 54 → **55**;「合计」行改 `… + 8 = 55`;§4.0 标题 / 说明 54 → **55**;§4.0 总表 `D8.1` 行改指新文件并**新增存根行**;§4.0 尾「注入校验」**50 → 51**(`开发文档\*.md` 41 → **42**) |
| 交叉引用 | `D1.6` 新增 §4.34(`W9`)/ §4.35(`W10`);`D2.1` 标题 **v6.20 → v6.22**(补 v6.21 / v6.22 两段) |
| **未做(诚实声明)** | ① 未改 `D7.1` / `D7.2` 的品牌,沿用 §4.7「只标注不改品牌」的既有裁定;② 未追改 §11.2 的历史计数(第八轮史实,不追溯);③ `CLAUDE.md` **未删除**(文件名被 A2/A4 与 `ai\D8.3` 引用);④ `docs\46` / `docs\47` **未计入**本节 55 份(它们是仓库 `docs\` 编号空间,与 `开发文档\` 编号体系两套) |
@@ -708,6 +712,40 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
---
## 22. 第十八轮:`W12` 合并与权威文档入库(2026-09-20)
> **本轮做什么**:把投顾组 3 个远端提交并入 `qyqy_develop`(**「投顾整体清除」被取代**),并把权威文档目录**入库**。代码与门禁见 `D1.6` §4.37—§4.38。
| 项 | 本轮落地 |
|---|---|
| 合并 | `e5b4d02`(parents = `5d0becb` + `74b7d00`):14 文件重叠 / 12 处冲突;**未用 `--force`** |
| 🔴 **结论被取代** | `D4.4`/`D4.5` 的「投顾整体清除」**不再成立**(组员新功能反向依赖被删模块)⇒ `D4.5` 顶部加**状态更新**;`D4.4` 的范围清单**仍是有效的历史留痕** |
| 新增文档 | 本轮**无**新编号文档(`D4.7` 为下一轮补记) |
| 文档入库 | 仓库 `客服agent\` 21→**24** 文件、`开发文档\` 40→**50** 文件;**权威覆盖过期**,入库后逐文件零差异 |
| 计数影响 | `W12` 当轮**未改本文件计数**(56 份不变)—— 本轮只做「替换过期副本」,未新增体系编号 |
| ⚠️ 顺带登记 | `docs\46` / `docs\47` 编号撞车(我方与投顾组**同号**),当时未登记;**下一轮修正**(见 §23) |
---
## 23. 第十九轮:`W13` 密钥轮换工具 + 两份目录审计与口径校准(2026-09-20)
> **本轮做什么**:新增模型密钥轮换工具与操作手册;对 `客服agent\` + `开发文档\` 做一次**逐份审计**,修掉 12 处事实漂移与 1 处门禁失败。会话留痕见 `D1.6` §4.39。
| 项 | 本轮落地 |
|---|---|
| 新增文档 | `开发文档\D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md`(域 3 续号,**CS-OPS-2026-023**);`开发文档\D4.7-投顾模块恢复记录-2026-09-20.md`(域 4 续号,**CS-PURGE-2026-014**) |
| 为什么单独成文(`D3.8`) | 工具 `tools/rotate_api_keys.py` 已在,但**没有任何文档说明它为什么存在、怎么复核、怎么回退**;`D2.6` §10-4 当时只指向 `D1.6` 的一个自然段。密钥轮换是**答辩后当轮就要执行**的动作,必须有可独立执行的 SOP |
| 为什么单独成文(`D4.7`) | `D4.4`/`D4.5` 两份文档标题都叫「**清除**」,未来检索「投顾 恢复」**查不到**;而「你删了投顾又恢复了?」是答辩**必被追问**的一点。需要一个描述**仓库现状**的单据 |
| 计数同步 | §0 结论 56 → **58**;§0 域表 D3 7 → **8**、D4 6 → **7**;§0 盘点范围 `开发文档\` 50 → **52 个文件**;§3.1 第 3 层 56 → **58**;§3.2 域表与「合计」行 → `6 + 6 + 8 + 7 + 1 + 17 + 5 + 8 = **58 份**`;§4.0 标题与说明 56 → **58**;§4.0 总表新增 `D3.8` / `D4.7` 两行;§4.2 标题(7 → **8 份**)与 §4.4 标题(5 → **6 份**)各新增一行;§4.0 尾「注入校验」52 → **54 份**(`开发文档\*.md` 42 → **44**) |
| 🔴 **修正一处长期错误** | §4.0 总表与 §4.1 明细把 `D2.1` 的版本记为 **v5.3**(第十一轮口径),而 `D2.1` 早已到 **v6.26**;§1 权威链与 §2「开工只读 5 份」表也同步更正 |
| 🔴 **修正一处门禁失败** | `tools/check_authoritative_docs.py`(`D3.4` `N-14` 登记的门禁)因 `docs\46`/`docs\47` **编号撞车实测 FAIL**。按「**后到者让位**」(组员 2026-09-16 建、我方 2026-09-20 建)把我方两份改名为 `docs\48-可改文件白名单.md` / `docs\49-底座会签申请单-2026-09-19.md`,并同步 **8 处引用** |
| §8 遗留项闭合 | `D-5`(前端品牌面)、`D-6`(系统名统一)、`D-7`(语料入库门禁)、以及「`_chunks.jsonl` 未重灌库 / 仓库副本未同步」**四行**逐项标注为**已闭环**,并如实登记 `D-5` 的 **2 处真实残留**(`W12` 合并引入,本轮修正) |
| §10.2 表述更正 | 「本区不在任何 git 仓库内」→ **已于 2026-09-20 入库** |
| 外部文档同步 | `客服agent\`:`D2.1` → **v6.26**(新增 v6.26 段);`D2.2`/`D2.3`/`D2.4` 三份 HTML 加**投顾口径状态更新**并修正过期徽标;`D2.5` 修正 `advisor_t` 口径 + 切入一键脚本;`D2.6` 更新门禁数字并闭环两项「未做项」 |
| ⚠️ **未做(诚实声明)** | ① 未追改 §11.2 的历史计数(第八轮史实,不追溯);② `docs\` 仍是另一套编号空间,**不并入本节 58 份**;③ 未改 `D7.1`/`D7.2` 的品牌(§4.7 既定裁定);④ 投顾 `config_release` 工具白名单(`advisor:*`)**仍未发布** —— 与客服线无关,见 `D4.7` §5 |
---
> **维护责任**:本文件为活文档。**新增 / 改名 / 归档 / 改版本号后,须同步更新本文件 §3 与 §4.0 总表对应行**。
>
> 编制:项目文档组 | 审核:合规稽核部 | 日期:2026-09-17
@@ -2391,7 +2391,7 @@ pytest **2 failed / 1577 passed / 2 skipped**(= `T0` 基线同两项)、ruff
| 6 | **`F-07` 目录锚点** | 8 份交付 HTML | ✅ **实测失效锚点 = 0**(`D7.1` 63 个 `href` / 24 个目录锚点全部命中)⇒ **零工作量,非「未做」** |
| 7 | **`E-08` 三条红线对照** | `tests\unit\service\test_customer_service_red_lines.py` | ✅ **11 passed**,逐条映射:红线 1(风险等级唯一来源)5 例 / 红线 2(先揭示后确认)2 例 / 红线 3(不生成交易指令)4 例。**未发现缺口 ⇒ 无新增拦截代码**(对照验证 + 留痕结项) |
| 8 | **`H-05` 档位分区隔离收口**(由「未通过」改判达标) | `tools\setup_milvus_knowledge_collections.py`、`tools\load_knowledge_milvus.py`、`app\service\knowledge_search_service.py` | ✅ ① 四集合 `visibility` 均 `is_partition_key=true` / `max_length=16` / `num_partitions=16`;② **fail-closed 实测**:写入缺 `visibility` 的行被拒(`Insert missed an field 'visibility' …`);③ **双向实测**:同一 `registered` 档内容访客看不到、客户 top1 = 0.8717;④ `over-fetch` 全仓零命中;⑤ **双 schema 收敛**:`build_schema` 全仓**仅 1 处**定义,灌库脚本**反向 import 复用** |
| 9 | **`A-09` / `A-10` 落档** | 新增 `group_fqcd_jr\docs\46-可改文件白名单.md`、`docs\47-底座会签申请单-2026-09-19.md` | ✅ 四类白名单 + **零 DDL 声明**(89 张业务表 / 无 `alembic` 改动 / 新增字段全部复用既有列)+ 相对 `HEAD` 的**实际改动对照表**;会签单按 `D2.1` 附录B 模板逐项写 |
| 9 | **`A-09` / `A-10` 落档** | 新增 `group_fqcd_jr\docs\48-可改文件白名单.md`、`docs\49-底座会签申请单-2026-09-19.md` | ✅ 四类白名单 + **零 DDL 声明**(89 张业务表 / 无 `alembic` 改动 / 新增字段全部复用既有列)+ 相对 `HEAD` 的**实际改动对照表**;会签单按 `D2.1` 附录B 模板逐项写 |
| 10 | **全量回归**(停常驻 Worker 后跑,跑完重新拉起) | 见下表 | ✅ 全部通过 |
**二、全量回归实测(`W10`)**
@@ -2482,7 +2482,7 @@ pytest **2 failed / 1577 passed / 2 skipped**(= `T0` 基线同两项)、ruff
| 4 | **四处一次性收紧 + 8 条路径参数补约束** | 同上两文件 + `app\api\controllers\public_platform.py`、`agent_runs.py`、`conversations.py` | ✅ 只**收紧**(不可能让既有合法调用变红):`message ≤ 8000`、`session_id 1—64`、`idempotency_key 16—64`、`feedback_type ≤ 32`;路径参数 `{session_id}`/`{run_id}`/`{handover_id}` 补 `min_length=1, max_length=64, pattern=r"^[A-Za-z0-9_-]+$"`(沿用 `admin.py`/`risk.py` 既有写法) |
| 5 | **新增前端边界守卫测试** | 新增 `tests\unit\api\test_frontend_boundaries.py`(**33 例**) | ✅ 口径一致(前端 `maxlength` == 后端 `max_length`)、入参上限 ≤ 落库列宽、超限必须 `422` 信封、路径参数 OpenAPI 契约、缺权限 `403` / 未登录 `401`、**端点表 ↔ OpenAPI 全量对照**、`HEALTH` 探针路径必须真实存在 |
| 6 | **12 条真机边界复验** | `_fe_boundary_http.py`(脚本一次性) | ✅ **12/12 符合预期**,见下表「三」 |
| 7 | **纪律凭据同步** | `group_fqcd_jr\docs\46-可改文件白名单.md`、`docs\47-底座会签申请单-2026-09-19.md` | ✅ `knowledge_tier.py` 登记进 `A-09` 类 1 与 `A-10` §3(第 12 项);**新增 `A-10` §4「前端入参边界对齐」组(5 文件,须补签)**,`A-09` 同步登记 |
| 7 | **纪律凭据同步** | `group_fqcd_jr\docs\48-可改文件白名单.md`、`docs\49-底座会签申请单-2026-09-19.md` | ✅ `knowledge_tier.py` 登记进 `A-09` 类 1 与 `A-10` §3(第 12 项);**新增 `A-10` §4「前端入参边界对齐」组(5 文件,须补签)**,`A-09` 同步登记 |
| 8 | **答辩报告成文** | 新增 `客服agent\D2.6-客服Agent答辩报告-2026-09-19.md` | ✅ 12 节:问题定义 → 根因 → 五出口 → 安全不变量 → **金标前后对比** → 零容忍词挂载点口径 → 演示口径 → 工程纪律 → **坑与教训(含我 3 处判断更正)** → 诚实未做项 → 现场速答 → 一页速览 |
| 9 | **全量回归**(停常驻 Worker 后跑,跑完重新拉起) | 见下表「二」 | ✅ 全部通过;`pytest` 由 1810 → **1856 passed** |
| 10 | **临时脚本清零** | `D:\桌面\金融\_*.py` | ✅ 本轮新建的扫描 / 修补 / 校验脚本全部删除(保留件清单见「五」) |
@@ -2684,6 +2684,51 @@ pytest **2 failed / 1577 passed / 2 skipped**(= `T0` 基线同两项)、ruff
---
### 4.39 2026-09-20 第三十五轮会话记录(`W13`:密钥轮换工具 + 两份文档目录审计 —— 修掉 12 处事实漂移与 1 处门禁失败)
> **本轮指令(原文)**:「现在就带你走一遍,然后你再检查一遍我这两个文件夹里的文档 看看还有什么需要完善和补充的 我需要你仔细的去推理以及补充」
**一、密钥轮换:把「必须人工、易错」的操作收敛成一个脚本**
| 项 | 内容 |
|---|---|
| 新增工具 | `group_fqcd_jr\tools\rotate_api_keys.py`(6899 → 6877 字节):`--check` 体检 + 交互式轮换 |
| 设计要点 | ① `getpass` **不回显**;② 自动备份 `.env.bak-<时间戳>`(被 `.gitignore` 命中);③ **三个 Qwen 变量写同一值**、**两个 DeepSeek 变量写同一值**(这是手改最容易漏的一步);④ 校验不通过则**整体不写入**;⑤ 只替换目标行,其余行原样保留(**保编码、保换行**);⑥ 不落日志、不落文档 |
| `--check` 实测 | Qwen 三变量 **✅ 同值且非空**(115 位);DeepSeek 两变量 **✅ 同值且非空**(35 位) |
| 🔴 **边写边修(如实登记,两处硬伤)** | ① 第 75 行提示语把 ASCII 双引号当成了中文引号(`"入库/检索"`),直接 **`SyntaxError`**,`ruff` 也拦下 —— 这是我在写之前**自己预判过、又差点放过**的坑,实测第一时间暴露;② 文档字符串里 22 处「反斜杠 + 反引号」触发 `SyntaxWarning: invalid escape sequence`。均已修正,`ruff check` + `ruff format` 通过 |
| 口径确认 | `model_endpoint_config.secret_ref` 存的是**变量名**(`env:QWEN_API_KEY` / `env:DEEPSEEK_API_KEY`)⇒ **轮换只改 `.env`,不动 DB**;但**必须重启 API + Worker** |
| 新增手册 | `开发文档\D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md`(5 步 / 3 个坑 / 复核清单 / 回退 / 能力边界) |
**二、文档审计:逐份推理「还缺什么」,修掉 12 处漂移 + 1 处门禁失败**
| # | 发现(按严重度) | 处置 |
|---|---|---|
| 1 | 🔴 **门禁实测 FAIL**:`tools/check_authoritative_docs.py`(`D3.4` `N-14` 登记的验收命令集之一)报 `docs 编号撞车:{'46': [...], '47': [...]}` —— 我方 `docs\46`/`docs\47`(2026-09-20 建,`5d0becb`)与投顾组 `docs\46-投顾Agent需求文档.md` / `docs\47-投顾Agent功能架构文档.md`(2026-09-16 建,`5607751`)**同号** | 按「**后到者让位**」(我方后到)`git mv` 我方两份 → `docs\48-可改文件白名单.md` / `docs\49-底座会签申请单-2026-09-19.md`;同步 **8 处引用**(`D1.1` 2 + `D1.6` 4 + `D2.1` 2 处所在的 4 个文件,含仓库内自引用 1 处) |
| 2 | 🔴 **前端品牌残留 2 处**:`app\static\portal\employee-advisor\dashboard\index.html`、`app\static\portal\customer\advisor-plans\index.html` 的 `<title>` 仍是 **`南方财富`** | 按 `DEC-27`(已批「改,并升 P1」)改为 **`南方基金`**;全仓 `app\` 复查 `南方财富` = **0**。根因:`W12` 合并取远端时把投顾组分支的旧品牌一起带了回来(**D-5 的「已清零」被合并静默回退**) |
| 3 | 🔴 **`D1.1` 版本号严重过期**:§4.0 总表与 §4.1 明细把 `D2.1` 记为 **v5.3**(第十一轮口径),实际已 **v6.25**;§1 权威链、§2「开工只读 5 份」表同样是 v5.3 | 全部更正为 **v6.26**(含本轮 `D2.1` 升版) |
| 4 | 🔴 **三份 HTML 交付文档口径与事实相反**:`D2.2`/`D2.3`/`D2.4` 仍写「**投顾已清除**」,而 `W12` 已恢复 | 三份均加「**2026-09-20 状态更新**」;**关键区分**:投顾模块恢复 **≠** 客服可承接投顾能力 ⇒ `D2.2` §1.7 范围与 `RK-10` 裁定**原样不变** |
| 5 | 🔴 **`D2.2` 自相矛盾**:顶栏徽标写 `v2.4`、文档元数据写 `v2.5`;`D2.3` 徽标写 `v1.0 · 7 批次 51 项`(实际 v1.1 / 8 批次 / 57 项) | 徽标更正为 `v2.5 · 双主体 · 五出口 · 投顾已恢复` / `v1.1 · 8 批次 57 项 · 12 步关键路径` |
| 6 | 🟡 **`D2.5` §2.1 内部矛盾**:同一段先写「**不要念** `advisor_t`,该账号在库里**不存在**」,紧接着写「该账号已于 2026-09-20 重建 ⇒ 重新有效」 | 改写为**账号表里的一行**(`advisor_t` / `abc12345`,可用)+ 一段口径更正;并登记「投顾 `advisor:*` 工具白名单**未发布**」这一事实 |
| 7 | 🟡 **`D2.6` 门禁数字落后一轮**:仍写 1856 passed / 2 skipped、`ruff` 19 | 更新为 **1909 / 3 skipped**、`ruff` **20**(远端分支本身 22 / 客服线基线 19 ⇒ 未引入新债),并补 `portal_api_check` 40 项一行 |
| 8 | 🟡 **`D2.6` §10 把两个「已闭环」的项目仍列在「未做项」** | ① 密钥轮换 → 改为「**已就绪:跑一个脚本**」(指向 `D3.8`);② `A-10` 组 3/4 签字 → 改为「**2026-09-20 已补签受理**,本项已闭环」 |
| 9 | 🟡 **`D1.1` §8「遗留与待决」四行过期**:`D-5`(前端品牌面已执行,但本轮发现 2 处合并回退)、`D-6`(系统名已统一)、`D-7`(语料门禁已实现)、「`_chunks.jsonl` 未重灌 / 仓库副本未同步」(均已闭环) | 逐行标注**已闭环**并保留证据;`D-5` 如实登记**2 处真实残留** |
| 10 | 🟡 **`D1.1` §10.2 表述过期** | 「本区不在任何 git 仓库内」→ **已于 2026-09-20 入库**(`W12`),并保留「移动/改名仍须先整包备份」的理由 |
| 11 | 🟢 **缺一份「投顾现状」单据**:`D4.4`/`D4.5` 标题都叫「清除」,检索「投顾 恢复」查不到 | 新增 `开发文档\D4.7-投顾模块恢复记录-2026-09-20.md`(时间线 / 恢复动作 8 项 / 客服线不变的结论 / **一处必须更正的理由表述** / 遗留 / 我的失误) |
| 12 | 🟢 **`_consistency.py` §三 口径会误报**:原文假设「投顾命中必须出现在『已清除』语境」,恢复后合法叙述会被标「疑似仍在依赖」 | 改为「**投顾状态口径检查**」,合法语境扩为 **清除史 / 恢复史 / 不属本 Agent 范围**,并在表头写明判读规则 |
| 13 | 📌 **一处未做(如实登记)** | 投顾 `config_release` 工具白名单(`advisor:*`)**当前未发布**(连库实测 `active_agent_tools` = `customer_service:*` 4 项 + `risk:*` 4 项,共 8 项,**无 advisor**)⇒ 投顾 Agent 工具调用 fail closed。**与客服线无关**;要演投顾线先跑 `tools/publish_advisor_demo_config.py --apply`(组员提交 `9c6f839` 提供的脚本) |
**三、计数同步(`D1.1` 的强制义务)**
`开发文档\` 50 → **52 个文件**;总份数 56 → **58**(57 编号 + 1 存根);§4.0 总表 / §4.2 / §4.4 / §3.1 / §3.2 / §0 全部同步;「注入校验」52 → **54 份**。**新增两个编号**:`D3.8`(域 3 续号)、`D4.7`(域 4 续号)。
**四、仍待你决定的(1 项)**
| # | 事项 | 我的最优建议 |
|---|---|---|
| 1 | **两把 key 轮换的时点**(控制台建新 key 这一步我无法代做) | **演示后当轮立刻做**:按 `D3.8` 第 1→5 步走;演示前做要额外付一个「重启 + 复核 11/11」的窗口(约 5 分钟),若你希望演示前就换掉,也完全可以 —— 脚本会先体检、再写入、再备份,**失败整体不落盘** |
---
## 5. 建议的开工顺序(在 `DEC-11` 拍板后)
```
@@ -0,0 +1,170 @@
# D3.8 · 模型密钥轮换与凭据安全操作手册
> **体系编号**:`D3.8` · 域:三、现行权威·完整版与专项 · 编号体系见 `D1.1` §4.0
> **编号**:CS-OPS-2026-023 | **版本**:v1.0 | **日期**:2026-09-20 | **状态**:**现行(操作手册,答辩后当轮执行)**
> **性质**:**操作手册(SOP)** —— 回答「两把模型密钥已经在会话/文档里出现过明文,怎么安全地换掉、怎么证明换对了」。
> **配套**:工具 `group_fqcd_jr\tools\rotate_api_keys.py`(**本手册的唯一执行入口**);演示脚本 `D2.5`;答辩报告 `D2.6` §10 第 4 项;会话留痕 `D1.6` §4.35 第五节第 2 项与 §4.39。
> **🔴 铁律**:**任何文档、任何提交、任何截图都不落 key 值**(含掩码全量、含片段)。本手册只写**变量名**与**校验方法**。
---
## 0. 一句话结论
**控制台建新 key(人工,不可自动化)→ 跑一个脚本改 `.env` → 重启 API + Worker → 复核 → 再吊销旧 key。**
核心风险不是「会不会改错一个变量」,而是**同一个 key 在 `.env` 里有多个变量名**:漏改一个,就会出现「入库成功但检索失败」这类**没有任何报错**的症状。
---
## 1. 为什么要轮换(触发条件)
| # | 事实 |
|---|---|
| 1 | 本项目的两把模型密钥(DashScope/Qwen 一把、DeepSeek 一把)**在对话过程中出现过明文**(用户于 2026-09-18 直接粘贴在会话里) |
| 2 | 会话记录(`D1.6` 等)与全部文档**从未写入 key 值**(`W12` 入库前已做过全仓密钥扫描:真实密钥只出现在被 `.gitignore` 命中的 `.env`) |
| 3 | 但「明文已在会话里出现过」本身就是凭据泄露事件 ⇒ **按泄露处置:轮换 + 吊销旧 key** |
> **轮换不等于改一行**:改完之后系统必须**仍然能工作**,这要用第 4 步的复核来证明,而不是靠「密码改成功了」的直觉。
---
## 2. 轮换前的现状(2026-09-20 实测,只写变量名与长度)
### 2.1 `.env` 里的 5 个变量(`group_fqcd_jr\.env`,已被 `.gitignore` 命中,**不入库**)
| 变量名 | 实测长度 | 谁在读它(代码取证) |
|---|---|---|
| `QWEN_API_KEY` | 115 | 🔴 **`model_endpoint_config.id=1` 的 `secret_ref` = `env:QWEN_API_KEY`**(检索侧向量化);`tools\load_knowledge_milvus.py:50`(灌库侧) |
| `QWEN_EMBEDDING_API_KEY` | 115 | `tools\configure_embedding_endpoint.py:92`(**仅在端点不存在时**用于创建端点) |
| `DASHSCOPE_API_KEY` | 115 | `app\service\promotion_image_service.py:28`(推广素材配图,缺则**降级**不报错) |
| `DEEPSEEK_API_KEY` | 35 | 🔴 **`model_endpoint_config.id=2` 的 `secret_ref` = `env:DEEPSEEK_API_KEY`**(生成/判定);`app\service\advisor_reason_service.py:145` |
| `OFFSITE_DEEPSEEK_API_KEY` | 35 | `app\service\offsite_document_recognition_adapter.py:610`(场外文档识别) |
> 📌 脚本侧另有一个变量名 `DEEPSEEK_API_KEY_NL2SQL`(`nl2sql_yc.py:156` 优先取它、缺省回退 `DEEPSEEK_API_KEY`)。当前 `.env` **未定义**该变量 ⇒ 走回退,**无需处理**。
### 2.2 数据库侧:`secret_ref` 存的是**变量名**,不是值
| id | endpoint_code | provider | model_name | secret_ref | status |
|---|---|---|---|---|---|
| 1 | `knowledge-embedding-qwen-v3` | qwen | `text-embedding-v3` | `env:QWEN_API_KEY` | active |
| 2 | `deepseek-flash` | deepseek | `deepseek-chat` | `env:DEEPSEEK_API_KEY` | active |
⇒ **轮换只改 `.env`,不需要动 DB**。(`EnvironmentSecretResolver` 在**运行时**按名字去环境变量取值。)
### 2.3 键值分组(**这就是脚本存在的理由**)
| 组 | 变量 | 必须满足 |
|---|---|---|
| **Qwen(DashScope)** | `QWEN_API_KEY` / `QWEN_EMBEDDING_API_KEY` / `DASHSCOPE_API_KEY` | **三个 = 同一个值**(同源同一把千问 key) |
| **DeepSeek** | `DEEPSEEK_API_KEY` / `OFFSITE_DEEPSEEK_API_KEY` | **两个 = 同一个值**(场外识别复用同一把) |
---
## 3. 五步流程
### 第 1 步 · 控制台建新 key(人工,不可自动化)
| 服务 | 入口 | 建什么 |
|---|---|---|
| 阿里云 DashScope(百炼) | 控制台 → API-KEY 管理 | **新建**一把 Qwen key(形如 `sk-…`,实测长度 115) |
| DeepSeek 开放平台 | 控制台 → API keys | **新建**一把 DeepSeek key(形如 `sk-…`,实测长度 35) |
⚠️ **先不要吊销旧 key** —— 旧的留作回退,直到第 4 步复核通过。
### 第 2 步 · 体检(不改任何文件,先看现状)
```powershell
cd D:\桌面\金融\group_fqcd_jr
$env:PYTHONPATH='D:\桌面\金融\group_fqcd_jr'
& '.\.venv\Scripts\python.exe' tools\rotate_api_keys.py --check
```
**期望输出**(2026-09-20 实测即为此):
```
· Qwen(DashScope):
QWEN_API_KEY sk-ws-…(115 位)
QWEN_EMBEDDING_API_KEY sk-ws-…(115 位)
DASHSCOPE_API_KEY sk-ws-…(115 位)
✅ 同值且非空
· DeepSeek:
DEEPSEEK_API_KEY sk-e35…(35 位)
OFFSITE_DEEPSEEK_API_KEY sk-e35…(35 位)
✅ 同值且非空
```
出现 `❌ 有变量为空` 或 `❌ 取值不一致` 时**先别轮换**,先查清为什么(不一致本身就是缺陷)。
### 第 3 步 · 轮换(交互式,输入不回显)
```powershell
& '.\.venv\Scripts\python.exe' tools\rotate_api_keys.py
```
脚本会:① 提示输入新的 Qwen key(`getpass`,**不回显**);② 提示输入新的 DeepSeek key;③ 校验格式(`sk-` 前缀 + 长度下限),**不通过则整体不写入**;④ 备份 `.env.bak-<时间戳>`(已被 `.gitignore` 命中);⑤ 把**三个 Qwen 变量写成同一个值、两个 DeepSeek 变量写成同一个值**;⑥ 只替换目标行,其余行(含注释、缩进、顺序)**原样保留**。
### 第 4 步 · 重启 + 复核(**不做这步等于没换**)
```powershell
# 1) 先停 API 与 Worker(进程里还是旧 key)
Get-CimInstance Win32_Process -Filter "Name like '%python%'" | Where-Object { $_.CommandLine -match 'uvicorn app.main:app|app.worker' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
# 2) 再起(日志见 _http_api.log / _http_worker.log)
Start-Process -FilePath 'D:\桌面\金融\group_fqcd_jr\.venv\Scripts\python.exe' -ArgumentList '-m','uvicorn','app.main:app','--host','127.0.0.1','--port','8000','--log-level','warning' -WorkingDirectory 'D:\桌面\金融\group_fqcd_jr' -WindowStyle Hidden
Start-Process -FilePath 'D:\桌面\金融\group_fqcd_jr\.venv\Scripts\python.exe' -ArgumentList '-m','app.worker' -WorkingDirectory 'D:\桌面\金融\group_fqcd_jr' -WindowStyle Hidden
```
| # | 复核项 | 命令 / 判据 |
|---|---|---|
| 1 | 服务就绪 | `GET /internal/health/ready` → `{"status":"ready", ...}` |
| 2 | 全链路探针 | `_eval_harness\http_probe.py` → **11/11 succeeded**(含访客线 + 客户线 + 反诈/账户/代办/画像/费率/多轮) |
| 3 | 一键自检 | `demo.ps1 -SkipStart -NoBrowser` → **五项自检全过**(其中第 2 项「向量库三集合可查」即检索侧向量化的**真实证明**) |
| 4 | 行情链路 | `tools\sync_market_prices.py` → 跑通(另证明外部 HTTP 出口正常) |
| 5 | 检索侧单独验(可选) | 发一句知识型问句(如「七日年化是什么意思」)必须走 `E3` 正常作答;**若变成「答不上来」或转人工,就是 key 没配对** |
### 第 5 步 · 吊销旧 key(**只在第 4 步全过之后**)
回到两个控制台,把**旧 key 删除/禁用**。删除后建议再跑一次第 4 步的第 2 项(证明系统确实已不再依赖旧 key)。
---
## 4. 三个必须知道的坑
| # | 坑 | 症状 | 为什么 |
|---|---|---|---|
| 1 | **只改了 `QWEN_API_KEY`,漏改另两个** | 「入库成功 / 检索失败」,或旧 key 吊销后才炸 | 三个变量来自**同一把 key 的三个变量名**,被三处不同代码读取(§2.1) |
| 2 | **只改了 `DEEPSEEK_API_KEY`,漏改 `OFFSITE_DEEPSEEK_API_KEY`** | 主链路正常,**场外文档识别**静默降级 | 场外适配器读的是另一个变量名(§2.1) |
| 3 | **手改 `.env` 时带了 BOM / 改了编码 / 改了换行** | **第一个变量读不出来**(后面的都正常)⇒ 症状诡异、难查 | `.env` 是纯文本按行解析;脚本改写时**保留原编码与原换行**,就是为了防这一条 |
> 📌 补充坑:**改完不重启**。`UVicorn` / `Worker` 进程内的环境变量是**启动时**读入的,`.env` 改了但进程没重启 ⇒ 仍然用旧 key。
---
## 5. 回退方式
| 场景 | 怎么做 |
|---|---|
| 第 3 步写入后想撤回 | 用同目录的 `.env.bak-<时间戳>` **原样覆盖回去**,然后重启 API + Worker |
| 新 key 在控制台建错了 / 想作废 | 在控制台禁用新 key,再用备份回退 |
| 旧 key 已吊销、新 key 又不能用 | 只能回控制台再建一把 —— 所以**第 5 步必须排在第 4 步之后** |
---
## 6. 与其它文档的关系(避免两处口径漂移)
| 文档 | 关系 |
|---|---|
| `D2.6` §10 第 4 项「两把 API key 轮换」 | 本手册是它的**执行细则**;该项状态已从「手工 4 步」改为「跑一个脚本(`D3.8`)」 |
| `D2.5` §1 五项自检 | 第 4 步复核复用其中的「向量库三集合可查」 |
| `D1.6` §4.39 | 本轮(`W13`)新增工具与本手册的会话留痕 |
| `group_fqcd_jr\docs\26-JWT密钥管理与轮换.md` | 讲的是 **JWT 签名密钥**,与本手册的**模型 API key** 是两回事(前者是自签自验,后者是外部服务凭据) |
---
## 7. 如实声明(本手册的能力边界)
| 项 | 说明 |
|---|---|
| **不能自动化第 1 步 / 第 5 步** | 控制台建 key 与吊销 key **必须由你本人在浏览器操作**(无 API 授权、也不应为此开授权) |
| **本手册不记录任何 key 值** | 连掩码全量都不落 —— 掩码+长度对穷举无用,但**片段会缩小搜索空间**,故一并省略 |
| **`--check` 只能证明「一致且非空」** | 它**不联网验证 key 是否有效**;key 是否可用由第 4 步的**真实链路**证明 |
| **未覆盖** | 阿里云/DeepSeek 控制台的**子账号与权限模型**(本手册只处理主账号 key 轮换);密钥托管系统(KMS/Vault)接入 —— 当前项目用 `.env`,未上密钥托管 |
@@ -24,6 +24,9 @@
> + `tools/create_test_user.py --id 9020 --username advisor_t`(重建演示账号)。
> **`D4.4` 的范围与影响面清单仍是有效的历史留痕**(尤其 §0-②③ 对"清投顾会拆掉 MVP 硬阻断"的预警,
> 正是本次裁定的依据)。
>
> 📌 **想看「投顾现在的状态」请读 `D4.7-投顾模块恢复记录-2026-09-20.md`** —— 本件(`D4.5`)描述的是
> 2026-09-17 当时的清除动作,**不再是仓库现状**;`D4.7` 是「现状」单据(时间线 / 恢复动作 / 客服线不变的结论 / 遗留)。
---
@@ -0,0 +1,96 @@
# D4.7 · 投顾模块恢复记录
> **体系编号**:`D4.7` · 域:四、清除与重建留痕 · 编号体系见 `D1.1` §4.0
> **编号**:CS-PURGE-2026-014 | **版本**:v1.0 | **日期**:2026-09-20 | **状态**:**现行(本件描述仓库现状;`D4.4`/`D4.5` 降级为历史留痕)**
> **性质**:**状态单据(现状)**。回答「投顾模块到底删了没有、现在还在不在、怎么恢复的」—— 这是答辩现场**必被追问**的一点(因为 `D4.4`/`D4.5` 两份文档的标题与结论都写着「清除」)。
> **时间线证据**:会话留痕 `D1.6` §4.37;合并提交 `e5b4d02`;远端 3 个投顾提交 `5607751` / `2fe7d0c` / `74b7d00`。
> **配套**:范围与影响面 `D4.4`(仍是有效的历史留痕);清除执行报告 `D4.5`(顶部已加状态更新);纪律凭据 `docs\48` / `docs\49`。
---
## 0. 一句话结论(现状)
**投顾模块在仓库中「有效存在」** —— 2026-09-17 曾按 `D4.4`/`D4.5` 整体清除(94 个文件),2026-09-20 合并远端组员 3 个投顾提交时,因其代码**反向依赖被删除的模块**,裁定「**组员新功能 > 本地清除**」⇒ **恢复**。
| 问题 | 答案 |
|---|---|
| 投顾代码还在吗? | **在**(`app\service\*.py` 投顾服务、`app\api\controllers\recommendations.py`、`employee-advisor/` 前端等,取远端版本) |
| `D4.4` / `D4.5` 还有效吗? | **作为历史留痕有效**;作为「仓库现状」**已失效**(`D4.5` 顶部已加状态更新指向本件) |
| 客服 Agent 受影响吗? | **不受影响**。客服的范围裁定(`D2.2` §1.7 两条业务线 / `RK-10` 不越界实现投顾)**原样成立** |
| 恢复有遗留吗? | **有 1 项**:投顾的 `config_release` 工具白名单(`advisor:*`)**尚未发布** ⇒ 投顾 Agent 工具调用 fail closed(见 §5) |
---
## 1. 时间线
| 时间 | 事件 | 依据 |
|---|---|---|
| 2026-09-16 | 组员在 `qyqy_develop` 落 **3 个投顾提交**(需求与架构文档 / 客户主动申报 + 受理出草稿 / 方案交付落点与 LLM 增强) | `5607751` `2fe7d0c` `74b7d00` |
| 2026-09-17 | 本线按 `D4.4`/`D4.5` **整体清除投顾**(94 文件删除) | `D4.5` |
| 2026-09-20 | 推送被拒(**non-fast-forward**):`fetch` 后发现远端领先 3 个提交,且新代码 `import` 了已删模块 | `D1.6` §4.37 一 |
| 2026-09-20 | **裁定恢复投顾**:合并提交 `e5b4d02`(parents = `5d0becb` + `74b7d00`) | `D1.6` §4.37 二 / 九 |
| 2026-09-20 | 补数据库夹具 + 修 1 条「远端本身就是红的」断言 ⇒ 全量回归 **1909 passed / 3 skipped / 0 failed** | `D1.6` §4.37 六/七/八 |
**为什么不能两全**:`D4.4` 在清除**之前**就预警过两条风险 —— ① 按名字清投顾会**同时删掉产品数据底座与 MVP 的硬阻断实现**;② 会**失去「改 6 个底座文件时的对照组」**。组员在**同一个模块**上落了新功能 ⇒ 清除与新功能**不可能同时成立**。裁定取「组员新功能」,依据正是 `D4.4` 自己写下的预警。
---
## 2. 恢复动作清单(做了什么)
| # | 动作 | 说明 |
|---|---|---|
| 1 | **取远端版本** | 8 处 `modify/delete` 冲突全部**取远端**:`recommendations.py`、`product_recommendation_service.py`、`employee-advisor/dashboard/` 4 文件、`tools/check_portal_modules.py`、`tools/grant_advisor_role.py` |
| 2 | **取远端版本(内容冲突 3 处)** | `app/main.py`(投顾 import + `include_router`)、`common/api-client.js`(投顾端点表)、`tests/unit/api/test_portal_frontend.py`(4 条投顾前端契约) |
| 3 | **定点保留我方改动 1 处** | `app/main.py` 解除冲突的同时,**保留**本轮「移除 `/customer-service-test` 挂载」(联调页与用例已随重构作废) |
| 4 | **语义回滚(4 处)** | 因「取消清除」而必须回滚:`app/service/agent/bootstrap.py`(`AdvisorAgent` + 5 个投顾工具注册)、`app/core/config.py`(`advisor_rollout_*`)、`app/static/portal/common/layout/app-shell.js`(投顾工作台导航 + `advisor` 角色名)、`tools/seed_test_rbac.py`(admin 全量元组授权模型) |
| 5 | **叠加我方修复** | `app/static/portal/common/api-client.js`:以远端为基准,**重新叠加**本轮的「访客令牌 `Authorization` 优先」修复(否则该修复被合并静默吃掉) |
| 6 | **数据库夹具同步** | `tools/grant_advisor_role.py` → 新建 `advisor` 角色(实测 **34 项**权限);`tools/create_test_user.py --id 9020 --username advisor_t --role advisor --password abc12345` → 重建演示账号。补夹具前 3 条投顾集成用例全红,补后 **2 passed / 1 skipped** |
| 7 | **修一条「远端本身就是红的」断言** | `tests/unit/test_advisor_migration_contract.py` 把 alembic 末端钉死在 `20260914_baseline_auto_increment`,而远端新增 `20260916_advisor_service_request` ⇒ 该断言在远端 `qyqy_develop` 上**本身就失败**(不是合并引入的)。已更新到新末端并补注释 |
| 8 | **顺带修掉一类假红** | `tools/portal_api_check.py` 新增 `empty_collection_note()`:被渲染的集合为空时判 **`SKIP`(附原因)** 而非 `FAIL` ——「没有行」与「字段没带」是两回事。改后 40 项:**通过 35 / 失败 0 / 跳过 5** |
> 🔑 **未用 `--force`**:强推会毁掉组员 3 个提交。
---
## 3. 恢复后,「客服线」的哪些结论**不变**
| 结论 | 状态 |
|---|---|
| 客服 Agent 范围 = **两条业务线**(游客线 + 客服线),不承接投顾能力 | **不变**(`D2.2` §1.7 / `RK-10`) |
| 长期记忆召回 = **关**、画像 = 客户侧字段级只读 | **不变**(`DEC-19`;理由见 §4) |
| 客服知识库三集合 / 三档可见性 / 分区隔离 | **不变**(`D2.4`;投顾知识**不进**客服知识库) |
| 五出口决策链 + 安全不变量 `INV-1`~`INV-5` | **不变**(`D3.6`) |
| 46 条金标与全部指标(转人工率 10.9% / 出口 100% / 事实 100%) | **不变**(`D3.7` / `D2.6`) |
| 全量门禁 | **更强了**:对照组回归 ⇒ 1909 passed / 3 skipped / 0 failed |
---
## 4. 一处必须更正的**理由表述**(不是结论)
`DEC-19`(长期记忆召回 = 关)的**结论不变**,但**理由**里有一条曾经写着「收益方(投顾)已整体清除」—— 该理由**随恢复失效**。
**正确理由(继续成立、且更硬)**:
1. **客服 Agent 不产生长期记忆**:`app/worker/runtime.py` 的 `NO_LONG_TERM_MEMORY_AGENT_TYPES` 显式含 `customer_service`(`W7` 发现并修回的真实合规回归 `D-10`);
2. **注入生成上下文与证据约束生成直接冲突**:`E4` 的输入必须是**证据包**(`INV-2`),把长期记忆注入进去会让答案出现证据包外的内容;
3. **`PROFILE_CANDIDATE_AGENT_TYPES` 为空集是裁定落地**,不是「重构期临时状态」—— 要重开必须**先改口径**,而不是往集合里加 `agent_type`。
---
## 5. 遗留(**1 项,与客服线无关**)
| # | 遗留 | 说明 | 怎么解 |
|---|---|---|---|
| 1 | 投顾 `config_release` 工具白名单(`advisor:*`)**未发布** | 2026-09-20 连库实测 `active_agent_tools` 只有 `customer_service:*` 与 `risk:*` 共 8 项 ⇒ 投顾 Agent 的工具调用**正确 fail closed**(代码在、白名单不在) | 要演投顾线时跑 `tools/publish_advisor_demo_config.py --apply`(该脚本由组员提交 `9c6f839` 提供)。**客服线完全不受影响** |
| 2 | 投顾演示数据未灌 | `AD011` / `A047` 需要带 `source_url` + `document_sha256` 的适当性证据行,披露文件不在仓库;`tools/seed_advisor_demo.py` 明令**不编证据**(fail closed) | 空集 `SKIP`,不是缺陷(`D1.6` §4.37 十-2) |
---
## 6. 如实登记(含我的失误)
| # | 事项 | 说明 |
|---|---|---|
| 1 | 🔴 **清理临时产物时误删 `_advisor_db_backup.sql`** | 属我的操作失误。缓解:真正的代码级备份 `_advisor_purge_backup/`(**51 文件**)与 `_cs_purge_backup/`(34 文件)**完好**;且该库投顾表本为空 |
| 2 | **本件是「补记」,不是当时的记录** | 恢复发生在 2026-09-20(`W12`),当时的留痕在 `D1.6` §4.37 与 `D4.5` 顶部状态更新。本件是 `W13` 文档审计时**补的上游单据** —— 补记的理由是:`D4.4`/`D4.5` 两份文档标题都叫「清除」,未来检索「投顾 恢复」会**查不到** |
| 3 | 「恢复」的**边界**需明确 | 恢复的是**模块代码 + 数据库夹具**;投顾的**演示数据/工具白名单**仍是空的(§5)—— 「恢复了」≠「投顾线演示已就绪」 |