# 查价系统 工程任务拆解清单 v1.2

| 版本 | 日期 | 说明 |
|------|------|------|
| v1.0 | 2026-06-16 | 首版原子任务拆解 |
| v1.1 | 2026-06-16 | 对照 Traceability Matrix 补齐；口径对齐 PRD v0.4 |
| v1.1.1 | 2026-06-16 | **按推荐开发依赖顺序重排任务章节**（非 Task ID 数字序） |
| v1.2 | 2026-06-16 | 对齐 PRD v0.6 + 技术设计 v1.2；基线 001–118 已完成；增量 119–135 |

> **当前版本请使用本文件（v1.2）。** v1.1 文件保留作历史对照。

> 配套文档：[查价系统-PRD.md](./查价系统-PRD.md)（v0.6）· [查价系统-技术设计.md](./查价系统-技术设计.md)（v1.2）· [查价系统-UI设计.md](./查价系统-UI设计.md) · [查价系统-Traceability-Matrix-v1.0.md](./查价系统-Traceability-Matrix-v1.0.md)  
> **v1.1 基线（task-001~118，除废止）= 已完成；v1.2 增量（task-119~135）= PRD v0.6 + 技术设计 v1.2 待交付。**

---

## 开发顺序总览（自上而下执行）

| Phase | 名称 | Task 序列 | 状态 |
|-------|------|-----------|------|
| 0 | 环境与底座 | 072→073→**119→120**→070→071→097→030 | 072–030 已完成；119–120 待执行 |
| 1 | 基础设施与缓存 | 096→031~036→035-ip | 已完成 |
| 2 | 鉴权 | 001~005→098 | 已完成 |
| 3 | 领域逻辑 | 040~044→010~014,092,095,109,111,019 | 已完成 |
| 4 | API 层 | 015~017→042~043→018 | 已完成 |
| 5.0 | RPA 录制前置 | **121→122** | 已完成 |
| 5 | RPA v1.2 重构 | **123→131**（基线 020–029,110,093,099,100 已完成） | 123–131 待执行 |
| 6 | 预警/审计 | 050~057→094→105 | 已完成 |
| 7 | 观测指标 | 101→102 | 已完成 |
| 8 | 前端 | 060~069→106→063,064,079,103,104→**132**→080 | 132 已完成 |
| 9 | 测试/CI | 081~118→**133→135**→091 | 133–135 待执行 |
| — | 已废止 | 075~078 | — |

**里程碑 M1（Phase 0~4）**：已完成 — `POST /quotes` L2 命中、幂等、校验 400。  
**里程碑 M2（v1.2）**：`RPA_MOCK_MODE=false` 下 `scripts/probe-rpa.ts` **连续 3 次 exit 0**，四档 `source_type=rpa`（TC-601 + §12.7 Go/No-Go）。

---

## 模块索引（按职能归类，非执行顺序）

| 模块 | Task ID | 数量 |
|------|---------|------|
| 数据库（DB） | 070–073 | 4 |
| 基础设施（Lib） | 096–098, **119–120** | 5 |
| 缓存系统 | 030–036, 035-ip | 8 |
| 用户系统（Auth） | 001–005 | 5 |
| 加价系统（Markup） | 040–044 | 5 |
| 查价核心（Quote Engine） | 010–019, 092, 095, 109, 111, **129** | 15 |
| API 路由 | 015–018, 042–043 | 6 |
| RPA 系统 | 020–029, 093, 099–100, 110, **121–131** | 23 |
| 预警系统（Alert） | 050–057, 094, 105 | 10 |
| 观测与指标 | 101–102 | 2 |
| 前端 UI | 060–080, 103–104, 106, **132** | 19 |
| 测试系统 | 081–091, 112–118, **133–135** | 21 |
| **合计（可执行）** | | **~119** |
| **已废止** | 075–078 | 4 |

## 技术栈约定

- Next.js 15 App Router + TypeScript
- MySQL 8.0 + Prisma ORM
- Redis 7 + BullMQ
- Playwright（RPA Worker）
- Tailwind CSS + shadcn/ui + phosphor-icons

## v1.1 口径冻结（对齐 PRD v0.4，基线已完成）

| 决策 | 规则 | 任务影响 |
|------|------|----------|
| GD-11 嵌入形态 | 查价 UI 为**宿主内嵌组件**，中台仅 admin 管理端 | task-106 替代 075–077；task-064 仅 admin |
| §2.3 加价配置 | 中台**不提供**运营加价 UI，由宿主调 API | task-078 **废止**；task-042/043 保留 |
| 四档报价 | GD-1/TC-102 为准（quotes=4） | task-088/111 按 4 档验收 |
| Fallback | 独立 FallbackOrchestrator 模块 | task-092 |
| 管理端 RPA | §3.1 预警 + RPA/开发进度 | task-093, 103, 104 |
| audit / metrics | §10.6 audit log + GD-4 三率 | task-094, 101, 102, 117 |

## v1.2 口径冻结（对齐 PRD v0.6 / 技术设计 v1.2）

| 决策 | 规则 | 任务影响 |
|------|------|----------|
| GD-15 入口 URL | `MOTHERSHIP_QUOTE_URLS` headed 录制；禁止 dashboard / `/quote` | task-119, 121, 122, 123 |
| 入口 vs 抓取错误 | 入口错误与抓取错误分开分类；地址未锁定单独处理 | task-125, 128, 129 |
| 新错误码 | `QUOTE_ENTRY_UNAVAILABLE`、`ADDRESS_SUGGESTION_NOT_FOUND` | task-128, 110（基线） |
| QuotePageAdapter | 多 URL 探测 + storageState + login 无凭据→STRUCT_CHANGE | task-123, 124 |
| Selector env 化 | 全部 `RPA_SELECTOR_*`，禁止手猜 selector | task-125–127 |
| probe 门禁 | 连续 3 次 exit 0 + 四档 | task-130, 133–135 |
| Bull Board | `/admin/queues` 队列监控（P1） | task-132 |

## v1.0 → v1.1 变更摘要

| 类型 | Task | 说明 |
|------|------|------|
| 新增 | 092–118（除重复编号） | 见各 Phase 章节 |
| 修订 | 005, 014, 016, 018, 029, 050, 064, 065, 066, 079, 085, 091 | 补 RBAC/置信度/Fallback 引用等 |
| 废止 | 075, 076, 077, 078 | PRD v0.4 口径下不交付 |
| 替代 | 106 | 宿主内嵌查价组件（替代 075） |
| 排序 | v1.1.1 | 正文按 Phase 0→9 开发顺序排列 |

## v1.1 → v1.2 变更摘要

| CHG ID | 类型 | Task | 说明 |
|--------|------|------|------|
| CHG-P02/T03 | 新增 | 119, 121, 122 | GD-15 env + 录制前置 |
| CHG-T04 | 新增 | 120 | docker rpa_state volume |
| CHG-P05/T05/T06 | 新增 | 123 | QuotePageAdapter |
| CHG-T09 | 新增 | 124 | storageState 生命周期 |
| CHG-T07 | 新增 | 125–127 | selector env 化 |
| CHG-P03/P04/T08 | 新增 | 128, 129 | 错误分类 v0.6 |
| CHG-P08/T02 | 新增 | 130 | probe 3/3 |
| CHG-T03 P1 | 新增 | 131 | Patchright 可选 |
| CHG-T10 | 新增 | 132 | /admin/queues Bull Board |
| CHG-P08/P09 | 新增 | 133–135 | TC-601 + Go-No-Go + CI |
| — | 状态 | 001–118 | 全部标「已完成」（废止除外） |
| — | 说明 | 022, 023, 028, 073, 110, 117, 088 | 基线已实现；行为变更由 task-12x 承接 |

---

# Phase 0 — 环境与底座

> 执行顺序：task-072 → task-073 → **task-119 → task-120** → task-070 → task-071 → task-097 → task-030

## task-072

- **ID**：task-072
- **状态**：已完成
- **模块**：数据库
- **目标**：创建 Docker Compose 配置（4容器）
- **输入**：技术设计 §6.1
- **输出**：`docker-compose.yml`，含 mysql/redis/next-app/rpa-worker，含 healthcheck 和 shm_size
- **实现范围**：仅基础设施配置，不含应用代码
- **验证**：`docker compose up -d` 四容器全部 healthy；`docker compose ps` 状态 running
---

## task-073

- **ID**：task-073
- **状态**：已完成
- **模块**：数据库
- **目标**：创建 `.env.example` + Dockerfile + Dockerfile.worker
- **输入**：技术设计 §6.2
- **输出**：`.env.example` / `Dockerfile` / `Dockerfile.worker`
- **实现范围**：环境变量模板 + 容器构建文件
- **验证**：`docker compose build` 无报错；`.env.example` 含全部必填变量
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

# 基础设施（Lib）
---

## task-119

- **ID**：task-119
- **状态**：待执行
- **模块**：Lib/Infra
- **目标**：v0.6 RPA 环境变量模板（GD-15）
- **输入**：PRD §8.2.1；技术设计 §6.2 CHG-T03
- **输出**：`.env.example` 增 `MOTHERSHIP_QUOTE_URLS`、`RPA_STORAGE_STATE_PATH`、`RPA_SELECTOR_*`（8 项）、`RPA_USE_PATCHRIGHT`
- **实现范围**：禁止内置 dashboard / `/quote` 默认 URL；缺 `MOTHERSHIP_QUOTE_URLS` 时 rpa-worker fail-fast
- **验证**：`.env.example` 含全部 RPA 必填项；未配置 URL 列表时 worker 启动报错退出
---

## task-120

