Files
zclaw_openfang/docs/superpowers/specs/2026-03-28-post-debug-remediation-design.md
iven 5fdf96c3f5 chore: 提交所有工作进度 — SaaS 后端增强、Admin UI、桌面端集成
包含大量 SaaS 平台改进、Admin 管理后台更新、桌面端集成完善、
文档同步、测试文件重构等内容。为 QA 测试准备干净工作树。
2026-03-29 10:46:41 +08:00

207 lines
8.4 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.

# 联合调试后修复行动计划
> **日期**: 2026-03-28
> **来源**: 联合调试报告 V1 + 专家代码审查
> **状态**: 执行中
---
## 一、修复优先级矩阵
### P0 — 阻塞级(必须立即修复)
| # | 问题 | 模块 | 影响 | 修复方案 |
|---|------|------|------|----------|
| P0-1 | SSE Relay 死锁 | relay/service.rs | 流式中转完全不可用 | `blocking_lock()``tokio::sync::Mutex` 或 channel |
| P0-2 | Token 过期无法刷新 | auth/mod.rs | 自动续期失效,用户被迫重新登录 | middleware 对 `/auth/refresh` 路径跳过过期检查 |
| P0-3 | 设备清理 SQL 崩溃 | main.rs:58 | stale devices 永远不清理 | TEXT 比较: 绑定 rfc3339 字符串替代 NOW() |
### P1 — 功能缺陷(本周修复)
| # | 问题 | 模块 | 影响 | 修复方案 |
|---|------|------|------|----------|
| P1-1 | 注册返回类型不匹配 | auth/handlers.rs | 桌面端注册后无法自动登录 | register handler 返回含 token 的响应 |
| P1-2 | account_api_keys 未被 Relay 消费 | relay/handlers.rs | 用户级 API Key 无效 | relay 优先查 account_api_keys回退到 provider key |
| P1-3 | TOTP nonce 硬编码 | auth/totp.rs | 相同明文+相同密钥产生相同密文 | 使用随机 nonce |
| P1-4 | Config 5 端点缺审计日志 | migration/handlers.rs | 配置变更无 audit trail | 添加 log_operation() 调用 |
| P1-5 | Admin 缺路由守卫 | admin/src | URL 直通可绕过侧边栏 | 添加 AuthGuard 组件 |
| P1-6 | SSE usage 计数竞态 | relay/service.rs | token 使用量记录为 0 | stream 结束后同步记录 usage |
### P2 — 代码质量(下周修复)
| # | 问题 | 模块 | 修复方案 |
|---|------|------|----------|
| P2-1 | 35 个时间戳字段为 TEXT | db.rs + 全部 service | ALTER TABLE → TIMESTAMPTZ + chrono::DateTime<Utc> |
| P2-2 | dashboard_stats 7 次串行查询 | account/service.rs | 合并为 1-2 次 SQL |
| P2-3 | 6 个端点缺 OpenAPI 文档 | 各 handlers | 添加 #[utoipa::path] |
| P2-4 | computeConfigDiff/syncConfig 未调用 | desktop | 迁移向导统一调用路径 |
| P2-5 | list_providers 返回禁用项 | model_config/service.rs | 添加 enabled=true 过滤 |
### P3 — 长期改进
| # | 问题 | 方案 |
|---|------|------|
| P3-1 | Token 创建/撤销无权限控制 | 添加 token:manage / key:manage 权限 |
| P3-2 | 12 个 self-scoped handler 无显式权限 | 评估是否需要细粒度权限 |
| P3-3 | SSRF DNS rebinding 风险 | DNS 解析后二次检查 |
| P3-4 | 邮箱验证过于简单 | 使用 email 格式正则或 validator crate |
| P3-5 | 限流内存状态不共享 | Redis/分布式限流(多实例时) |
---
## 二、P0 修复详细方案
### P0-1: SSE Relay 死锁
**根因**: `relay/service.rs` 中 SSE 流式响应使用 `std::sync::Mutex::blocking_lock()``inspect()` 回调内。Tokio 运行时在等待锁时阻塞 worker thread导致死锁。
**修复**: 替换为 `tokio::sync::Mutex` 或使用 `tokio::sync::watch` channel 传递状态。
```rust
// Before (deadlock):
let state = Arc::new(std::sync::Mutex::new(RelayState::new()));
stream.inspect(move |chunk| {
let mut s = state.blocking_lock(); // DEADLOCK in async context
...
})
// After (async-safe):
let state = Arc::new(tokio::sync::Mutex::new(RelayState::new()));
// Use tokio::spawn for async lock acquisition
```
### P0-2: Token 过期无法刷新
**根因**: auth middleware 在请求到达 handler 前验证 JWT 有效性。过期的 JWT 被中间件拦截返回 401永远到不了 `/auth/refresh` handler。
**修复**: 中间件对 refresh 路径放行,或在 refresh handler 中独立验证 expired token 的签名。
```rust
// middleware.rs - 在 JWT 验证前添加路径检查
if request.path() == "/api/v1/auth/refresh" {
// 跳过过期检查,仅验证签名
return verify_signature_only(token);
}
```
### P0-3: 设备清理 SQL 类型不匹配
**根因**: `last_seen_at` 为 TEXT 类型SQL `NOW() - INTERVAL '90 days'` 返回 TIMESTAMPTZ。
**修复**: 使用 rfc3339 字符串比较TEXT 排序等价于时间排序)。
```rust
// Before:
sqlx::query("DELETE FROM devices WHERE last_seen_at < NOW() - INTERVAL '90 days'")
// After:
let cutoff = (chrono::Utc::now() - chrono::Duration::days(90)).to_rfc3339();
sqlx::query("DELETE FROM devices WHERE last_seen_at < $1").bind(&cutoff)
```
---
## 三、P1 修复详细方案
### P1-1: 注册返回类型不匹配
**问题**: 桌面端 `saasStore.register()` 期望响应包含 `token` 字段(`SaaSLoginResponse`),但后端 `register` handler 返回 `AccountPublic`(无 token
**修复**: register handler 在创建账号后自动签发 JWT返回与 login 一致的响应格式。
### P1-2: account_api_keys 未被 Relay 消费
**问题**: relay handler 仅查找 provider 级 API key忽略了 `account_api_keys` 表。
**修复**: relay 查找顺序: account_api_keys → provider.api_key → 401
### P1-3: TOTP nonce 硬编码
**问题**: AES-256-GCM 使用固定 nonce `"zclaw_totp_nce"`,违反加密语义安全性。
**修复**: 每次加密生成随机 12-byte nonce存储在密文前缀中。
### P1-4: Config 端点审计日志
**问题**: create/update/delete/seed/sync 5 个 config 端点无 operation_logs 记录。
**修复**: 每个 handler 在执行后调用 `log_operation()`
### P1-5: Admin 路由守卫
**问题**: 侧边栏权限已修复,但 URL 直通仍可访问页面。
**修复**: 添加 `AuthGuard` 组件包裹 dashboard layout。
### P1-6: SSE usage 计数竞态
**问题**: usage 记录通过 `tokio::spawn` + 1s sleep 异步写入,与 stream 消费者竞争,导致 token 计数为 0。
**修复**: stream 结束后同步记录 usage使用 stream `on_complete` 回调)。
---
## 四、执行计划
### Week 1: P0 修复 (3 项)
| 天 | 任务 | 文件 | 验证 |
|----|------|------|------|
| Day 1 | P0-1 SSE 死锁修复 | relay/service.rs | 流式请求不死锁 |
| Day 2 | P0-2 Token 刷新修复 | auth/mod.rs + middleware.rs | 过期 token 可刷新 |
| Day 3 | P0-3 设备清理修复 + 集成测试 | main.rs | 清理 SQL 正常执行 |
### Week 1-2: P1 修复 (6 项)
| 天 | 任务 | 文件 |
|----|------|------|
| Day 4 | P1-1 注册返回类型 | auth/handlers.rs |
| Day 5 | P1-2 Relay API Key 消费 | relay/handlers.rs + service.rs |
| Day 6 | P1-3 TOTP nonce 随机化 | auth/totp.rs |
| Day 7 | P1-4 Config 审计日志 | migration/handlers.rs |
| Day 8 | P1-5 Admin 路由守卫 | admin/src |
| Day 9 | P1-6 SSE usage 同步记录 | relay/service.rs |
### Week 3: P2 修复 (5 项)
| 天 | 任务 |
|----|------|
| Day 10-11 | P2-1 时间戳类型迁移 (35 字段) |
| Day 12 | P2-2 dashboard 查询优化 |
| Day 13 | P2-3~P2-5 OpenAPI + 未调用方法 + 过滤 |
---
## 五、验证策略
### 每个 P0 修复的验证标准
| P0 | 验证方法 | 通过标准 |
|----|----------|----------|
| P0-1 | 发送 SSE 流式请求 | 响应流完整传输,无死锁,无超时 |
| P0-2 | 等待 token 过期后 refresh | 新 token 有效,后续请求正常 |
| P0-3 | 执行设备清理任务 | SQL 正常执行,删除 >90天设备 |
### 回归测试
每次 P0/P1 修复后,重新执行联合调试报告中已通过的 20 个测试用例,确保不回退。
---
## 六、专家结论
### 安全工程师结论
> P0-1 (SSE 死锁) 是最高优先级 — 它意味着 Relay 核心功能完全不可用。P1-3 (TOTP nonce) 虽然不会直接被利用,但违反加密基本原则,应在 P1 中修复。P3 级的权限细粒度改进是长期安全加固方向。
### 后端架构师结论
> 35 个 TEXT 时间戳字段是系统性技术债。短期用 rfc3339 字符串比较可以工作ISO 8601 文本排序等价时间排序),但中长期必须迁移到 TIMESTAMPTZ。SEED_ROLES 中 NOW() 与 to_rfc3339() 格式不一致也需要在迁移时解决。
### 前端负责人结论
> P1-1 (注册返回类型不匹配) 是前端最迫切的修复 — 它阻塞了新用户注册流程。Admin 路由守卫 (P1-5) 是用户体验和安全的双重需求。
### QA 主管结论
> 12 项未执行测试中B-03 (Relay SSE)、C-01 (新用户旅程)、C-03 (TOTP) 风险最高。建议在 P0 修复后立即补充执行,覆盖率为先。
### DevOps 结论
> 服务在测试期间中断说明需要1) 进程管理systemd/windows service2) 健康检查告警 3) 日志持久化。建议在 P0 修复后添加基础的运维监控。