docs: sync review reports, runbooks, and checklists
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# AI-Customer-Service Codex 代码审查报告
|
||||
|
||||
> 审查工具:Codex CLI v0.125.0 (gpt-5.4)
|
||||
> 审查范围:`/home/long/project/立交桥/projects/ai-customer-service`
|
||||
> 审查范围:`/home/long/project/ai-customer-service`
|
||||
> 代码基准:`3e9022a` + `01135ec`
|
||||
> 审查时间:2026-05-01
|
||||
> 审查方法:静态分析 + 工具链验证(go vet、go build、go test -race)
|
||||
|
||||
326
docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md
Normal file
326
docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# AI-Customer-Service 系统性代码审查报告
|
||||
|
||||
> 审查日期:2026-05-11
|
||||
> 审查范围:全仓库代码、构建、测试、安全、架构
|
||||
> 审查人:Hermes Agent (go-project-review + two-stage-review)
|
||||
> 基线 commit:`67922c5` (HEAD)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目规模总览
|
||||
|
||||
| 维度 | 数值 | 备注 |
|
||||
|------|------|------|
|
||||
| Go 源文件 | 109 | 含测试文件 |
|
||||
| 生产代码行 | ~4,347 | 不含测试、注释、空行 |
|
||||
| 测试代码行 | ~11,072 | 含 integration + e2e |
|
||||
| SQL migration 行 | 130 | 3 个 migration 文件 |
|
||||
| 模块依赖 | 2 | `github.com/google/uuid`, `github.com/lib/pq` |
|
||||
| Go 版本 | 1.22 | 与 Dockerfile 一致 |
|
||||
| 单服务架构 | 是 | `cmd/ai-customer-service` 单一入口 |
|
||||
|
||||
### 包级覆盖率(internal/)
|
||||
|
||||
| 包 | 覆盖率 | 状态 |
|
||||
|----|--------|------|
|
||||
| `internal/platform/health` | 100.0% | ✅ |
|
||||
| `internal/platform/logging` | 100.0% | ✅ |
|
||||
| `internal/service/handoff` | 100.0% | ✅ |
|
||||
| `internal/service/intent` | 100.0% | ✅ |
|
||||
| `internal/service/reply` | 100.0% | ✅ |
|
||||
| `internal/domain/error/cserrors` | 97.2% | ✅ |
|
||||
| `internal/http/middleware` | 92.0% | ✅ |
|
||||
| `internal/service/dialog` | 88.5% | ✅ |
|
||||
| `internal/store/memory` | 85.6% | ✅ |
|
||||
| `internal/config` | 86.1% | ✅ |
|
||||
| `internal/platform/httpx` | 83.3% | ✅ |
|
||||
| `internal/http/handlers` | 79.9% | ⚠️ |
|
||||
| `internal/http` | 80.2% | ⚠️ |
|
||||
| `internal/service/platformevents` | 84.0% | ⚠️ |
|
||||
| `internal/service/platformdelivery` | 65.2% | ⚠️ |
|
||||
| `internal/platformadapter` | 66.1% | ⚠️ |
|
||||
| `internal/app` | 64.6% | ⚠️ |
|
||||
| `internal/domain/platformevent` | 58.8% | ⚠️ |
|
||||
| `internal/store/postgres` | ~10.0% | ❌ 测试因缺少 DB 失败 |
|
||||
| **internal/ 总覆盖率** | **63.4%** | ⚠️ |
|
||||
|
||||
---
|
||||
|
||||
## 2. 构建与测试验证
|
||||
|
||||
### 2.1 静态验证
|
||||
|
||||
| 检查项 | 结果 | 详情 |
|
||||
|--------|------|------|
|
||||
| `go build ./...` | ✅ 通过 | 零错误 |
|
||||
| `go vet ./...` | ✅ 通过 | 零警告 |
|
||||
| `go test -race ./internal/...` | ✅ 通过 | 零 DATA RACE |
|
||||
| `go test ./internal/... -count=1 -p 1` | ✅ 24/24 通过 | 全部通过 |
|
||||
| `go test ./... -count=1 -p 1` | ⚠️ 部分失败 | `postgres`, `e2e`, `integration` 因缺少本地 PostgreSQL(端口 5434)失败 |
|
||||
|
||||
### 2.2 测试环境缺口
|
||||
|
||||
- **postgres 包**:21 个测试全部因 `dial tcp 127.0.0.1:5434: connection refused` 失败
|
||||
- **e2e 包**:2 个测试同样因缺少 PostgreSQL 失败
|
||||
- **integration 包**:1 个测试同样因缺少 PostgreSQL 失败
|
||||
|
||||
**判断**:这属于**环境前置条件缺失**,不是代码缺陷。但 CI 若未配置共享测试 DB,则本地无法完整验证全链路。
|
||||
|
||||
---
|
||||
|
||||
## 3. 安全审查
|
||||
|
||||
### 3.1 SQL 注入风险
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|--------|------|
|
||||
| `fmt.Sprintf.*SELECT/INSERT/UPDATE/DELETE` 危险模式 | ✅ 未发现 |
|
||||
| `$1, $2...` 参数化查询占位符 | ✅ 29 处 |
|
||||
| 字符串拼接 SQL | ✅ 未发现 |
|
||||
|
||||
**结论**:PostgreSQL 层全面使用参数化查询,SQL 注入风险为零。
|
||||
|
||||
### 3.2 硬编码凭证
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|--------|------|
|
||||
| 代码中硬编码 password/secret/api_key | ✅ 未发现 |
|
||||
| 配置读取 secret 的代码 | ✅ 仅有正常的 `getEnv("AI_CS_WEBHOOK_SECRET", "")` |
|
||||
| 环境变量示例文件 | ⚠️ `.env.platform-adapters.example` 存在,但无真实值 |
|
||||
|
||||
### 3.3 敏感日志泄露
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|--------|------|
|
||||
| log.*password/secret/token/key | ✅ 未发现 |
|
||||
| audit 日志中是否记录敏感字段 | ✅ audit payload 仅记录 intent/reply/error_code 等业务字段 |
|
||||
|
||||
### 3.4 Webhook 安全机制
|
||||
|
||||
- **HMAC-SHA256** 签名验证 ✅
|
||||
- **时间戳防重放**(默认 300 秒偏移窗口)✅
|
||||
- **常量时间比较** `hmac.Equal` ✅
|
||||
- **请求体回置** `r.Body = io.NopCloser(bytes.NewReader(body))` ✅
|
||||
- **平台级秘钥隔离** Sub2API / NewAPI 各自独立 secret ✅
|
||||
|
||||
### 3.5 goroutine 安全
|
||||
|
||||
- `app.go:184` 启动 callback worker:`go worker.Start(workerCtx)`
|
||||
- worker 通过 `context.WithCancel` 管理生命周期
|
||||
- `Shutdown` 方法中调用 `cancel()` 并通过 `workerClosers` 链式关闭
|
||||
- **判断**:有明确的取消路径,无泄漏风险 ✅
|
||||
|
||||
### 3.6 Token / 密钥文件泄露
|
||||
|
||||
| 检查项 | 结果 |
|
||||
|--------|------|
|
||||
| `*.pem`, `*.key`, `token.txt` | ✅ 未发现 |
|
||||
| `.env*` 文件 | ⚠️ 仅 `.env.platform-adapters.example`(示例值,无真实凭证)|
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码质量与架构审查
|
||||
|
||||
### 4.1 架构分层(A 级)
|
||||
|
||||
```
|
||||
cmd/ → 启动入口(main.go 干净,仅装配 + signal 处理)
|
||||
internal/app/ → 依赖注入装配中心(New 函数显式创建所有组件)
|
||||
internal/domain/ → 纯领域模型(无外部依赖)
|
||||
internal/service/ → 业务逻辑(dialog/intent/reply/handoff/delivery/events)
|
||||
internal/http/ → HTTP 传输层(handlers + middleware + router)
|
||||
internal/store/ → 持久化抽象(memory + postgres 双实现)
|
||||
internal/platform/ → 基础设施(health/httpx/logging)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- `dialog.Service` 仅通过接口依赖(`SessionRepository`, `AuditRepository`, `TicketRepository`, `DedupRepository`, `IntentRecognizer`, `HandoffDecider`),可测试性高
|
||||
- `app.go` 作为装配中心,清晰表达组件依赖图
|
||||
- memory store 与 postgres store 可互换,支持 test/dev/prod 多环境
|
||||
|
||||
### 4.2 配置管理(A- 级)
|
||||
|
||||
- 全量环境变量驱动,无配置文件硬编码
|
||||
- `config.Load()` 包含 10+ 项校验规则:
|
||||
- production 强制要求 Postgres + Webhook Secret
|
||||
- 平台适配器启用时强制要求 Ingress Secret
|
||||
- 正整数校验(timeout、batch size、retry schedule)
|
||||
- **建议**:`getEnvInt` / `getEnvBool` 在解析失败时静默回退到 fallback 值,这在生产环境可能导致预期外行为。建议对关键配置(如 DSN、secret)增加 "解析失败即报错" 的严格模式。
|
||||
|
||||
### 4.3 数据库设计(A 级)
|
||||
|
||||
- Migration 文件使用 Flyway 风格命名(`0001_init.up.sql`)
|
||||
- 约束完整:CHECK 约束覆盖 channel、status、priority 枚举值
|
||||
- 索引合理:`(channel, open_id)`、`(status, priority, created_at)`、`(session_id, created_at DESC)`
|
||||
- 外键正确:ON DELETE CASCADE / SET NULL
|
||||
- Outbox 模式:平台事件通过 `cs_platform_event_outbox` + `cs_platform_event_dead_letters` 实现可靠投递
|
||||
|
||||
### 4.4 限流与防护(B+ 级)
|
||||
|
||||
- `httpx.RateLimiter`:滑动窗口限流,基于 IP / X-Forwarded-For
|
||||
- `http.MaxBytesReader`:请求体大小限制
|
||||
- **P1 风险**:RateLimiter 的 `Allow` 方法每次请求都重新分配 `valid` 切片(`var valid []time.Time`),在高并发下产生 GC 压力。建议改为原地过滤或环形缓冲区。
|
||||
- **P2 风险**:`clientIP` 和 `rateLimitKey` 中对 IPv6 地址的处理可能不正确(IPv6 地址包含多个 `:`,`strings.LastIndex(addr, ":")` 会在第一个冒号处截断)。
|
||||
|
||||
### 4.5 认证授权(B 级)
|
||||
|
||||
- Webhook 层:HMAC 签名验证 ✅
|
||||
- 内部 API 层:基于 Header 的 RBAC(`X-CS-Actor-ID`, `X-CS-Actor-Role`)
|
||||
- **P1 风险**:`RequireRoles` 中间件**不验证 Actor 身份真实性**,仅检查 header 存在性和角色是否在白名单。这在生产环境中需要配合反向代理(如 API Gateway)的 JWT/OAuth 验证,否则存在 header 伪造风险。文档中需明确标注此信任边界。
|
||||
|
||||
### 4.6 错误处理(A- 级)
|
||||
|
||||
- 统一的错误码体系(`cserrors` 包,100%+ 覆盖率)
|
||||
- 错误码分级:`CS_AUTH_403x`、`CS_REQ_400x`、`CS_SYS_500x`、`CS_SES_400x`
|
||||
- audit 写入失败不阻断主流程(` _ = s.Audit.Add(...)`),符合 P0 质量标准
|
||||
- **建议**:部分 `fmt.Errorf("db is nil")` 错误信息可以更丰富(如包含调用上下文)。
|
||||
|
||||
### 4.7 Graceful Shutdown(A 级)
|
||||
|
||||
- 10 秒超时 `context.WithTimeout(context.Background(), 10*time.Second)`
|
||||
- 关闭顺序:SetReady(false) → SetLive(false) → Server.Shutdown → closers 链式调用
|
||||
- worker cancel 包含在 closers 中
|
||||
|
||||
---
|
||||
|
||||
## 5. 已知问题清单
|
||||
|
||||
### P0 — 阻塞级
|
||||
|
||||
| # | 问题 | 位置 | 影响 | 建议 |
|
||||
|---|------|------|------|------|
|
||||
| P0-1 | **未提交改动规模过大** | 全仓库 | 评审边界模糊、回滚困难、CI 基线不稳定 | `git status` 显示 15+ 个 modified 文件 + 3 个 untracked 文件。建议立即收口,分批次提交并打 tag |
|
||||
| P0-2 | **Makefile test 目标与 README/CI 不一致** | `Makefile:2` | 本地执行 `make test` 时并发跑测试,可能污染共享 PostgreSQL 测试库 | 将 `Makefile` 中 `go test ./...` 改为 `go test ./... -count=1 -p 1`,与 README 和 CI 保持一致 |
|
||||
|
||||
### P1 — 必须修复
|
||||
|
||||
| # | 问题 | 位置 | 影响 | 建议 |
|
||||
|---|------|------|------|------|
|
||||
| P1-1 | **ticket_handler.List 覆盖率 0%** | `internal/http/handlers/ticket_handler.go:33` | 列表接口无回归保护 | 补充单元测试或 integration 测试 |
|
||||
| P1-2 | **newapi_adapter.BuildIngressAck 覆盖率 0%** | `internal/platformadapter/newapi_adapter.go:24` | NewAPI 占位逻辑无验证 | 补充 501 响应测试 |
|
||||
| P1-3 | **Authz header 伪造风险未文档化** | `internal/http/middleware/authz.go` | 内部 API 若直接暴露可被绕过 | 在 `docs/SECURITY_BOUNDARY.md` 中明确标注:RequireRoles 依赖上游网关做真实身份验证 |
|
||||
| P1-4 | **RateLimiter GC 压力** | `internal/platform/httpx/limits.go:67` | 高并发下频繁分配切片 | 改为原地过滤或预分配环形缓冲区 |
|
||||
| P1-5 | **IPv6 地址截断风险** | `internal/platform/httpx/limits.go:110-114` | IPv6 地址在 rate limit key 中被错误截断 | 使用 `net.SplitHostPort` 替代手动字符串操作 |
|
||||
|
||||
### P2 — 建议修复
|
||||
|
||||
| # | 问题 | 位置 | 影响 | 建议 |
|
||||
|---|------|------|------|------|
|
||||
| P2-1 | **配置解析失败静默回退** | `internal/config/config.go:201-255` | 拼写错误的环境变量值被静默忽略 | 对生产模式的关键配置增加解析失败报错 |
|
||||
| P2-2 | **callback worker 无连接池限制** | `internal/app/app.go:172` | `&http.Client{Timeout: ...}` 使用默认连接池 | 显式配置 `Transport.MaxIdleConns` 和 `MaxIdleConnsPerHost` |
|
||||
| P2-3 | **缺少 SQLite/内存测试回退** | `test/integration`, `test/e2e` | 无 PostgreSQL 时无法跑 integration/e2e | 引入 testcontainers-go 或 SQLite 内存模式作为测试回退 |
|
||||
| P2-4 | **平台事件 worker 缺少优雅关闭等待** | `internal/app/app.go:164-188` | `cancel()` 后立即返回,不等待 worker goroutine 真正退出 | 使用 `sync.WaitGroup` 等待 worker 完成当前轮次 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 与 spec 的合规性检查(Stage 1)
|
||||
|
||||
基于 `IMPLEMENTATION_PLAN.md` 和 `README.md` 的 spec 对照:
|
||||
|
||||
| Spec 项 | 实现状态 | 位置 |
|
||||
|---------|----------|------|
|
||||
| HTTP 服务启动 | ✅ 完成 | `cmd/ai-customer-service/main.go` |
|
||||
| Health/Live/Ready | ✅ 完成 | `internal/platform/health`, `internal/http/handlers/health_handler.go` |
|
||||
| Webhook 接收最小 JSON | ✅ 完成 | `internal/http/handlers/webhook_handler.go` |
|
||||
| Session 管理(内存 + Postgres) | ✅ 完成 | `internal/store/memory/session_store.go`, `internal/store/postgres/session_store.go` |
|
||||
| Intent 识别(规则版) | ✅ 完成 | `internal/service/intent/service.go` |
|
||||
| Reply 生成(规则版) | ✅ 完成 | `internal/service/reply/service.go` |
|
||||
| Handoff 转人工 | ✅ 完成 | `internal/service/handoff/service.go` |
|
||||
| Audit 日志 | ✅ 完成 | `internal/store/memory/audit_store.go`, `internal/store/postgres/audit_store.go` |
|
||||
| PostgreSQL 持久化 | ✅ 完成 | `internal/store/postgres/` |
|
||||
| Migration | ✅ 完成 | `db/migration/` |
|
||||
| HMAC Webhook 安全校验 | ✅ 完成 | `internal/http/handlers/webhook_security.go` |
|
||||
| Sub2API 平台适配 | ✅ 完成 | `internal/platformadapter/sub2api_adapter.go` |
|
||||
| NewAPI 平台适配 | ⚠️ 501 占位 | `internal/platformadapter/newapi_adapter.go` |
|
||||
| OpenAPI 占位文档 | ⚠️ 未找到 | `internal/openapi/` 目录为空? |
|
||||
| Rate Limiting | ✅ 完成 | `internal/platform/httpx/limits.go` |
|
||||
| Ticket 工作流(assign/resolve/close) | ✅ 完成 | `internal/store/postgres/ticket_workflow.go` |
|
||||
| Platform Event Outbox + Dead Letter | ✅ 完成 | `internal/store/postgres/platform_event_store.go` |
|
||||
| Callback Worker + Retry | ✅ 完成 | `internal/service/platformdelivery/worker.go` |
|
||||
|
||||
**OpenAPI 文档缺口**:spec 要求 "生成最小 OpenAPI 占位文档",但 `internal/openapi/` 目录为空,需确认是否已迁移到 `docs/` 或其他位置。
|
||||
|
||||
---
|
||||
|
||||
## 7. 依赖健康度
|
||||
|
||||
| 依赖 | 版本 | 状态 | 备注 |
|
||||
|------|------|------|------|
|
||||
| `github.com/google/uuid` | v1.6.0 | ✅ 最新稳定 | — |
|
||||
| `github.com/lib/pq` | v1.10.9 | ✅ 最新稳定 | 注意:pgx v5 是更现代的替代,但 pq 仍被维护 |
|
||||
|
||||
**建议**:`lib/pq` 已进入维护模式,官方推荐新项目使用 `pgx`。若未来需要连接池高级功能(如 statement cache、batch insert),建议迁移到 `pgx/v5`。
|
||||
|
||||
---
|
||||
|
||||
## 8. 交付风险
|
||||
|
||||
| 风险 | 等级 | 说明 |
|
||||
|------|------|------|
|
||||
| Dirty worktree | 🔴 高 | 15+ modified + 3 untracked,未收口前不应视为可发布基线 |
|
||||
| 测试环境依赖 | 🟡 中 | postgres/e2e/integration 测试需要本地 PostgreSQL 5434 端口 |
|
||||
| Authz 信任边界 | 🟡 中 | RequireRoles 不验证身份真实性,需上游网关配合 |
|
||||
| 覆盖率缺口 | 🟡 中 | ticket_handler.List、newapi_adapter、postgres 包覆盖不足 |
|
||||
| 依赖维护 | 🟢 低 | lib/pq 长期维护模式,但当前无功能缺口 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 结论与评级
|
||||
|
||||
### 综合评级:B+(良好,有条件可进入下一阶段)
|
||||
|
||||
| 维度 | 评级 | 说明 |
|
||||
|------|------|------|
|
||||
| 架构设计 | A | 分层清晰,依赖注入完整,接口隔离到位 |
|
||||
| 安全基线 | A- | SQL 注入零风险,Webhook HMAC 完善,Authz 信任边界需文档化 |
|
||||
| 测试覆盖 | B | 核心业务逻辑覆盖良好,但 postgres 层和 e2e 受环境限制无法验证 |
|
||||
| 代码质量 | B+ | 零 vet 警告,零 race,命名规范,错误码体系完整 |
|
||||
| 交付就绪 | C+ | **Dirty worktree 严重**,未收口前不可视为可发布基线 |
|
||||
|
||||
### 下一步行动
|
||||
|
||||
1. **立即收口**:将当前 dirty worktree 分批次提交,核心代码与文档变更分开 commit
|
||||
2. **修复 P0-2**:同步 Makefile test 目标与 README/CI 的 `-p 1` 约束
|
||||
3. **补充 P1 项测试**:ticket_handler.List、newapi_adapter 的 501 响应
|
||||
4. **文档化 Authz 信任边界**:在 `docs/SECURITY_BOUNDARY.md` 中标注 RequireRoles 的前置条件
|
||||
5. **评估 PostgreSQL 测试策略**:引入 testcontainers-go 或 CI 中预置测试 DB,使 e2e/integration 可在本地完整运行
|
||||
6. **OpenAPI 占位文档**:确认 `internal/openapi/` 是否仍需要补充,或已迁移至其他位置
|
||||
|
||||
---
|
||||
|
||||
## 附录:审查工具链输出
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
cd /home/long/project/ai-customer-service && go build ./...
|
||||
# → exit 0, 零输出
|
||||
|
||||
# 静态分析
|
||||
cd /home/long/project/ai-customer-service && go vet ./...
|
||||
# → exit 0, 零输出
|
||||
|
||||
# Race 检测
|
||||
cd /home/long/project/ai-customer-service && go test -race ./internal/... -count=1 -p 1
|
||||
# → 24/24 ok, 零 DATA RACE
|
||||
|
||||
# 覆盖率(internal/)
|
||||
cd /home/long/project/ai-customer-service && go test ./internal/... -coverprofile=/tmp/ai_cs_cov.out -count=1 -p 1
|
||||
go tool cover -func=/tmp/ai_cs_cov.out | grep "^total"
|
||||
# → total: (statements) 63.4%
|
||||
|
||||
# SQL 注入扫描
|
||||
grep -rn "fmt\.Sprintf.*SELECT\|fmt\.Sprintf.*INSERT\|fmt\.Sprintf.*UPDATE\|fmt\.Sprintf.*DELETE" internal/ --include="*.go" | grep -v _test.go
|
||||
# → 零匹配
|
||||
|
||||
# 参数化查询验证
|
||||
grep -rn "\$[0-9]" internal/store/postgres --include="*.go" | grep -v _test.go | wc -l
|
||||
# → 29
|
||||
|
||||
# 硬编码凭证扫描
|
||||
grep -rn "password.*=\|secret.*=\|api_key.*=\|token.*=.*\"" internal/ --include="*.go" | grep -v "_test.go\|testutil\|factory\|Config\|struct {"
|
||||
# → 仅 webhook security 的配置读取代码,无硬编码
|
||||
|
||||
# Git 状态
|
||||
git status --short
|
||||
# → 15+ modified, 3 untracked
|
||||
```
|
||||
@@ -127,7 +127,6 @@
|
||||
这份文档不是泛化监控说明,而是**灰度放量门禁文档**。
|
||||
任何放量决策都必须引用:
|
||||
|
||||
- [GRAY_DASHBOARD_MINIMUM.md](/home/long/project/立交桥/projects/ai-customer-service/docs/GRAY_DASHBOARD_MINIMUM.md)
|
||||
- [SERVICE_SLA.md](/home/long/project/立交桥/projects/ai-customer-service/prd/SERVICE_SLA.md)
|
||||
- [GRAY_RELEASE_ROLLBACK_RUNBOOK.md](/home/long/project/立交桥/projects/ai-customer-service/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md)
|
||||
|
||||
- [GRAY_DASHBOARD_MINIMUM.md](/home/long/project/ai-customer-service/docs/GRAY_DASHBOARD_MINIMUM.md)
|
||||
- [SERVICE_SLA.md](/home/long/project/ai-customer-service/prd/SERVICE_SLA.md)
|
||||
- [GRAY_RELEASE_ROLLBACK_RUNBOOK.md](/home/long/project/ai-customer-service/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
本记录对应 Task 5 的 Gate B 验证脚本:
|
||||
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
|
||||
脚本覆盖的检查项:
|
||||
|
||||
@@ -43,7 +43,7 @@ AI_CS_RUNTIME_ENV=production \
|
||||
AI_CS_ADDR=127.0.0.1:18080 \
|
||||
AI_CS_POSTGRES_ENABLED=true \
|
||||
AI_CS_POSTGRES_DSN='host=localhost port=5434 user=ai_cs password=ai_cs_secret dbname=ai_customer_service sslmode=disable' \
|
||||
AI_CS_POSTGRES_MIGRATION_DIR='/home/long/project/立交桥/projects/ai-customer-service/db/migration' \
|
||||
AI_CS_POSTGRES_MIGRATION_DIR='/home/long/project/ai-customer-service/db/migration' \
|
||||
AI_CS_WEBHOOK_SECRET='gate-b-secret-20260504' \
|
||||
AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \
|
||||
AI_CS_WEBHOOK_SIGNATURE_HEADER='X-CS-Signature' \
|
||||
@@ -81,8 +81,8 @@ scripts/verify_preprod_gate_b.sh
|
||||
|
||||
2. handler 层 audit 事件 ID 不是合法 UUID,导致 PostgreSQL audit 写入静默失败
|
||||
已修复文件:
|
||||
- [audit_helper.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/handlers/audit_helper.go)
|
||||
- [audit_helper_test.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/handlers/audit_helper_test.go)
|
||||
- [audit_helper.go](/home/long/project/ai-customer-service/internal/http/handlers/audit_helper.go)
|
||||
- [audit_helper_test.go](/home/long/project/ai-customer-service/internal/http/handlers/audit_helper_test.go)
|
||||
|
||||
这两项修复后,Gate B 本地/容器化预演已全部通过。
|
||||
|
||||
@@ -104,4 +104,3 @@ scripts/verify_preprod_gate_b.sh
|
||||
因此当前正确结论是:
|
||||
|
||||
> **Gate B 脚本与本地/容器化联调证据已经建立并通过,但还不能把这直接等同于“真实预生产环境已经放行”。**
|
||||
|
||||
|
||||
207
docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md
Normal file
207
docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# ai-customer-service 生产上线 Closure Board
|
||||
|
||||
> 生成时间:2026-05-11
|
||||
> 基线 commit:`67922c5` (HEAD)
|
||||
> 小龙:验证 review 报告 → QA 交叉核对 → TechLead 制定方案 → 整合 closure
|
||||
> 上游产物:
|
||||
> - `docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md` — 系统性 review 报告
|
||||
> - `docs/REMEDIATION_TASK_BOARD_2026-05-06.md` — 旧整改任务板
|
||||
> - `docs/REMEDIATION_PLAN_2026-05-11.md` — TechLead 技术修复方案
|
||||
|
||||
---
|
||||
|
||||
## 0. 当前门控结论
|
||||
|
||||
| 门控 | 结论 | 证据 |
|
||||
|------|------|------|
|
||||
| 代码级门禁(build/vet/race) | **PARTIAL** | `go build` ✅ / `go vet` ✅ / race ✅ 零 DATA RACE;但 `go test ./...` 整体失败(postgres/e2e/integration 缺少 DB) |
|
||||
| sub2api 单平台主链 | **PASS WITH RISKS** | 已验证 webhook → dialog → outbox → callback worker 链路;但事务外盒不严格、并发 claim 缺失 |
|
||||
| newapi 平台能力 | **FAIL** | 入口返回 501,但 app.go 允许注册 adapter 并启动 worker |
|
||||
| 真实预生产 Gate B | **FAIL** | 未复跑 |
|
||||
| 生产灰度 Gate C | **FAIL** | 未复跑 |
|
||||
| Dirty worktree | **BLOCKED** | 13 modified + 4 untracked,未收口前不可视为可发布基线 |
|
||||
|
||||
**综合评级:BLOCKED — 不可进入生产上线**
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题清单(已整合 QA 交叉验证结果)
|
||||
|
||||
### P0 — 阻塞级(必须修复后才能进展)
|
||||
|
||||
| ID | 问题 | 位置 | 影响 | 责任角色 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| P0-1 | Dirty worktree 未收口 | 全仓库 | 评审边界模糊、回滚困难、CI 基线不稳定 | Engineer | 未开始 |
|
||||
| P0-2 | Makefile test 目标缺少 `-p 1` | `Makefile:2` | 本地执行 `make test` 并发污染共享 DB | Engineer | 未开始 |
|
||||
| L-1 | **newapi 假接通**:app.go 允许注册并启动 worker,但 adapter 返回 501 | `app.go:129-130,186-187` | 能力口径失真,安全配置浪费 | Engineer | 未开始 |
|
||||
|
||||
### P1 — 必须修复
|
||||
|
||||
| ID | 问题 | 位置 | 影响 | 责任角色 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| P1-1 | ticket_handler.List 覆盖率 0% | `ticket_handler.go:33` | 列表接口无回归保护 | Engineer | 未开始 |
|
||||
| P1-2 | newapi_adapter.BuildIngressAck 覆盖率 0% | `newapi_adapter.go:24` | 占位逻辑无验证 | Engineer | 未开始 |
|
||||
| P1-3 | Authz header 伪造风险未文档化 | `middleware/authz.go` | 内部 API 若直接暴露可被绕过 | TechLead/Engineer | 未开始 |
|
||||
| P1-4 | RateLimiter GC 压力 | `httpx/limits.go:67` | 高并发下频繁分配切片 | Engineer | 未开始 |
|
||||
| P1-5 | IPv6 地址在 rate limit key 中被错误截断 | `httpx/limits.go:110-114` | IPv6 客户端共用限流配额 | Engineer | 未开始 |
|
||||
| L-2 | callback_target 契约漂移:builder 写入但 worker 不消费 | `builder.go:31-34` | 数据模型与运行时行为不一致 | TechLead | 未开始 |
|
||||
| L-3 | outbox 并发 claim 缺失 | `platform_event_store.go:78-86` | 多实例重复投递 | TechLead | 未开始 |
|
||||
| L-4 | platform webhook 非严格事务外盒 | `platform_webhook_handler.go:86-103` | 业务状态与事件持久化不一致 | TechLead | 未开始 |
|
||||
| L-5 | E2E 稳定性根因 | `test/e2e/` | 事件顺序异常与 DB 关闭 | QA/Engineer | 未开始 |
|
||||
|
||||
### P2 — 建议修复
|
||||
|
||||
| ID | 问题 | 位置 | 影响 | 责任角色 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| P2-1 | 配置解析失败静默回退 | `config.go:201-255` | 拼写错误的环境变量值被静默忽略 | Engineer | 未开始 |
|
||||
| P2-2 | callback worker 无连接池限制 | `app.go:172` | 默认连接池可能耗尽 | Engineer | 未开始 |
|
||||
| P2-3 | 缺少 SQLite/内存测试回退 | `test/e2e`, `test/integration` | 无 PostgreSQL 时无法跑测试 | Engineer | 未开始 |
|
||||
| P2-4 | worker 缺少优雅关闭等待 | `app.go:164-188` | cancel() 后不等待 worker 真正退出 | Engineer | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 修复任务清单(整合 TechLead 方案 + QA 发现的遗漏项)
|
||||
|
||||
### 阶段 A:收口与基线稳定(P0)
|
||||
|
||||
| 任务 ID | 描述 | 文件 | 角色 | 验证方式 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| A-01 | 批次 1 提交 docs 文件(8 modified + 4 untracked) | 全仓库 | Engineer | `git status --short` 显示 docs 已清 | 未开始 |
|
||||
| A-02 | 批次 2 提交代码文件(3 modified internal) | `internal/` | Engineer | `git status --short` 显示 internal 已清 | 未开始 |
|
||||
| A-03 | 打 tag `v0.9.1-pre` | 全仓库 | Engineer | `git describe --tags` 确认 | 未开始 |
|
||||
| A-04 | 修复 Makefile test 目标 | `Makefile:2` | Engineer | `make test` 执行 `-p 1` | 未开始 |
|
||||
| A-05 | 禁用 newapi 装配与 worker 启动(在实现完成前) | `app.go:129-130,186-187` | Engineer | 读取确认 newapi 分支已关闭 | 未开始 |
|
||||
|
||||
### 阶段 B:P1 代码修复
|
||||
|
||||
| 任务 ID | 描述 | 文件 | 角色 | 验证方式 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| B-01 | 补充 ticket_handler.List 测试 | `ticket_handler_test.go` | Engineer | 覆盖率 > 0% | 未开始 |
|
||||
| B-02 | 补充 newapi_adapter.BuildIngressAck 测试 | `newapi_adapter_test.go` | Engineer | 覆盖率 > 0% | 未开始 |
|
||||
| B-03 | 新建 SECURITY_BOUNDARY.md + authz 注释 | `docs/SECURITY_BOUNDARY.md`, `authz.go` | Engineer | 文件存在且口径一致 | 未开始 |
|
||||
| B-04 | RateLimiter 原地过滤优化 | `httpx/limits.go` | Engineer | `go test -race ./internal/platform/httpx/...` 通过 | 未开始 |
|
||||
| B-05 | IPv6 rate limit key 修复 | `httpx/limits.go` | Engineer | 新增 IPv6 测试通过 | 未开始 |
|
||||
|
||||
### 阶段 C:P2 代码修复
|
||||
|
||||
| 任务 ID | 描述 | 文件 | 角色 | 验证方式 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| C-01 | 生产模式配置严格解析 | `config.go` | Engineer | 设置无效值后启动报错 | 未开始 |
|
||||
| C-02 | callback worker 连接池限制 | `app.go:172` | Engineer | `grep MaxIdleConns` 确认 | 未开始 |
|
||||
| C-03 | e2e/integration skip 回退 | `test/e2e/`, `test/integration/` | Engineer | 无 PostgreSQL 时测试被 skip | 未开始 |
|
||||
| C-04 | worker 优雅关闭等待 | `app.go` | Engineer | SIGTERM 后 5s 内正常退出 | 未开始 |
|
||||
|
||||
### 阶段 D:QA 发现的遗漏项(需 TechLead 先输出架构决策)
|
||||
|
||||
| 任务 ID | 描述 | 文件 | 角色 | 验证方式 | 状态 |
|
||||
|---|---|---|---|---|---|
|
||||
| D-01 | callback_target 契约漂移:删除字段或让 worker 消费 | `builder.go`, `worker.go` | TechLead 决策 → Engineer 实现 | 代码与文档一致 | 未开始 |
|
||||
| D-02 | outbox 并发 claim:行级锁或单实例约束 | `platform_event_store.go` | TechLead 决策 → Engineer 实现 | 多实例场景下无重复投递 | 未开始 |
|
||||
| D-03 | platform webhook 事务外盒:统一事务或文档化边界 | `platform_webhook_handler.go` | TechLead 决策 → Engineer 实现 | 事务一致性证据 | 未开始 |
|
||||
| D-04 | E2E 稳定性:定位事件顺序/数据库关闭根因 | `test/e2e/` | QA 分析 → Engineer 修复 | E2E 多轮复跑稳定 | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 角色责任
|
||||
|
||||
| 角色 | 本轮职责 | 交付物 | 完成判据 |
|
||||
|------|---------|---------|--------|
|
||||
| **Engineer** | 执行 A/B/C 阶段所有代码修复 | 修改后的源码 + 测试 + 验证记录 | 每项有 `go build` / `go vet` / `go test -race` 通过证据 |
|
||||
| **TechLead** | 对 D 阶段输出架构决策;审查 Engineer 交付物 | 架构决策记录(针对 D-01/D-02/D-03) | 决策有文档化记录、不引入回退风险 |
|
||||
| **QA** | 对 A/B/C 做回归验证;对 D-04 做根因分析 | 验证报告 + 漂移检测报告 | 所有 CP 检查点通过 |
|
||||
| **DevOps** | 待 A/B/C/D 全部完成后,执行上线前检查 | 上线检查清单 | 服务启动、健康检查、监控、回滚路径就绪 |
|
||||
| **PM** | 更新对外发布口径(如需要) | 口径更新记录 | 无失真口径 |
|
||||
| **小龙** | 整合、门控、抽样自验、汇报用户 | Closure board + 进度汇报 | 每个阶段有明确通过/阻塞结论 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 最短闭环执行顺序
|
||||
|
||||
```
|
||||
阶段 A:收口与基线
|
||||
├── A-01 提交 docs
|
||||
├── A-02 提交代码
|
||||
├── A-03 打 tag
|
||||
├── A-04 Makefile 修复
|
||||
└── A-05 禁用 newapi 假接通
|
||||
→ QA 回归验证(CP-01, CP-02, CP-03)
|
||||
|
||||
阶段 B:P1 代码修复
|
||||
├── B-01 List 测试
|
||||
├── B-02 newapi 测试
|
||||
├── B-03 Authz 文档
|
||||
├── B-04 RateLimiter 优化
|
||||
└── B-05 IPv6 修复
|
||||
→ QA 回归验证(CP-04 ~ CP-07)
|
||||
|
||||
阶段 C:P2 代码修复
|
||||
├── C-01 配置严格解析
|
||||
├── C-02 连接池限制
|
||||
├── C-03 测试 skip 回退
|
||||
└── C-04 优雅关闭
|
||||
→ QA 回归验证(CP-08 ~ CP-10)
|
||||
|
||||
阶段 D:遗漏项架构决策
|
||||
→ TechLead 输出 D-01/D-02/D-03 决策
|
||||
→ Engineer 按决策执行
|
||||
→ D-04 QA 分析后 Engineer 修复
|
||||
→ QA 回归验证
|
||||
|
||||
阶段 E:上线前门控
|
||||
→ DevOps 执行上线前检查
|
||||
→ 小龙抽样自验
|
||||
→ 更新本 board 为 APPROVED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 明确禁止的错误结论
|
||||
|
||||
在以下任务完成前,禁止出现这些说法:
|
||||
|
||||
- “多平台接入已经完成”
|
||||
- “newapi 已支持,只差联调”
|
||||
- “当前整体生产可上线”
|
||||
- “预生产 / 灰度已通过”
|
||||
- “callback_target 已支持多目标回调”
|
||||
- “平台回调 outbox 已具备多实例生产级可靠性”
|
||||
- “当前 review 报告已全部完成修复” — 未完成 D 阶段前不得如此声明
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证检查点(CP)
|
||||
|
||||
| CP ID | 验证命令 | 通过标准 | 所属阶段 |
|
||||
|---|---|---|---|
|
||||
| CP-01 | `git status --short` | 零 modified / 零 untracked | A |
|
||||
| CP-02 | `make test` | 等价于 `go test ./... -count=1 -p 1`,零失败 | A |
|
||||
| CP-03 | `go test -race ./internal/... -count=1 -p 1` | 24/24 pass,零 DATA RACE | A/B/C |
|
||||
| CP-04 | `go test ./internal/http/handlers/... -coverprofile=/tmp/h.out && go tool cover -func=/tmp/h.out \| grep List` | List 覆盖率 > 0% | B |
|
||||
| CP-05 | `go test ./internal/platformadapter/... -coverprofile=/tmp/pa.out && go tool cover -func=/tmp/pa.out \| grep BuildIngressAck` | BuildIngressAck 覆盖率 > 0% | B |
|
||||
| CP-06 | `go test ./internal/platform/httpx/...` | 全部通过,含 IPv6 用例 | B |
|
||||
| CP-07 | `ls docs/SECURITY_BOUNDARY.md && head -n 20` | 文件存在,首段含 RequireRoles + upstream gateway | B |
|
||||
| CP-08 | `grep -n "sync.WaitGroup\|workerWg" internal/app/app.go` | 出现 Add/Done/Wait 三处 | C |
|
||||
| CP-09 | `grep -n "MaxIdleConns" internal/app/app.go` | 出现 MaxIdleConns 与 MaxIdleConnsPerHost | C |
|
||||
| CP-10 | `go vet ./...` | 零警告 | A/B/C |
|
||||
|
||||
---
|
||||
|
||||
## 7. 交付风险
|
||||
|
||||
| 风险 | 等级 | 说明 | 缓解 |
|
||||
|------|------|------|------|
|
||||
| 批量 commit 引入未评审代码 | 🔴 高 | 13 modified 中包含 code 变更 | 分 docs/code 两批次提交,单独 revert |
|
||||
| RateLimiter 优化引入 panic | 🔴 高 | 原地过滤索引越界 | 改前跑测试覆盖,改后 race 检测 |
|
||||
| Worker shutdown 死锁 | 🔴 高 | WaitGroup 使用不当 | wg.Add 在 goroutine 前,Wait 带超时宕底 |
|
||||
| 配置严格解析打断现有环境 | 🟡 中 | 生产模式下解析失败启动失败 | 仅对生产模式生效,开发环境保持 fallback |
|
||||
| IPv6 兼容改变 IPv4 行为 | 🟡 中 | net.SplitHostPort 可能有边界情况差异 | 保留原有 IPv4 测试用例 |
|
||||
| D 阶段架构决策延迟 | 🟡 中 | TechLead 决策需时间 | 不阻塑 A/B/C 执行 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续跟踪
|
||||
|
||||
- 本 board 位于:`/home/long/project/ai-customer-service/docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md`
|
||||
- 每完成一个阶段,更新本 board 的状态列
|
||||
- 每次更新后,小龙执行抽样自验
|
||||
- 最终目标:所有 CP 检查点通过,所有阶段状态为"已完成",门控结论更新为 APPROVED
|
||||
246
docs/REMEDIATION_PLAN_2026-05-11.md
Normal file
246
docs/REMEDIATION_PLAN_2026-05-11.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# ai-customer-service 生产上线修复方案与技术任务拆解
|
||||
|
||||
> 生成日期:2026-05-11
|
||||
> 基线 commit:`67922c5` (HEAD)
|
||||
> 技术负责人:TechLead
|
||||
> 对应 Review 报告:`docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md`
|
||||
|
||||
---
|
||||
|
||||
## 1. 设计范围
|
||||
|
||||
### 1.1 本次覆盖
|
||||
|
||||
| Review 报告项 | 优先级 | 本次是否覆盖 |
|
||||
|---|---|---|
|
||||
| P0-1 Dirty worktree 收口 | P0 | ✅ 覆盖 |
|
||||
| P0-2 Makefile test 目标缺少 `-p 1` | P0 | ✅ 覆盖 |
|
||||
| P1-1 ticket_handler.List 覆盖率 0% | P1 | ✅ 覆盖 |
|
||||
| P1-2 newapi_adapter.BuildIngressAck 覆盖率 0% | P1 | ✅ 覆盖 |
|
||||
| P1-3 Authz header 伪造风险未文档化 | P1 | ✅ 覆盖 |
|
||||
| P1-4 RateLimiter GC 压力 | P1 | ✅ 覆盖 |
|
||||
| P1-5 IPv6 地址在 rate limit key 中被错误截断 | P1 | ✅ 覆盖 |
|
||||
| P2-1 配置解析失败静默回退 | P2 | ✅ 覆盖 |
|
||||
| P2-2 callback worker 无连接池限制 | P2 | ✅ 覆盖 |
|
||||
| P2-3 缺少 SQLite/内存测试回退 | P2 | ✅ 覆盖 |
|
||||
| P2-4 worker 缺少优雅关闭等待 | P2 | ✅ 覆盖 |
|
||||
|
||||
### 1.2 明确不做
|
||||
|
||||
- **不引入 pgx 替换 lib/pq**:review 报告建议项,但当前无功能缺口,不属于阻塞问题。
|
||||
- **不实现 newapi 完整 ingress 逻辑**:旧 remediation board(D-01/I-01)已覆盖该设计缺口,本次仅补充 `BuildIngressAck` 的单元测试(与 review P1-2 对应),不改变 newapi 仍为 501 占位的事实。
|
||||
- **不引入 testcontainers-go**:P2-3 仅做 skip 回退与文档标注,不做完整容器化测试基础设施。
|
||||
- **不改写 outbox 并发 claim / transactional outbox**:属于旧 remediation board(I-04/I-05)范围,本次不做。
|
||||
|
||||
### 1.3 与 review 报告对应关系
|
||||
|
||||
| 本方案章节 | Review 报告章节 | 问题编号 |
|
||||
|---|---|---|
|
||||
| 3.1 P0 修复 | 5. P0 — 阻塞级 | P0-1, P0-2 |
|
||||
| 3.2 P1 修复 | 5. P1 — 必须修复 | P1-1 ~ P1-5 |
|
||||
| 3.3 P2 修复 | 5. P2 — 建议修复 | P2-1 ~ P2-4 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 修复方案总览
|
||||
|
||||
| 问题 | 技术方案概述 |
|
||||
|---|---|
|
||||
| **P0-1 Dirty worktree** | 分 2 批 commit:① 文档文件(8 modified + 4 untracked docs)② 代码文件(3 modified internal)。commit 后打 tag `v0.9.1-pre`。 |
|
||||
| **P0-2 Makefile test** | `Makefile:2` 改为 `go test ./... -count=1 -p 1`,与 README/CI 保持一致。 |
|
||||
| **P1-1 ticket_handler.List 0%** | 在 `ticket_handler_test.go` 补充 `List` 的成功与错误分支单元测试,使用现有 `mockTicketService` + `memory.TicketStore`。 |
|
||||
| **P1-2 newapi_adapter.BuildIngressAck 0%** | 新建 `internal/platformadapter/newapi_adapter_test.go`,覆盖 `BuildIngressAck(meta=nil)` 与 `meta!=nil` 两个分支。 |
|
||||
| **P1-3 Authz 伪造风险** | 新建 `docs/SECURITY_BOUNDARY.md`,明确 `RequireRoles` 的信任边界;在 `authz.go` 函数注释中标注“依赖上游网关完成真实身份验证”。 |
|
||||
| **P1-4 RateLimiter GC 压力** | `limits.go:67-73` 将 `var valid []time.Time` 新分配改为原地双指针过滤,复用 `sw.tokens` 底层数组。 |
|
||||
| **P1-5 IPv6 截断** | `limits.go:110-114` 将手动 `lastIndexByte(addr, ':')` 替换为 `net.SplitHostPort`,正确提取 IPv6 host;补充 IPv6 单元测试。 |
|
||||
| **P2-1 配置静默回退** | `config.go` 新增 `mustGetEnvInt` / `mustGetEnvBool`(解析失败返回 error);在 `Load()` 生产模式下对关键数值型配置启用严格解析。 |
|
||||
| **P2-2 worker 连接池** | `app.go:172` 将裸 `&http.Client{Timeout: ...}` 替换为显式配置 `Transport.MaxIdleConns` / `MaxIdleConnsPerHost` 的 client。 |
|
||||
| **P2-3 测试回退** | `test/e2e/*_test.go` 与 `test/integration/*_test.go` 在 `TestMain` 或每个 `Test*` 开头增加 PostgreSQL 连通性检测,不通则 `t.Skip`。 |
|
||||
| **P2-4 worker 优雅关闭** | `app.go` 在 startWorker 中引入 `sync.WaitGroup`:`wg.Add(1)` + goroutine `defer wg.Done()`;closer 中 `cancel()` 后执行 `wg.Wait()`(带 5s 超时兜底)。 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 任务拆解表
|
||||
|
||||
> 粒度约束:每个任务 2-5 分钟,必须有具体文件路径和函数名。
|
||||
|
||||
### 3.1 P0 — 阻塞级
|
||||
|
||||
| 任务 ID | 文件路径 | 函数/位置 | 预期变更 | 估计耗时 |
|
||||
|---|---|---|---|---|
|
||||
| **P0-1a** | 全仓库 | `git status` | 确认 dirty 文件清单,按 "docs" / "code" 分组 | 2 min |
|
||||
| **P0-1b** | 全仓库 | `git commit` | 批次 1:提交 12 个 docs 文件(modified + untracked);消息 `docs: sync review reports, runbooks, and checklists` | 3 min |
|
||||
| **P0-1c** | 全仓库 | `git commit` | 批次 2:提交 3 个 internal/ 文件;消息 `fix: platform event store and builder drift` | 3 min |
|
||||
| **P0-1d** | 全仓库 | `git tag` | 打 tag `v0.9.1-pre`;推送 tag | 2 min |
|
||||
| **P0-2a** | `Makefile:2` | `test` target | 将 `go test ./...` 改为 `go test ./... -count=1 -p 1` | 2 min |
|
||||
|
||||
### 3.2 P1 — 必须修复
|
||||
|
||||
| 任务 ID | 文件路径 | 函数/位置 | 预期变更 | 估计耗时 |
|
||||
|---|---|---|---|---|
|
||||
| **P1-1a** | `internal/http/handlers/ticket_handler_test.go` | `TestTicketHandlerList_Success` | 新增测试:向 `mockTicketService.tickets` Create 2 条 ticket,调用 `h.List`,断言返回 200 且 `items` 数组长度为 2 | 3 min |
|
||||
| **P1-1b** | `internal/http/handlers/ticket_handler_test.go` | `TestTicketHandlerList_ServiceError` | 新增测试:注入一个返回 error 的 `TicketService` mock(或改 `ListOpen` 返回 `errors.New("db down")`),断言返回 500 且 error code 为 `CS_SYS_5002` | 3 min |
|
||||
| **P1-2a** | `internal/platformadapter/newapi_adapter_test.go` | `TestNewAPIAdapter_BuildIngressAck_NilMeta` | 新建文件与测试:`adapter.BuildIngressAck(nil, nil)` 断言返回 `map[string]any{"accepted":false,"platform":"newapi"}` | 3 min |
|
||||
| **P1-2b** | `internal/platformadapter/newapi_adapter_test.go` | `TestNewAPIAdapter_BuildIngressAck_WithMeta` | 同上文件:传入 `&PlatformInboundMeta{EventID:"evt-1"}`,断言返回包含 `event_id:"evt-1"` | 2 min |
|
||||
| **P1-3a** | `docs/SECURITY_BOUNDARY.md` | — | 新建文档:明确标注 `internal/http/middleware/authz.go:RequireRoles` 仅做 RBAC 白名单校验,不验证 header 真实性;生产部署必须前置 API Gateway / JWT 验证 | 3 min |
|
||||
| **P1-3b** | `internal/http/middleware/authz.go:42` | `RequireRoles` | 在函数注释头增加 `// SECURITY: This middleware trusts the upstream gateway to authenticate the actor headers.` | 2 min |
|
||||
| **P1-4a** | `internal/platform/httpx/limits.go:67-73` | `RateLimiter.Allow` | 将 `var valid []time.Time` + `append` 循环改为原地双指针过滤:`n := 0; for _, t := range sw.tokens { if t.After(cutoff) { sw.tokens[n] = t; n++ } }; sw.tokens = sw.tokens[:n]` | 3 min |
|
||||
| **P1-4b** | `internal/platform/httpx/limits_test.go` | 现有测试 | 运行 `go test -race ./internal/platform/httpx/...`,确认零 DATA RACE | 2 min |
|
||||
| **P1-5a** | `internal/platform/httpx/limits.go:98-115` | `rateLimitKey` | 导入 `net`;将 `lastIndexByte(addr, ':')` 截断逻辑替换为 `host, _, err := net.SplitHostPort(addr)`,err==nil 则返回 host,否则返回原值 | 3 min |
|
||||
| **P1-5b** | `internal/platform/httpx/limits.go:117-124` | `lastIndexByte` | 删除 `lastIndexByte` 函数(若已无其他引用) | 2 min |
|
||||
| **P1-5c** | `internal/platform/httpx/limits_test.go` | `TestRateLimitKey_IPv6` | 新增测试:`rateLimitKey` 对 `req.RemoteAddr = "[::1]:8080"` 应返回 `"::1"`;对 `"[2001:db8::1]:8080"` 应返回 `"2001:db8::1"` | 3 min |
|
||||
|
||||
### 3.3 P2 — 建议修复
|
||||
|
||||
| 任务 ID | 文件路径 | 函数/位置 | 预期变更 | 估计耗时 |
|
||||
|---|---|---|---|---|
|
||||
| **P2-1a** | `internal/config/config.go:201-255` | `getEnvInt` / `getEnvBool` | 新增 `mustGetEnvInt(key string) (int, error)` 与 `mustGetEnvBool(key string) (bool, error)`:解析失败时返回 `fmt.Errorf` 而非静默 fallback | 3 min |
|
||||
| **P2-1b** | `internal/config/config.go:66-148` | `Load()` | 生产模式下,对 `AI_CS_WEBHOOK_MAX_SKEW_SECONDS` 等关键数值配置若环境变量存在但解析失败,返回 error(可选:仅替换最危险的 2-3 个字段以控制范围) | 4 min |
|
||||
| **P2-2a** | `internal/app/app.go:172` | `startWorker` 内 client 创建 | 将 `&http.Client{Timeout: ...}` 替换为 `&http.Client{Timeout: ..., Transport: &http.Transport{MaxIdleConns: 100, MaxIdleConnsPerHost: 10}}` | 3 min |
|
||||
| **P2-3a** | `test/e2e/*_test.go` | `TestMain` 或首个 Test | 增加 `pgCheck()`:尝试 `sql.Open("postgres", dsn).Ping()`,失败则 `t.Skip("PostgreSQL not available")` | 3 min |
|
||||
| **P2-3b** | `test/integration/*_test.go` | `TestMain` 或首个 Test | 同上增加 skip 逻辑 | 3 min |
|
||||
| **P2-4a** | `internal/app/app.go:158-188` | `startWorker` 闭包 | 在 `New` 函数内声明 `var workerWg sync.WaitGroup`;`startWorker` 中 `workerWg.Add(1)`;goroutine 内 `defer workerWg.Done()`;`worker.Start(workerCtx)` | 3 min |
|
||||
| **P2-4b** | `internal/app/app.go:164-167` | worker closer | 将 closer 改为:`cancel(); done := make(chan struct{}); go func() { workerWg.Wait(); close(done) }(); select { case <-done: return nil; case <-time.After(5 * time.Second): return fmt.Errorf("worker %s shutdown timeout", platform) }` | 4 min |
|
||||
| **P2-4c** | `internal/app/app.go` | `import` | 确认新增 `sync` 和 `time`(time 通常已有)导入 | 2 min |
|
||||
|
||||
---
|
||||
|
||||
## 4. 风险与保护
|
||||
|
||||
### 4.1 风险清单
|
||||
|
||||
| 风险 ID | 来源任务 | 风险描述 | 等级 |
|
||||
|---|---|---|---|
|
||||
| R-01 | P0-1b/c | 批量 commit 可能将未评审代码带入基线 | 🟡 中 |
|
||||
| R-02 | P1-4a | 限流器原地过滤改动若索引越界,可能 panic 核心路径 | 🔴 高 |
|
||||
| R-03 | P1-5a | `net.SplitHostPort` 对 IPv6 兼容但可能改变 IPv4 行为(实际不会,但需验证) | 🟡 中 |
|
||||
| R-04 | P2-1b | 严格配置解析可能破坏现有开发/测试环境(如 `AI_CS_MAX_BODY_BYTES=1MB` 拼写错误导致启动失败) | 🟡 中 |
|
||||
| R-05 | P2-4a/b | `sync.WaitGroup` 使用不当可能导致 `Shutdown` 死锁或 panic(如 `wg.Add` 在 goroutine 启动后调用) | 🔴 高 |
|
||||
| R-06 | 全量 | 任何代码改动引入 race condition | 🟡 中 |
|
||||
|
||||
### 4.2 降级策略
|
||||
|
||||
| 风险 ID | 降级策略 |
|
||||
|---|---|
|
||||
| R-01 | commit 前执行 `git diff --cached` 复核;docs 与代码分开 commit,一旦有问题可单独 revert。 |
|
||||
| R-02 | ① 改前通读 `limits_test.go` 确保有覆盖;② 改后必跑 `go test -race ./internal/platform/httpx/...`;③ 若发现异常,立即回滚到 `var valid []time.Time` 方案。 |
|
||||
| R-03 | 在 `limits_test.go` 中保留原有 IPv4 用例并追加 IPv6 用例;若 CI 失败,回滚到字符串处理但修复 IPv6 专用分支。 |
|
||||
| R-04 | P2-1b 仅对生产模式 (`cfg.Runtime.Env == "production"`) 生效;开发/测试环境保持静默回退。若生产启动失败,工程师可立即切回旧 `getEnvInt`。 |
|
||||
| R-05 | ① `wg.Add(1)` 必须紧接在 `go func()` 之前(同一线程);② closer 中 `wg.Wait()` 必须带 `time.After` 超时;③ 改后运行 `go test ./internal/app/...` 并手动发送 SIGTERM 验证无死锁。 |
|
||||
| R-06 | 全量代码任务完成后统一执行 `go test -race ./internal/... -count=1 -p 1`;任何 race 报告阻塞合并。 |
|
||||
|
||||
---
|
||||
|
||||
## 5. QA 交接与实施约束
|
||||
|
||||
### 5.1 编码后漂移检查点(QA 可验证)
|
||||
|
||||
| 检查点 ID | 验证命令 / 步骤 | 通过标准 |
|
||||
|---|---|---|
|
||||
| CP-01 | `cd /home/long/project/ai-customer-service && git status --short` | 零 modified / 零 untracked(或仅有本次计划外的新 review 报告) |
|
||||
| CP-02 | `make test` | 等价于 `go test ./... -count=1 -p 1`,零失败(postgres/e2e/integration skip 属于预期行为) |
|
||||
| CP-03 | `go test -race ./internal/... -count=1 -p 1` | 24/24 pass,零 DATA RACE |
|
||||
| CP-04 | `go test ./internal/http/handlers/... -coverprofile=/tmp/handlers.out && go tool cover -func=/tmp/handlers.out \| grep ticket_handler.go` | `List` 函数覆盖率 > 0% |
|
||||
| CP-05 | `go test ./internal/platformadapter/... -coverprofile=/tmp/pa.out && go tool cover -func=/tmp/pa.out \| grep newapi_adapter.go` | `BuildIngressAck` 覆盖率 > 0% |
|
||||
| CP-06 | `go test ./internal/platform/httpx/...` | 全部通过,包括新增 IPv6 用例 |
|
||||
| CP-07 | `ls docs/SECURITY_BOUNDARY.md && head -n 20 docs/SECURITY_BOUNDARY.md` | 文件存在,且首段包含 "RequireRoles" 和 "upstream gateway" 关键词 |
|
||||
| CP-08 | `grep -n "sync.WaitGroup\|workerWg" internal/app/app.go` | 至少出现 `workerWg.Add(1)`、`defer workerWg.Done()`、`workerWg.Wait()` 三处 |
|
||||
| CP-09 | `grep -n "MaxIdleConns" internal/app/app.go` | 出现 `MaxIdleConns` 与 `MaxIdleConnsPerHost` |
|
||||
| CP-10 | `go vet ./...` | 零警告 |
|
||||
|
||||
### 5.2 必查真实调用链路
|
||||
|
||||
| 链路 | 验证方式 |
|
||||
|---|---|
|
||||
| **RateLimiter 核心路径** | `TestRateLimiter_WithRateLimit` 必须实际触发 `Allow` 并通过;QA 可单步确认 `rateLimitKey` 返回预期值。 |
|
||||
| **Authz 信任边界** | QA 手动阅读 `docs/SECURITY_BOUNDARY.md` 与 `authz.go` 注释,确认两者口径一致。 |
|
||||
| **Worker Graceful Shutdown** | QA 本地启动服务后发送 SIGTERM(`kill -TERM <pid>`),观察日志确认 worker 在 5s 内完成退出,无 `shutdown timeout` error。 |
|
||||
| **Config 严格模式** | QA 设置 `AI_CS_RUNTIME_ENV=production` + `AI_CS_WEBHOOK_MAX_SKEW_SECONDS=not_a_number`,启动服务应报错并退出。 |
|
||||
|
||||
---
|
||||
|
||||
## 6. Engineer 实施说明
|
||||
|
||||
### 6.1 文件级落点
|
||||
|
||||
| 目标文件 | 落点行号 | 改动性质 |
|
||||
|---|---|---|
|
||||
| `Makefile` | 第 2 行 | 替换 |
|
||||
| `internal/http/handlers/ticket_handler_test.go` | 文件末尾 | 追加 2 个 Test 函数 |
|
||||
| `internal/platformadapter/newapi_adapter_test.go` | 新建 | 2 个 Test 函数 |
|
||||
| `docs/SECURITY_BOUNDARY.md` | 新建 | Markdown 文档 |
|
||||
| `internal/http/middleware/authz.go` | 第 42 行上方 | 添加注释块 |
|
||||
| `internal/platform/httpx/limits.go` | 第 67-73 行 | 替换为原地过滤 |
|
||||
| `internal/platform/httpx/limits.go` | 第 98-124 行 | 替换 `rateLimitKey` + 删除 `lastIndexByte` |
|
||||
| `internal/platform/httpx/limits_test.go` | 文件末尾 | 追加 IPv6 Test 函数 |
|
||||
| `internal/config/config.go` | 第 201-255 行之后 | 追加 `mustGetEnvInt` / `mustGetEnvBool` |
|
||||
| `internal/config/config.go` | 第 66-148 行 | 条件性替换部分 `getEnvInt` 调用 |
|
||||
| `internal/app/app.go` | 第 158-188 行 | 重构 startWorker 闭包 |
|
||||
| `internal/app/app.go` | 第 172 行 | 替换 http.Client 创建 |
|
||||
| `test/e2e/*_test.go` | 首个 Test 或 TestMain | 追加 skip 逻辑 |
|
||||
| `test/integration/*_test.go` | 首个 Test 或 TestMain | 追加 skip 逻辑 |
|
||||
|
||||
### 6.2 最小验证项(Engineer 每完成一个 P1/P2 任务必须自测)
|
||||
|
||||
1. `go build ./...` 零错误。
|
||||
2. `go vet ./...` 零警告。
|
||||
3. 涉及改动的包:`go test -race ./<changed_pkg>/...` 通过。
|
||||
4. 若修改了 exported 函数签名,确认调用方编译通过。
|
||||
|
||||
---
|
||||
|
||||
## 7. 阶段门控结论
|
||||
|
||||
### 7.1 当前状态
|
||||
|
||||
- **设计完整性**:本方案已覆盖 review 报告全部 P0/P1/P2 项,任务粒度 <= 5 分钟,文件路径与函数名已精确锁定。
|
||||
- **风险可控性**:P1-4/P2-4 有较高风险,但已设计明确的降级策略(超时兜底 + race 检测 + 回滚路径)。
|
||||
- **与旧 remediation board 兼容性**:
|
||||
- 本次 P1-2(newapi_adapter 测试)与旧 board 的 I-01(newapi 假接通)正交:本次仅补测试覆盖,不改变 newapi 仍为 501 占位的事实。
|
||||
- 本次不涉及旧 board 的 D-01/D-02/D-03/D-04(平台能力矩阵、callback_target 契约、outbox 并发 claim),这些仍按旧 board 排期执行。
|
||||
|
||||
### 7.2 结论
|
||||
|
||||
**✅ 可进入 Engineer 实现。**
|
||||
|
||||
前提条件:Engineer 必须严格按照本方案第 3 章的任务拆解顺序执行,禁止自行扩大范围(如顺带重写 newapi adapter 或引入 pgx)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 下游执行约束摘要
|
||||
|
||||
### 8.1 Engineer 禁止偏离
|
||||
|
||||
- **禁止**在修复 P1-2 时顺带实现 newapi 完整 ingress 逻辑(仍保持 501 占位)。
|
||||
- **禁止**改动 `lib/pq` 为 `pgx`。
|
||||
- **禁止**修改任何不属于本方案列出的文件,除非发现编译阻断。
|
||||
- **禁止**跳过 `go test -race` 自测。
|
||||
- P0-1 必须分 2 批 commit(docs / code),禁止一次性混提交。
|
||||
|
||||
### 8.2 QA 必查链路
|
||||
|
||||
- `make test` 行为与 `-p 1` 一致性。
|
||||
- `go test -race ./internal/... -count=1 -p 1` 零 race。
|
||||
- ticket_handler.List 与 newapi_adapter.BuildIngressAck 的覆盖率从 0% 提升到 > 0%。
|
||||
- `docs/SECURITY_BOUNDARY.md` 与 `authz.go` 注释口径一致。
|
||||
- IPv6 rate limit key 的正确性(通过单元测试)。
|
||||
- Worker graceful shutdown 的 SIGTERM 手动验证。
|
||||
|
||||
### 8.3 XL(TechLead / 负责人)必补门控
|
||||
|
||||
- P0-1 commit 后复核 `git log --oneline -5` 与 `git status`,确认 worktree 已清。
|
||||
- P0-1d 打 tag 后,XL 必须亲自确认 tag 存在:`git describe --tags`。
|
||||
- 全量任务完成后,XL 执行一次 `go test ./... -count=1 -p 1` 并留存输出截图/文本作为最终证据。
|
||||
- 旧 remediation board 中与本方案无冲突的项(D-01 ~ I-05)继续保留,不得因本次方案而关闭或删除。
|
||||
|
||||
---
|
||||
|
||||
## 自检清单(返回时显式列出打勾状态)
|
||||
|
||||
- [x] 架构设计覆盖 review 报告所有 P0/P1 项
|
||||
- [x] 每个任务 < 5分钟,有明确文件路径
|
||||
- [x] 风险评估完整
|
||||
- [x] 降级策略已设计
|
||||
- [x] 实施漂移检测点已定义
|
||||
- [x] 已明确标记是否可进入 Engineer 实现
|
||||
- [x] 已给出 Engineer / QA / XL 的下游执行约束摘要
|
||||
151
docs/REMEDIATION_TASK_BOARD_2026-05-06.md
Normal file
151
docs/REMEDIATION_TASK_BOARD_2026-05-06.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# ai-customer-service 整改任务板(基于 2026-05-06 最新 review)
|
||||
|
||||
> 生成时间:2026-05-06
|
||||
> 代码基线:`ee3a31e`
|
||||
> 目标:把本轮 review 结论转成可执行整改任务,而不是停留在结论层。
|
||||
> 当前总状态:**代码级门禁通过;sub2api 单平台主链通过但带风险;newapi 平台能力未通过;真实预生产 / 生产放行门禁未通过。**
|
||||
|
||||
---
|
||||
|
||||
## 0. 使用规则
|
||||
|
||||
- 状态仅允许:`未开始 / 进行中 / 已完成 / 已阻塞`
|
||||
- 每项必须包含:责任角色、交付物、完成判据、验证方式、阻塞依赖
|
||||
- 任何“已完成”必须附文件证据或命令证据
|
||||
- 任务按四段拆分:`设计 / 实现 / 验证 / 放行证据`
|
||||
- 未完成设计段,不得直接跳实现
|
||||
- 未完成验证段,不得更新为“可放行”
|
||||
- 未完成放行证据段,不得对外给出“预生产通过 / 可灰度 / 可上线”口径
|
||||
|
||||
---
|
||||
|
||||
## 1. 当前阶段门控结论
|
||||
|
||||
### 1.1 代码级门禁
|
||||
- 结论:**PARTIAL / 不稳定**
|
||||
- 依据:`go build ./...`、`go vet ./...` 当前通过;但本轮再次实测 `go test ./... -count=1` 失败,失败用例为 `test/e2e/sub2api_callback_flow_test.go:186`,实际 `event[0].type = reply.generated`,预期 `message.received`,并伴随 `sql: database is closed`
|
||||
|
||||
### 1.2 sub2api 单平台主链
|
||||
- 结论:**PASS WITH RISKS / E2E 当前不稳定**
|
||||
- 已验证链路:webhook → dialog → outbox → callback worker
|
||||
- 仍有风险:严格事务外盒不成立、并发 claim 缺失、callback_target 语义未落地、E2E 已再次复现顺序异常与 `sql: database is closed`
|
||||
|
||||
### 1.3 newapi 平台能力
|
||||
- 结论:**FAIL**
|
||||
- 直接证据:
|
||||
- `internal/platformadapter/newapi_adapter.go:20-21` 仍直接返回 `501 not implemented`
|
||||
- `internal/app/app.go:129-130` 仍允许注册 `newapi` adapter
|
||||
- `internal/app/app.go:186-187` 仍允许启动 `newapi` callback worker
|
||||
|
||||
### 1.4 真实预生产 / 生产放行
|
||||
- 结论:**FAIL**
|
||||
- 依据:`test/QA_GATE_STATUS.md`、`prd/PRODUCTION_CHECKLIST.md` 仍明确:代码级通过,但真实共享预生产 Gate B 与生产 Gate C 未闭环
|
||||
|
||||
---
|
||||
|
||||
## 2. 整改总览(按 gap taxonomy)
|
||||
|
||||
| Gap Type | 当前问题 | 优先级 | 默认责任角色 |
|
||||
|---|---|---|---|
|
||||
| design_gap | 平台启用状态与真实可用能力未统一建模 | P1 | TechLead |
|
||||
| implementation_gap | newapi 入口未实现但可装配;callback_target 运行时未消费 | P1 | Engineer |
|
||||
| test_gap | newapi 无对等正向链路测试;sub2api callback 稳定性回归不足 | P1 | QA / Engineer |
|
||||
| evidence_gap | 真实共享预生产 / 灰度环境证据未闭环 | P0 | QA / DevOps |
|
||||
| call_chain_gap | newapi 对外看似支持,真实入口未接通 | P1 | TechLead / Engineer |
|
||||
| contract_gap | event.CallbackTarget 字段语义与 worker 实际行为不一致 | P1 | TechLead |
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计段任务(先收口设计与口径)
|
||||
|
||||
| ID | 优先级 | 任务 | 责任角色 | 交付物 | 完成判据 | 验证方式 | 依赖 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| D-01 | P1 | 收口平台能力矩阵,明确 `sub2api`/`newapi` 的真实支持级别 | TechLead | 平台能力设计说明 | 明确区分:已生产可用 / 占位未实现 / 禁止启用 | 设计文档包含平台能力表、入口/出口/测试覆盖状态 | 无 | 未开始 |
|
||||
| D-02 | P1 | 定义“平台启用”与“平台真实可用”统一门控规则 | TechLead | 装配门控设计 | 明确哪些条件满足后才允许注册 adapter / 启动 worker | 文档中给出代码落点与判定规则 | D-01 | 未开始 |
|
||||
| D-03 | P1 | 定义 `callback_target` 的正式契约:删除伪能力或落地多目标路由 | TechLead | 契约设计说明 | 不能再存在“模型字段存在但运行时完全不消费”的状态 | 文档中明确 event model、worker 路由、回调配置关系 | 无 | 未开始 |
|
||||
| D-04 | P1 | 设计 outbox 并发 claim / 多实例投递策略 | TechLead | 并发与重试设计 | 至少明确单实例约束,或设计 `claim/skip locked` 方案 | 设计文档中有状态流转与失败恢复说明 | 无 | 未开始 |
|
||||
| D-05 | P0 | 更新项目对外发布口径:只允许宣称“sub2api 代码级可用”,禁止宣称“多平台已完成 / 生产可上线” | PM | 更新后的发布/状态文档口径 | 相关文档不再出现失真口径 | 复核文档章节与结论语句 | 无 | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 实现段任务(把设计缺口落到代码)
|
||||
|
||||
| ID | 优先级 | 任务 | 责任角色 | 交付物 | 完成判据 | 验证方式 | 依赖 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| I-01 | P1 | 处理 newapi 假接通:二选一——禁用装配,或补完整 ingress 实现 | Engineer | 代码修改 + 测试 | 不能再出现“入口未实现但仍可注册/启动 worker” | `go test ./... -count=1`;读取关键文件确认分支收口 | D-01, D-02 | 未开始 |
|
||||
| I-02 | P1 | 让 worker 与平台能力门控一致,不允许未实现平台独立启动 callback worker | Engineer | 代码修改 + 测试 | worker 启动条件与平台真实能力一致 | 读取 `internal/app/app.go` 关键分支;相关测试通过 | D-02 | 未开始 |
|
||||
| I-03 | P1 | 处理 `callback_target` 契约漂移:删除字段伪能力或在 worker 中真正消费 | Engineer | 代码修改 + 测试 | 数据模型与运行时行为一致 | 读取 builder / event / worker;定向测试通过 | D-03 | 未开始 |
|
||||
| I-04 | P1 | 补 outbox 并发 claim 机制或显式限制单实例语义并固化到代码/文档 | Engineer | 代码或明确限制说明 | 不再处于“默认可多实例、实际会重复投递”的灰区 | 代码/文档与测试或注释证据一致 | D-04 | 未开始 |
|
||||
| I-05 | P2 | 评估并落地更严格 transactional outbox 方案,或明确记录当前非强一致边界 | TechLead / Engineer | 设计决策记录 + 代码/文档 | 团队对一致性边界有明确基线 | 决策文档 + 相关代码/文档证据 | D-04 | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证段任务(不是“改完就算完成”)
|
||||
|
||||
| ID | 优先级 | 任务 | 责任角色 | 交付物 | 完成判据 | 验证方式 | 依赖 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| V-01 | P1 | 为 newapi 增加对等验证:若实现则补 integration/e2e;若禁用则补门控测试 | QA / Engineer | 测试代码 + 验证记录 | newapi 状态与测试口径一致,不再空缺 | `go test ./test/integration ./test/e2e -count=1` 或对应定向用例 | I-01, I-02 | 未开始 |
|
||||
| V-02 | P1 | 对 sub2api callback 链路做多轮稳定性复跑,专查事件顺序和 DB 关闭时机脆弱性 | QA | 稳定性复跑记录 | 至少形成重复执行证据,并记录是否复现 `reply.generated`/`message.processing` 顺序异常与 `database is closed` 类问题 | 定向多轮 `go test` 记录 | 无 | 未开始 |
|
||||
| V-03 | P1 | 对 outbox 并发 / 重试 / dead-letter 行为补定向验证 | QA / Engineer | 测试与验证报告 | claim/重试/死信路径与设计一致 | 定向测试命令 + 输出 | I-03, I-04 | 未开始 |
|
||||
| V-04 | P1 | 回收旧 review 漂移:更新/替代 `CODE_REVIEW_REPORT.md` 等过期结论 | QA / PM | 新旧报告差异说明 | 不再让过期 P0/P1 结论继续作为现状依据 | 文档 diff + QA 复核 | D-05 | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 放行证据段任务(真实环境,不是仓库内自嗨)
|
||||
|
||||
| ID | 优先级 | 任务 | 责任角色 | 交付物 | 完成判据 | 验证方式 | 依赖 | 状态 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| E-01 | P0 | 在共享预生产环境复跑 Gate B 并留痕 | QA / DevOps | 预生产验证记录 | 启动、DB、migration、webhook、ticket、audit、ready 全链路留痕 | 复跑脚本与记录文件 | 无 | 未开始 |
|
||||
| E-02 | P0 | 在共享预生产 / 灰度环境完成监控接线与回滚演练留痕 | DevOps / QA | 监控与回滚证据 | 不再只有本地/容器化演练记录 | 真实环境演练记录、告警/仪表盘证据 | E-01 | 未开始 |
|
||||
| E-03 | P1 | 形成“平台能力 + 环境门禁”统一放行页 | XL / PM / QA | 放行基线文档 | 明确平台支持边界、代码级门禁、预生产门禁、生产门禁 | 文档校验无失真口径 | D-05, E-01, E-02 | 未开始 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 最短闭环执行顺序
|
||||
|
||||
### 7.1 当前最短关键路径
|
||||
1. V-02 先定位并修复 `sub2api` callback E2E 不稳定失败,恢复 `go test ./...` 稳定通过能力
|
||||
2. D-01 收口平台能力矩阵
|
||||
3. D-02 收口平台启用 / worker 启动门控
|
||||
4. I-01 处理 newapi 假接通
|
||||
5. I-02 同步 worker 启动逻辑
|
||||
6. V-01 补 newapi 对等验证
|
||||
7. E-01 共享预生产 Gate B 留痕
|
||||
8. E-02 共享预生产 / 灰度环境运行证据留痕
|
||||
9. E-03 更新统一放行页
|
||||
|
||||
### 7.2 原则
|
||||
- 若目标是先消除“能力口径失真”,优先做 D-01/D-02/I-01/I-02/V-01
|
||||
- 若目标是先冲放行证据,仍不能跳过平台能力收口;否则会带着失真口径进入预生产
|
||||
|
||||
---
|
||||
|
||||
## 8. 明确禁止的错误结论
|
||||
|
||||
在以下任务完成前,禁止出现这些说法:
|
||||
|
||||
- “多平台接入已经完成”
|
||||
- “newapi 已支持,只差联调”
|
||||
- “当前整体生产可上线”
|
||||
- “预生产 / 灰度已通过”
|
||||
- “callback_target 已支持多目标回调”
|
||||
- “平台回调 outbox 已具备多实例生产级可靠性”
|
||||
|
||||
---
|
||||
|
||||
## 9. 当前最小可接受对外口径
|
||||
|
||||
1. 代码级门禁:通过
|
||||
2. sub2api 单平台主链:通过,但带重要风险
|
||||
3. newapi:未通过,不应按已接入汇报
|
||||
4. 真实预生产 Gate B:未通过
|
||||
5. 生产灰度 Gate C:未通过
|
||||
|
||||
---
|
||||
|
||||
## 10. 任务板维护要求
|
||||
|
||||
- 每完成一项,必须同步:状态、证据、对应文档
|
||||
- 任何“已完成”若无命令输出或文件证据,视为无效
|
||||
- 若验证失败,必须把失败现象写回对应任务,不允许口头跳过
|
||||
- 小龙负责最终事实校准,不接受“看起来差不多”
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 审查时间:2026-05-04
|
||||
> 审查方式:静态代码审查 + 文档对照 + 本地构建/测试验证
|
||||
> 审查范围:`/home/long/project/立交桥/projects/ai-customer-service`
|
||||
> 审查范围:`/home/long/project/ai-customer-service`
|
||||
|
||||
## 1. 结论摘要
|
||||
|
||||
|
||||
290
docs/REVIEW_REPORT_2026-05-06.md
Normal file
290
docs/REVIEW_REPORT_2026-05-06.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# ai-customer-service 最新审查报告(2026-05-06)
|
||||
|
||||
> 审查时间:2026-05-06
|
||||
> 代码基线:`ee3a31e77c00d47802e7b3a54057467b3a713400`
|
||||
> 审查方式:小龙主审 + 双视角 QA 复核 + 现有门禁文档对照
|
||||
> 目标:给出当前代码与放行状态的真实结论,回收过期结论,并把问题转成可执行整改基线。
|
||||
> 配套任务板:`docs/REMEDIATION_TASK_BOARD_2026-05-06.md`
|
||||
|
||||
---
|
||||
|
||||
## 0. 阶段门控结论
|
||||
|
||||
### 总结论
|
||||
- **代码级门禁:PARTIAL / 不稳定**
|
||||
- **sub2api 单平台主链:PASS WITH RISKS**
|
||||
- **newapi 平台能力:FAIL**
|
||||
- **真实共享预生产 Gate B:FAIL**
|
||||
- **生产灰度 Gate C:FAIL**
|
||||
|
||||
### 是否可进入下一阶段
|
||||
- **是否可按“整体生产可上线”口径放行:否**
|
||||
- **是否可按“多平台能力已完成”口径汇报:否**
|
||||
- **是否可按“sub2api 单平台代码级链路已可用”口径汇报:是,但必须同时声明关键风险与未完成门禁**
|
||||
|
||||
### 当前最小可接受口径
|
||||
1. 代码级门禁已通过
|
||||
2. sub2api 的 webhook → outbox → callback worker 主链已接通
|
||||
3. newapi 当前未通过,不应按已接入汇报
|
||||
4. 真实共享预生产 / 灰度环境证据未闭环,因此不能按生产可上线口径放行
|
||||
|
||||
---
|
||||
|
||||
## 1. 审查输入与证据来源
|
||||
|
||||
### 1.1 已读取的关键代码
|
||||
- `cmd/ai-customer-service/main.go`
|
||||
- `internal/app/app.go`
|
||||
- `internal/http/router.go`
|
||||
- `internal/http/handlers/platform_webhook_handler.go`
|
||||
- `internal/http/handlers/platform_webhook_security.go`
|
||||
- `internal/platformadapter/sub2api_adapter.go`
|
||||
- `internal/platformadapter/newapi_adapter.go`
|
||||
- `internal/service/platformevents/builder.go`
|
||||
- `internal/service/platformdelivery/worker.go`
|
||||
- `internal/store/postgres/platform_event_store.go`
|
||||
|
||||
### 1.2 已读取的关键文档
|
||||
- `docs/CODE_REVIEW_REPORT.md`
|
||||
- `docs/RECTIFICATION_REVIEW_REPORT_V2.md`
|
||||
- `docs/REVIEW_REPORT_2026-05-04.md`
|
||||
- `docs/P0_P1_P2_RECTIFICATION_EXECUTION_BOARD.md`
|
||||
- `docs/PRODUCTION_LAUNCH.md`
|
||||
- `test/QA_GATE_STATUS.md`
|
||||
- `prd/PRODUCTION_CHECKLIST.md`
|
||||
|
||||
### 1.3 已采纳的实测依据
|
||||
- 本轮主审曾捕获两次 `go test ./...` 失败信号:
|
||||
- `test/e2e/sub2api_callback_flow_test.go:186`
|
||||
- 一次实际 `event[0].type = message.processing`,预期 `message.received`
|
||||
- 本轮复核再次实际 `event[0].type = reply.generated`,预期 `message.received`
|
||||
- 两次都伴随 `platform callback event delivery failed` 与 `sql: database is closed`
|
||||
- 曾有并行 QA 复核在当时现态仓库上复跑 `go test ./... -count=1`:通过
|
||||
- 但本轮再次实测 `go test ./... -count=1`:失败
|
||||
- 因此当前最真实口径应为:`go build ./...`、`go vet ./...` 通过;`go test ./...` 存在不稳定失败,代码级门禁不能再写成稳定 PASS
|
||||
|
||||
### 1.4 证据解释原则
|
||||
- `go build ./...`、`go vet ./...` 当前可支持通过结论
|
||||
- `go test ./...` 当前不能稳定支持通过口径,应视为不稳定失败状态
|
||||
- 真实失败信号不能被抹掉,应作为当前事实与稳定性风险保留
|
||||
- 不以历史报告或角色自报覆盖当前代码与命令事实
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前真实状态
|
||||
|
||||
### 2.1 已成立的事实
|
||||
1. `sub2api` 主链路已真实接通,不是只有定义没有调用
|
||||
2. `newapi` 不是“待联调”,而是“入口未实现但装配可开”的未通过状态
|
||||
3. 代码级门禁与本地/仓库内验证不能等同于真实预生产或生产放行
|
||||
4. 当前最主要的阻塞,不再是早期报告中的旧 P0,而是:
|
||||
- 平台能力口径失真
|
||||
- callback / outbox 一致性与扩展性边界未收口
|
||||
- 真实环境放行证据未闭环
|
||||
|
||||
### 2.2 已不应继续沿用的旧结论
|
||||
以下表述对当前代码已不再成立,不应继续作为“当前事实”引用:
|
||||
1. `RateLimiter` 存在 P0 并发写问题
|
||||
2. ticket resolve/close 不区分不存在与状态冲突
|
||||
3. 后台接口“完全无鉴权”
|
||||
4. prod 默认仍可 fallback 到 memory
|
||||
5. readiness 相关生产约束仍未收紧
|
||||
|
||||
说明:
|
||||
- 这些项在旧报告中曾成立或部分成立,但已与当前代码和现有门禁文档不一致
|
||||
- 继续引用会造成报告漂移
|
||||
|
||||
### 2.3 仍然成立的高层判断
|
||||
1. 当前项目还不是完整 PRD 意义上的完整 AI 客服系统
|
||||
2. 当前不能按“整体生产可上线”口径放行
|
||||
3. 文档漂移仍是风险,只是漂移重心已经从早期代码 P0 转向旧报告口径过期
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键调用链路核查
|
||||
|
||||
### 3.1 sub2api 主链路核查
|
||||
|
||||
#### 结论
|
||||
- **PASS WITH RISKS**
|
||||
|
||||
#### 四层核查
|
||||
1. 定义
|
||||
- `PlatformAdapter` 接口存在
|
||||
- `Sub2APIAdapter` 已实现
|
||||
- `Worker`、`PlatformEventStore`、event builder 均存在
|
||||
2. 装配
|
||||
- `internal/app/app.go` 会在配置开启时注册 `sub2api` adapter
|
||||
- 在 `platformEvents != nil` 且配置满足时启动 `sub2api` worker
|
||||
3. 调用
|
||||
- `router` → `PlatformWebhookHandler`
|
||||
- `PlatformWebhookHandler` → `registry.Resolve(platform)` → adapter `ParseInbound`
|
||||
- `dialog.Process` 后进入 `BuildInboundEvents`
|
||||
- `InsertPendingBatch` 写入 outbox
|
||||
- worker `RunOnce` / `deliver` 发起 callback
|
||||
4. 入口
|
||||
- `/api/v1/customer-service/platforms/sub2api/webhook`
|
||||
|
||||
#### 结论解释
|
||||
- 这条链路是实链路,不是“有结构、没接线”
|
||||
- 但它仍然带有一致性、并发和稳定性风险,不能直接放大为“平台回调系统已生产级完成”
|
||||
|
||||
### 3.2 newapi 主链路核查
|
||||
|
||||
#### 结论
|
||||
- **FAIL**
|
||||
|
||||
#### 直接证据
|
||||
1. `internal/platformadapter/newapi_adapter.go:20-21`
|
||||
- `ParseInbound` 直接返回 `501 not implemented`
|
||||
2. `internal/app/app.go:129-130`
|
||||
- 当 `cfg.PlatformAdapters.NewAPI.Enabled` 时,仍会注册 `newapi` adapter
|
||||
3. `internal/app/app.go:186-187`
|
||||
- `startWorker("newapi", cfg.PlatformAdapters.NewAPI)` 仍可能启动 `newapi` callback worker
|
||||
|
||||
#### 结论解释
|
||||
- 当前状态不是“newapi 已接通但未验证”,而是“配置上看似支持,真实入口未实现”
|
||||
- 这是典型 `call_chain_gap + implementation_gap`
|
||||
|
||||
---
|
||||
|
||||
## 4. 自动化与验证结果结论
|
||||
|
||||
| 检查项 | 结论 | 说明 |
|
||||
|---|---|---|
|
||||
| 代码级门禁 | PARTIAL / 不稳定 | `go build ./...`、`go vet ./...` 当前通过;`go test ./... -count=1` 本轮再次失败于 `test/e2e/sub2api_callback_flow_test.go:186` |
|
||||
| sub2api 主链存在性 | PASS | 入口、调用、outbox、worker 四层均可追踪 |
|
||||
| newapi 平台能力 | FAIL | 入口未实现但装配仍可打开 |
|
||||
| callback worker 基本投递能力 | PASS | 成功/失败/重试/死信路径存在 |
|
||||
| callback_target 契约一致性 | FAIL | 字段存在,但 worker 不消费该字段 |
|
||||
| outbox 并发投递安全性 | PARTIAL | `ListDue` 无 claim / `skip locked`,多实例下有重复投递窗口 |
|
||||
| outbox 强一致性 | PARTIAL | 非严格 transactional outbox |
|
||||
| 真实共享预生产 Gate B | FAIL | 当前仍缺真实共享预生产复跑与留痕 |
|
||||
| 生产灰度 Gate C | FAIL | 当前仍缺真实监控接线、灰度稳定性与回滚证据 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 问题清单
|
||||
|
||||
### Critical
|
||||
|
||||
#### C-01 真实共享预生产 / 生产放行证据未闭环
|
||||
- 影响:不能按“生产可上线”口径放行
|
||||
- 证据:
|
||||
- `test/QA_GATE_STATUS.md`
|
||||
- `prd/PRODUCTION_CHECKLIST.md`
|
||||
- 当前状态:代码级通过,但真实共享预生产 Gate B 和生产 Gate C 仍未通过
|
||||
- 建议:继续按 Gate B / Gate C 在真实共享环境复跑并留痕
|
||||
|
||||
### Important
|
||||
|
||||
#### I-01 newapi 平台入口未实现但仍可装配/启动 worker
|
||||
- 影响:对外口径容易失真,误报为“平台已支持”
|
||||
- 证据:
|
||||
- `internal/platformadapter/newapi_adapter.go:20-21`
|
||||
- `internal/app/app.go:129-130`
|
||||
- `internal/app/app.go:186-187`
|
||||
- 建议:二选一收口
|
||||
1. 禁止未实现平台被注册/启动 worker
|
||||
2. 补完整 newapi ingress 实现与对等测试
|
||||
|
||||
#### I-02 平台启用状态与真实可用能力未统一建模
|
||||
- 影响:未完成平台也可能启动 callback worker,形成半接通状态
|
||||
- 证据:`internal/app/app.go:158-187`
|
||||
- 建议:把 adapter 注册条件、worker 启动条件、入口可用条件收成同一门控
|
||||
|
||||
#### I-03 `callback_target` 契约与运行时行为不一致
|
||||
- 影响:数据模型表达了能力,运行时并未支持,后续易造成静默错投或错误认知
|
||||
- 证据:
|
||||
- event builder 写入 `callback_target`
|
||||
- `worker.go` 实际只使用 `Worker.CallbackURL`
|
||||
- 建议:删除伪能力,或真正按 event target 路由
|
||||
|
||||
#### I-04 outbox 多实例并发投递风险未收口
|
||||
- 影响:水平扩展时可能重复投递
|
||||
- 证据:`internal/store/postgres/platform_event_store.go:78-86` 的 `ListDue` 仅查询,不做 claim / 锁定
|
||||
- 建议:补 claim 机制、`FOR UPDATE SKIP LOCKED` 或显式限定单实例运行边界
|
||||
|
||||
#### I-05 严格事务外盒未成立
|
||||
- 影响:业务主写成功但 outbox 写失败时,一致性不可保证
|
||||
- 证据:业务处理与 `InsertPendingBatch` 不在同一事务中
|
||||
- 建议:后续评估 transactional outbox 或明确记录一致性边界
|
||||
|
||||
#### I-06 sub2api callback E2E 已再次复现失败,说明链路存在现实不稳定性
|
||||
- 影响:当前 `go test ./...` 不能稳定通过,代码级门禁不能再按稳定 PASS 对外汇报
|
||||
- 证据:本轮复核再次在 `test/e2e/sub2api_callback_flow_test.go:186` 失败,实际出现 `reply.generated` 先于 `message.received`,并伴随 `sql: database is closed`
|
||||
- 建议:先把该问题提升为当前优先闭环项,做定向稳定性定位与修复,再恢复代码级门禁口径
|
||||
|
||||
### Minor
|
||||
|
||||
#### M-01 旧 review 文档存在显著漂移
|
||||
- 影响:继续引用会误导后续判断
|
||||
- 典型文档:
|
||||
- `docs/CODE_REVIEW_REPORT.md`
|
||||
- `docs/PRODUCTION_EXECUTION_PLAN.md`
|
||||
- 建议:以后续新报告和任务板为准,逐步回收旧结论
|
||||
|
||||
---
|
||||
|
||||
## 6. Gap Taxonomy Summary
|
||||
|
||||
| Gap Type | 具体表现 | 主责任方向 |
|
||||
|---|---|---|
|
||||
| design_gap | 平台启用 / 平台可用性门控未统一;多实例投递策略未正式定义 | TechLead |
|
||||
| implementation_gap | newapi 入口未实现;callback_target 运行时未消费 | Engineer |
|
||||
| test_gap | newapi 缺对等测试;sub2api callback E2E 已复现不稳定失败,需优先定位与修复 | QA / Engineer |
|
||||
| evidence_gap | 真实共享预生产 / 灰度环境证据未闭环 | QA / DevOps |
|
||||
| call_chain_gap | newapi 看似支持但入口未接通 | TechLead / Engineer |
|
||||
| contract_gap | callback_target 字段与 worker 真实行为不一致 | TechLead |
|
||||
|
||||
---
|
||||
|
||||
## 7. 与旧报告的关系
|
||||
|
||||
### 7.1 可以继续保留的主结论
|
||||
- 当前不能按“整体生产可上线”口径放行
|
||||
- 代码级通过不等于预生产和生产放行通过
|
||||
- 文档漂移仍需持续治理
|
||||
|
||||
### 7.2 必须回收的过期口径
|
||||
- 旧版把若干已修复问题继续当作当前 P0
|
||||
- 旧版把“无鉴权 / prod fallback / readiness 过宽”继续描述为当前代码事实
|
||||
- 旧版若暗示“修完早期两个 P0 即可灰度”,现阶段已不再成立
|
||||
|
||||
### 7.3 新基线文件
|
||||
后续应以以下文件作为当前基线:
|
||||
- `docs/REVIEW_REPORT_2026-05-06.md`
|
||||
- `docs/REMEDIATION_TASK_BOARD_2026-05-06.md`
|
||||
- `test/QA_GATE_STATUS.md`
|
||||
- `prd/PRODUCTION_CHECKLIST.md`
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续执行建议
|
||||
|
||||
### 最短闭环顺序
|
||||
1. 先定位并修复 `sub2api` callback E2E 不稳定失败,恢复 `go test ./...` 稳定通过能力
|
||||
2. 收口平台能力矩阵
|
||||
3. 收口平台启用 / worker 启动统一门控
|
||||
4. 处理 newapi 假接通
|
||||
5. 补 newapi 对等验证
|
||||
6. 在共享预生产环境复跑 Gate B 并留痕
|
||||
7. 在共享预生产 / 灰度环境补监控接线与回滚证据
|
||||
|
||||
### 对应任务板
|
||||
- 详见:`docs/REMEDIATION_TASK_BOARD_2026-05-06.md`
|
||||
|
||||
---
|
||||
|
||||
## 9. 最终判定
|
||||
|
||||
**当前项目应被定义为:**
|
||||
|
||||
> **`go build ./...` 与 `go vet ./...` 当前通过,但 `go test ./...` 已再次实测失败,说明代码级门禁并不稳定;sub2api 单平台回调主链已可用,但仍带重要一致性/并发/稳定性风险;newapi 当前未通过;真实共享预生产与生产放行门禁未闭环,因此不能按“整体生产可上线”或“多平台已完成”口径汇报。**
|
||||
|
||||
正式门控结论:
|
||||
- **代码级门禁:部分通过 / 当前不稳定**
|
||||
- **sub2api 单平台主链:通过,但带重要风险且 E2E 当前不稳定**
|
||||
- **newapi 平台能力:未通过**
|
||||
- **真实共享预生产门禁:未通过**
|
||||
- **生产放行门禁:未通过**
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
本记录对应 Gate C 回滚演练脚本:
|
||||
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
|
||||
脚本覆盖的检查项:
|
||||
|
||||
@@ -39,7 +39,7 @@ AI_CS_RUNTIME_ENV=production \
|
||||
AI_CS_ADDR=127.0.0.1:18081 \
|
||||
AI_CS_POSTGRES_ENABLED=true \
|
||||
AI_CS_POSTGRES_DSN='host=localhost port=5434 user=ai_cs password=ai_cs_secret dbname=ai_customer_service sslmode=disable' \
|
||||
AI_CS_POSTGRES_MIGRATION_DIR='/home/long/project/立交桥/projects/ai-customer-service/db/migration' \
|
||||
AI_CS_POSTGRES_MIGRATION_DIR='/home/long/project/ai-customer-service/db/migration' \
|
||||
AI_CS_WEBHOOK_SECRET='gate-c-secret-20260505' \
|
||||
AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \
|
||||
AI_CS_WEBHOOK_SIGNATURE_HEADER='X-CS-Signature' \
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
预生产 Gate B 不再建议靠零散手工命令拼接验证。优先使用:
|
||||
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
- 最近一次实测记录:[PREPROD_VERIFICATION_RECORD.md](/home/long/project/立交桥/projects/ai-customer-service/docs/PREPROD_VERIFICATION_RECORD.md)
|
||||
- Gate C 回滚演练入口:[scripts/verify_gate_c_rollback.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- 最近一次回滚演练记录:[ROLLBACK_DRILL_RECORD.md](/home/long/project/立交桥/projects/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
- 最近一次实测记录:[PREPROD_VERIFICATION_RECORD.md](/home/long/project/ai-customer-service/docs/PREPROD_VERIFICATION_RECORD.md)
|
||||
- Gate C 回滚演练入口:[scripts/verify_gate_c_rollback.sh](/home/long/project/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- 最近一次回滚演练记录:[ROLLBACK_DRILL_RECORD.md](/home/long/project/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
|
||||
脚本会完成:
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
|
||||
基线文档:
|
||||
|
||||
- [CONFIG_CONTRACT_BASELINE.md](/home/long/project/立交桥/projects/ai-customer-service/docs/CONFIG_CONTRACT_BASELINE.md)
|
||||
- [CONFIG_CONTRACT_BASELINE.md](/home/long/project/ai-customer-service/docs/CONFIG_CONTRACT_BASELINE.md)
|
||||
|
||||
必须至少能回答:
|
||||
|
||||
@@ -204,11 +204,11 @@
|
||||
|
||||
执行入口:
|
||||
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
- [scripts/verify_preprod_gate_b.sh](/home/long/project/ai-customer-service/scripts/verify_preprod_gate_b.sh)
|
||||
|
||||
对应证据模板:
|
||||
|
||||
- [PREPROD_VERIFICATION_RECORD.md](/home/long/project/立交桥/projects/ai-customer-service/docs/PREPROD_VERIFICATION_RECORD.md)
|
||||
- [PREPROD_VERIFICATION_RECORD.md](/home/long/project/ai-customer-service/docs/PREPROD_VERIFICATION_RECORD.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -236,11 +236,11 @@
|
||||
|
||||
执行入口:
|
||||
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
|
||||
对应证据模板:
|
||||
|
||||
- [ROLLBACK_DRILL_RECORD.md](/home/long/project/立交桥/projects/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
- [ROLLBACK_DRILL_RECORD.md](/home/long/project/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
201
docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md
Normal file
201
docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# ai-customer-service 快照差异清单(2026-05-08)
|
||||
|
||||
## 背景
|
||||
|
||||
本清单用于比较以下两个目录,并给出后续合并策略:
|
||||
|
||||
- 主仓:`/home/long/project/ai-customer-service`
|
||||
- 快照:`/home/long/project/ai-customer-service-lijiaoqiao-snapshot-2026-05-08`
|
||||
|
||||
目标不是整包回灌快照,而是只挑有明确工程价值的差异,避免把 `立交桥` 大仓里的分叉历史污染回主仓。
|
||||
|
||||
## 本轮已合并
|
||||
|
||||
这一组改动已回灌主仓,不再重复评估:
|
||||
|
||||
- [internal/service/platformevents/builder.go](/home/long/project/ai-customer-service/internal/service/platformevents/builder.go:34)
|
||||
- 事件时间从毫秒级顺延改为纳秒级顺延
|
||||
- `NextAttemptAt` 对齐到事件自身时间
|
||||
- [internal/store/postgres/platform_event_store.go](/home/long/project/ai-customer-service/internal/store/postgres/platform_event_store.go:81)
|
||||
- `ListDue` 排序从 `next_attempt_at, created_at` 强化为
|
||||
`next_attempt_at, occurred_at, created_at, id`
|
||||
- [internal/store/postgres/platform_event_store_test.go](/home/long/project/ai-customer-service/internal/store/postgres/platform_event_store_test.go:59)
|
||||
- 测试改为验证真实的顺序语义,而不是只比较插入次序
|
||||
|
||||
结论:这组改动有完整闭环,已经属于“已吸收差异”。
|
||||
|
||||
## 分类结果
|
||||
|
||||
## 逐项判定表
|
||||
|
||||
下表覆盖当前剩余差异的逐项处理结论,只使用两种状态:
|
||||
|
||||
- `保留主仓`:主仓内容继续作为唯一演进基线,不从快照回灌
|
||||
- `仅归档`:快照内容只保留在快照目录中,供审计/回看,不进入主仓
|
||||
|
||||
| 路径 | 判定 | 备注 |
|
||||
|------|------|------|
|
||||
| `.git/` | `保留主仓` | 主仓独立 Git 仓库元数据 |
|
||||
| `.gitea/` | `保留主仓` | 主仓仓库外壳配置 |
|
||||
| `.github/` | `保留主仓` | 主仓 CI/模板配置 |
|
||||
| `.tmp/` | `仅归档` | 快照运行期临时产物 |
|
||||
| `CONTRIBUTING.md` | `保留主仓` | 主仓独有仓库文档 |
|
||||
| `README.md` | `保留主仓` | 主仓独有仓库说明 |
|
||||
| `docs/CODE_REVIEW_REPORT.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/MONITORING_ALERTING.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/PREPROD_VERIFICATION_RECORD.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/REVIEW_REPORT_2026-05-04.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/ROLLBACK_DRILL_RECORD.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/RUNBOOK.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md` | `保留主仓` | 主仓新增的差异收敛文档 |
|
||||
| `docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `internal/domain/audit/audit.go` | `保留主仓` | 快照为更早版本,主要是格式/旧内容 |
|
||||
| `internal/domain/audit/audit_test.go` | `保留主仓` | 快照未提供更强断言 |
|
||||
| `internal/domain/error/cserrors/codes_test.go` | `保留主仓` | 主要是格式对齐差异 |
|
||||
| `internal/domain/intent/intent.go` | `保留主仓` | 主要是常量对齐差异 |
|
||||
| `internal/domain/session/session.go` | `保留主仓` | 主要是常量对齐差异 |
|
||||
| `internal/domain/session/session_test.go` | `保留主仓` | 未发现新增行为覆盖 |
|
||||
| `internal/domain/ticket/ticket.go` | `保留主仓` | 主要是 struct 对齐差异 |
|
||||
| `internal/domain/ticket/ticket_test.go` | `保留主仓` | 未发现新增行为覆盖 |
|
||||
| `internal/domain/ticketstats/stats.go` | `保留主仓` | 主要是格式差异 |
|
||||
| `internal/http/handlers/health_handler_test.go` | `保留主仓` | 仅测试格式差异 |
|
||||
| `internal/http/handlers/webhook_handler_test.go` | `保留主仓` | 仅末尾换行差异 |
|
||||
| `internal/http/handlers/webhook_security_test.go` | `保留主仓` | 仅空行/格式差异 |
|
||||
| `internal/platform/httpx/limits_test.go` | `保留主仓` | 未发现更强断言 |
|
||||
| `internal/service/dialog/service_test.go` | `保留主仓` | 主要是格式差异 |
|
||||
| `internal/service/reply/service_test.go` | `保留主仓` | 主要是格式差异 |
|
||||
| `internal/store/postgres/platform_event_store_test.go` | `保留主仓` | 顺序语义部分已吸收,其余不再单独回灌 |
|
||||
| `prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `test/QA_CHECKLIST.md` | `保留主仓` | 已改为新路径口径 |
|
||||
| `test/e2e/sub2api_callback_flow_test.go` | `保留主仓` | 快照版放宽断言,不回灌 |
|
||||
| `test/integration/dialog_service_test.go` | `保留主仓` | 主要是格式差异 |
|
||||
| `test/integration/ticket_stats_handler_test.go` | `保留主仓` | 主要是格式差异 |
|
||||
|
||||
附加说明:
|
||||
|
||||
- 快照目录本身 `ai-customer-service-lijiaoqiao-snapshot-2026-05-08/` 的整体状态是 `仅归档`
|
||||
- 已并入主仓的两份快照独有文档不再列为“剩余差异”:
|
||||
- [REMEDIATION_TASK_BOARD_2026-05-06.md](/home/long/project/ai-customer-service/docs/REMEDIATION_TASK_BOARD_2026-05-06.md:1)
|
||||
- [REVIEW_REPORT_2026-05-06.md](/home/long/project/ai-customer-service/docs/REVIEW_REPORT_2026-05-06.md:1)
|
||||
|
||||
### 1. 旧版本 / 不合并
|
||||
|
||||
这些差异要么明显更旧,要么只是格式变化,要么会削弱现有严格约束,不建议继续回灌。
|
||||
|
||||
#### 纯格式或对齐差异
|
||||
|
||||
- [internal/domain/intent/intent.go](/home/long/project/ai-customer-service/internal/domain/intent/intent.go:1)
|
||||
- [internal/domain/session/session.go](/home/long/project/ai-customer-service/internal/domain/session/session.go:1)
|
||||
- [internal/domain/ticket/ticket.go](/home/long/project/ai-customer-service/internal/domain/ticket/ticket.go:1)
|
||||
- [internal/domain/audit/audit.go](/home/long/project/ai-customer-service/internal/domain/audit/audit.go:1)
|
||||
- [test/integration/ticket_stats_handler_test.go](/home/long/project/ai-customer-service/test/integration/ticket_stats_handler_test.go:1)
|
||||
- [internal/domain/error/cserrors/codes_test.go](/home/long/project/ai-customer-service/internal/domain/error/cserrors/codes_test.go:1)
|
||||
- [internal/http/handlers/webhook_handler_test.go](/home/long/project/ai-customer-service/internal/http/handlers/webhook_handler_test.go:1)
|
||||
- [internal/http/handlers/webhook_security_test.go](/home/long/project/ai-customer-service/internal/http/handlers/webhook_security_test.go:1)
|
||||
- [internal/http/handlers/health_handler_test.go](/home/long/project/ai-customer-service/internal/http/handlers/health_handler_test.go:1)
|
||||
- [internal/platform/httpx/limits_test.go](/home/long/project/ai-customer-service/internal/platform/httpx/limits_test.go:1)
|
||||
- [internal/service/dialog/service_test.go](/home/long/project/ai-customer-service/internal/service/dialog/service_test.go:1)
|
||||
- [internal/service/reply/service_test.go](/home/long/project/ai-customer-service/internal/service/reply/service_test.go:1)
|
||||
- [internal/domain/audit/audit_test.go](/home/long/project/ai-customer-service/internal/domain/audit/audit_test.go:1)
|
||||
- [internal/domain/session/session_test.go](/home/long/project/ai-customer-service/internal/domain/session/session_test.go:1)
|
||||
- [internal/domain/ticket/ticket_test.go](/home/long/project/ai-customer-service/internal/domain/ticket/ticket_test.go:1)
|
||||
- [internal/domain/ticketstats/stats.go](/home/long/project/ai-customer-service/internal/domain/ticketstats/stats.go:1)
|
||||
- [test/integration/dialog_service_test.go](/home/long/project/ai-customer-service/test/integration/dialog_service_test.go:1)
|
||||
|
||||
判断依据:
|
||||
|
||||
- diff 主要表现为字段对齐、空行、表格对齐、末尾换行等
|
||||
- 未发现新增业务分支、校验逻辑或更严格的断言
|
||||
- 快照时间普遍更早,不构成“新逻辑来源”
|
||||
|
||||
#### 不应回灌的测试放宽
|
||||
|
||||
- [test/e2e/sub2api_callback_flow_test.go](/home/long/project/ai-customer-service/test/e2e/sub2api_callback_flow_test.go:1)
|
||||
|
||||
不回灌原因:
|
||||
|
||||
- 快照版删除了 `resetE2EPlatformDB`
|
||||
- 快照版去掉了更严格的顺序断言,改成“事件集合存在即可”
|
||||
- 快照版把 dead-letter 断言从精确数量收窄成 `> 0`
|
||||
|
||||
这会降低主仓对“顺序稳定”和“事件数量精确性”的要求,不适合直接合并。
|
||||
|
||||
#### 仓库外壳与临时文件
|
||||
|
||||
- `.tmp/`
|
||||
- `.gitea/`
|
||||
- `.github/`
|
||||
- `README.md`
|
||||
- `CONTRIBUTING.md`
|
||||
|
||||
处理建议:
|
||||
|
||||
- `.tmp/` 明显属于临时产物,不合并
|
||||
- `.gitea/.github/README/CONTRIBUTING` 属于仓库外壳差异,和本轮业务逻辑 merge 无关,单独处理
|
||||
|
||||
### 2. 仅文档口径差异
|
||||
|
||||
这些文件主要是路径从旧目录切到新目录、审查范围说明、运行记录引用变化。它们不涉及业务逻辑。
|
||||
|
||||
- [docs/CODE_REVIEW_REPORT.md](/home/long/project/ai-customer-service/docs/CODE_REVIEW_REPORT.md:1)
|
||||
- [docs/MONITORING_ALERTING.md](/home/long/project/ai-customer-service/docs/MONITORING_ALERTING.md:1)
|
||||
- [docs/PREPROD_VERIFICATION_RECORD.md](/home/long/project/ai-customer-service/docs/PREPROD_VERIFICATION_RECORD.md:1)
|
||||
- [docs/REVIEW_REPORT_2026-05-04.md](/home/long/project/ai-customer-service/docs/REVIEW_REPORT_2026-05-04.md:1)
|
||||
- [docs/ROLLBACK_DRILL_RECORD.md](/home/long/project/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md:1)
|
||||
- [docs/RUNBOOK.md](/home/long/project/ai-customer-service/docs/RUNBOOK.md:1)
|
||||
- [docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md](/home/long/project/ai-customer-service/docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md:1)
|
||||
- [docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md](/home/long/project/ai-customer-service/docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md:1)
|
||||
- [prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md](/home/long/project/ai-customer-service/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md:1)
|
||||
- [test/QA_CHECKLIST.md](/home/long/project/ai-customer-service/test/QA_CHECKLIST.md:1)
|
||||
|
||||
说明:
|
||||
|
||||
- 这些文件里旧路径已经在主仓改到新位置
|
||||
- 快照中的文档差异本质上不再构成 merge 任务
|
||||
- 两份快照独有文档已另行并入主仓:
|
||||
- [REMEDIATION_TASK_BOARD_2026-05-06.md](/home/long/project/ai-customer-service/docs/REMEDIATION_TASK_BOARD_2026-05-06.md:1)
|
||||
- [REVIEW_REPORT_2026-05-06.md](/home/long/project/ai-customer-service/docs/REVIEW_REPORT_2026-05-06.md:1)
|
||||
|
||||
### 3. 值得继续 cherry-pick 的少量代码改动
|
||||
|
||||
本轮筛查后,剩余差异里没有发现第二组像“平台事件顺序稳定性”那样成体系、且明显优于主仓现状的代码改动。
|
||||
|
||||
当前结论:
|
||||
|
||||
- **已确认并合并的唯一高价值代码组**:平台事件顺序稳定性
|
||||
- **剩余代码差异**:暂时归为旧版本/格式差异/测试放宽,不建议继续自动 cherry-pick
|
||||
|
||||
## 建议的后续动作
|
||||
|
||||
### 立即可做
|
||||
|
||||
1. 保留快照目录,不删除:
|
||||
`/home/long/project/ai-customer-service-lijiaoqiao-snapshot-2026-05-08`
|
||||
2. 把本清单作为后续 merge 的唯一入口,避免重新做全量 diff
|
||||
|
||||
### 只有在出现明确需求时再做
|
||||
|
||||
1. 如果要恢复 `sub2api adapter` 方向的某一块能力,再按文件逐个重看快照中的:
|
||||
- `internal/store/postgres/platform_event_store_test.go`
|
||||
- `test/e2e/sub2api_callback_flow_test.go`
|
||||
- `internal/service/platformevents/builder.go`
|
||||
2. 如果要统一仓库外壳,再单独比较:
|
||||
- `.gitea/`
|
||||
- `.github/`
|
||||
- `README.md`
|
||||
- `CONTRIBUTING.md`
|
||||
|
||||
## 当前结论
|
||||
|
||||
快照已经完成了它该做的事:
|
||||
|
||||
- 作为迁移前的完整保底副本
|
||||
- 提供了一组已成功吸收的高价值顺序修复
|
||||
- 其余差异不值得继续大范围回灌
|
||||
|
||||
因此,后续策略应当是:
|
||||
|
||||
- **主仓继续前进**
|
||||
- **快照只保留作审计和按需检索**
|
||||
- **不再做整包式合并**
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
当前服务真实接收的消息结构在:
|
||||
|
||||
- [message.go](/home/long/project/立交桥/projects/ai-customer-service/internal/domain/message/message.go)
|
||||
- [message.go](/home/long/project/ai-customer-service/internal/domain/message/message.go)
|
||||
|
||||
真实字段如下:
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
|
||||
真实入口在:
|
||||
|
||||
- [router.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/router.go)
|
||||
- [webhook_handler.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/handlers/webhook_handler.go)
|
||||
- [router.go](/home/long/project/ai-customer-service/internal/http/router.go)
|
||||
- [webhook_handler.go](/home/long/project/ai-customer-service/internal/http/handlers/webhook_handler.go)
|
||||
|
||||
可用路径:
|
||||
|
||||
@@ -192,7 +192,7 @@ decoder.DisallowUnknownFields()
|
||||
|
||||
真实逻辑在:
|
||||
|
||||
- [webhook_security.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/handlers/webhook_security.go)
|
||||
- [webhook_security.go](/home/long/project/ai-customer-service/internal/http/handlers/webhook_security.go)
|
||||
|
||||
默认请求头:
|
||||
|
||||
|
||||
@@ -124,8 +124,8 @@ Gate C 前至少完成一次回滚演练,且留下证据:
|
||||
|
||||
推荐入口:
|
||||
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- 最近一次本地/容器化记录:[ROLLBACK_DRILL_RECORD.md](/home/long/project/立交桥/projects/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
- [scripts/verify_gate_c_rollback.sh](/home/long/project/ai-customer-service/scripts/verify_gate_c_rollback.sh)
|
||||
- 最近一次本地/容器化记录:[ROLLBACK_DRILL_RECORD.md](/home/long/project/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# AI-Customer-Service 生产一期 QA 检查清单
|
||||
|
||||
> 生成时间:2026-04-30
|
||||
> 项目路径:/home/long/project/立交桥/projects/ai-customer-service
|
||||
> 项目路径:/home/long/project/ai-customer-service
|
||||
> 覆盖范围:文档-实现一致性 · 威胁建模 · AC/失败路径/安全/性能矩阵 · 灰度回滚 · 漂移检测 · 阻断条件
|
||||
|
||||
---
|
||||
@@ -317,7 +317,7 @@ Ticket Resolve → 工单状态变更 → 审计写入
|
||||
|
||||
```bash
|
||||
# 快速回归(当前可执行)
|
||||
cd /home/long/project/立交桥/projects/ai-customer-service
|
||||
cd /home/long/project/ai-customer-service
|
||||
go test ./test/e2e/... ./test/integration/... ./internal/http/handlers/... ./internal/config/... -v
|
||||
|
||||
# 覆盖率报告(需补齐)
|
||||
|
||||
Reference in New Issue
Block a user