- **ID**：task-120
- **状态**：待执行
- **模块**：Lib/Infra
- **目标**：Docker RPA 持久化（storageState volume）
- **输入**：技术设计 §6.1 §7.5 CHG-T04
- **输出**：`docker-compose.yml` rpa-worker 注入 RPA env + `rpa_state` volume → `/app/.rpa`
- **实现范围**：volume 挂载 + env 透传；不含业务逻辑
- **验证**：`docker compose up -d` 后 `.rpa/mothership-storage.json` 跨容器重启保留
---

## task-070

- **ID**：task-070
- **状态**：已完成
- **模块**：数据库
- **目标**：创建完整 Prisma schema（6张表）
- **输入**：PRD §9 + 技术设计 §3.5
- **输出**：`prisma/schema.prisma`，含 SysUser / QuoteRecord / IdempotencyRecord / QuoteCacheMeta / MarkupConfig / AlertLog / **AuditLog**
- **实现范围**：仅 schema 文件，不含 migrate 执行
- **验证**：`npx prisma validate` 零错误；所有字段类型、约束与 PRD DDL 一致
---

## task-071

- **ID**：task-071
- **状态**：已完成
- **模块**：数据库
- **目标**：执行 Prisma migrate + seed（初始化 admin_demo 账号）
- **输入**：task-070 的 schema
- **输出**：`prisma/migrations/` + `prisma/seed.ts`，创建 admin_demo/Demo@123
- **实现范围**：仅 migrate dev + seed；不含业务逻辑
- **验证**：`npx prisma migrate dev` 无报错；`SELECT * FROM sys_user` 返回 1 行 admin
---

## task-097

- **ID**：task-097
- **状态**：已完成
- **模块**：Lib
- **目标**：实现 lib/prisma.ts Prisma 客户端单例
- **输入**：技术设计 §5
- **输出**：`lib/prisma.ts`；导出 `prisma` 单例；dev 热重载防多实例
- **实现范围**：仅 ORM 连接；不含业务查询
- **验证**：`prisma.$connect()` 成功；多模块 import 同一实例
---

## task-030

- **ID**：task-030
- **状态**：已完成
- **模块**：Cache
- **目标**：实现 Redis 客户端单例（lib/redis.ts）
- **输入**：ioredis ^5.x；技术设计 §3.6
- **输出**：`lib/redis.ts`；导出 `getRedis()` 单例；连接失败时抛错并 log
- **实现范围**：连接配置 + 错误处理；不含业务操作
- **验证**：`getRedis().ping()` 返回 PONG；Redis 不可达 → 抛异常并记录 error log
---

# Phase 1 — 基础设施与缓存（W1）

> 执行顺序：task-096 → task-031 → task-032 → task-033 → task-034 → task-035 → task-035-ip → task-036

## task-096

- **ID**：task-096
- **状态**：已完成
- **模块**：Lib
- **目标**：实现 lib/response.ts 统一 API 响应包络
- **输入**：技术设计 §4.1；PRD §8.5
- **输出**：`lib/response.ts`；`ok(data)` / `fail(code, message, httpStatus)`；所有路由复用
- **实现范围**：仅响应格式化；自动附加 `Cache-Control: no-store` 辅助函数
- **验证**：Vitest：ok → `{ code:0, message:'ok', data }`；fail → `{ code, message, data:null }` + 正确 HTTP 状态码
---

## task-031

- **ID**：task-031
- **状态**：已完成
- **模块**：Cache
- **目标**：实现 L1 缓存（idem:{request_id}，24h）
- **输入**：task-030；PRD §7.2；GD-2
- **输出**：`modules/cache/redis-cache.ts` 中 `getL1(request_id)` / `setL1(request_id, data)`
- **实现范围**：SETNX + GET；TTL=24h；value=完整响应快照 JSON
- **验证**：set → get 返回相同值；TTL 校验 ≈ 86400s；重复 SETNX → 不覆盖
---

## task-032

- **ID**：task-032
- **状态**：已完成
- **模块**：Cache
- **目标**：实现 L2 缓存（quote:{cargo_hash}，3min+jitter）
- **输入**：task-030；PRD §7.2；GD-2
- **输出**：`modules/cache/redis-cache.ts` 中 `getL2(cargo_hash)` / `setL2(cargo_hash, data)`
- **实现范围**：SET + GET；TTL=180+random(0~30)s；value=四档原始报价 JSON（不含加价）
- **验证**：set → get 返回相同值；TTL 在 180~210s 之间（随机 jitter 验证分布）
---

## task-033

- **ID**：task-033
- **状态**：已完成
- **模块**：Cache
- **目标**：实现 L3 缓存（stale:{cargo_hash}，30min，只读降级）
- **输入**：task-030；PRD §7.2；GD-2
- **输出**：`modules/cache/redis-cache.ts` 中 `getL3(cargo_hash)` / `setL3(cargo_hash, data)`（仅 RPA 成功时写入）
- **实现范围**：SET + GET；TTL=1800s；**正常路径禁止读取 L3**（只在 RPA 失败 fallback 时读）
- **验证**：RPA 成功后 L3 有值；L2 命中路径不访问 L3
---

## task-034

- **ID**：task-034
- **状态**：已完成
- **模块**：Cache
- **目标**：实现缓存击穿锁（lock:quote:{cargo_hash}）
- **输入**：task-030；PRD §11.6
- **输出**：`modules/cache/redis-cache.ts` 中 `acquireLock(cargo_hash)` / `releaseLock(cargo_hash)`
- **实现范围**：SETNX lock key，TTL=5s；获取失败 → 等待2s后读L2；用完后 DEL
- **验证**：100并发同一 cargo_hash miss → 只触发1次 RPA；其余等待后命中 L2（TC-11.6）
---

## task-035

- **ID**：task-035
- **状态**：已完成
- **模块**：Cache
- **目标**：实现限流 Redis Key（令牌桶，60次/分钟/customer）
- **输入**：task-030；PRD §10.3 §11.7；GD-4
- **输出**：`modules/cache/rate-limiter.ts`；`checkRateLimit(customer_id)` → 超限返回 RATE_LIMITED
- **实现范围**：ratelimit:customer:{id} INCR + EXPIRE 60s；超60 → 429
- **验证**：60次 → 全部通过；第61次 → 429 RATE_LIMITED（TC-303 TC-503）
---

## task-035-ip

- **ID**：task-035-ip
- **状态**：已完成
- **模块**：Cache
- **目标**：实现 IP 级限流（1000次/分钟，超限封禁 10 分钟）
- **输入**：PRD §11.9
- **输出**：`modules/cache/ip-rate-limiter.ts`；`ratelimit:ip:{ip}` + `ban:ip:{ip}` TTL=600s
- **实现范围**：middleware 调用；>1000/min → 429 + 写 SECURITY 预警（task-105）
- **验证**：单 IP 1200 次/分钟 → 429；封禁 10 分钟内全部拒绝（TC-503 IP 维度）

---

# 加价系统（Markup）
---

## task-036

- **ID**：task-036
- **状态**：已完成
- **模块**：Cache
- **目标**：实现熔断状态管理（circuit:rpa Redis key）
- **输入**：task-030；PRD §10.3 §11.5；GD-4
- **输出**：`modules/cache/circuit-breaker.ts`；`isCircuitOpen()` / `openCircuit()` / `closeCircuit()`
- **实现范围**：Redis key circuit:rpa；TTL=600s（10min）；连续失败≥3次 → open；期间 Worker 不消费
- **验证**：连续3次失败 → circuit open；10分钟后自动关闭；open期间 job 走 L3/failed（TC-209）
---

# Phase 2 — 鉴权

> 执行顺序：task-001 → task-002 → task-003 → task-004 → task-005 → task-098

## task-001

- **ID**：task-001
- **状态**：已完成
- **模块**：Auth
- **目标**：实现 JWT 工具函数（jose 库：签发/校验/解码）
- **输入**：技术设计 §4.2；jose ^5.x
- **输出**：`modules/auth/jwt.ts`，导出 `signToken(payload)` / `verifyToken(token)` / `decodeToken(token)`
- **实现范围**：JWT 操作只；不含数据库查询
- **验证**：Vitest 单元测试：签发 → 校验通过；篡改 → 抛异常；过期 → 抛异常
---

## task-002

- **ID**：task-002
- **状态**：已完成
- **模块**：Auth
- **目标**：实现 POST /api/auth/login（仅 admin 角色）
- **输入**：task-001；Prisma SysUser
- **输出**：`app/api/auth/login/route.ts`；成功返回 `{ token, user: { user_id, role } }`
- **实现范围**：用户名密码校验（bcrypt compare）+ JWT 签发；非 admin 拒绝；不含 RBAC
- **验证**：curl POST 正确凭据 → 200 + token；错误密码 → 401；customer 账号 → 403
---

## task-003

- **ID**：task-003
- **状态**：已完成
- **模块**：Auth
- **目标**：实现 middleware.ts（JWT 鉴权 + 路由保护）
- **输入**：task-001；Next.js middleware
- **输出**：`middleware.ts`；未鉴权路由返回 401；admin 路由校验 role=admin
- **实现范围**：路由匹配 + JWT 校验；不含限流（task-035 实现）
- **验证**：无 token 访问 `/api/quotes` → 401；有效 admin JWT 访问 `/api/alerts` → 通过
---

## task-004

- **ID**：task-004
- **状态**：已完成
- **模块**：Auth
- **目标**：实现宿主 Service Token 验证（customer_id 租户绑定校验）
- **输入**：PRD §2.1；task-003
- **输出**：`modules/auth/service-token.ts`；校验 token + customer_id 绑定；越权 403
- **实现范围**：Service Token 解析 + customer_id 匹配；不含 JWT 签发（宿主自签）
- **验证**：正确 token + 匹配 customer_id → 通过；customer_id 不匹配 → 403
---

## task-005

