docs: sync review reports, runbooks, and checklists

This commit is contained in:
Your Name
2026-05-11 12:19:15 +08:00
parent 67922c589a
commit 9319583ee3
16 changed files with 1450 additions and 31 deletions

View File

@@ -1,7 +1,7 @@
# AI-Customer-Service Codex 代码审查报告 # AI-Customer-Service Codex 代码审查报告
> 审查工具Codex CLI v0.125.0 (gpt-5.4) > 审查工具Codex CLI v0.125.0 (gpt-5.4)
> 审查范围:`/home/long/project/立交桥/projects/ai-customer-service` > 审查范围:`/home/long/project/ai-customer-service`
> 代码基准:`3e9022a` + `01135ec` > 代码基准:`3e9022a` + `01135ec`
> 审查时间2026-05-01 > 审查时间2026-05-01
> 审查方法:静态分析 + 工具链验证go vet、go build、go test -race > 审查方法:静态分析 + 工具链验证go vet、go build、go test -race

View 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 ShutdownA 级)
- 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
```

View File

@@ -127,7 +127,6 @@
这份文档不是泛化监控说明,而是**灰度放量门禁文档**。 这份文档不是泛化监控说明,而是**灰度放量门禁文档**。
任何放量决策都必须引用: 任何放量决策都必须引用:
- [GRAY_DASHBOARD_MINIMUM.md](/home/long/project/立交桥/projects/ai-customer-service/docs/GRAY_DASHBOARD_MINIMUM.md) - [GRAY_DASHBOARD_MINIMUM.md](/home/long/project/ai-customer-service/docs/GRAY_DASHBOARD_MINIMUM.md)
- [SERVICE_SLA.md](/home/long/project/立交桥/projects/ai-customer-service/prd/SERVICE_SLA.md) - [SERVICE_SLA.md](/home/long/project/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_RELEASE_ROLLBACK_RUNBOOK.md](/home/long/project/ai-customer-service/prd/GRAY_RELEASE_ROLLBACK_RUNBOOK.md)

View File

@@ -10,7 +10,7 @@
本记录对应 Task 5 的 Gate B 验证脚本: 本记录对应 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_ADDR=127.0.0.1:18080 \
AI_CS_POSTGRES_ENABLED=true \ 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_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_SECRET='gate-b-secret-20260504' \
AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \ AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \
AI_CS_WEBHOOK_SIGNATURE_HEADER='X-CS-Signature' \ 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 写入静默失败 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.go](/home/long/project/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_test.go](/home/long/project/ai-customer-service/internal/http/handlers/audit_helper_test.go)
这两项修复后Gate B 本地/容器化预演已全部通过。 这两项修复后Gate B 本地/容器化预演已全部通过。
@@ -104,4 +104,3 @@ scripts/verify_preprod_gate_b.sh
因此当前正确结论是: 因此当前正确结论是:
> **Gate B 脚本与本地/容器化联调证据已经建立并通过,但还不能把这直接等同于“真实预生产环境已经放行”。** > **Gate B 脚本与本地/容器化联调证据已经建立并通过,但还不能把这直接等同于“真实预生产环境已经放行”。**

View 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 分支已关闭 | 未开始 |
### 阶段 BP1 代码修复
| 任务 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 测试通过 | 未开始 |
### 阶段 CP2 代码修复
| 任务 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 内正常退出 | 未开始 |
### 阶段 DQA 发现的遗漏项(需 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
阶段 BP1 代码修复
├── B-01 List 测试
├── B-02 newapi 测试
├── B-03 Authz 文档
├── B-04 RateLimiter 优化
└── B-05 IPv6 修复
→ QA 回归验证CP-04 ~ CP-07
阶段 CP2 代码修复
├── 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

View 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 boardD-01/I-01已覆盖该设计缺口本次仅补充 `BuildIngressAck` 的单元测试(与 review P1-2 对应),不改变 newapi 仍为 501 占位的事实。
- **不引入 testcontainers-go**P2-3 仅做 skip 回退与文档标注,不做完整容器化测试基础设施。
- **不改写 outbox 并发 claim / transactional outbox**:属于旧 remediation boardI-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-2newapi_adapter 测试)与旧 board 的 I-01newapi 假接通)正交:本次仅补测试覆盖,不改变 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 批 commitdocs / 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 XLTechLead / 负责人)必补门控
- 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 的下游执行约束摘要

View 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. 任务板维护要求
- 每完成一项,必须同步:状态、证据、对应文档
- 任何“已完成”若无命令输出或文件证据,视为无效
- 若验证失败,必须把失败现象写回对应任务,不允许口头跳过
- 小龙负责最终事实校准,不接受“看起来差不多”

View File

@@ -2,7 +2,7 @@
> 审查时间2026-05-04 > 审查时间2026-05-04
> 审查方式:静态代码审查 + 文档对照 + 本地构建/测试验证 > 审查方式:静态代码审查 + 文档对照 + 本地构建/测试验证
> 审查范围:`/home/long/project/立交桥/projects/ai-customer-service` > 审查范围:`/home/long/project/ai-customer-service`
## 1. 结论摘要 ## 1. 结论摘要

View 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 BFAIL**
- **生产灰度 Gate CFAIL**
### 是否可进入下一阶段
- **是否可按“整体生产可上线”口径放行:否**
- **是否可按“多平台能力已完成”口径汇报:否**
- **是否可按“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 平台能力:未通过**
- **真实共享预生产门禁:未通过**
- **生产放行门禁:未通过**

View File

@@ -10,7 +10,7 @@
本记录对应 Gate C 回滚演练脚本: 本记录对应 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_ADDR=127.0.0.1:18081 \
AI_CS_POSTGRES_ENABLED=true \ 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_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_SECRET='gate-c-secret-20260505' \
AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \ AI_CS_WEBHOOK_TIMESTAMP_HEADER='X-CS-Timestamp' \
AI_CS_WEBHOOK_SIGNATURE_HEADER='X-CS-Signature' \ AI_CS_WEBHOOK_SIGNATURE_HEADER='X-CS-Signature' \

View File

@@ -11,10 +11,10 @@
预生产 Gate B 不再建议靠零散手工命令拼接验证。优先使用: 预生产 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)
- 最近一次实测记录:[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)
- Gate C 回滚演练入口:[scripts/verify_gate_c_rollback.sh](/home/long/project/立交桥/projects/ai-customer-service/scripts/verify_gate_c_rollback.sh) - 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/立交桥/projects/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md) - 最近一次回滚演练记录:[ROLLBACK_DRILL_RECORD.md](/home/long/project/ai-customer-service/docs/ROLLBACK_DRILL_RECORD.md)
脚本会完成: 脚本会完成:

View File

@@ -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)
--- ---

View 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`
## 当前结论
快照已经完成了它该做的事:
- 作为迁移前的完整保底副本
- 提供了一组已成功吸收的高价值顺序修复
- 其余差异不值得继续大范围回灌
因此,后续策略应当是:
- **主仓继续前进**
- **快照只保留作审计和按需检索**
- **不再做整包式合并**

