You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chajia/docs/查价系统-工程任务拆解-v1.2.md

1719 lines
68 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 查价系统 工程任务拆解清单 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 |