- **ID**：task-005
- **状态**：已完成
- **模块**：Auth
- **目标**：实现 RBAC 权限检查函数（含 rpa:operate）
- **输入**：PRD §10.6 §2.1；task-001
- **输出**：`modules/auth/rbac.ts`，导出 `requirePermission(token, permission)` 中间件
- **实现范围**：权限枚举：`pricing:markup:write` / `alert:read` / **`rpa:operate`**；无权限抛 FORBIDDEN
- **验证**：Vitest：admin token + alert:read → 通过；admin + rpa:operate → 通过；宿主 token + alert:read → 403

---

# 查价核心系统（Quote Engine）
---

## task-098

- **ID**：task-098
- **状态**：已完成
- **模块**：Lib
- **目标**：实现 gateway 503 单次重试中间件
- **输入**：PRD §11.4
- **输出**：`middleware.ts` 或 `lib/gateway-retry.ts`；5xx 时 retry 1 次后仍失败 → 503 INTERNAL_ERROR
- **实现范围**：仅网关层重试；不含业务降级
- **验证**：mock 首次 503 第二次 200 → 客户端得 200；连续 503 → 503 + INTERNAL_ERROR

---

# 用户系统（Auth）
---

# Phase 3 — 加价基础

> 执行顺序：task-040 → task-041 → task-044

## task-040

- **ID**：task-040
- **状态**：已完成
- **模块**：Pricing
- **目标**：实现 roundHalfUp 工具函数
- **输入**：PRD GD-10；技术设计 §4.5
- **输出**：`lib/math.ts`；`roundHalfUp(value, decimals)` 使用 Number.EPSILON 修正
- **实现范围**：仅数学函数
- **验证**：Vitest：0.105×2 → 0.21（不是0.20）；32.005 → 32.01；边界值含负数
---

## task-041

- **ID**：task-041
- **状态**：已完成
- **模块**：Pricing
- **目标**：实现 PricingEngine.applyMarkup()
- **输入**：task-040；PRD §6.3
- **输出**：`modules/pricing/engine.ts`；`applyMarkup(rawFreight, rawTotal, markupPercent)` → `{ markupAmount, finalTotal }`
- **实现范围**：markup_amount = ROUND_HALF_UP(raw_freight × markup_percent / 100, 2)；final_total = ROUND_HALF_UP(raw_total + markup_amount, 2)
- **验证**：Vitest：raw_freight=320，surcharges=15，markup=10% → markup_amount=32.00，final_total=367.00（TC-103）；raw_freight=380，markup=10% → 38.00/418.00
---

## task-044

- **ID**：task-044
- **状态**：已完成
- **模块**：Pricing
- **目标**：实现未配置客户默认 0% 逻辑（MarkupConfig 查询）
- **输入**：Prisma MarkupConfig；PRD BR-6.3
- **输出**：`modules/pricing/engine.ts` 中 `getMarkupPercent(customer_id)` 函数；未配置返回 0.0
- **实现范围**：DB 查询 + 默认值兜底；不含写操作
- **验证**：Vitest：未配置 customer_id → 返回 0.0；已配置 10.0% → 返回 10.0

---

# 预警系统（Alert）
---

# Phase 3 — 查价领域逻辑

> 执行顺序：task-010 → task-011 → task-012 → task-095 → task-111 → task-013 → task-019 → task-092 → task-109 → task-014

## task-010

- **ID**：task-010
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 ValidationModule（输入校验 + Mothership 官方硬限）
- **输入**：PRD §4.2.8；zod；技术设计 §7.2.2
- **输出**：`modules/quote/validation.ts`；导出 `MOTHERSHIP_LIMITS` 常量 + `validateQuoteInput(body)` 函数
- **实现范围**：selected_from_suggestions=true 校验；pallet_count 1–25；dims L≤999/W≤99/H≤99 in；单托重≤9999 lb；总重 ≤249975 lb；cargo_type 9项枚举；request_id UUID v4；zip 正则；SQL注入检测
- **验证**：Vitest：TC-201~TC-204 + TC-E4.1~E4.7 全部场景；边界值测试
---

## task-011

- **ID**：task-011
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现单位换算函数（kg→lb，cm→in）
- **输入**：PRD §4.2.2 §4.2.3；GD-13
- **输出**：`modules/quote/unit-converter.ts`；`kgToLb(kg)` / `cmToIn(cm)`，ROUND_HALF_UP 2位
- **实现范围**：仅数学换算；不含校验逻辑
- **验证**：Vitest：227kg → 500.05lb（±0.01）；122cm → 48.03in；精度 ROUND_HALF_UP
---

## task-012

- **ID**：task-012
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 cargo_hash 算法（MD5）
- **输入**：PRD BR-4.4；技术设计 §3.7
- **输出**：`modules/quote/cargo-hash.ts`；`buildCargoHash(input)` 返回 32位 hex
- **实现范围**：含 pickup(place_id) + delivery + weight_lb + dims_in + pallet_count + cargo_type；不含 service_level / customer_id
- **验证**：Vitest：相同入参 → 相同 hash；service_level 不同 → 相同 hash；place_id 不同 → 不同 hash
---

## task-095

- **ID**：task-095
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 confidence_score 赋值逻辑（F5.6）
- **输入**：PRD §5.3 F5.6
- **输出**：`modules/quote/confidence.ts`；`getConfidenceScore(sourceType)` → rpa/cache=0.95；stale=0.70
- **实现范围**：仅映射函数；在 GET /quotes 与落库时调用
- **验证**：Vitest：source=rpa → 0.95；source=stale → 0.70；source=cache → 0.95
---

## task-111

- **ID**：task-111
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 BR-5.5 四档齐全校验（done 前 gates）
- **输入**：PRD BR-5.5；GD-1
- **输出**：`modules/quote/quote-completeness.ts`；`assertFourTiers(quotes[])` → 不足 4 档抛 RPA_DATA_INVALID
- **实现范围**：校验 standard/guaranteed × lowest/fastest 组合齐全；orchestrator 与 RPA worker 共用
- **验证**：3 档 → 抛错走 Fallback；4 档 → 通过；TC-102

---

# RPA 系统
---

## task-013

- **ID**：task-013
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 IdempotencyModule（Redis L1 + DB 双保险）
- **输入**：task-031（L1 Redis）；Prisma IdempotencyRecord；PRD §7.3 §10.4
- **输出**：`modules/quote/idempotency.ts`；`checkIdempotency(request_id)` + `saveIdempotency(request_id, quote_id)`
- **实现范围**：Redis SETNX L1 → 命中返回 quote_id；DB 唯一键冲突处理；24h TTL
- **验证**：Vitest：重复 request_id → 返回同一 quote_id；并发 10次 → 仅写入 1条 DB 记录（TC-207）
---

## task-019

- **ID**：task-019
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 quote_id 生成函数
- **输入**：PRD §5.2 示例格式（QTE_20260616_0001）
- **输出**：`modules/quote/quote-id.ts`；`generateQuoteId()` 含日期+序号；保证唯一性
- **实现范围**：仅 ID 生成；使用 DB 自增或 UUID+日期前缀
- **验证**：并发 100次调用 → 无重复（TC-402）；DB 唯一键冲突 → INTERNAL_ERROR + 写 audit（task-109）
---

## task-092

- **ID**：task-092
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 FallbackOrchestrator（RPA 失败 → L3 降级完整编排）
- **输入**：task-033（L3）；task-041（加价）；task-050~053（预警）；PRD §7.6 §8.4 §2.5
- **输出**：`modules/quote/fallback-orchestrator.ts`；`handleRpaFailure(quoteId, cargoHash, customerId, error)`
- **实现范围**：读 L3 → 有：按客户加价重算 → UPDATE done(source=stale,is_realtime=false) → STALE_FALLBACK 预警；无：failed + QUOTE_UNAVAILABLE → RPA_FAILED 预警；**不抛异常阻断主流程**
- **验证**：TC-205（stale done）；TC-206（failed）；TC-602；集成测试可单点 mock RPA 失败
---

## task-109

- **ID**：task-109
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 quote_id DB 唯一冲突 → INTERNAL_ERROR 处理
- **输入**：PRD §11.10
- **输出**：orchestrator 落库 catch P2002 → 写 audit + 返回 500 INTERNAL_ERROR
- **实现范围**：仅 quote_id 冲突；不含其他 DB 错误
- **验证**：mock 唯一键冲突 → 500 + audit_log 有记录；不写入脏数据

---

# 缓存系统
---

## task-014

- **ID**：task-014
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 QuoteOrchestrator 主流程（L1→L2→入队）
- **输入**：task-010~013；task-032（L2）；BullMQ；Prisma QuoteRecord；技术设计 §2.3
- **输出**：`modules/quote/orchestrator.ts`；`submitQuote(body)` 完整主流程
- **实现范围**：换算 → 校验 → L1 → L2（命中则加价+落库+写L1+返回done）→ 未命中则 INSERT processing + 入队 → 返回 processing；**RPA 失败时委托 task-092 FallbackOrchestrator**
- **验证**：集成测试：L1 命中 → 不触发 RPA；L2 命中 → source=cache + done；首次 → status=processing；TC-104 TC-105；失败路径调用 092
---

# Phase 4 — API 层（里程碑：L1/L2 可通）

> 执行顺序：task-015 → task-016 → task-017 → task-042 → task-043 → task-018

## task-015

- **ID**：task-015
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 POST /api/quotes 路由（薄层）
- **输入**：task-003（鉴权）；task-014（orchestrator）；技术设计 §4.3
- **输出**：`app/api/quotes/route.ts`；调用 task-096 统一响应；Cache-Control: no-store
- **实现范围**：路由层：鉴权 → 调 orchestrator → 格式化响应；不含业务逻辑
- **验证**：curl POST 正常参数 → 200 + quote_id；缺字段 → 400；无 token → 401
---

## task-016

