# Codex 同步恢复与验收

## 2026-10-20 排查结论

此次中断发生在本地采集阶段：Codex 的采集命令仍指向已停用的 3100 测试目录，而正式桌面客户端使用另一套账号隔离的状态目录。旧目录中 336 个事件均已获云端 ACK，没有待传事件；旧 helper 在心跳文件缺失时直接返回，因而后续输入与最终回答没有写入待传队列。

云端最后一个事件是输入，没有对应结束事件，因此旧界面一直显示“处理中”。读取云端副本也被错误描述为“来自其他电脑”；同一台电脑的不同运行目录同样会遇到此情况。

## 本轮代码

- 采集服务每次确认当前账号后写入最长 10 分钟的本地暂存授权。正常停止或重启期间，helper 可短暂落盘，恢复后沿用原事件去重与上传机制。
- 显式登出、切换账号及撤销授权仍立即停止采集；过期、账号不符、暂停、排除任务/项目均不得继续收集。暂存授权不会因仅调用 helper 而续期。
- 同步状态只读取当前 Codex 配置明确指向的采集目录，并核对账号、团队、来源、任务编号及 Codex 配置目录身份。服务在线、短暂暂存、离线、暂停与无法核实分别展示。
- “采集端在线”仅代表心跳有效，不等于 Hook 已信任、当前任务正在运行或所有事件均已上传。云端未收到结束事件时使用“等待结束状态”。
- 读取云端副本不再直接判断“来自其他电脑”。

## 定向历史恢复工具

`scripts/recover-codex-sync.py` 默认只预览。仅在用户已明确批准目标任务和同步范围后使用。

必须提供 `--source`、`--target`、`--owner`、`--session`、`--rollout`、`--audit`，检查预览后才加 `--apply`。工具不扫描会话目录，不自行选择任务，也不配置或信任 Hooks。

约束：

1. 来源账号唯一、来源服务已停用、没有待处理 inbox；目标为同账号正在运行且日志为空的正式客户端。
2. 保留原 installation、source、task、turn、event、ACK 与上传同意记录；不会重新创建一份云端任务。
3. 仅恢复指定会话最后采集时间之后的用户文字和 `task_complete.last_agent_message`；不导入工具结果、推理内容、助手进度消息或子代理事件。
4. 按原轮次和事件类型去重，保留已有输入，沿用原始事件时间。敏感凭据模式脱敏，长文本明确标记截断。
5. 写入前用 SQLite backup 保存两个日志；数据库变更采用事务。恢复审计目录已存在时禁止直接重跑，先确认上次结果。
6. 同步连接的迁移与 Hook 信任独立处理。运行正式目录的 `bridge.py enroll <installation>` 后，保留其他 Hooks，仅移除明确属于旧目录的失效命令。不得编辑 Codex 的信任哈希或伪造 Hook 验证事件。

备份含已有会话正文，必须保存在本机受限目录，不提交 Git、不发送给同事。若恢复后云端已接收数据，回退本地数据库不会撤回云端内容，不应把数据库回退当作云端回滚。

## 本次验证边界

- 19 个相关测试文件、224 项测试通过；类型声明构建、打包与入口检查通过。
- 本机执行了单任务恢复：92 条用户输入与 92 条最终回复；原有一条输入未重复写入。
- 迁移后 520 个本地云端事件全部 ACK，pending 为 0，blocked 为空；原任务与来源编号保持。
- 3102 页面已看到目标任务最后收到时间从 10 月 12 日 19:16 更新到 10 月 13 日 16:39，状态为“本轮结束”。
- Hooks 配置已指向正式桌面客户端目录，并保留原文件备份。未修改 Codex 信任配置；持续实时同步必须在用户审核新 Hook 后，用一次真实输入和最终回答验收，不能用历史恢复代替。
- 新增短暂离线保护和状态界面属于本地开发分支，3102 使用其构建。正式桌面客户端仍使用已安装的发布包；本轮未更换其运行代码、合并分支或发布新版。

后续实时验收应同时核对：真实输入进入本地日志、最终回答进入本地日志、两个事件得到云端 ACK、页面显示完整轮次，以及重启后不会产生重复事件。
