From 9319583ee333f8b280452c4ccca284f358f8a2bd Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 11 May 2026 12:19:15 +0800 Subject: [PATCH] docs: sync review reports, runbooks, and checklists --- docs/CODE_REVIEW_REPORT.md | 4 +- ...ODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md | 326 ++++++++++++++++++ docs/MONITORING_ALERTING.md | 7 +- docs/PREPROD_VERIFICATION_RECORD.md | 9 +- docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md | 207 +++++++++++ docs/REMEDIATION_PLAN_2026-05-11.md | 246 +++++++++++++ docs/REMEDIATION_TASK_BOARD_2026-05-06.md | 151 ++++++++ docs/REVIEW_REPORT_2026-05-04.md | 2 +- docs/REVIEW_REPORT_2026-05-06.md | 290 ++++++++++++++++ docs/ROLLBACK_DRILL_RECORD.md | 4 +- docs/RUNBOOK.md | 8 +- ...SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md | 10 +- docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md | 201 +++++++++++ docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md | 8 +- prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md | 4 +- test/QA_CHECKLIST.md | 4 +- 16 files changed, 1450 insertions(+), 31 deletions(-) create mode 100644 docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md create mode 100644 docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md create mode 100644 docs/REMEDIATION_PLAN_2026-05-11.md create mode 100644 docs/REMEDIATION_TASK_BOARD_2026-05-06.md create mode 100644 docs/REVIEW_REPORT_2026-05-06.md create mode 100644 docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md diff --git a/docs/CODE_REVIEW_REPORT.md b/docs/CODE_REVIEW_REPORT.md index 2495e5f..c470b81 100644 --- a/docs/CODE_REVIEW_REPORT.md +++ b/docs/CODE_REVIEW_REPORT.md @@ -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) @@ -285,4 +285,4 @@ if actorID == "" { 代码整体质量良好,测试覆盖充分,安全设计(HMAC/防重放/幂等/P0审计标准)到位。主要风险集中在 **RateLimiter 并发安全** 和 **工单操作错误消息模糊** 两个P0问题,修复后即可达到生产级质量。 > 审查基准:`3e9022a` + `01135ec`(PRODUCTION_LAUNCH.md) -> 三端同步状态:GitHub ✅ / Gitea ✅ / TKSea ✅ \ No newline at end of file +> 三端同步状态:GitHub ✅ / Gitea ✅ / TKSea ✅ diff --git a/docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md b/docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md new file mode 100644 index 0000000..eb02e66 --- /dev/null +++ b/docs/CODE_REVIEW_REPORT_SYSTEMATIC_2026-05-11.md @@ -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 +``` diff --git a/docs/MONITORING_ALERTING.md b/docs/MONITORING_ALERTING.md index 261838c..b152050 100644 --- a/docs/MONITORING_ALERTING.md +++ b/docs/MONITORING_ALERTING.md @@ -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) diff --git a/docs/PREPROD_VERIFICATION_RECORD.md b/docs/PREPROD_VERIFICATION_RECORD.md index 409d758..21aeb20 100644 --- a/docs/PREPROD_VERIFICATION_RECORD.md +++ b/docs/PREPROD_VERIFICATION_RECORD.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 脚本与本地/容器化联调证据已经建立并通过,但还不能把这直接等同于“真实预生产环境已经放行”。** - diff --git a/docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md b/docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md new file mode 100644 index 0000000..e5c4840 --- /dev/null +++ b/docs/PRODUCTION_CLOSURE_BOARD_2026-05-11.md @@ -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 diff --git a/docs/REMEDIATION_PLAN_2026-05-11.md b/docs/REMEDIATION_PLAN_2026-05-11.md new file mode 100644 index 0000000..5d287e5 --- /dev/null +++ b/docs/REMEDIATION_PLAN_2026-05-11.md @@ -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 `),观察日志确认 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 .//...` 通过。 +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 的下游执行约束摘要 diff --git a/docs/REMEDIATION_TASK_BOARD_2026-05-06.md b/docs/REMEDIATION_TASK_BOARD_2026-05-06.md new file mode 100644 index 0000000..81cf9bb --- /dev/null +++ b/docs/REMEDIATION_TASK_BOARD_2026-05-06.md @@ -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. 任务板维护要求 + +- 每完成一项,必须同步:状态、证据、对应文档 +- 任何“已完成”若无命令输出或文件证据,视为无效 +- 若验证失败,必须把失败现象写回对应任务,不允许口头跳过 +- 小龙负责最终事实校准,不接受“看起来差不多” diff --git a/docs/REVIEW_REPORT_2026-05-04.md b/docs/REVIEW_REPORT_2026-05-04.md index cafc39a..7dd9cf7 100644 --- a/docs/REVIEW_REPORT_2026-05-04.md +++ b/docs/REVIEW_REPORT_2026-05-04.md @@ -2,7 +2,7 @@ > 审查时间:2026-05-04 > 审查方式:静态代码审查 + 文档对照 + 本地构建/测试验证 -> 审查范围:`/home/long/project/立交桥/projects/ai-customer-service` +> 审查范围:`/home/long/project/ai-customer-service` ## 1. 结论摘要 diff --git a/docs/REVIEW_REPORT_2026-05-06.md b/docs/REVIEW_REPORT_2026-05-06.md new file mode 100644 index 0000000..8978085 --- /dev/null +++ b/docs/REVIEW_REPORT_2026-05-06.md @@ -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 平台能力:未通过** +- **真实共享预生产门禁:未通过** +- **生产放行门禁:未通过** diff --git a/docs/ROLLBACK_DRILL_RECORD.md b/docs/ROLLBACK_DRILL_RECORD.md index ba117d5..64425ea 100644 --- a/docs/ROLLBACK_DRILL_RECORD.md +++ b/docs/ROLLBACK_DRILL_RECORD.md @@ -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' \ diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md index 9578e65..e68105d 100644 --- a/docs/RUNBOOK.md +++ b/docs/RUNBOOK.md @@ -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) 脚本会完成: diff --git a/docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md b/docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md index 2a845db..60f9e9f 100644 --- a/docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.md +++ b/docs/SHARED_PREPROD_ACCESS_HANDOFF_CHECKLIST.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) --- diff --git a/docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md b/docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md new file mode 100644 index 0000000..6b047dc --- /dev/null +++ b/docs/SNAPSHOT_DIFF_CHECKLIST_2026-05-08.md @@ -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` + +## 当前结论 + +快照已经完成了它该做的事: + +- 作为迁移前的完整保底副本 +- 提供了一组已成功吸收的高价值顺序修复 +- 其余差异不值得继续大范围回灌 + +因此,后续策略应当是: + +- **主仓继续前进** +- **快照只保留作审计和按需检索** +- **不再做整包式合并** diff --git a/docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md b/docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md index 06890b4..aeb3136 100644 --- a/docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.md +++ b/docs/SUB2API_MINIMAL_WEBHOOK_MAPPING.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) 默认请求头: diff --git a/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md b/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md index 8aaf12d..15ac791 100644 --- a/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md +++ b/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md @@ -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) --- diff --git a/test/QA_CHECKLIST.md b/test/QA_CHECKLIST.md index 20daef3..93ad957 100644 --- a/test/QA_CHECKLIST.md +++ b/test/QA_CHECKLIST.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 # 覆盖率报告(需补齐)