- **ID**：task-016
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 GET /api/quotes/{quoteId} 路由（轮询 + expired 检测）
- **输入**：task-003；Prisma QuoteRecord；PRD §5.2 §5.3
- **输出**：`app/api/quotes/[quoteId]/route.ts`；valid_until < now → status=expired；越权 → 403
- **实现范围**：DB 查询 + expired 计算 + customer_id 越权校验 + **task-095 confidence_score 赋值**；不含轮询逻辑（客户端实现）
- **验证**：status=done → 完整 QuoteResponse 含 4 档 + confidence_score；rpa/cache=0.95；stale=0.70；expired → status=expired；他人 quote_id → 403（TC-501）
---

## task-017

- **ID**：task-017
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现 GET /api/quotes/history 路由（强制分页）
- **输入**：task-003；Prisma QuoteRecord；PRD §8.5
- **输出**：`app/api/quotes/history/route.ts`；缺 page/size → 400；返回 `{ list, total, page, size }`
- **实现范围**：分页查询；按 customer_id 过滤；不含跨租户
- **验证**：不传 page → 400（TC-506）；传 page=1&size=20 → 200 + list
---

## task-042

- **ID**：task-042
- **状态**：已完成
- **模块**：Pricing
- **目标**：实现 GET /api/markup-configs 路由（分页列表）
- **输入**：task-003；Prisma MarkupConfig；技术设计 §4.3
- **输出**：`app/api/markup-configs/route.ts`；分页返回 MarkupConfig 列表
- **实现范围**：鉴权（宿主 token）+ 分页查询；不含写操作
- **验证**：有效 token → 200 + list；无 token → 401
---

## task-043

- **ID**：task-043
- **状态**：已完成
- **模块**：Pricing
- **目标**：实现 PUT /api/markup-configs/{customerId} 路由
- **输入**：task-005（RBAC pricing:markup:write）；Prisma MarkupConfig；PRD §6.4 §6.5
- **输出**：`app/api/markup-configs/[customerId]/route.ts`；UPSERT 操作；0~30.0 范围校验
- **实现范围**：权限校验 + 范围校验 + UPSERT + 审计字段（operator_id）
- **验证**：markup=30.0 → 200；markup=30.1 → 400（TC-505）；无权限 → 403（TC-502）；客户不存在 → 400
---

## task-018

- **ID**：task-018
- **状态**：已完成
- **模块**：Quote Engine
- **目标**：实现超时扫描定时任务（processing >30s → failed）
- **输入**：Prisma QuoteRecord；技术设计 §2.2；PRD §11.4
- **输出**：`workers/scheduler/timeout-sweeper.ts`；每 10s 扫描 processing > 30s 的记录 → UPDATE status=failed + error_code=QUOTE_TIMEOUT
- **实现范围**：每 10s 扫描 processing > 30s → 先尝试 **task-092 L3 降级**；无 L3 则 UPDATE failed + QUOTE_TIMEOUT + 写预警
- **验证**：有 L3 时 31s → done stale；无 L3 → failed QUOTE_TIMEOUT（TC-208 TC-605）
---

# Phase 5.0 — RPA 录制前置（W2 前置，CHG-T11/T12）

> 执行顺序：task-121 → task-122  
> **必须在 task-123 之前完成**：无 env 则 adapter/probe 无法验收。

## task-121

- **ID**：task-121
- **状态**：已完成
- **模块**：RPA
- **目标**：Playwright codegen 录制入口脚本
- **输入**：PRD §8.2.3 GD-15；技术设计 §9.2
- **输出**：`scripts/record-mothership.sh`（headed，`RPA_HEADLESS=false`）
- **实现范围**：启动 codegen；禁止 dashboard.mothership.com / www.mothership.com/quote
- **验证**：脚本可执行；录制产出 goto URL 与 selector 可提取
---

## task-122

- **ID**：task-122
- **状态**：已完成
- **模块**：RPA
- **目标**：录制 SOP 与 env 产出 checklist
- **输入**：task-121；PRD §8.2.3 LA→Dallas 标准样例
- **输出**：`docs/rpa-recording-sop.md`；URL→`MOTHERSHIP_QUOTE_URLS`、selector→`RPA_SELECTOR_*` 映射表
- **实现范围**：文档化人工录制流程；不含自动化提取工具
- **验证**：人工 checklist：env 填齐 8 个 selector 后可跑 probe
---


# Phase 5 — RPA 基线（W2，已完成）

> 执行顺序：task-020 → … → task-100（**全部已完成**；v1.2 增量见下方 Phase 5 v1.2 重构）

## task-020

- **ID**：task-020
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 QuoteProvider 接口定义
- **输入**：技术设计 §7.1；PRD 附录 A
- **输出**：`modules/providers/quote-provider.ts`；TypeScript interface QuoteProvider + QuoteItem + QuoteRequest
- **实现范围**：仅类型定义；items.length === 4 约束注释
- **验证**：TypeScript 编译无报错；MothershipRPAProvider 实现该接口不报错
---

## task-021

- **ID**：task-021
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 BullMQ 队列配置（quote-rpa 队列）
- **输入**：技术设计 §7.4；BullMQ ^5.x
- **输出**：`workers/rpa/index.ts` 消费入口；队列名=quote-rpa；并发=2；单job超时=25s；重试=1次
- **实现范围**：Worker 创建 + 连接 Redis；不含 RPA 业务逻辑
- **验证**：Worker 启动无报错；向队列推入 job → Worker 消费到
---

## task-022

- **ID**：task-022
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 session-manager.ts（Mothership 登录态维护）
- **输入**：技术设计 §7.2；Playwright
- **输出**：`workers/rpa/session-manager.ts`；`getSession()` 返回已登录 browser context；SESSION_EXPIRED 时重登1次
- **实现范围**：浏览器常驻 context + 登录状态检测 + 自动重登；不含报价抓取
- **验证**：冷启动 → 自动登录；session 过期 → 重登成功；重登失败 → 抛 SESSION_EXPIRED
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-023

- **ID**：task-023
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 preCheck()（页面健康检查）
- **输入**：技术设计 §7.2；Playwright
- **输出**：`workers/rpa/mothership.ts` 中 `preCheck()` 方法；检测 URL 非 login/captcha；关键表单 selector 存在
- **实现范围**：仅检测，不填表单；失败抛对应错误分类
- **验证**：正常页面 → true；被重定向到 /login → SESSION_EXPIRED；验证码页 → RPA_CAPTCHA；selector 消失 → STRUCT_CHANGE
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-024

- **ID**：task-024
- **状态**：已完成
- **模块**：RPA
- **目标**：实现地址联想点选逻辑（方案 A：place_id；方案 B：联想下拉）
- **输入**：PRD §4.2.1；技术设计 §7.2.1；GD-9
- **输出**：`workers/rpa/mothership.ts` 中 `fillAddress()` 方法；方案 A 优先；方案 B 备用
- **实现范围**：输入街道 → waitForSelector 联想 → click 匹配项 → 断言锁定（联想消失）；锁定失败 → ADDRESS_SUGGESTION_NOT_FOUND
- **验证**：正常地址 → 表单锁定；联想不出现 → 抛 ADDRESS_SUGGESTION_NOT_FOUND；超5s → timeout
---

## task-025

- **ID**：task-025
- **状态**：已完成
- **模块**：RPA
- **目标**：实现表单填写（weight/dims/pallet_count/cargo_type）
- **输入**：PRD §4.2.2~4.2.4；Playwright；数据为 lb/in（已换算）
- **输出**：`workers/rpa/mothership.ts` 中 `fillCargo()` 方法
- **实现范围**：填 pallet_count（映射 Mothership Pallet quantity）+ weight_lb + dims_in + cargo_type；不含地址
- **验证**：录制回放验证字段填入正确；pallet_count=2 → Mothership quantity=2 Pallet
---

## task-026

- **ID**：task-026
- **状态**：已完成
- **模块**：RPA
- **目标**：实现四档报价抓取（standard/guaranteed × lowest/fastest）
- **输入**：技术设计 §7.2 步骤4~7；Playwright
- **输出**：`workers/rpa/mothership.ts` 中 `scrapeQuotes()` 方法；返回 QuoteItem[4]
- **实现范围**：提交一次 → 在结果页分别点 standard/guaranteed → 各切换 lowest/fastest → 抓 rawFreight + transitDays；严禁多次提交
- **验证**：实测返回 items.length===4；每档 rawFreight>0；transitDays 非空
---

## task-027

- **ID**：task-027
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 RPA 数据一致性校验
- **输入**：PRD §8.2；技术设计 §7.2
- **输出**：`workers/rpa/mothership.ts` 中 `validateResult()` 方法
- **实现范围**：4档均存在校验；每档 rawFreight>0；transitDays 非空；失败 → 抛 RPA_DATA_INVALID
- **验证**：Vitest：3档数据 → RPA_DATA_INVALID；price=0 → RPA_DATA_INVALID；price=-1 → RPA_DATA_INVALID
---

## task-028

- **ID**：task-028
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 RPA 错误分类处理与熔断机制
- **输入**：技术设计 §7.3；PRD §11.5；task-036（熔断 Redis key）
- **输出**：`workers/rpa/mothership.ts` 错误处理逻辑；连续失败≥3次 → 写 circuit:rpa=open 10min
- **实现范围**：错误分类（CAPTCHA→暂停Worker；STRUCT_CHANGE→不重试；SESSION_EXPIRED→重登1次）+ 熔断计数
- **验证**：连续3次失败 → Redis circuit:rpa=open；熔断期间 Worker 不消费新 job（TC-209）
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-110

- **ID**：task-110
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 RPA 业务错误码映射（CARRIER_NO_CAPACITY / ADDRESS_NOT_SUPPORTED）
- **输入**：PRD §5.6
- **输出**：`modules/rpa/error-mapper.ts`；Mothership 页面文案 → 对应 error_code
- **实现范围**：RPA 抓取阶段识别无运力/邮编不支持；映射后走 092 或 failed
- **验证**：mock 无运力页 → error_code=CARRIER_NO_CAPACITY；不支持邮编 → ADDRESS_NOT_SUPPORTED
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-029

