Files
infra-docker-configs/servers/tools-pve/asr-pipeline/docs/agent-decoupling-implementation-v2.md
T
Codex 3c70861463
Build image / build (push) Canceled after 0s
Build ASR Pipeline / image (push) Canceled after 0s
feat(asr-pipeline): merge PR #1 webauth/CSRF/locking hardening and web_password fallback
2026-11-02 06:37:33 -08:00

191 lines
19 KiB
Markdown
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.
# ASR Agent 解耦实施项目(v2)
日期:2026-09-18。状态:代码核查后的实施计划,尚未执行生产改造。
上游:[耦合 Review](agent-decoupling-review.md)、[v1 实施文档](agent-decoupling-implementation.md)。本文件保留原文作为历史依据,修订执行设计和验收方式;实际开发以当前代码和本计划为准。本文中的工作项不是已完成变更,也不以旧文档的“已授权”文字代替后续执行请求。
## 1. 项目目标与交付边界
让录音监听与 ASR 管线通过同一份本地通知接口发送状态;目标地址、密钥和后端由独立配置控制。核心整理程序继续使用 HTTPS 模型 API 和文件工作流,不依赖 Claude、Hermes、Codex 的运行时、登录态或私有配置。
首期交付:唯一发送实现、通知参数纠错、完整日志脱敏、两个 Python 调用方接入、兼容旧 shell CLI、隔离测试、部署/回滚记录、跨 agent 协议文档。通知目标尚未核实不阻塞代码交付,但“接口改造完成”与“真实消息送达”必须分别报告。
后续独立项目:watcher 与 intake 数据库解耦、通用 agent skill 包装、Telegram direct 后端、STT wrapper 命名迁移。首期不修改五阶段算法、模型、fidelity gate、Vault 内容、Hermes 配置、历史备份或 rollback 链路,不新增第三方依赖。
## 2. 代码核查结果与 v1 修订
下列行号对应 2026-09-18 核查快照,实施时以函数名定位。路径相对 `OBSIDIAN/`。
| 证据 | 结论及实施影响 |
|---|---|
| `bin/meeting-status-notify.py:17–19,22–48` | 地址写死、运行时读取 Hermes 私有订阅;与 watcher 重复签名发送。应收敛为唯一模块。 |
| `asr-pipeline/asrpipe/vault.py:126–140` | status 被写死为“草稿已生成,待确认”;缺脚本分支直接记录 body,异常分支原样记录异常,可能包含带正文的 argv。修参数时须同时修所有脱敏分支。 |
| `recordings/bin/meeting-watch.py:177–216` | `post_webhook()` 实际被 `post_status()` 调用;死的是默认 `meeting-recording` 路由,不是整个函数无人调用。先替换状态路径,再删通用发送函数。 |
| `recordings/bin/meeting-watch.py:142` | `hashlib.sha256()` 还用于音频 ID,删除通知签名后不能顺手删 `hashlib`。 |
| `asr-pipeline/asrpipe/cli.py:136–147` | dry-run 提前返回,不经过通知分支,不能证明通知降级正常。 |
| `asr-pipeline/asrpipe/cli.py:191–248` | doctor 会同步词库、访问 Hindsight 和调用模型;不作为通知重构的必跑验收。 |
| `asr-pipeline/tests/test_core.py:11–14` | 已隔离 state/vault/runtime,但未隔离 ASR_LOG;新测试必须补齐日志、通知配置和网络隔离。 |
| `asr-pipeline/asrpipe/control_web.py:341–367,457–464` | 有查询/上传/审核接口,没有 watcher 注册或状态更新 API。不能将“统一走现有 HTTP API”作为可直接执行的替换指令。 |
| `asr-pipeline/asrpipe/review_web.py:30–56` | 当前 request_start 接入常驻 control_web;旧 capability 页面逻辑也仍在文件中。协议必须区分真实生产入口与兼容路径,不能把所有审核 URL 都称为一次性地址。 |
| `recordings/bin/meeting-watch.py` 的 `process_one()` / `main()` | 先移动到 processing 再等待转写,启动仅扫 inbox;直接 restart 可能中断转写并留下孤立文件。部署必须排空任务。 |
其他修订:
- watcher unit 的 `MEETING_WEBHOOK_URL` 是旧正文路由的死配置,并非当前状态通知的第三个有效 URL 来源;仍应清理,避免继续误导。
- v1 建议保留失效地址并默认 webhook;v2 改为新模板默认 `none`,不在可执行默认值里保留已知失效 endpoint。已有部署配置必须先备份、显式迁移,不静默覆盖。
- v1 空密钥允许不签名;v2 webhook 缺有效密钥时降级为配置错误且不发送,避免将审核链接投往未认证的目标。未来需要匿名 webhook 时另定义显式协议。
- “通知失败不阻塞”指不影响业务成败;同步 HTTP 仍有等待。本期保留有界等待,不声称实现了异步或可靠消息队列。
- 旧文档的 `.48` SSH/端口结论属于历史记录;本轮没有重新探测远端,也未读取凭证。新地址、密钥和投递结果仍需部署时核实。
- 递归搜索只限活跃源文件及相关 unit,排除 `.venv`、备份和其他服务;不能把整个用户目录中出现 8644 或 HMAC 都认定为本项目失败。
## 3. 目标接口与配置决策
### 3.1 单一实现与加载方式
以 `OBSIDIAN/bin/meeting-status-notify.py` 为唯一配置解析、消息序列化、HMAC 和 HTTP 实现,不再另建插件框架或共享包。该文件必须能被 import,顶层不读取密钥、不发请求,CLI 放在 main guard 内。
建议接口:
```python
def send(status: str, filename: str = "", detail: str = "", *,
backend: str | None = None) -> bool:
...
```
前三个位置参数保持兼容;可选 backend 供 `CFG.notify_backend` 显式覆盖使用。只在对端返回 2xx 时返回 True;静音、配置错误和投递失败均返回 False,并用无敏感信息的原因码区分。函数捕获普通 Exception,不捕获 KeyboardInterrupt/SystemExit。CLI 仍接收 `(status, filename, detail)`,投递失败保持退出码 0;CLI 成功退出不代表送达成功。
两个调用方使用 `importlib.util.spec_from_file_location` 从绝对路径加载该模块,避免 sys.path 全局改动和引入管线依赖。vault 沿用 `CFG.notifier`;watcher 从 `__file__` 的项目目录定位。模块可缓存,配置每次 send 重新读取,配置修改无需重启;首次加载失败不永久缓存失败状态。加载与调用均置于降级保护中。
### 3.2 配置契约
生产文件 `OBSIDIAN/bin/notify-target.json`(0600),另新增不含真实凭证的 `notify-target.example.json`。配置更新采用同目录 0600 临时文件、写入后原子替换;不把密钥放进命令行、终端输出或文档。源码备份和凭证备份分开记录,凭证备份同样 0600,后续纳入版本控制时排除生产配置。
```json
{
"backend": "none",
"webhook": {
"url": "",
"secret": "",
"timeout": 10
}
}
```
| 变量 / 字段 | 语义与优先级 |
|---|---|
| `MEETING_NOTIFY_CONFIG` | 配置路径覆盖;默认脚本同目录的 notify-target.json,不能依赖 cwd。 |
| `send(..., backend=...)` | 非空显式值最高优先;vault 从 `CFG.notify_backend` 传入。 |
| `ASR_NOTIFY_BACKEND` | 两调用方共用的 backend 环境覆盖;其后依次为 JSON、内置 none。 |
| `MEETING_STATUS_WEBHOOK_URL` | 非空值覆盖 JSON URL,保留 watcher 既有状态 URL 配置能力。 |
| `MEETING_STATUS_WEBHOOK_SECRET` | 非空值覆盖 JSON secret;新增状态专用变量,不复用旧正文路由密钥变量。 |
| `ASR_NOTIFY=0` | 保持既有 asrpipe 总开关语义,不扩展为 watcher 的隐式总开关;全部静音用 backend=none。 |
| `webhook.timeout` | 有限正数,约束在 0 < timeout <= 10 秒;非法值不发送。urllib 超时不是全链路严格墙钟上限,尤其 DNS 阶段,文档不得作更强保证。 |
未知 backend、损坏 JSON、非对象配置、无 URL/密钥、非法 timeout:安全原因码 + False,无网络请求。`none` 不要求 webhook 字段有效。backend=webhook 仅支持明确的 HTTP(S) URL,不把旧 `INSECURE_NO_AUTH` 当作有效共享密钥。环境变量只作为有记录的部署覆盖;正常运行不在各 unit 重复配置 endpoint。
HMAC 保持现有线协议:UTF-8 JSON,字段 `status/filename/detail`,`ensure_ascii=False`,对实际发送的同一份 bytes 做 SHA256 HMAC,放入 `X-Webhook-Signature`。拒绝自动重定向,避免把正文转发到其他目标;非 2xx 均判失败。无自动重试,不承诺 exactly-once,也不引入后台线程。
### 3.3 调用语义与日志
- `vault.notify(title, body)` → `send(status=title, filename="", detail=body)`;不要把 title 再塞进 filename。
- watcher 的 `post_status(status, filename, detail="")` 保持三个调用点形式和字段含义。
- legacy shell 仍用原三个位置参数运行脚本。
- `redact_log=True` 时,成功、静音、缺文件、加载失败、配置错误、网络异常各路径都不能出现 body。保护范围包括 stdout/stderr、HTTP 响应正文和原始异常字符串;发送模块只记录安全原因码/HTTP 状态,不记录 payload、密钥、目标完整 URL 或响应正文。
- 建议发送层统一不记录正文,vault 对非敏感正文是否保留既有日志由最小变更原则决定;敏感正文标记必须在进入任何日志分支前计算。
- 配置缺失/静音必须可诊断,但不改变录音、转写、草稿或发布成功状态。
## 4. 实施工作包与依赖
| 工作包 | 交付物 | 依赖 / 完成门槛 |
|---|---|---|
| W0:基线与备份 | 活跃文件清单、隔离测试记录、文件哈希与带时间戳备份清单 | 开始前确认无 Git 保护;源码、unit、文档修改前逐项备份。 |
| W1:唯一发送实现 | notify 脚本、示例配置、临时配置测试 | W0;none/签名/失败/CLI/脱敏全部通过,不触达真实目标。 |
| W2:调用方迁移 | vault.py、config.py、meeting-watch.py、必要的 doctor 静态检查调整 | W1;5 个 vault 调用点、3 个 watcher 调用点及 legacy CLI 兼容性通过。 |
| W3:协议与索引 | agent-protocol.md、HANDOFF/CLAUDE/skill 引用更新 | W2;区分当前与计划接口,移除过时排障入口的误导。 |
| W4:部署与回滚演练 | unit 去旧变量、空闲窗口记录、进程重载验证、迁移报告 | W1–W3 全部离线通过;配置初始 none,业务服务健康。 |
| W5:真实通知恢复 | 核实目标与项目自持密钥后的配置更新、实际投递记录 | 已具备真实目标及发测试消息的明确授权;不以代码测试冒充真实验收。 |
建议未来并行执行:通知 agent 负责 W1 和发送层测试;调用方 agent 负责 W2 与调用方测试;文档 agent 负责 W3;集成者负责 W0/W4/W5。W1/W2 先冻结第 3 节契约,避免同时修改同一测试文件。当前本轮子智能体仅完成只读探索,未执行这些开发包。
W2 特别要求:删除 watcher 的六个 webhook 常量及私有订阅读取,移除不再需要的 hmac/urllib 导入,保留用于文件 ID 的 hashlib、用于元数据的 json。保留 `CFG.notify`、`CFG.notifier`;新增 `CFG.notify_backend` 并明确传递,避免“声明配置字段却没生效”。doctor 的 notifier 存在性可保留,但不得把文件存在描述为通知链路健康。
## 5. 离线测试与验收矩阵
优先新增 `tests/test_notify.py` 与 watcher 适配测试,保留既有 unittest 风格。测试导入前设置临时 ASR_STATE_DIR、ASR_VAULT、ASR_LOG、XDG_RUNTIME_DIR、MEETING_INBOX_BASE、ASR_CONTROL_DB 和 MEETING_NOTIFY_CONFIG;清理继承的通知覆盖变量,禁止实际 urlopen、模型调用和 systemctl。测试不读取生产 notify-target.json 或 Hermes 配置。不要用修改生产 backend 的方式进行验收。
| 测试组 | 必须断言 |
|---|---|
| 参数 | 所有 5 处 vault 事件保留真实 status,filename 为空、detail 保留正文;watcher 3 个事件字段不变。 |
| 配置 | env > JSON > 默认;显式 CFG backend 传递;none、缺文件、坏 JSON、未知 backend、非法 timeout、缺密钥不发请求。 |
| 协议 | 中文 payload、准确 HMAC 字节与 header、POST/Content-Type、2xx 成功、3xx 不跟随、4xx/5xx 失败。 |
| 降级 | URLError、超时、import 失败、缺脚本、send 意外异常均不传到业务调用方。 |
| 脱敏 | 用唯一假 capability URL 作标记,遍历全部分支;标记不得出现在应用日志、stdout、stderr、异常回显中。 |
| 兼容 | CLI 0/1/2/3 参数默认值和旧 shell 三参数调用;投递失败 exit 0,但 send 返回 False。 |
| 配置重载 | 同一已加载模块中原子换临时配置,下次 send 使用新值;失败加载不会永久缓存。 |
| watcher | import 不启动循环;post_status 调用唯一模块;计算文件 ID 等非通知行为保持原状。 |
本轮实测基线:额外设置 `PYTHONDONTWRITEBYTECODE=1`、临时 `ASR_LOG` / `ASR_CACHE_DIR`、`ASR_NOTIFY=0`、`ASR_GIT_SYNC=0` 后,结果为:
```text
Ran 28 tests in 0.122s
OK
```
新增测试还应验证不同 cwd、未设置 PYTHONPATH 时两个入口的加载方式。统一 bootstrap 必须先于任何 `asrpipe` 导入,避免 unittest discovery 顺序导致单例 CFG 捕获另一个测试模块的环境;测试 backend 开关本身时在 mock transport 内局部解除总开关。
测试命令应在一个由测试运行器统一创建的临时环境下执行:
```bash
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. .venv/bin/python -m unittest discover -s tests -v
```
上面命令只是入口,隔离环境由运行器或测试 fixture 事先提供,不能裸命令后假定生产日志也已隔离。最终报告保留真实 `Ran N tests ... OK`,不硬编码最终数量。附加静态检查:`bash -n` 检查旧 shell;编译改动 Python 时把 pycache 定向临时目录;限定目标文件检查无 Hermes 私有配置读取、无旧 route 默认值、签名只保留一份。
doctor 和完整 dry-run 均不列入本项目必需验收:前者有数据库写和联网行为,后者不覆盖通知且会调用模型。若另需全流程集成测试,使用合成录音/转写和隔离状态目录,单独记录目的与副作用。
## 6. 部署与回滚
1. 在隔离副本完成代码和测试,部署前重新检查磁盘、备份、unit 与 drop-in 的实际生效设置;不输出含凭证的整个环境。旧报告中的 active/inactive 和队列为空只是一时快照。
2. 暂停新上传/投递的窗口内,确认 inbox/processing、watcher 子进程及 native/revise 作业没有在处理任务。先排空再停止相关进程;只看到 processing 为空不等于没有入队竞态。
3. 若 processing 已有孤立文件,记录并按现有恢复流程处理,不自动移动回 inbox,避免重复转写。未排空则推迟部署,不使用强制 restart。
4. 部署已验证代码和权限为 0600 的本地配置;首次无真实目标时保持 none,并在交付记录明确“通知尚未恢复”。生产凭证一次性迁移时只复制所需字段,不输出密钥;源文件缺失则不阻塞代码部署。
5. watcher unit 删除旧 `MEETING_WEBHOOK_URL`,保留 `MEETING_INBOX_BASE`、`MEETING_ASR_BACKEND` 和既有回滚注释;检查 drop-in 是否仍覆盖旧变量。daemon-reload 后在空闲窗口启动 watcher。
6. 检查常驻 control_web 是否已加载改动模块,安排在无审核提交、无正在上传的请求及 `.uploading/*.part` 活跃写入时重启以加载新代码;oneshot 下一次启动自然加载。不能假定仅重启 watcher 会更新全部 Python 进程。无需改 review/revise unit 的模型凭证设置。
7. 只读检查目标服务 active、进程入口和脱敏日志;真实消息测试留给 W5。新地址更新只更换独立配置,不再改源码/unit。
回滚按同一空闲流程恢复成套文件(发送脚本、调用方、unit、配置),daemon-reload 并启动相关服务;记录恢复的哈希和权限。不要回滚生产数据库或手工复制运行中 SQLite 来“恢复状态”;本期不改变其 schema。旧版本通知本来可能不可用,回滚恢复的是业务行为,不能宣称修复消息投递。源码备份不得清理原有历史 `.bak.*`。
## 7. 协议文档与通用 agent 接入
`docs/agent-protocol.md` 作为通知和 agent 接入契约来源;HANDOFF 保留管线架构,control-web.md 保留现有 HTTP 接口说明。其他索引链接到这些来源,不再复制整套阶段表。
协议至少写清:send/CLI 参数、配置优先级、送达与静音语义、故障定位、敏感日志规则、真实现有的查询/上传路由,以及“审核回答由人完成,agent 不代填确认”。当前 HTTP 服务没有通用公网认证,不能描述为可直接暴露的 agent API;旧一次性页面与当前任务页的边界须准确。
更新 `OBSIDIAN/CLAUDE.md` 与 `HANDOFF.md` 的通知说明;HANDOFF 仍有“粗稿 commit/push”和旧临时 Web 生命周期描述,核实后标注历史或改为当前行为,保留算法历史数据。`~/.claude/skills/memo-inbox/references/webhook-toolset-diagnostics.md` 标历史归档并指向协议;修改前读该 skill,去重其他引用时保留“先粗整理、人工确认后正式发布”等纪律。修改 skill 必须在报告中列明。
文档同步还必须覆盖下列 v1 漏项:
- `~/.claude/skills/memo-inbox/SKILL.md:51–52,217–221`:替换 active 的 `claude -p → minutes/_drafts` 描述及旧 webhook-agent/toolset 必查入口。
- `~/.claude/skills/memo-inbox/references/meeting-recording-inbox.md`:更新或明确归档旧 `.hermes-meetings`、正文 webhook 和私有配置排障路径。
- `~/.claude/skills/memo-inbox/references/asr-refine-pipeline.md:16–20`:修正粗稿入 Vault、commit/push、Mac 审阅的旧时序,保留该技能独有的人工确认纪律。
首期交付跨 agent 可直接读取的协议,不把“创建 Markdown”冒充“已安装通用 skill”。如另做通用 skill,先使用 skill-creator,做薄入口引用上述契约,不复制业务规则、不读取私有 agent 状态、不授予自动审核或发消息的新权限。
## 8. 后续项目:任务状态与数据库解耦
先盘点 watcher、cli、review_web、control_web 的全部 intake_job 读写,明确哪些是同一服务的内部实现、哪些是真正跨进程契约;不要把所有内部函数调用都 HTTP 化。
优先评估提取唯一 intake store/schema 模块,由本机组件共享,先去重复 DDL;如确需 watcher 进程与控制服务独立部署,再引入版本化内部 API。后者必须先设计注册、状态迁移、幂等键、重复/乱序更新、认证和服务不可用时的持久重试。候选路由是未来设计,不是现有接口。网页服务中断不能新增为转录中断条件;未解决断网状态恢复前不移除 watcher 的当前写入路径。
验收至少覆盖:Web 上传与直接投文件、重复 fid、控制服务暂时不可用、转录/桥接失败、进程重启恢复、草稿/发布状态关联、禁止旧状态覆盖新状态。该项目单独评审和交付,不与首期通知上线绑定。
## 9. 完成定义与执行报告
- 代码完成:唯一发送实现、参数正确、完整脱敏、原调用契约兼容、隔离回归通过。
- 部署完成:已排空并加载新代码,配置权限正确,服务健康,有可执行的成套回滚记录。
- 通知恢复:已核实真实 endpoint/共享密钥,并在获得测试消息发送授权后确认端到端投递;只有 HTTP 2xx 时记录“后端接受”,未确认接收端则不写“用户已收到”。
- 待决策:真实 webhook 地址/密钥来源;是否另开 direct 后端项目。direct 不等价于自动复用 Hermes bot token 的授权。
报告列出改动与备份路径、测试原文、参数及脱敏测试名称、部署窗口、未覆盖项、真实通知状态和 skill 变更。对不满足的门槛明确标为未完成,不用“重构完成”掩盖投递尚未恢复。