View File

@@ -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) - [router.go](/home/long/project/ai-customer-service/internal/http/router.go)
- [webhook_handler.go](/home/long/project/立交桥/projects/ai-customer-service/internal/http/handlers/webhook_handler.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)
默认请求头: 默认请求头:

View File

@@ -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) - [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)
--- ---

View File

@@ -1,7 +1,7 @@
# AI-Customer-Service 生产一期 QA 检查清单 # AI-Customer-Service 生产一期 QA 检查清单
> 生成时间2026-04-30 > 生成时间2026-04-30
> 项目路径:/home/long/project/立交桥/projects/ai-customer-service > 项目路径:/home/long/project/ai-customer-service
> 覆盖范围:文档-实现一致性 · 威胁建模 · AC/失败路径/安全/性能矩阵 · 灰度回滚 · 漂移检测 · 阻断条件 > 覆盖范围:文档-实现一致性 · 威胁建模 · AC/失败路径/安全/性能矩阵 · 灰度回滚 · 漂移检测 · 阻断条件
--- ---
@@ -317,7 +317,7 @@ Ticket Resolve → 工单状态变更 → 审计写入
```bash ```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 go test ./test/e2e/... ./test/integration/... ./internal/http/handlers/... ./internal/config/... -v
# 覆盖率报告(需补齐) # 覆盖率报告(需补齐)