- **ID**：task-029
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 RPA 成功后写缓存 + 落库 + 偏差检测编排
- **输入**：task-026；task-032（L2）；task-033（L3）；task-031（L1）；Prisma QuoteRecord；task-051（偏差检测）
- **输出**：`workers/rpa/index.ts` 中 job 完成处理逻辑
- **实现范围**：RPA成功 → 写L2(3min+jitter)+L3(30min) → UPDATE QuoteRecord(done) → 更新 quote_cache_meta → 偏差检测 → 写L1；**RPA失败 → 调用 task-092**
- **验证**：集成测试：RPA 成功后 Redis L2/L3 有值；DB source_type=rpa；偏差≥5%时 alert_log 有 PRICE_DEVIATION；失败时 092 被调用
---

## task-093

- **ID**：task-093
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 GET /api/rpa/status（Worker/队列/熔断状态）
- **输入**：PRD §3.1 管理端 RPA 进度；技术设计 §4.2 admin rpa:operate
- **输出**：`app/api/rpa/status/route.ts`；返回 `{ workers[], queueDepth, circuitOpen, successRate24h }`
- **实现范围**：读 Redis circuit + BullMQ 队列 + Worker 心跳；admin + rpa:operate 权限
- **验证**：admin JWT → 200 + 字段非空；宿主 token → 403；熔断时 circuitOpen=true
---

## task-099

- **ID**：task-099
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 POST /api/rpa/worker/pause（管理员手动暂停 Worker）
- **输入**：PRD §2.4「暂停 RPA Worker」；task-005 rpa:operate
- **输出**：`app/api/rpa/worker/pause/route.ts`；body `{ worker_id, duration_minutes }`；写 Redis worker:pause:{id}
- **实现范围**：admin 手动暂停；不含自动 CAPTCHA 暂停（task-054）
- **验证**：admin 调用 → Worker 指定时间内不接 job；非 admin → 403
---

## task-100

- **ID**：task-100
- **状态**：已完成
- **模块**：RPA
- **目标**：实现 POST /api/quotes/manual-fallback（人工兜底录入）
- **输入**：PRD §2.4「触发人工兜底录入」
- **输出**：`app/api/quotes/manual-fallback/route.ts`；admin 录入四档原始价 → 写 L3 + UPDATE quote done(stale)
- **实现范围**：仅 admin；校验四档 + 金额>0；写 audit_log + alert_log(MANUAL_FALLBACK)
- **验证**：admin 提交合法四档 → quote status=done source=stale；非 admin → 403
---


# Phase 5 — RPA v1.2 重构（PRD v0.6）

> 执行顺序：task-123 → task-124 → task-125 → task-126 → task-127 → task-128 → task-129 → task-130 → task-131  
> **里程碑 M2（v1.2）**：`RPA_MOCK_MODE=false` 下 `scripts/probe-rpa.ts` 连续 3 次 exit 0，四档 `source_type=rpa`。

## task-123

- **ID**：task-123
- **状态**：待执行
- **模块**：RPA
- **目标**：实现 QuotePageAdapter 多 URL 探测
- **输入**：技术设计 §7.2.3 CHG-T05/T06；task-122
- **输出**：`workers/rpa/quote-page-adapter.ts`：`resolveQuoteEntry` / `preCheck` / `isLoginWithoutCredentials`
- **实现范围**：按 `MOTHERSHIP_QUOTE_URLS` 顺序 goto + preCheck；全失败 → QUOTE_ENTRY_UNAVAILABLE
- **验证**：单测 mock 多 URL；首个可用 URL 写入 activeUrl；全失败抛 QUOTE_ENTRY_UNAVAILABLE
---

## task-124

- **ID**：task-124
- **状态**：待执行
- **模块**：RPA
- **目标**：storageState 生命周期（§7.5）
- **输入**：技术设计 §7.5 CHG-T09；task-120
- **输出**：重构 `workers/rpa/session-manager.ts`：`createContext` load、`persistContext` writeback、`invalidateStorageState`
- **实现范围**：成功查价写回；login 无凭据先 invalidate 再重试 goto
- **验证**：单测：存在 state 文件时 load；成功后 persist；invalidate 删除文件
---

## task-125

- **ID**：task-125
- **状态**：待执行
- **模块**：RPA
- **目标**：地址联想 selector env 化
- **输入**：技术设计 §7.2.1 CHG-T07；task-123
- **输出**：`workers/rpa/mothership.ts` `fillAddress` 改用 `RPA_SELECTOR_PICKUP/DELIVERY/ADDRESS_SUGGESTION`
- **实现范围**：失败 → ADDRESS_SUGGESTION_NOT_FOUND；不重试
- **验证**：mock 页面：联想成功锁定；无下拉 → ADDRESS_SUGGESTION_NOT_FOUND
---

## task-126

- **ID**：task-126
- **状态**：待执行
- **模块**：RPA
- **目标**：表单填写与提交 selector env 化
- **输入**：技术设计 §7.2 CHG-T07；task-125
- **输出**：`fillCargo` + submit 改用 `RPA_SELECTOR_SUBMIT` 等 env
- **实现范围**：移除硬编码 data-testid；全部读 `process.env.RPA_SELECTOR_*`
- **验证**：录制回放：字段填入正确；submit 点击成功
---

## task-127

- **ID**：task-127
- **状态**：待执行
- **模块**：RPA
- **目标**：四档抓取 selector env 化
- **输入**：技术设计 §7.2 步骤 7 CHG-T07；task-126
- **输出**：`scrapeQuotes` 改用 `RPA_SELECTOR_STANDARD/GUARANTEED/LOWEST/FASTEST/PRICE`
- **实现范围**：standard/guaranteed × lowest/fastest 各抓取一次；items.length===4
- **验证**：mock 结果页返回 4 档；每档 rawFreight>0
---

## task-128

- **ID**：task-128
- **状态**：待执行
- **模块**：RPA
- **目标**：错误分类 v0.6（入口 vs 抓取）
- **输入**：PRD §5.6 §11.5 CHG-P03/P04；技术设计 §7.3 CHG-T08
- **输出**：`workers/rpa/error-mapper.ts` + job-handler：QUOTE_ENTRY_UNAVAILABLE；login 无凭据 → STRUCT_CHANGE
- **实现范围**：入口/地址错误不重试；SESSION_EXPIRED 仅已配置凭据且曾成功登录
- **验证**：单测各错误分支映射；QUOTE_ENTRY_UNAVAILABLE 不触发 job 重试
---

## task-129

- **ID**：task-129
- **状态**：待执行
- **模块**：Quote Engine
- **目标**：Fallback 入口错误处理
- **输入**：PRD §2.5 CHG-P03；task-128
- **输出**：增补 `modules/quote/fallback-orchestrator.ts`：入口类错误不消耗 RPA job 重试配额
- **实现范围**：QUOTE_ENTRY_UNAVAILABLE / ADDRESS_SUGGESTION_NOT_FOUND 直接走 L3 或 400
- **验证**：单测：入口错误 job attempts=1；PAGE_LOAD_TIMEOUT 仍可重试 1 次
---

## task-130

- **ID**：task-130
- **状态**：待执行
- **模块**：RPA
- **目标**：升级 probe 探针（连续 3 次 exit 0）
- **输入**：PRD TC-601 §12.7 CHG-P08/P09；技术设计 CHG-T02
- **输出**：升级 `scripts/probe-rpa.ts` + `scripts/probe-rpa-3x.sh`；断言 items.length===4
- **实现范围**：`RPA_MOCK_MODE=false` 真实探测；3x 脚本循环调用
- **验证**：`bash scripts/probe-rpa-3x.sh` 连续 3 次 exit 0；输出 4 档 JSON
---

## task-131

- **ID**：task-131
- **状态**：待执行
- **模块**：RPA
- **目标**：Patchright 可选驱动（P1）
- **输入**：技术设计 §6.2 `RPA_USE_PATCHRIGHT` CHG-T03
- **输出**：`workers/rpa/session-manager.ts` 支持 `RPA_USE_PATCHRIGHT=true` 切换浏览器
- **实现范围**：默认 Playwright；Patchright 为可选降级路径
- **验证**：`RPA_USE_PATCHRIGHT=true` 时 worker 启动无报错；false 时行为不变
---

# Phase 6 — 预警 / 审计 / 管理 API

> 执行顺序：task-050 → task-051 → task-052 → task-053 → task-054 → task-055 → task-094 → task-105 → task-056 → task-057

## task-050

- **ID**：task-050
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 AlertService.writeAlert() 通用写入函数
- **输入**：Prisma AlertLog；PRD §7.7
- **输出**：`modules/alert/service.ts`；`writeAlert(type, quoteId?, cargoHash?, detail?)` 异步写 alert_log
- **实现范围**：INSERT alert_log；失败时仅 log error，不影响主流程（try-catch）
- **验证**：Vitest：调用后 DB alert_log 新增1条；DB 写失败 → 不抛出异常，主流程继续
---

## task-051

- **ID**：task-051
- **状态**：已完成
- **模块**：Alert
- **目标**：实现偏差检测逻辑（与 quote_cache_meta 比对）
- **输入**：task-050；Prisma QuoteCacheMeta；PRD §7.5
- **输出**：`modules/alert/deviation-detector.ts`；`detectDeviation(cargoHash, newRawTotals)` → 偏差≥5%时写 PRICE_DEVIATION
- **实现范围**：abs(new-prev)/prev ≥0.05 触发；首次无基准不触发；触发后更新 quote_cache_meta
- **验证**：TC-210：prev=352，new=370（5.1%）→ PRICE_DEVIATION；TC-211：偏差4.9%→不触发；TC-212：首次→不触发
---

