chore(audit): 补追 96 份未入仓审计孤本 — 覆盖 2026-02-26 ~ 2026-04-08
这些审计记录原本堆积在 docs/audit/changes/changes/ 嵌套误产物目录下(由开发机迁移
79d3c2e 前后的不明批量操作产生)。由于同期 .gitignore 屏蔽了 docs/audit/ 全目录,
它们从未入过 git 任何分支 history。删除即永久丢失。
按 docs/specs/audit-gap-recovery/tasks.md 阶段 1 执行,将全部 96 份 D 类孤本
(主目录无同名、git history 亦无记录)复制到 docs/audit/changes/ 主目录入仓。
涵盖主题: P1-P18 全栈集成 / 多模块累积变更 / ETL bug 修复 / 业务日切 /
召回与任务引擎改造 / 租户管理与审批 / 董事会财务 / 客户与助教详情 /
DDL 基线合并 / Kiro 到 Claude Code 迁移
阶段 2(B 类内容漂移 1 份)和阶段 4(嵌套目录删除)独立推进。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# 变更审计记录:僵尸任务修复 + 优雅关闭 + 重新执行按钮
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|-----|
|
||||
| 日期 | 2026-03-22 |
|
||||
| 触发原因 | 执行 ID bb9a0761 永远卡在 running 状态,uvicorn 重启导致子进程丢失 |
|
||||
|
||||
## 操作摘要
|
||||
|
||||
解决后端重启时 ETL 子进程丢失导致 DB 记录永远停留在 running 的架构缺陷。新增优雅关闭(3s 超时)、启动时僵尸清理(仅本机)、interrupted 状态、重新执行按钮(所有历史任务)。
|
||||
|
||||
## 根因分析
|
||||
|
||||
`asyncio.create_task()` 启动的协程在 uvicorn 重启时被丢弃,`execute()` 的 `finally` 块未执行,DB 中 `task_execution_log` 记录永远停留在 `status='running'`、`finished_at=NULL`。
|
||||
|
||||
## 文件变更清单
|
||||
|
||||
### 后端
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `apps/backend/app/services/task_executor.py` | 修改 | 新增 `shutdown()` 优雅关闭方法(terminate→wait→kill)、`recover_stale()` 启动僵尸清理、`execute()` finally 末尾添加 `cleanup()` 防内存泄漏 |
|
||||
| `apps/backend/app/main.py` | 修改 | lifespan startup 调用 `recover_stale()`,shutdown 调用 `shutdown(timeout=3.0)` |
|
||||
| `apps/backend/app/routers/execution.py` | 修改 | 新增 `POST /api/execution/{id}/rerun` 端点 |
|
||||
|
||||
### 前端
|
||||
| 文件 | 变更类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `apps/admin-web/src/api/execution.ts` | 修改 | 新增 `rerunExecution()` API 函数 |
|
||||
| `apps/admin-web/src/pages/TaskManager.tsx` | 修改 | STATUS_COLOR 添加 interrupted、操作列添加重新执行按钮(所有非 running 任务) |
|
||||
|
||||
### 数据修复
|
||||
- 测试库 `bb9a0761` 记录已手动标记为 `interrupted`
|
||||
|
||||
## 关键设计决策
|
||||
|
||||
1. **优雅关闭 3s 超时**:uvicorn 关闭后子进程无意义,快速终止
|
||||
2. **僵尸清理仅限本机**:通过 `command LIKE '[hostname]%'` 匹配,多实例安全
|
||||
3. **interrupted 状态**:区别于 cancelled(用户主动取消)和 failed(执行出错)
|
||||
4. **rerun 用默认 config**:DB 未存完整 TaskConfig,仅保留 task_codes,用 `api_full` + `increment_only` 默认配置重新执行
|
||||
5. **cleanup 调用**:execute() finally 末尾释放内存缓冲区,防止长期运行内存泄漏
|
||||
|
||||
## 风险评估
|
||||
|
||||
- shutdown 超时后强制 kill,不会阻塞 uvicorn 关闭流程
|
||||
- recover_stale 在 task_queue.start() 之前执行,不会与新任务冲突
|
||||
- rerun 不保留原始 window/lookback 参数(已知限制,后续可扩展存储完整 config)
|
||||
|
||||
## 回滚策略
|
||||
|
||||
revert 5 个文件即可。DB 中 interrupted 状态记录无副作用,无需回滚数据。
|
||||
|
||||
## 验证步骤
|
||||
|
||||
```sql
|
||||
-- 1. 确认无僵尸任务
|
||||
SELECT id, status FROM task_execution_log WHERE status = 'running';
|
||||
-- 预期:0 行(或仅有真正在运行的任务)
|
||||
|
||||
-- 2. 确认 bb9a0761 已修复
|
||||
SELECT id, status, finished_at FROM task_execution_log
|
||||
WHERE id = 'bb9a0761-95ff-4663-9053-9bb68b2603bb';
|
||||
-- 预期:status='interrupted', finished_at IS NOT NULL
|
||||
|
||||
-- 3. 确认 interrupted 状态存在
|
||||
SELECT DISTINCT status FROM task_execution_log ORDER BY status;
|
||||
-- 预期:包含 interrupted
|
||||
```
|
||||
Reference in New Issue
Block a user