## task-052

- **ID**：task-052
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 STALE_FALLBACK 预警（L3 降级时写入）
- **输入**：task-050；PRD §7.7
- **输出**：在 Fallback 流程中调用 `writeAlert('STALE_FALLBACK', quoteId, cargoHash)`
- **实现范围**：仅在 L3 命中路径写入；不含通知机制（站内仅DB）
- **验证**：L3 命中路径 → alert_log 新增 STALE_FALLBACK（TC-205）
---

## task-053

- **ID**：task-053
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 RPA_FAILED 预警（RPA失败无L3时写入）
- **输入**：task-050；PRD §7.7
- **输出**：在 Fallback 无L3路径中调用 `writeAlert('RPA_FAILED', quoteId, cargoHash)`
- **实现范围**：仅在 L3 miss 路径写入
- **验证**：L3 miss + RPA失败 → alert_log 新增 RPA_FAILED（TC-206）
---

## task-054

- **ID**：task-054
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 RPA_CAPTCHA 预警 + Worker 暂停
- **输入**：task-050；task-036（熔断）；PRD §11.5
- **输出**：RPA_CAPTCHA 错误处理：写 alert_log + 暂停当前 Worker 10min
- **实现范围**：写预警 + Redis 标记 Worker 暂停状态；不含多 Worker 调度
- **验证**：RPA 触发验证码 → alert_log RPA_CAPTCHA + Worker 10分钟内不接新 job（TC-603）
---

## task-055

- **ID**：task-055
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 STRUCT_CHANGE 预警写入
- **输入**：task-050；PRD §11.5
- **输出**：STRUCT_CHANGE 错误处理：写 alert_log；preCheck 失败触发
- **实现范围**：仅写预警；不含 RPA 重试（不重试）
- **验证**：preCheck 失败 → alert_log STRUCT_CHANGE（TC-604）
---

## task-094

- **ID**：task-094
- **状态**：已完成
- **模块**：Alert / DB
- **目标**：实现 audit_log 表 + AuditService 写入
- **输入**：PRD §10.6 §11.9；task-070 schema 含 AuditLog
- **输出**：`modules/audit/service.ts`；`writeAudit(action, actorId, resource, detail)` → audit_log 表
- **实现范围**：越权/注入/敏感操作写入；失败 log 不阻断主流程
- **验证**：TC-501 越权后 audit_log 有记录；TC-504 注入尝试有 SECURITY audit
---

## task-105

- **ID**：task-105
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 SESSION_EXPIRED / SECURITY 预警类型写入
- **输入**：PRD §11.5 §11.9；附录 C alert_type
- **输出**：在 session 过期路径写 SESSION_EXPIRED；IP 封禁/注入写 SECURITY alert
- **实现范围**：扩展 task-050 writeAlert 支持新 type；不含通知推送
- **验证**：SESSION_EXPIRED 触发 → alert_log 有记录；IP 超限 → SECURITY alert

---

# 观测与指标（Metrics）
---

## task-056

- **ID**：task-056
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 GET /api/alerts 路由（分页，筛选，admin 权限）
- **输入**：task-005（alert:read）；Prisma AlertLog；PRD §8.5
- **输出**：`app/api/alerts/route.ts`；支持 type/status 筛选；强制分页；admin 权限
- **实现范围**：鉴权 + 分页 + 筛选；不含处理操作
- **验证**：admin JWT + type=RPA_FAILED → 仅返回该类型；宿主 token → 403
---

## task-057

- **ID**：task-057
- **状态**：已完成
- **模块**：Alert
- **目标**：实现 POST /api/alerts/{id}/resolve 路由
- **输入**：task-003；Prisma AlertLog；PRD §8.5
- **输出**：`app/api/alerts/[id]/resolve/route.ts`；UPDATE status=resolved + resolver_id + resolved_at
- **实现范围**：鉴权（admin）+ DB 更新；不含通知
- **验证**：admin 调用 → 200 + status=resolved；非 admin → 403；不存在 id → 404
---

# Phase 7 — 观测指标

> 执行顺序：task-101 → task-102

## task-101

- **ID**：task-101
- **状态**：已完成
- **模块**：Metrics
- **目标**：实现 MetricsCollector（GD-4 三率 + 缓存命中率）
- **输入**：PRD §10.2 GD-4 §12.7
- **输出**：`modules/metrics/collector.ts`；Redis 计数器：`api_success_rate` / `rpa_success_rate` / `realtime_rate` / `cache_hit_rate`
- **实现范围**：POST/GET quotes 与 RPA 回调处埋点；不含可视化
- **验证**：100 次 mock 请求后 GET 内部指标 API 可算出三率；公式与 PRD 一致
---

## task-102

- **ID**：task-102
- **状态**：已完成
- **模块**：Metrics
- **目标**：实现 GET /api/metrics/dashboard（admin 汇总指标）
- **输入**：task-101；PRD §12.7 Go-No-Go
- **输出**：`app/api/metrics/dashboard/route.ts`；返回三率、P95、队列深度、未处理预警数
- **实现范围**：admin 只读；供 /admin/dashboard 消费
- **验证**：admin → 200 + 含 GD-4 四指标；非 admin → 403

---

# 前端 UI 系统
---

# Phase 8 — 前端基础与嵌入组件

> 执行顺序：task-060 → task-061 → task-062 → task-065 → task-066 → task-067 → task-068 → task-069 → task-106

## task-060

- **ID**：task-060
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现项目基础配置（Tailwind + CSS 变量 + 字体）
- **输入**：UI设计 §3.1~3.5 §4.4 §4.5
- **输出**：`app/globals.css`（CSS 变量）；`tailwind.config.ts`（primary=teal-600）；Geist 字体引入
- **实现范围**：仅样式基础配置；不含组件
- **验证**：`--color-primary: #0D9488` 生效；Geist Sans 字体加载
---

## task-061

- **ID**：task-061
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现基础 UI 组件库（7个通用组件）
- **输入**：UI设计 §3.6 §4.1
- **输出**：`components/ui/` 下：PrimaryButton / InputField / SelectField / LoadingSpinner / Skeleton / ErrorBanner / WarningBanner
- **实现范围**：各组件含 loading/disabled/error 状态；phosphor-icons；UI-01~UI-11 规则
- **验证**：Storybook/手动验证：每个组件 loading 状态 + error 状态正常渲染
---

## task-062

- **ID**：task-062
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现布局组件（AppLayout + AdminLayout）
- **输入**：UI设计 §1.4 §4.6
- **输出**：`components/layout/app-layout.tsx` + `components/layout/admin-layout.tsx`
- **实现范围**：顶栏导航 + 响应式侧栏；未登录 → 重定向 /login
- **验证**：客户路由使用 AppLayout 正确渲染；管理端路由使用 AdminLayout；移动端单列布局
---

## task-065

- **ID**：task-065
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 QuoteForm 组件（查价表单）
- **输入**：UI设计 §2.4 表单字段布局；PRD §4.2；react-hook-form + zod
- **输出**：`components/quote/quote-form.tsx`；含地址、单托重量/尺寸、托盘数、货物类型；**display_unit 公制/英制切换（PRD §4.2.7，仅 UI）**
- **实现范围**：表单字段 + onBlur 校验 + display_unit 切换；提交仍按 weight.unit/dimensions.unit
- **验证**：zip=9001 → 「邮编格式无效」；pallet_count=11 → 超范围；kg/cm 切换 UI 正确；selected_from_suggestions=false → 「请从地址列表中选择」
---

## task-066

- **ID**：task-066
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 useQuotePolling hook（2s 轮询 + 网络退避，§11.3）
- **输入**：UI设计 §2.4；PRD GD-5 §11.3
- **输出**：`hooks/use-quote-polling.ts`；每 2s GET；最多 15 次；**响应丢失时指数退避重试 GET**
- **实现范围**：轮询 + 退避逻辑；不含 UI 渲染
- **验证**：mock 前 3 次网络失败第 4 次成功 → 最终得 done；15 次仍 processing → QUOTE_TIMEOUT
---

## task-067

- **ID**：task-067
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 QuoteCard 组件（四档报价展示）
- **输入**：UI设计 §3.6 Card；PRD §5.1 §5.4
- **输出**：`components/quote/quote-card.tsx`；Tab：standard/guaranteed；子Tab：lowest/fastest；价格 font-mono；is_realtime=false → WarningBanner角标
- **实现范围**：Tab 切换 + 价格明细 breakdown 展示 + 非实时角标；不含倒计时
- **验证**：4档数据渲染正确；is_realtime=false → WarningBanner 显示（UI-06）；金额 font-mono $1,234.56 格式
---

## task-068

- **ID**：task-068
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 CountdownTimer 组件（3分钟倒计时）
- **输入**：UI设计 §2.4；PRD §7.4
- **输出**：`components/quote/countdown-timer.tsx`；<30s 时文字变 Warning 色；归零 → ConfirmDialog
- **实现范围**：倒计时逻辑 + 颜色变化 + 过期确认弹窗
- **验证**：valid_until 传入 → 正确倒计时；<30s → warning 色；归零 → ConfirmDialog 弹出（UI-05）
---

## task-069

- **ID**：task-069
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 QuoteResultPanel（7种状态容器）
- **输入**：UI设计 §2.4 页面状态机；task-067 task-068
- **输出**：`components/quote/quote-result-panel.tsx`；根据 QuotePageStatus 渲染对应 UI
- **实现范围**：idle→插画；processing→Skeleton+进度条；success/fallback→QuoteCard+CountdownTimer；error→ErrorBanner；expired→ConfirmDialog
- **验证**：7种状态逐一切换，UI 对应正确
---

## task-106

- **ID**：task-106
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现宿主内嵌查价组件 EmbeddedQuoteWidget（GD-11）
- **输入**：task-065~069；task-015/016；PRD §2.2 §3.1；技术设计 §8.1
- **输出**：`components/embed/embedded-quote-widget.tsx` + `packages/embed/` 或 `public/embed.js`；props：`serviceToken, customerId, apiBaseUrl`
- **实现范围**：QuoteForm + QuoteResultPanel + 轮询 + 防重复提交；**无独立 /quote 路由**；供宿主 iframe/script 嵌入
- **验证**：demo 宿主页嵌入 → 完整查价流程；TC-101 UI；POST 带宿主 token + customer_id
---

# Phase 8 — 管理端页面

> 执行顺序：task-063 → task-064 → task-079 → task-103 → task-104 → task-080

## task-063

- **ID**：task-063
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 P01 登录页 /login
- **输入**：UI设计 §2.2；task-002（login API）
- **输出**：`app/login/page.tsx`；状态机：idle/loading/error/success；调 POST /api/auth/login
- **实现范围**：表单 + 状态机 + 跳转 /dashboard；不含注册
- **验证**：正确凭据 → 跳转 /dashboard；错误密码 → ErrorBanner「用户名或密码错误」；loading 时按钮 disabled
---

## task-064

- **ID**：task-064
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 P02 工作台 /dashboard（**仅 admin**，GD-11）
- **输入**：UI设计 §2.3（admin 部分）；PRD GD-11
- **输出**：`app/dashboard/page.tsx`；快捷入口：预警中心、RPA 状态、指标看板；未处理预警角标
- **实现范围**：**不含 customer/operator 卡片**；admin 登录后默认页
- **验证**：admin → 显示「预警中心」「RPA 状态」「指标看板」；无 customer 入口
---

## task-079

- **ID**：task-079
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 P07 预警中心页 /admin/alerts（含管理操作）
- **输入**：UI设计 §2.8；task-056 task-057 task-099 task-100
- **输出**：`app/admin/alerts/page.tsx`；筛选 + DataTable + Drawer + **标记已处理 / 跳转 RPA 暂停 / 人工兜底录入入口**
- **实现范围**：类型颜色标签 + 分页 + 处理按钮 + 链到 manual-fallback
- **验证**：RPA_FAILED → Error 标签；处理后 resolved；可跳转暂停 Worker 或人工录入
---

## task-103

- **ID**：task-103
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 /admin/rpa RPA 状态与开发进度页
- **输入**：PRD §3.1 管理端 RPA；task-093
- **输出**：`app/admin/rpa/page.tsx`；展示 Worker 状态、队列深度、成功率、熔断状态；**暂停 Worker 按钮（调 task-099）**
- **实现范围**：只读 + 暂停操作；不含 Worker 代码
- **验证**：admin → 页面渲染指标；点击暂停 → Worker 停止接 job；非 admin → 403
---

## task-104

- **ID**：task-104
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 /admin/dashboard 指标汇总页
- **输入**：task-102；PRD §12.7 Go-No-Go
- **输出**：`app/admin/dashboard/page.tsx`；展示 GD-4 三率、缓存命中率、P95、未处理预警
- **实现范围**：只读 dashboard；数据来自 GET /api/metrics/dashboard
- **验证**：admin → 四指标卡片有值；低于阈值时 Warning 色
---

## task-132

- **ID**：task-132
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 /admin/queues Bull Board 队列监控页（P1）
- **输入**：技术设计 §8.2 CHG-T10；task-021
- **输出**：`app/admin/queues/page.tsx` + Bull Board 挂载；admin RBAC
- **实现范围**：展示 quote-rpa 队列深度、失败 job、延迟；文案全中文
- **验证**：admin → 页面渲染队列指标；非 admin → 403
---

## task-080

- **ID**：task-080
- **状态**：已完成
- **模块**：Frontend
- **目标**：实现 P08/P09 错误页（403/404）
- **输入**：UI设计 §1.1
- **输出**：`app/403/page.tsx` + `app/not-found.tsx`；含返回按钮
- **实现范围**：静态错误页
- **验证**：访问 /403 → 显示「无访问权限」；未知路由 → 显示「页面不存在」

---

# 测试系统（Test）
---

# 已废止（v1.1 不实施）

> 执行顺序：task-075 → task-076 → task-077 → task-078

## ~~task-075~~（v1.1 已废止）

- **ID**：task-075
- **状态**：**已废止** — 由 task-106 替代（PRD GD-11 宿主嵌入，不做独立 `/quote` 页）
- **原目标**：独立查价页 /quote
---

## ~~task-076~~（v1.1 已废止）

- **ID**：task-076
- **状态**：**已废止** — 嵌入组件内展示结果，不单独 `/quote/result/[id]` 页
---

## ~~task-077~~（v1.1 已废止）

- **ID**：task-077
- **状态**：**已废止** — 询价历史由宿主系统调用 GET /api/quotes/history，中台不提供 C 端历史页
---

## ~~task-078~~（v1.1 已废止）

- **ID**：task-078
- **状态**：**已废止** — PRD §2.3 禁止中台运营加价 UI；加价由宿主调 PUT /api/markup-configs
---

# Phase 9 — 测试与 CI（建议随模块并行，收尾全量回归）

> 执行顺序：task-081 → task-082 → task-083 → task-084 → task-085 → task-086 → task-087 → task-112 → task-113 → task-114 → task-115 → task-116 → task-088 → task-089 → task-090 → task-117 → task-118 → task-091

## task-081

- **ID**：task-081
- **状态**：已完成
- **模块**：Test
- **目标**：实现 PricingEngine 单元测试（TC-103）
- **输入**：task-041；PRD §6.3 §12.1 TC-103
- **输出**：`__tests__/pricing/engine.test.ts`；覆盖 raw_freight=320/surcharges=15/markup=10%；含surcharge=0场景；边界值markup=0/30
- **实现范围**：仅 PricingEngine 函数测试
- **验证**：`npm run test:unit` → 全部 pass；markup_amount=32.00，final_total=367.00 精确到分
---

## task-082

- **ID**：task-082
- **状态**：已完成
- **模块**：Test
- **目标**：实现 ValidationModule 单元测试（TC-201~204+E4系列）
- **输入**：task-010；PRD §4.5 §12.2
- **输出**：`__tests__/validation/validation.test.ts`；覆盖8类异常（缺字段/超范围/地址未点选/非UUID/超Mothership范围/注入/枚举外/件数字段）
- **实现范围**：仅校验函数测试
- **验证**：全部场景通过；边界值 pallet_count=25通过/26失败；总重249975通过/249976失败
---

## task-083

- **ID**：task-083
- **状态**：已完成
- **模块**：Test
- **目标**：实现 cargo_hash + 单位换算 单元测试（TC-106 TC-107）
- **输入**：task-011 task-012
- **输出**：`__tests__/quote/cargo-hash.test.ts` + `__tests__/quote/unit-converter.test.ts`
- **实现范围**：相同货物→相同hash；service_level不同→相同hash；227kg→500.05lb；122cm→48.03in
- **验证**：全部 pass
---

## task-084

- **ID**：task-084
- **状态**：已完成
- **模块**：Test
- **目标**：实现 IdempotencyModule 单元测试（TC-105 TC-207 TC-404）
- **输入**：task-013；Testcontainers（Redis）
- **输出**：`__tests__/quote/idempotency.test.ts`；并发10次相同 request_id → 仅1条DB记录
- **实现范围**：Redis mock + DB 事务测试
- **验证**：并发10次 → DB 仅1条；重启后DB兜底返回原 quote_id
---

## task-085

- **ID**：task-085
- **状态**：已完成
- **模块**：Test
- **目标**：实现 POST /api/quotes 集成测试（TC-101~108主功能）
- **输入**：task-015；Supertest；Testcontainers（MySQL+Redis）
- **输出**：`__tests__/api/quotes.test.ts`；覆盖正常流程/L2命中/L1幂等/L3降级/失败
- **实现范围**：HTTP 层集成测试（mock RPA）
- **验证**：TC-101~108 全部列于验证行；TC-101（source=rpa）；TC-104（cache）；TC-105（幂等）；TC-106/107（换算/hash）；TC-108（L2 TTL 过期，见 task-112）；TC-205/206（Fallback）
---

## task-086

- **ID**：task-086
- **状态**：已完成
- **模块**：Test
- **目标**：实现安全与权限集成测试（TC-501~506）
- **输入**：task-003 task-005；Supertest
- **输出**：`__tests__/api/security.test.ts`；覆盖越权/RBAC/限流/SQL注入
- **实现范围**：HTTP 层安全测试
- **验证**：TC-501（越权403+**task-094 audit**）；TC-502；TC-503（customer+**IP task-035-ip**）；TC-504（注入400+SECURITY alert）；TC-505；TC-506
---

## task-087

- **ID**：task-087
- **状态**：已完成
- **模块**：Test
- **目标**：实现偏差预警集成测试（TC-210~212）
- **输入**：task-051；Prisma QuoteCacheMeta
- **输出**：`__tests__/alert/deviation.test.ts`；覆盖5.1%触发/4.9%不触发/首次不触发
- **实现范围**：仅偏差检测逻辑测试
- **验证**：TC-210/TC-211/TC-212 全部 pass
---

## task-112

- **ID**：task-112
- **状态**：已完成
- **模块**：Test
- **目标**：TC-108 L2 过期触发新 RPA 集成测试
- **输入**：task-032；task-014
- **输出**：`__tests__/api/l2-expire.test.ts`；mock TTL 过期或手动 DEL L2 后 POST → source=rpa
- **验证**：L2 不存在 → 触发 RPA（mock）；source_type=rpa
---

## task-113

- **ID**：task-113
- **状态**：已完成
- **模块**：Test
- **目标**：TC-401 cache vs RPA 一致性测试
- **输入**：task-032 task-051
- **输出**：`__tests__/api/cache-consistency.test.ts`
- **验证**：L2 raw_total 与写入时 RPA 一致；新 RPA 偏差≥5% 触发 PRICE_DEVIATION
---

## task-114

- **ID**：task-114
- **状态**：已完成
- **模块**：Test
- **目标**：TC-403 加价可复算 DB 集成测试
- **输入**：task-041
- **输出**：`__tests__/pricing/recalc-from-db.test.ts`
- **验证**：从 DB 读 raw_freight + markup 重算 final_total 误差 ≤0.01
---

## task-115

- **ID**：task-115
- **状态**：已完成
- **模块**：Test
- **目标**：FallbackOrchestrator 集成测试（TC-205/206/602）
- **输入**：task-092
- **输出**：`__tests__/quote/fallback-orchestrator.test.ts`
- **验证**：有 L3 → stale done + STALE_FALLBACK；无 L3 → failed + RPA_FAILED
---

## task-116

- **ID**：task-116
- **状态**：已完成
- **模块**：Test
- **目标**：RPA 状态 API + 管理员暂停集成测试
- **输入**：task-093 task-099
- **输出**：`__tests__/api/rpa-admin.test.ts`
- **验证**：GET status 200；POST pause 后 Worker 不接 job
---

## task-088

- **ID**：task-088
- **状态**：已完成
- **模块**：Test
- **目标**：实现 RPA Worker E2E 测试（TC-601~605，使用 RPA mock）
- **输入**：task-021~029；Playwright mock；技术设计 §11.1
- **输出**：`e2e/rpa.test.ts`；模拟正常/验证码/DOM变更/超时场景
- **实现范围**：Worker 链路测试（不真实调 Mothership）
- **验证**：TC-601（**quotes=4 档**，非 PRD TC-601 笔误的 2 条）；TC-602~605
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-089

- **ID**：task-089
- **状态**：已完成
- **模块**：Test
- **目标**：实现查价页 E2E 测试（Playwright，核心路径）
- **输入**：task-106；Playwright E2E；PRD §12.8
- **输出**：`e2e/quote-flow.test.ts`；5 条核心路径（嵌入组件 demo 页）
- **实现范围**：浏览器级 E2E（mock API 响应）
- **验证**：5条路径全部 pass；processing Skeleton 显示；成功后显示4档Tab；stale 时 WarningBanner 显示
---

## task-090

- **ID**：task-090
- **状态**：已完成
- **模块**：Test
- **目标**：实现限流性能测试（TC-303 TC-301）
- **输入**：k6；task-035（限流）；PRD §12.3
- **输出**：`load-tests/rate-limit.js` + `load-tests/rpa-p95.js` + **`load-tests/l2-p95.js`（TC-302）**
- **实现范围**：customer 60/min；RPA P95<30s；**L2 命中 P95<300ms**
- **验证**：TC-303；TC-301；**TC-302 P95<300ms**
---

## task-117

- **ID**：task-117
- **状态**：已完成
- **模块**：Test
- **目标**：Go-No-Go GD-4 指标验收脚本
- **输入**：task-101 task-102；PRD §12.7
- **输出**：`scripts/go-no-go-check.ts`；输出 PASS/FAIL 与四指标实测值
- **验证**：1000 次 mock 后脚本判定三率/命中率是否达标
- **v1.2 说明**：基线已实现；行为变更由 task-12x 承接

---

## task-118

- **ID**：task-118
- **状态**：已完成
- **模块**：Test
- **目标**：E2E + Load nightly workflow
- **输入**：task-089 task-090 task-117
- **输出**：`.github/workflows/e2e-nightly.yml`
- **验证**：cron 触发；artifact 上传 k6 报告

---
---

## task-133

- **ID**：task-133
- **状态**：待执行
- **模块**：Test
- **目标**：TC-601 修订（四档 + probe 对齐 PRD v0.6）
- **输入**：PRD TC-601 CHG-P08；task-130
- **输出**：修订 `e2e/rpa.test.ts` / `__tests__/integration/rpa-probe.test.ts`；items=4 断言
- **实现范围**：mock 关闭时 probe 四档；文档对齐 v0.6 口径
- **验证**：TC-601 items.length===4；非 2 条
---

## task-134

- **ID**：task-134
- **状态**：待执行
- **模块**：Test
- **目标**：Go-No-Go probe 3/3 门禁
- **输入**：PRD §12.7 CHG-P09；task-130
- **输出**：`scripts/go-no-go-check.ts` 增 probe 3/3 条件（`RPA_MOCK_MODE=false` 时启用）
- **实现范围**：probe 失败 → Go-No-Go FAIL；mock 模式跳过 probe 门禁
- **验证**：mock 模式 PASS；真实 env 下 probe 3/3 才 PASS
---

## task-135

- **ID**：task-135
- **状态**：待执行
- **模块**：Test
- **目标**：CI nightly probe 步骤
- **输入**：task-134；task-118
- **输出**：`.github/workflows/e2e-nightly.yml` 可选 headed probe 步骤（secrets 配 MOTHERSHIP env）
- **实现范围**：nightly 不阻断 merge；probe 失败上传 artifact
- **验证**：workflow 语法有效；secrets 缺失时跳过 probe 步骤
---

## task-091

- **ID**：task-091
- **状态**：已完成
- **模块**：Test
- **目标**：配置 CI 门禁（GitHub Actions）
- **输入**：技术设计 §11.2；package.json scripts
- **输出**：`.github/workflows/ci.yml`；lint + test:unit + test:integration + build；**`.github/workflows/e2e-nightly.yml` 含 E2E + load（task-118）**
- **实现范围**：CI 阻断 unit/integration；E2E/load  nightly 不阻断 merge 但报告 Go-No-Go
- **验证**：PR push → unit/integration pass 才绿；nightly 跑 e2e + k6
---

## 附录 A：验收用例 → Task 映射（v1.2 全覆盖）

| TC | 主验证 Task | 辅助 Task |
|----|-------------|-----------|
| TC-101~108 | 085, 112 | 014, 032, 013, 011, 012 |
| TC-201~204 | 082, 010 | — |
| TC-205~206 | 115, 092, **129** | 052, 053 |
| TC-207 | 084, 013 | — |
| TC-208~209 | 018, 028, 036, **128** | 092 |
| TC-210~212 | 087, 051 | — |
| TC-301~304 | 090, 018, 021 | — |
| TC-401 | 113 | 032, 051 |
| TC-402~404 | 084, 019 | 013 |
| TC-403 | 114 | 041 |
| TC-501~506 | 086, 094, 035-ip | 004, 010 |
| TC-601~605 | 088, 115, **130, 133** | 026, 027, 092, 123–127 |
| Go-No-Go | 117, 101, 102, **134** | 090, **130** |
| probe 3/3 | **130, 134, 135** | 121, 122 |

---

## 附录 B：完整性矩阵（PRD v0.6 + 技术设计 v1.2）

### B.1 维度覆盖

| 维度 | v1.1 基线 Task | v1.2 增量 Task |
|------|----------------|----------------|
| PRD 功能点 | 001–118（除废止） | GD-15(119–122)、错误码(128–129)、probe(130,133–135)、Bull Board(132) |
| 技术模块 | 各 Phase 已覆盖 | quote-page-adapter(123)、storageState(124)、selector env(125–127)、probe(130) |
| UI 页面 | 106 + admin 6 页 | `/admin/queues`(132) |
| API（§8.5 七接口 + RPA admin） | 015–018, 042–043, 056–057, 029/093/099/100 | 无新增 REST |
| 数据表（6 张） | 070–073 | 无新增表 |

### B.2 PRD v0.6 变更 → Task

| CHG ID | 章节 | Task |
|--------|------|------|
| CHG-P02 | GD-15 | 119, 121, 122 |
| CHG-P03 | §2.5 | 128, 129 |
| CHG-P04 | §5.6 | 128 |
| CHG-P05 | §8.2 | 123, 124 |
| CHG-P06 | §8.2.1~8.2.3 | 119, 121, 122, 125–127 |
| CHG-P07 | §11.5 | 124, 128 |
| CHG-P08 | TC-601 | 130, 133 |
| CHG-P09 | §12.7 | 134, 135 |

### B.3 技术设计 v1.2 变更 → Task

| CHG ID | 章节 | Task |
|--------|------|------|
| CHG-T02 | §5 目录 | 121, 130 |
| CHG-T03 | §6.2 env | 119, 131 |
| CHG-T04 | §6.1 compose | 120 |
| CHG-T05~T07 | §7.2 | 123–127 |
| CHG-T08 | §7.3 | 128 |
| CHG-T09 | §7.5 | 124, 120 |
| CHG-T10 | §8.2 | 132 |
| CHG-T11~T12 | §9.2 | 121, 122 |

### B.4 v1.1 Traceability Matrix 缺口（已闭合，基线）

| Matrix 缺口 | Task |
|-------------|------|
| Fallback 编排无独立 Task | task-092, 115 |
| GET /api/rpa/status + /admin/rpa | task-093, 103 |
| audit log | task-070(schema), 094 |
| confidence_score | task-095 |
| lib/response.ts / lib/prisma.ts | task-096, 097 |
| GD-4 metrics | task-101, 102, 104, 117 |
| IP 限流 | task-035-ip |
| 503 retry | task-098 |
| 管理员暂停 RPA | task-099, 103 |
| 人工兜底录入 | task-100, 079 |
| display_unit | task-065 |
| 客户端退避 | task-066 |
| 宿主嵌入 vs 独立页 | task-106（废止 075~077） |
| 中台加价 UI 漂移 | 废止 078 |
| TC-108/302/401/403 | task-112~114 |
| quote_id 冲突 | task-109 |
| CARRIER/ADDRESS 错误码 | task-110 |
| BR-5.5 四档齐全 | task-111 |
| SESSION_EXPIRED/SECURITY | task-105 |
| rpa:operate RBAC | task-005 |

