|
|
# implementation-plan
|
|
|
|
|
|
> 功能实施步骤与执行日志。**v1.1 基线(Phase 0~9)已完成**;**v1.2 增量从 Phase 0 的 task-119 起按序执行**。
|
|
|
> 任务详情见 `docs/查价系统-工程任务拆解-v1.2.md`(当前权威版本)。
|
|
|
|
|
|
## 里程碑
|
|
|
|
|
|
| 里程碑 | 条件 | 验收 |
|
|
|
|--------|------|------|
|
|
|
| M1 | Phase 0~4 基线 | 已完成 — `POST /quotes` L2 命中、幂等、校验 400 |
|
|
|
| M2-v1.2 | Phase 0(119–120) + Phase 5.0~5 + Phase 10 | `RPA_MOCK_MODE=false` 下 `probe-rpa.ts` **连续 3 次 exit 0**,四档 `source_type=rpa` |
|
|
|
| M3 | Phase 8 基线 | 已完成 — 宿主内嵌组件 + 管理端 |
|
|
|
| M4 | Phase 9 基线 + task-133~135 | CI 全绿 + Go/No-Go probe 门禁 |
|
|
|
|
|
|
## v1.2 当前执行入口(Phase 0 增量)
|
|
|
|
|
|
| 顺序 | Task | 状态 | 说明 |
|
|
|
|------|------|------|------|
|
|
|
| 1 | task-119 | **Done** | GD-15 RPA env 模板 + fail-fast |
|
|
|
| 2 | task-120 | **Done** | docker `rpa_state` volume |
|
|
|
| 3 | task-121~122 | 待执行 | RPA 录制前置 |
|
|
|
| 4 | task-123~131 | **Done** | RPA v1.2 重构 |
|
|
|
| 5 | task-132 | 已完成 | Bull Board |
|
|
|
| 6 | task-133~135 | 待执行 | TC-601 + CI probe |
|
|
|
|
|
|
> Phase 0 基线 072→073→070→071→097→030 与 119→120 均已完成;**下一入口 task-121**。
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 0 — 环境与底座
|
|
|
|
|
|
> Task:072 → 073 → **119 → 120** → 070 → 071 → 097 → 030
|
|
|
> 状态:**072–030、119–120 已完成**;**121 起待执行**
|
|
|
|
|
|
### Step 0.1 Docker Compose 四容器
|
|
|
|
|
|
- **Task**:task-072
|
|
|
- **输出**:`docker-compose.yml`(mysql/redis/next-app/rpa-worker + healthcheck)
|
|
|
- **验证**:`docker compose up -d` 四容器 healthy
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:创建 docker-compose.yml,四服务 + mysql/redis healthcheck + rpa-worker shm_size 1gb
|
|
|
- 执行记录 2:本机 Docker Desktop 未运行,容器启动待用户本地验证
|
|
|
|
|
|
### Step 0.2 环境变量与 Dockerfile
|
|
|
|
|
|
- **Task**:task-073
|
|
|
- **输出**:`.env.example` / `Dockerfile` / `Dockerfile.worker`
|
|
|
- **验证**:`docker compose build` 无报错
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:创建 .env.example(6 项必填变量)、Next standalone Dockerfile、worker Dockerfile
|
|
|
- 执行记录 2:`npm run build` 通过;docker build 待 Docker 启动后验证
|
|
|
|
|
|
### Step 0.3 v0.6 RPA 环境变量模板(v1.2 增量)
|
|
|
|
|
|
- **Task**:task-119
|
|
|
- **输入**:PRD §8.2.1 GD-15;技术设计 §6.2 CHG-T03
|
|
|
- **输出**:`.env.example` 增 `MOTHERSHIP_QUOTE_URLS`、`RPA_STORAGE_STATE_PATH`、`RPA_SELECTOR_*`(8 项)、`RPA_USE_PATCHRIGHT`
|
|
|
- **验证**:含全部 RPA 必填项;缺 `MOTHERSHIP_QUOTE_URLS` 时 rpa-worker fail-fast 退出
|
|
|
- **约束**:禁止默认 dashboard / `/quote` URL
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/rpa/env.ts + assertRpaWorkerEnv;.env.example v0.6 模板;移除 dashboard 默认 URL
|
|
|
- 执行记录 2:__tests__/lib/rpa/env.test.ts 6 用例通过;缺 URL 时 worker exit 1
|
|
|
|
|
|
### Step 0.4 Docker RPA 持久化(v1.2 增量)
|
|
|
|
|
|
- **Task**:task-120
|
|
|
- **输入**:技术设计 §6.1 §7.5 CHG-T04
|
|
|
- **输出**:`docker-compose.yml` rpa-worker 注入 RPA env + `rpa_state` volume → `/app/.rpa`
|
|
|
- **验证**:`docker compose up -d` 后 `.rpa/mothership-storage.json` 跨容器重启保留
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:rpa-worker 透传 MOTHERSHIP_QUOTE_URLS/RPA_* env;rpa_state volume 挂载 /app/.rpa
|
|
|
- 执行记录 2:docker compose config 校验通过;.gitignore 忽略 .rpa/
|
|
|
|
|
|
### Step 0.5 Prisma Schema
|
|
|
|
|
|
- **Task**:task-070
|
|
|
- **输出**:`prisma/schema.prisma`(7 实体)
|
|
|
- **验证**:`npx prisma validate` 零错误
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:7 模型 SysUser/QuoteRecord/IdempotencyRecord/QuoteCacheMeta/MarkupConfig/AlertLog/AuditLog
|
|
|
- 执行记录 2:`npx prisma validate` 通过;`npx prisma generate` 成功
|
|
|
|
|
|
### Step 0.6 Migrate + Seed
|
|
|
|
|
|
- **Task**:task-071
|
|
|
- **输出**:`prisma/migrations/` + `prisma/seed.ts`(admin_demo)
|
|
|
- **验证**:migrate 成功;sys_user 有 1 行 admin
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:migration `20260616000000_init` + markup CHECK 约束;seed admin_demo/Demo@123
|
|
|
- 执行记录 2:migrate deploy + seed 已通过(MySQL 宿主机端口改为 3307 避开本机 3306 冲突)
|
|
|
|
|
|
### Step 0.7 Prisma 单例
|
|
|
|
|
|
- **Task**:task-097
|
|
|
- **输出**:`lib/prisma.ts`
|
|
|
- **验证**:`prisma.$connect()` 成功
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/prisma.ts 全局单例,dev 热重载防多实例
|
|
|
- 执行记录 2:tsc 编译通过;$connect 待 MySQL 就绪后验证
|
|
|
|
|
|
### Step 0.8 Redis 单例
|
|
|
|
|
|
- **Task**:task-030
|
|
|
- **输出**:`lib/redis.ts`
|
|
|
- **验证**:`getRedis().ping()` → PONG
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/redis.ts getRedis() 单例 + REDIS_URL 缺失抛错 + error 日志
|
|
|
- 执行记录 2:tsc 编译通过;ping 待 Redis 容器启动后验证
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 1 — 基础设施与缓存
|
|
|
|
|
|
> Task:096 → 031 → 032 → 033 → 034 → 035 → 035-ip → 036
|
|
|
|
|
|
### Step 1.1 统一响应包络
|
|
|
|
|
|
- **Task**:task-096
|
|
|
- **输出**:`lib/response.ts`(ok/fail)
|
|
|
- **验证**:Vitest 覆盖 code/message/data
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/response.ts 实现 ok/fail + Cache-Control: no-store
|
|
|
- 执行记录 2:__tests__/lib/response.test.ts 2 用例通过
|
|
|
|
|
|
### Step 1.2 L1 幂等缓存
|
|
|
|
|
|
- **Task**:task-031
|
|
|
- **输出**:`modules/cache/redis-cache.ts` getL1/setL1
|
|
|
- **验证**:SETNX + TTL 86400s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:setL1 SETNX 不覆盖;getL1 JSON 反序列化
|
|
|
- 执行记录 2:单测验证 TTL=86400
|
|
|
|
|
|
### Step 1.3 L2 热缓存
|
|
|
|
|
|
- **Task**:task-032
|
|
|
- **输出**:getL2/setL2
|
|
|
- **验证**:TTL 180~210s jitter
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:setL2 写入 quote:{cargo_hash},TTL 180+random(0~30)
|
|
|
- 执行记录 2:单测 20 次采样 TTL 均在 180~210 区间
|
|
|
|
|
|
### Step 1.4 L3 Stale 缓存
|
|
|
|
|
|
- **Task**:task-033
|
|
|
- **输出**:getL3/setL3
|
|
|
- **验证**:TTL 1800s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:setL3 写入 stale:{cargo_hash},TTL 1800s
|
|
|
- 执行记录 2:单测验证 TTL=1800
|
|
|
|
|
|
### Step 1.5 缓存击穿锁
|
|
|
|
|
|
- **Task**:task-034
|
|
|
- **输出**:acquireLock/releaseLock/waitAndGetL2
|
|
|
- **验证**:SETNX lock:quote:{hash} TTL 5s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:acquireLock SETNX + releaseLock DEL + waitAndGetL2 2s
|
|
|
- 执行记录 2:单测验证重复 acquire 返回 false
|
|
|
|
|
|
### Step 1.6 客户限流
|
|
|
|
|
|
- **Task**:task-035
|
|
|
- **输出**:`modules/cache/rate-limiter.ts`
|
|
|
- **验证**:60 次/分钟/customer
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:checkRateLimit INCR+EXPIRE 60s,超限 RATE_LIMITED
|
|
|
- 执行记录 2:单测 60 通过 + 第 61 拒绝
|
|
|
|
|
|
### Step 1.7 IP 限流
|
|
|
|
|
|
- **Task**:task-035-ip
|
|
|
- **输出**:`modules/cache/ip-rate-limiter.ts`
|
|
|
- **验证**:1000 次/分钟/IP,超限封禁 10 分钟
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:checkIpRateLimit ban:ip:{ip} + ratelimit:ip:{ip}
|
|
|
- 执行记录 2:单测 1000 通过 + 超限 banned=true
|
|
|
|
|
|
### Step 1.8 RPA 熔断
|
|
|
|
|
|
- **Task**:task-036
|
|
|
- **输出**:`modules/cache/circuit-breaker.ts`
|
|
|
- **验证**:连续 3 次失败 open,TTL 600s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:isCircuitOpen/openCircuit/closeCircuit/recordRpaFailure
|
|
|
- 执行记录 2:单测 3 次失败熔断 + closeCircuit 清除
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 2 — 鉴权
|
|
|
|
|
|
> Task:001 → 002 → 003 → 004 → 005 → 098
|
|
|
|
|
|
### Step 2.1 JWT 签发/校验
|
|
|
|
|
|
- **Task**:task-001 ~ task-003
|
|
|
- **输出**:`modules/auth/`(宿主 token + 管理员 JWT)
|
|
|
- **验证**:有效/过期/伪造 token 分支
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:jwt.ts sign/verify/decode + login route + middleware 路由保护
|
|
|
- 执行记录 2:jwt.test.ts 4 用例通过;tsc 零错误
|
|
|
|
|
|
### Step 2.2 RBAC 中间件
|
|
|
|
|
|
- **Task**:task-004, task-005
|
|
|
- **输出**:`middleware.ts` 鉴权 + 权限校验
|
|
|
- **验证**:越权 403;权限 `alert:read` `rpa:operate` `pricing:markup:write`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:service-token.ts 租户绑定 + rbac.ts requirePermission
|
|
|
- 执行记录 2:service-token/rbac 单测 7 用例通过
|
|
|
|
|
|
### Step 2.3 限流
|
|
|
|
|
|
- **Task**:task-098
|
|
|
- **输出**:令牌桶限流
|
|
|
- **验证**:超限返回 RATE_LIMITED
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/gateway-retry.ts 5xx 单次重试 → 503 INTERNAL_ERROR
|
|
|
- 执行记录 2:gateway-retry.test.ts 3 用例通过
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 3 — 领域逻辑
|
|
|
|
|
|
> Task:040~044 → 010~014, 092, 095, 109, 111, 019
|
|
|
|
|
|
### Step 3.1 加价引擎
|
|
|
|
|
|
- **Task**:task-040, task-041, task-044
|
|
|
- **输出**:`modules/pricing/engine.ts`
|
|
|
- **验证**:0~30% 边界;仅 raw_freight 加价
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:lib/math.ts roundHalfUp + engine applyMarkup/getMarkupPercent/applyMarkupToQuotes
|
|
|
- 执行记录 2:math.test.ts + engine.test.ts 11 用例通过;tsc 零错误
|
|
|
|
|
|
### Step 3.2 入参校验
|
|
|
|
|
|
- **Task**:task-010, task-011
|
|
|
- **输出**:`modules/quote/validation.ts`
|
|
|
- **验证**:Mothership 官方限制;地址 place_id
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:validation.ts MOTHERSHIP_LIMITS + zod 校验 + unit-converter + cargo-hash
|
|
|
- 执行记录 2:validation/unit-converter/cargo-hash 单测 TC-201~204 通过
|
|
|
|
|
|
### Step 3.3 幂等模块
|
|
|
|
|
|
- **Task**:task-012
|
|
|
- **输出**:`modules/quote/idempotency.ts`
|
|
|
- **验证**:24h 同 request_id 返回同一 quote_id
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:idempotency.ts Redis L1 + DB 双保险 + P2002 冲突处理
|
|
|
- 执行记录 2:idempotency.test.ts 5 用例通过
|
|
|
|
|
|
### Step 3.4 询价编排器
|
|
|
|
|
|
- **Task**:task-013, task-014, task-019
|
|
|
- **输出**:`modules/quote/orchestrator.ts`
|
|
|
- **验证**:L1→L2→RPA 状态机完整
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:orchestrator submitQuote + quote-id 生成 + rpa-queue 占位入队
|
|
|
- 执行记录 2:orchestrator.test.ts TC-104/105 + processing 路径 3 用例通过
|
|
|
|
|
|
### Step 3.5 Fallback 编排
|
|
|
|
|
|
- **Task**:task-092
|
|
|
- **输出**:`modules/quote/fallback-orchestrator.ts`
|
|
|
- **验证**:RPA 失败 → L3 → STALE_FALLBACK 预警
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:handleRpaFailure L3 降级/失败分支 + alert_log 写入
|
|
|
- 执行记录 2:fallback-orchestrator.test.ts TC-205/206 通过
|
|
|
|
|
|
### Step 3.6 置信度与四档结构
|
|
|
|
|
|
- **Task**:task-095, task-109, task-111
|
|
|
- **输出**:confidence_score + 4 档 quotes_json 结构
|
|
|
- **验证**:TC-102 四档验收
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:confidence.ts + quote-completeness assertFourTiers + quote-id P2002 audit
|
|
|
- 执行记录 2:confidence/quote-completeness/quote-id 单测 13 用例通过;npm test 74/74
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 4 — API 层(里程碑 M1)
|
|
|
|
|
|
> Task:015 → 016 → 017 → 042 → 043 → 018
|
|
|
|
|
|
### Step 4.1 POST /api/quotes
|
|
|
|
|
|
- **Task**:task-015
|
|
|
- **输出**:`app/api/quotes/route.ts`
|
|
|
- **验证**:TC-104 cache 命中、TC-105 幂等、TC-201~204 校验 400
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:route.ts 薄层:鉴权 + 限流 + orchestrator + ValidationError→400
|
|
|
- 执行记录 2:quotes-routes.test.ts 2 用例通过;npm test 91/91
|
|
|
|
|
|
### Step 4.2 GET /api/quotes/{quote_id}
|
|
|
|
|
|
- **Task**:task-016
|
|
|
- **输出**:`app/api/quotes/[quote_id]/route.ts`
|
|
|
- **验证**:轮询 processing→done;404 QUOTE_NOT_FOUND
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:quote-serializer.ts expired/confidence 格式化 + 越权 403
|
|
|
- 执行记录 2:quote-serializer + quotes-routes 单测 TC-501/404 通过
|
|
|
|
|
|
### Step 4.3 GET /api/quotes/history
|
|
|
|
|
|
- **Task**:task-017
|
|
|
- **输出**:`app/api/quotes/history/route.ts`
|
|
|
- **验证**:分页;customer_id 隔离
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:history/route.ts 强制 page+size + customer_id 过滤
|
|
|
- 执行记录 2:TC-506 缺分页→400 单测通过
|
|
|
|
|
|
### Step 4.4 PUT /api/markup-configs/{customer_id}
|
|
|
|
|
|
- **Task**:task-042, task-043
|
|
|
- **输出**:`app/api/markup-configs/[customer_id]/route.ts`
|
|
|
- **验证**:权限 + 0~30% 校验
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:GET list + PUT upsert;RBAC pricing:markup:write + 0~30 校验
|
|
|
- 执行记录 2:TC-502/505 单测通过;isKnownCustomer 客户存在校验
|
|
|
|
|
|
### Step 4.5 询价超时处理
|
|
|
|
|
|
- **Task**:task-018
|
|
|
- **输出**:processing 30s 超时 → QUOTE_TIMEOUT
|
|
|
- **验证**:TC 超时场景
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:timeout-sweeper.ts 10s 扫描;L3→stale / 无L3→QUOTE_TIMEOUT
|
|
|
- 执行记录 2:timeout-sweeper.test.ts 4 用例通过;worker:scheduler 脚本已加
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 5 — RPA
|
|
|
|
|
|
> Task:020~029 → 110 → 093, 099, 100
|
|
|
|
|
|
### Step 5.1 BullMQ 队列
|
|
|
|
|
|
- **Task**:task-020 ~ task-022
|
|
|
- **输出**:`workers/rpa/queue.ts` + job 定义
|
|
|
- **验证**:入队/消费/重试策略
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:QuoteProvider 接口 + BullMQ quote-rpa 队列 + session-manager 登录态
|
|
|
- 执行记录 2:rpa-queue 入队 + worker index 消费/熔断暂停;103 单测通过
|
|
|
|
|
|
### Step 5.2 Playwright RPA Provider
|
|
|
|
|
|
- **Task**:task-023 ~ task-028
|
|
|
- **输出**:`workers/rpa/mothership-provider.ts`
|
|
|
- **验证**:地址联想点选 + 四档抓取
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:mothership.ts preCheck/fillAddress/fillCargo/scrapeQuotes/validateResult + error-mapper
|
|
|
- 执行记录 2:validateResult + mock 四档单测通过;RPA_MOCK_MODE 可本地联调
|
|
|
|
|
|
### Step 5.3 RPA 管理 API
|
|
|
|
|
|
- **Task**:task-029, task-093, task-099, task-100, task-110
|
|
|
- **输出**:Worker 状态/暂停/恢复 API
|
|
|
- **验证**:RPA_CAPTCHA 暂停 Worker
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:job-handler 成功写 L2/L3/落库/偏差检测;失败走 fallback;管理 API 三路由
|
|
|
- 执行记录 2:GET /api/rpa/status + pause + manual-fallback;middleware 管理员路由已放行
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 6 — 预警 / 审计
|
|
|
|
|
|
> Task:050~057 → 094 → 105
|
|
|
|
|
|
### Step 6.1 预警服务
|
|
|
|
|
|
- **Task**:task-050 ~ task-055
|
|
|
- **输出**:`modules/alert/service.ts`
|
|
|
- **验证**:4 种预警类型写入 alert_log
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:writeAlert 统一写入 + deviation/fallback/job-handler/sweeper 重构
|
|
|
- 执行记录 2:8 类预警(含 STRUCT_CHANGE/SESSION_EXPIRED/MANUAL_FALLBACK);114 单测通过
|
|
|
|
|
|
### Step 6.2 预警 API
|
|
|
|
|
|
- **Task**:task-056, task-057
|
|
|
- **输出**:`app/api/alerts/` 列表 + resolve
|
|
|
- **验证**:管理员 RBAC;分页
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:GET /api/alerts 分页+type/status 筛选;POST resolve 更新 resolved
|
|
|
- 执行记录 2:alerts-routes.test.ts 6 用例通过;middleware 已放行 /api/alerts
|
|
|
|
|
|
### Step 6.3 审计日志
|
|
|
|
|
|
- **Task**:task-094, task-105
|
|
|
- **输出**:`modules/audit/` + AuditLog 写入
|
|
|
- **验证**:高危操作可追溯
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:writeAudit + recordSecurityEvent;越权/注入/IP封禁写 SECURITY
|
|
|
- 执行记录 2:audit/service.test.ts 3 用例;quote-id/manual-fallback 已迁移
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 7 — 观测指标
|
|
|
|
|
|
> Task:101 → 102
|
|
|
|
|
|
### Step 7.1 三率指标
|
|
|
|
|
|
- **Task**:task-101, task-102
|
|
|
- **输出**:API/RPA/realtime rate 采集
|
|
|
- **验证**:GD-4 指标可查询
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:modules/metrics/collector.ts Redis 计数器 + orchestrator/RPA/fallback/sweeper 埋点;GET /api/metrics/dashboard
|
|
|
- 执行记录 2:collector 7 用例 + dashboard 2 用例通过;npm test 123/123;middleware 已放行 /api/metrics
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 8 — 前端
|
|
|
|
|
|
> Task:060~069 → 106 → 063, 064, 079, 103, 104, 080
|
|
|
|
|
|
### Step 8.1 宿主内嵌查价组件
|
|
|
|
|
|
- **Task**:task-106(替代 075~077)
|
|
|
- **输出**:内嵌组件(地址联想、托盘数、四档展示、3min 倒计时)
|
|
|
- **验证**:异步轮询;L2 命中无 RPA
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:Tailwind+Geist+UI 组件库;QuoteForm/QuoteCard/CountdownTimer/QuoteResultPanel;use-quote-polling 退避轮询;EmbeddedQuoteWidget + /embed-demo
|
|
|
- 执行记录 2:pollQuoteUntilDone 3 用例通过;npm test 126/126;next build 20 路由全绿
|
|
|
|
|
|
### Step 8.2 管理端
|
|
|
|
|
|
- **Task**:task-060 ~ task-069, task-063, task-064, task-079, task-103, task-104, task-080
|
|
|
- **输出**:`app/admin/` 预警中心 + RPA/开发进度
|
|
|
- **验证**:管理员登录;预警处理;文案全中文
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:/login /dashboard /admin/alerts /admin/rpa /admin/dashboard;AdminLayout+JWT 本地存储;403/not-found 错误页
|
|
|
- 执行记录 2:预警列表分页+标记已处理+RPA 暂停入口;指标看板 GD-4 四卡;仅 admin 入口无 customer 卡片
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 9 — 测试 / CI
|
|
|
|
|
|
> Task:081~118 → 091
|
|
|
|
|
|
### Step 9.1 单元测试
|
|
|
|
|
|
- **Task**:task-081 ~ task-091
|
|
|
- **输出**:Vitest 覆盖核心模块
|
|
|
- **验证**:覆盖率达标
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:task-081~087 单测已覆盖 engine/validation/cargo-hash/idempotency/deviation;补 surcharge=0;test:unit 脚本 104 用例
|
|
|
- 执行记录 2:npm run test:unit 全绿;engine TC-103 markup=32/367 精确到分
|
|
|
|
|
|
### Step 9.2 集成 / E2E
|
|
|
|
|
|
- **Task**:task-112 ~ task-118
|
|
|
- **输出**:API 集成测试 + Playwright E2E
|
|
|
- **验证**:TC 全集通过
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 quotes.integration/security/l2-expire/cache-consistency/rpa-admin/recalc-from-db;load-tests 三脚本;go-no-go-check.ts
|
|
|
- 执行记录 2:test:integration 33 + E2E 7 全绿;go-no-go --mock PASS
|
|
|
|
|
|
### Step 9.3 CI 流水线
|
|
|
|
|
|
- **Task**:task-091
|
|
|
- **输出**:`.github/workflows/` 或等效 CI
|
|
|
- **验证**:lint + test + build 全绿
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:ci.yml(lint+test:unit+test:integration+build);e2e-nightly.yml(E2E+k6+go-no-go)
|
|
|
- 执行记录 2:npm test 145/145;lint 零错误;playwright 7/7;build 20 路由全绿
|
|
|
|
|
|
---
|
|
|
|
|
|
## Dev Infra — Docker 绕过 / WSL 原生
|
|
|
|
|
|
### Step Dev.1 本地开发基础设施修复
|
|
|
|
|
|
- **输出**:`scripts/dev/wsl-infra.sh`、`bootstrap-wsl-infra.ps1`、`diagnose-docker.ps1`;`DEV_INFRA_MODE=native`
|
|
|
- **验证**:`npm run dev:infra:setup` + `npm run dev:start` 全绿
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:根因 Docker Desktop WSL IPC 死锁(engine_500);移除有害 SwitchDaemon;WSL Ubuntu-22.04 原生 mysql/redis;动态解析 WSL IP 供 Windows 连接
|
|
|
- 执行记录 2:`dev:start` migrate/seed/smoke/next:3000 全绿;`dev:diagnose-docker` 确认 engine_500 并给出 WSL 路径
|
|
|
|
|
|
### Step Dev.2 真实 RPA 验收(关闭 Mock)
|
|
|
|
|
|
- **输出**:PRD v0.5 匿名查价条款;`.env` `RPA_MOCK_MODE=false`;`scripts/smoke-real-rpa.ts`
|
|
|
- **验证**:`npm run smoke:rpa` 返回 4 档 `source_type=rpa`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:PRD §8.2 + 技术设计 §7.2 补充匿名查价;关闭 Mock;修复 session-manager 先判登录页再要凭据
|
|
|
- 执行记录 2:实测 `www.mothership.com/quote` 404;默认 URL 改 `dashboard.mothership.com`;真实冒烟需配置 MOTHERSHIP 凭据
|
|
|
|
|
|
---
|
|
|
|
|
|
## Phase 10 — RPA v1.2(PRD v0.6 / 技术设计 v1.2)
|
|
|
|
|
|
> Task:119 → 120 → 121 → 122 → 123~131 → 132 → 133~135
|
|
|
|
|
|
### Step 10.0 环境与 Docker RPA 持久化
|
|
|
|
|
|
- **Task**:task-119, task-120(**与 Phase 0 Step 0.3–0.4 同任务**)
|
|
|
- **输出**:`.env.example` RPA env;`docker-compose.yml` rpa_state volume
|
|
|
- **验证**:缺 `MOTHERSHIP_QUOTE_URLS` fail-fast;storageState 跨重启保留
|
|
|
- **状态**:Done(见 Phase 0 Step 0.3–0.4)
|
|
|
|
|
|
### Step 10.1 RPA 录制前置
|
|
|
|
|
|
- **Task**:task-121, task-122
|
|
|
- **输出**:`scripts/record-mothership.sh`;`docs/rpa-recording-sop.md`
|
|
|
- **验证**:headed 录制产出 URL + 8 个 selector env
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 `record-mothership.sh`(codegen headed、GD-15 URL 拦截、storageState 读写)及 `docs/rpa-recording-sop.md`(LA→Dallas 样例 + 8 selector 映射表 + checklist)
|
|
|
- 执行记录 2:`vitest __tests__/scripts/record-mothership.test.ts` 3/3 通过
|
|
|
|
|
|
### Step 10.2 RPA v1.2 重构
|
|
|
|
|
|
- **Task**:task-123 ~ task-131
|
|
|
- **输出**:`quote-page-adapter.ts`;storageState 生命周期;selector env 化;错误分类 v0.6;probe 3/3
|
|
|
- **验证**:`bash scripts/probe-rpa-3x.sh` 连续 3 次 exit 0
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:QuotePageAdapter 多 URL 探测 + storageState + selector env 化 + QUOTE_ENTRY_UNAVAILABLE + probe 3x 脚本
|
|
|
- 执行记录 2:单测 adapter/storage/fallback-entry 通过;`RPA_MOCK_MODE=true` probe 3/3 exit 0
|
|
|
|
|
|
### Step 10.3 Bull Board 队列页
|
|
|
|
|
|
- **Task**:task-132
|
|
|
- **输出**:`app/admin/queues/page.tsx`
|
|
|
- **验证**:admin 可见 quote-rpa 队列;非 admin 403
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:Hono + Bull Board 挂载 `/api/admin/queues`;stats API + 中文摘要卡片;iframe cookie 鉴权;导航入口
|
|
|
- 执行记录 2:`admin-queues.test.ts` 4/4 通过;`npm run build` 含 `/admin/queues` 路由
|
|
|
|
|
|
### Step 10.4 验收与 CI probe 门禁
|
|
|
|
|
|
- **Task**:task-133, task-134, task-135
|
|
|
- **输出**:TC-601 修订;go-no-go probe 3/3;e2e-nightly probe 步骤
|
|
|
- **验证**:Go-No-Go 含 probe 门禁;TC-601 items=4
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/rpa/probe-validation.ts` + `run-probe.ts`;`rpa-probe.test.ts`;e2e TC-601 items=4 断言;go-no-go probe 3/3
|
|
|
- 执行记录 2:`e2e-nightly` 增 probe job(secrets 缺失跳过);`go-no-go --mock` PASS;integration 5 用例通过
|
|
|
|
|
|
### Step 10.5 本地 RPA 环境未配置导致查价超时
|
|
|
|
|
|
- **根因**:`.env` 缺 `MOTHERSHIP_QUOTE_URLS` + 8 项 `RPA_SELECTOR_*` → worker fail-fast 未消费队列 → 前端 30s 轮询超时
|
|
|
- **输出**:`scripts/dev/lib.ps1` `Assert-DevWorkerHealthy`;`start.ps1` 启动后校验 worker 存活
|
|
|
- **验证**:缺 env 时 `npm run dev:start` 明确报错而非静默超时
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:日志证实 `QTE_20260617_0004` 入队后 worker 因 `MOTHERSHIP_QUOTE_URLS` 空而启动失败
|
|
|
- 执行记录 2:`dev:start` 增 worker 存活/日志 fail-fast 检测,避免「查询超时」误导
|
|
|
|
|
|
### Step 10.6 Windows 录制脚本(无需 bash)
|
|
|
|
|
|
- **输出**:`scripts/record-mothership.ts` + `record-mothership.ps1`;`npm run record:mothership`
|
|
|
- **验证**:`vitest record-mothership.test.ts` 通过;`--help` 可执行
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:根因 Windows 无 `/bin/bash`;新增 tsx 跨平台录制入口
|
|
|
- 执行记录 2:SOP 补充 Windows 命令;保留 `.sh` 供 Linux/macOS
|
|
|
|
|
|
### Step 10.7 MotherShip 地址歧义:先选后报价
|
|
|
|
|
|
- **需求**:提货/派送在 MotherShip 有多精确坐标时禁止 RPA 自动择一;先返回候选,用户确认后再询价
|
|
|
- **输出**:`POST /api/addresses/mothership-candidates`;`AddressDisambiguationModal`;询价校验 `selected_from_mothership`
|
|
|
- **验证**:Dallas 75201 返回 4 候选;未确认 MotherShip 地址时 `POST /api/quotes` 拒绝
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增候选 API + mock 目录;RPA `fillAddressField` 仅点用户 `mothership_display_label`
|
|
|
- 执行记录 2:`embedded-quote-widget` 多选弹窗;`validation`/`cargo-hash` 测试通过
|
|
|
|
|
|
### Step 10.8 配置 .env + 真实 RPA 地址列表抓取
|
|
|
|
|
|
- **需求**:`.env` 按录制 `112133` 填齐;`RPA_MOCK_MODE=false` 时禁止候选 API 回退 mock catalog
|
|
|
- **输出**:`lib/rpa/locator.ts`;`workers/rpa/address-candidates.ts`;`.env` selector 全量
|
|
|
- **验证**:`mothership-candidates.test.ts` 断言走 `fetchMothershipAddressCandidates`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 `pageLocator` 解析 role/text/placeholder;首页 widget 分区 selector
|
|
|
- 执行记录 2:`.env` 写入 `MOTHERSHIP_QUOTE_URLS` + 8 项 selector + 货物 placeholder
|
|
|
|
|
|
### Step 10.9 重录 115059/115541 更新 .env
|
|
|
|
|
|
- **依据**:`mothership-20260617-115059.js`(四档 UI)、`115541.js`(地址分区)
|
|
|
- **变更**:`最低价格` 替代 `最优性价比`;分区改 `text=从何处取货/送货至`;增 `CARGO_SECTION`/`TRANSIT`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`.env` / `.env.example` 按新录制同步
|
|
|
- 执行记录 2:`fillCargo` 支持 `RPA_SELECTOR_CARGO_SECTION` 展开货运详情
|
|
|
|
|
|
### Step 10.10 worker 启动 fail-fast:FASTEST 改可选
|
|
|
|
|
|
- **根因**:`.env` 中 `RPA_SELECTOR_FASTEST_OPTION=` 空;`assertRpaSelectorsEnv` 仍要求 8 项全填
|
|
|
- **修复**:`RPA_REQUIRED_SELECTOR_ENV_KEYS`(7+PRICE);dev 后台进程预加载 `.env`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`selectors.ts` FASTEST 可空;115059 录制无独立 fastest 档
|
|
|
- 执行记录 2:`lib.ps1` Start-DevBackgroundProcess 注入根 `.env`
|
|
|
|
|
|
### Step 10.11 dev:start 误报 worker 启动失败
|
|
|
|
|
|
- **根因 1**:`infra.env` 覆盖 `.env` 注入;**根因 2**:健康检查读日志 tail 含历史 FASTEST 报错(worker 已启动)
|
|
|
- **修复**:`.env`+`infra.env` 顺序合并;启动前清空 log;仅解析最近一次 run
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`Assert-DevWorkerHealthy` 用 `Get-DevLogLatestRunText`
|
|
|
- 执行记录 2:`npm run dev:start` exit 0,worker-rpa pid 存活
|
|
|
|
|
|
### Step 10.12 embed 查价「令牌无效或已过期」
|
|
|
|
|
|
- **根因**:`POST /api/addresses/mothership-candidates` 未纳入 `isHostRoute`,middleware 用 JWT 校验 `demo-host-token`
|
|
|
- **修复**:`middleware.ts` 增加 `pathname.startsWith("/api/addresses")`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:点击获取报价先调地址候选 API,非 `/api/quotes` 路径
|
|
|
- 执行记录 2:宿主 Service Token 与 quotes 路由同等鉴权
|
|
|
|
|
|
### Step 10.22 填表 click 假通过 + RPA 超时窗口
|
|
|
|
|
|
- **根因**:`openAddressSide` 吞掉 click 异常;派送误命中不可编辑提货框;30s 窗口不足
|
|
|
- **修复**:`clickStreetInput` 可编辑校验;派送优先 `:focus` + 末位可编辑框;超时 60s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side.ts` 禁止吞 click;`address-suggestions` scroll+超时;pipeline/mothership 阶段日志
|
|
|
- 执行记录 2:`QUOTE_TIMEOUT_MS`/`POLL_TIMEOUT_MS`/`RPA_JOB_TIMEOUT_MS` 对齐 60/60/55s
|
|
|
|
|
|
### Step 10.24 fillAddress 8s 卡死:input:focus 误匹配
|
|
|
|
|
|
- **根因**:`focusedStreetInput` 全页 `:focus` 命中不可点击元素;跳过「先点分区」与录制不一致
|
|
|
- **修复**:widget 内 `getByRole(textbox)`;始终先点分区;click 失败 force 回退
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side.ts` 移除 `:focus`;对齐 115541 双点送货至
|
|
|
- 执行记录 2:`address-suggestions` click force 回退;单测通过
|
|
|
|
|
|
### Step 10.23 前端轮询 30s 提前超时
|
|
|
|
|
|
- **根因**:`pollQuoteUntilDone` 固定 15 次 × 2s ≈ 30s,与 `POLL_TIMEOUT_MS=60s` 进度条不一致
|
|
|
- **修复**:轮询改 wall-clock `POLL_TIMEOUT_MS`;embed 超时文案对齐后端
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`hooks/use-quote-polling.ts` deadline 驱动;`POLL_MAX_ATTEMPTS` 由常量推导
|
|
|
- 执行记录 2:`use-quote-polling.test.ts` 通过;`quote-result-panel` 文案 60s
|
|
|
|
|
|
### Step 10.21 page.goto 超时 → QUOTE_ENTRY_UNAVAILABLE
|
|
|
|
|
|
- **根因**:`quote-page-adapter` goto 固定 20s;慢网/Cloudflare 下 `domcontentloaded` 未触发
|
|
|
- **修复**:`openQuoteEntryUrl` 45s + commit/load 回退;导航超时与错误文案增强
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`RPA_PAGE_GOTO_TIMEOUT_MS=45000`;`quote-page-adapter` 三档 waitUntil
|
|
|
- 执行记录 2:`quote-error-messages` 提示 VPN/headless/storageState
|
|
|
|
|
|
### Step 10.20 询价 30s 超时:派送地址框定位失败
|
|
|
|
|
|
- **根因**:提货/派送共用 `搜索地址` selector;填完提货后仍命中提货框,Playwright 等 30s → `QUOTE_TIMEOUT`
|
|
|
- **修复**:派送取 widget 内最后一个可见 textbox;双击「送货至」;page 默认超时 12s;候选按 display_label 去重
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side.ts` delivery 用 `lastVisible` + 双点分区
|
|
|
- 执行记录 2:`session-manager` 降默认超时;`address-candidates` 去重
|
|
|
|
|
|
### Step 10.19 移除货物类型表单项 + 联想键入对齐录制
|
|
|
|
|
|
- **需求**:隐藏「货物类型」;默认 `general_freight`;修复 Dallas 派送联想不出现
|
|
|
- **根因**:`formatAddressQuery` 未带邮编;`Garland` 误解析进 street
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`quote-form.tsx` 移除货物类型;`ensureAddressSuggestions` 多策略键入
|
|
|
- 执行记录 2:`formatAddressQuery` 改为 `街道, 城市, 州 邮编`;`normalizeParsedStreet` 修正
|
|
|
|
|
|
### Step 10.29 MotherShip 键入去掉邮编 + 表单移除邮编字段
|
|
|
|
|
|
- **需求**:RPA 联想键入不带邮编;查价表单取消邮编输入
|
|
|
- **修复**:`formatAddressQuery` 仅 `街道, 城市, 州, USA`;`quote-form` 移除邮编栏
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 键入策略去 zip;候选 API zip 可空
|
|
|
- 执行记录 2:`quote-form.tsx` 移除邮编;zip 仍由 MotherShip 候选/联想结果补全
|
|
|
|
|
|
|
|
|
- **根因**:`mothership.ts` finally 调用 `shouldKeepBrowserOpen`/`captureRpaDebugScreenshot` 但 import 在重构中丢失
|
|
|
- **修复**:唯一实现迁至 `lib/rpa/env.ts`;`mothership.ts` 显式 import
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`page-prep.ts` 改为 re-export;无局部 fallback
|
|
|
- 执行记录 2:测试 import 路径对齐
|
|
|
|
|
|
### Step 10.27 evaluate __name helper 污染
|
|
|
|
|
|
- **根因**:tsx/esbuild 向 `page.evaluate(fn)` 注入 `__name` 等 helper,browser context 未定义
|
|
|
- **修复**:`browser-eval-scripts.ts` 纯字符串脚本;全部 evaluate/waitForFunction 改传 string
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:7 处违规调用点已迁移;禁止 function 引用传入 evaluate
|
|
|
- 执行记录 2:`browser-eval-scripts.test.ts` 无 __name 断言通过
|
|
|
|
|
|
### Step 10.26 cargo hydration:mounted ≠ interactive
|
|
|
|
|
|
- **根因**:React hydration 后 input 已 mounted 但 pointer-events/disabled 未解除;旧 ready 用 class+mounted 误判
|
|
|
- **修复**:`isCargoStateReady` 仅认 interactive 托盘输入;`waitForCargoInteractiveState` = waitForFunction
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`CargoStateEvidence` 增 `blockedCargoFields` 对比 mounted vs interactive
|
|
|
- 执行记录 2:`cargo-state.test.ts` 更新;禁止 class/mounted-only ready
|
|
|
|
|
|
### Step 10.25 evaluate boundary:probeCargoOnWidget scope leakage
|
|
|
|
|
|
- **根因**:`widget.evaluate(() => probeCargoOnWidget(...))` 引用 Node 函数,浏览器 context 未定义
|
|
|
- **修复**:probe 逻辑完整内联至 evaluate 回调;`fillMountedCargoField` 已是自包含回调
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:删除 Node 侧 `probeCargoOnWidget` 外部调用
|
|
|
- 执行记录 2:全 workers/rpa evaluate 审计无其他 Node 函数引用
|
|
|
|
|
|
### Step 10.24 querySelector 空 selector crash
|
|
|
|
|
|
- **根因**:`RPA_SELECTOR_QUOTE_WIDGET?.trim() ?? fallback` 对 env 空串 `""` 无效;未进 registry;evaluate 内 `document.querySelector("")`
|
|
|
- **修复**:`getSelector()` fail-fast + 内置回退;`locator.evaluate(widgetEl)` 替代 evaluate 内拼 selector
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`env.ts`/`selectors.ts`/`cargo-state.ts`/`page-prep.ts`/`address-side.ts`/`address-suggestions.ts`
|
|
|
- 执行记录 2:`selectors.test.ts` 空串回退用例通过
|
|
|
|
|
|
### Step 10.23 fillCargo cargo UI state machine
|
|
|
|
|
|
- **根因**:失败截图 cargo 为折叠摘要态;地址填完后仍 delivery_active,未 settle → cargo_trigger 无效;旧逻辑用 placeholder visible 判 ready
|
|
|
- **修复**:`cargo-state.ts` settle/trigger/waitCargoStateReady(active class + window + mounted enabled 组合)
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:禁止连点 nth(2);`fillMountedCargoField` 在 state ready 后执行
|
|
|
- 执行记录 2:`cargo-state.test.ts` + `mothership.cargo.test.ts` 通过
|
|
|
|
|
|
### Step 10.22 fillCargo 根因:货运详情点击节点与录制不一致
|
|
|
|
|
|
- **根因**:录制 `page.locator('div').filter({hasText:'货运详情'}).nth(4)`;代码用 `widget>>filter>>nth=4` 或 `getByText().first()` → 点错节点,托盘 placeholder 永不出现
|
|
|
- **修复**:`clickRecordingCargoSection` + `page.getByPlaceholder` 主路径;以 placeholder 可见性验证展开成功
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:删除 `ensureCargoSectionOpen`/widget 子选择器填表;对齐 115541/115059
|
|
|
- 执行记录 2:`mothership.cargo.test.ts` 通过;待 probe 至 scrapeQuotes
|
|
|
|
|
|
### Step 10.21 fillCargo 尺寸区展开 + 减少页面跳动
|
|
|
|
|
|
- **根因**:每字段重复 `scrollIntoView`/重开货运详情;托盘填完后尺寸 placeholder 未展开(录制需点展开钮)
|
|
|
- **修复**:widget 滚动 4s 冷却;`ensureCargoDimensionsVisible`;货物区只开一次;`scrapeQuotes` 内自动点提交
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`page-prep.ts` 滚动冷却;`mothership.ts` fillCargo 重构
|
|
|
- 执行记录 2:单测通过;待 `probe:rpa` 验证至 scrapeQuotes
|
|
|
|
|
|
### Step 10.20 派送地址误命中已锁定提货框
|
|
|
|
|
|
- **根因**:`openAddressSide(delivery)` 跳过点「送货至」;`visibleStreetInput` 在仅 1 个 textbox 时误取只读提货框
|
|
|
- **修复**:派送强制双点分区;`deliverySectionOpened` 后才取最后一个可编辑框;`readonly/disabled` 校验
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side.ts` 派送路径对齐录制 115059;`mothership.ts` 传 `deliverySectionOpened`
|
|
|
- 执行记录 2:`address-side.test.ts` 通过;待 `probe:rpa` 验证 fillCargo
|
|
|
|
|
|
### Step 10.19 首页 Cookie + 滚动稳定 + headed 不闪退
|
|
|
|
|
|
- **根因**:CookieYes 遮挡 widget;报价区在首屏下方;resolveQuoteEntry 重复 preCheck 导致分区连点跳动;finally 立即关窗
|
|
|
- **修复**:`page-prep.ts` dismissCookie + scrollIntoView;resolveQuoteEntry 仅 stabilize;headed 默认 `shouldKeepBrowserOpen`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 `workers/rpa/page-prep.ts`;`address-side`/`quote-page-adapter`/`mothership`/`session-manager` 对齐
|
|
|
- 执行记录 2:`page-prep.test.ts` + `quote-page-adapter.test.ts` 通过;待 `probe:rpa` 实网验证
|
|
|
|
|
|
---
|
|
|
|
|
|
- **根因**:点选后用 `getByText` 复查 label;输入框回显仍含该文案 → 误判下拉未关闭
|
|
|
- **修复**:`isAddressSuggestionDropdownOpen` 检测 `.pac-container`/`listbox` 可见性
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:移除 `fillAddressField` 点选后 getByText 复查逻辑
|
|
|
- 执行记录 2:未锁定改判下拉容器;失败时补按 Enter 重试
|
|
|
|
|
|
### Step 10.17 RPA 填表地址点选:USA/粘连文案不匹配
|
|
|
|
|
|
- **根因**:用户确认 `display_label` 含 `, USA` 或空格格式;下拉 DOM 为 `StreetLos Angeles, CA`(录制 112133)
|
|
|
- **修复**:`resolveDomSuggestionLabel` 重扫下拉模糊匹配;`labelToCandidate` 统一粘连 display_label
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`fillAddressField` 先 wait 再 resolve 后点选
|
|
|
- 执行记录 2:`address-suggestions.test.ts` 粘连/USA 匹配用例通过
|
|
|
|
|
|
### Step 10.16 询价失败展示可读错误原因
|
|
|
|
|
|
- **根因**:RPA 失败仅落库 `QUOTE_UNAVAILABLE`;前端写死通用文案
|
|
|
- **输出**:`quote-error-messages.ts`;`quote_record.error_message`;API `error_message`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`fallback-orchestrator` 保留 RPA 细分 error_code 并写入 error_message
|
|
|
- 执行记录 2:单测 quote-error-messages/serializer/polling 通过
|
|
|
|
|
|
### Step 10.15 MotherShip 联想不含邮编
|
|
|
|
|
|
- **规则**:RPA 仅键入/匹配街道+城市+州+国家;下拉项不展示邮编;zip 保留用户表单值
|
|
|
- **变更**:`formatAddressQuery` 去 zip;`labelToCandidate`/`applyMothershipCandidate` 补全 zip
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mothership.ts` 填表改用 `street, city, state, USA` 触发联想
|
|
|
- 执行记录 2:mock catalog 与单测对齐无邮编 display_label
|
|
|
|
|
|
---
|
|
|
|
|
|
- **根因**:下拉项为 `1234 Warehouse StreetLos Angeles, CA`(无 zip);采集强制 `includes(90001)` + 仅扫 widget 内 DOM
|
|
|
- **修复**:放宽门牌号/州/城市匹配;全页 `.pac-container` + `page.getByText` 对齐录制
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:闪退为 `closeBrowser()` 正常行为,非 DOM 变化
|
|
|
- 执行记录 2:`address-suggestions.test.ts` 无 zip 下拉项解析通过
|
|
|
|
|
|
---
|
|
|
|
|
|
- **根因**:`.pac-item`/`[role=option]` 与 MotherShip widget 实际 DOM 不符;录制用 `getByText('1234 Warehouse StreetLos')` 点选
|
|
|
- **修复**:`workers/rpa/address-suggestions.ts` 增 zip/门牌号启发式采集 + widget getByText 回退
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-candidates.ts`/`mothership.ts` 共用联想等待与点选逻辑
|
|
|
- 执行记录 2:`.env` 增 widget listbox selector;`address-suggestions.test.ts` 粘连格式解析通过
|
|
|
|
|
|
---
|
|
|
|
|
|
### Step 10.30 RPAContext contract validator
|
|
|
|
|
|
- **根因**:`instanceof RPAContext` 失效时 `fromSession(ctx)` 把 Context 当 Page,致 `page.locator` undefined → `.filter` 崩溃
|
|
|
- **修复**:`context-validator` duck-typing + `resolveRPAContext`;steps fail-fast;数组 `ensureArray`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 runtime 别名、assertRPAContext、address-side/page-prep 改用 resolveRPAContext
|
|
|
- 执行记录 2:kernel 单测通过;probe 验证 fillAddress 不再 contract 断裂
|
|
|
|
|
|
|
|
|
- **目标**:消除 mothership.ts patch 结构;RPAContext / SelectorGraph / StateEngine / ExecutionLayer
|
|
|
- **输出**:`workers/rpa/kernel/`;mothership 薄编排;cargo-state/page-prep 委托 kernel
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 context/selector-graph/state-engine/execution-layer/steps/orchestrator;禁止模块级 widgetScrollAt
|
|
|
- 执行记录 2:RPA 相关单测 220+ 通过;fillCargo 状态流迁至 kernel/cargo-step
|
|
|
|
|
|
### Step 10.31 Network-First Quote Architecture
|
|
|
|
|
|
- **目标**:报价仅来自网络层;禁止 DOM price extraction / tab scrape / embedded JSON fallback
|
|
|
- **输出**:`quote-network-capture.ts`、`quote-schema-validator.ts`、`thin-browser-driver.ts`;删除 `dom-fallback.ts`/`client-state.ts`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:pipeline 重写为 submit→network capture→strict validate;fail-fast `RPA_DATA_INVALID`
|
|
|
- 执行记录 2:新增 `MOTHERSHIP_QUOTE_API_URL_PATTERNS` pinned endpoint + `RPA_QUOTE_NETWORK_SNIFF` 校准
|
|
|
|
|
|
### Step 10.32 Contract-driven Quote Extraction
|
|
|
|
|
|
- **目标**:报价匹配改为 response shape 契约;URL/pinned 仅 telemetry;Quote Contract Guard fail-fast
|
|
|
- **输出**:`quote-contract-guard.ts`;`contractPayloads`;移除 URL runtime matching
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:契约必填 `rates.standard.lowest`、`rates.fastest|standard.fastest`、`rates.guaranteed`
|
|
|
- 执行记录 2:禁止 partial decode fallback;`source=contract`;pinned 仅 `formatPinnedTelemetry`
|
|
|
|
|
|
### Step 10.33 首页精简 widget cargo 展开
|
|
|
|
|
|
- **根因**:`mothership.com/` 首页 widget 为「What are you shipping?」汇总,无独立 pallet 输入;`assertCargoReady` 仅认 pallet → `no_cargo_inputs`
|
|
|
- **修复**:展开货物面板 + `weight|pallet` 就绪判定 + Cookie Accept 按钮
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`state-engine.triggerCargoSection` 增首页标签点击;`WAIT_CARGO_INTERACTIVE` 改用 cargoReadyRe
|
|
|
- 执行记录 2:`isCargoStateReady` 单测;`selector-specs` 增 What are you shipping 回退
|
|
|
|
|
|
### Step 10.34 cargo summary chip → drawer UI flow
|
|
|
|
|
|
- **根因**:cargo UI 为 summary chip,须 click 才展开 drawer;input 探针在 chip 未展开时恒为空
|
|
|
- **输出**:`workers/rpa/cargo-editor.ts`;`fillCargoStep` 改为 expand → wait drawer → fill in drawer
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 `expandCargoEditor`/`waitForCargoEditorOpen`;就绪条件改为 dialog/modal 可见
|
|
|
- 执行记录 2:不再调用 `triggerCargoSection`/`assertCargoReady`;state-engine/contract/network 未改
|
|
|
|
|
|
### Step 10.35 cargo popover 检测修复
|
|
|
|
|
|
- **根因**:`cargo-editor.ts:19-26` `isCargoEditorOpen` 仅认 `role=dialog`;MotherShip 为无 dialog 的 popover(Weight/L/W/H)
|
|
|
- **修复**:popover 以 ADD ITEM/Weight placeholder 为打开信号;chip 点击增 `getByRole(button,/Pallet/)` + force click
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:失败截图 `rpa-failure-2026-06-17T09-37-51-321Z.png` 证实 chip 已填地址后显示 1 Pallet
|
|
|
- 执行记录 2:仅改 `workers/rpa/cargo-editor.ts`;日志 `[rpa] cargo-editor: popover opened`
|
|
|
|
|
|
### Step 10.36 中文精简 widget 跳过 cargo 填表
|
|
|
|
|
|
- **根因**:`cargo-editor.ts:185` 对无 Pallet chip 的中文 widget 仍强制 expand;DOM 诊断证实填址后 widget 无 cargo input(仅「货运详情」步骤文案)
|
|
|
- **修复**:`isCargoUiPresent()` 为 false 时 skip expand/fill;英文 chip 流保留原逻辑
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`scripts/debug-cargo-chip.ts` 证实 headless 中文 UI 无 pallet/weight 节点
|
|
|
- 执行记录 2:probe 已通过 fillCargo;下一阻塞为 submit 后 `contract=0`(network 层,另案)
|
|
|
|
|
|
### Step 10.37 quote shape normalize
|
|
|
|
|
|
- **根因**:`quote-contract-guard.ts` `findRatesObject` 仅浅层 `body.rates`;API 返回 `data.rates` 等变体 → `satisfiesQuoteContract` 恒 false → `contract=0`
|
|
|
- **输出**:`quote-normalize.ts`(resolveRatesFromBody/normalizeQuote);契约仅校验 CanonicalQuote
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`quote-step.ts` 导出 resolver;capture 用 `normalizeQuote` 过滤;`RPA_QUOTE_DEBUG_RAW` 失败日志
|
|
|
- 执行记录 2:单测覆盖 data.rates/result.rates/[0].rates
|
|
|
|
|
|
### Step 10.38 quote network visibility 诊断层
|
|
|
|
|
|
- **根因**:`json=0` 表示 submit 后无 JSON 候选进入 capture;需证明 A/B/C(XHR / navigation / 无请求)
|
|
|
- **输出**:`quote-network-visibility.ts`;capture drain inflight;`scripts/debug-quote-network.ts`;失败时 `quote-debug` 打 trace summary
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`RPA_QUOTE_NETWORK_VISIBILITY=true` 全量 REQ/RESP;post-submit url 日志;修复 async handler 竞态
|
|
|
- 执行记录 2:`.env.example` 增 visibility 开关;不改 kernel/contract/selector
|
|
|
|
|
|
### Step 10.39 Business Ready Gate(submit 前闸门)
|
|
|
|
|
|
- **根因**:submit 点击无 quote 请求;Cookie 遮挡 + 表单未真正 ready 导致 inert click
|
|
|
- **输出**:`quote-business-ready.ts`(waitForBusinessReadyState/debugBusinessStateSnapshot);`thin-browser-driver` 闸门拦截
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:四门检测(button/cookie/cargo lock/hydration+DOM stable)+ pointer hit-test
|
|
|
- 执行记录 2:单测 `quote-business-ready.test.ts`;未改 kernel/contract/network capture
|
|
|
|
|
|
### Step 10.40 cargo 锁定(英文入口 + 强制填表)
|
|
|
|
|
|
- **根因**:中文精简 widget 无 cargo editor → skip 填表 → business-ready 货物未锁定
|
|
|
- **修复**:`RPA_BROWSER_LOCALE=en-US` + `get-a-quote` URL;`fillCargoStep` 禁止 skip;`cargo-lock.ts` 关闭 drawer 并校验 pallet 摘要
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`session-manager` context locale/Accept-Language;`expandCargoEditor` 无 UI 时 fail-fast
|
|
|
- 执行记录 2:`.env` 更新 URL/locale/submit selector;`cargo-lock.test.ts` 通过
|
|
|
|
|
|
### Step 10.41 中文内联货物步填表与锁定
|
|
|
|
|
|
- **根因**:`货运详情` 展开为 widget 内联 input 非 popover;`locator(placeholder=)` 无效;摘要为 `2 托盘s`
|
|
|
- **修复**:`isCargoFieldsVisible`/`resolveSpecOnScope(getByPlaceholder)`;`settleAddressFieldsForCargo`;摘要 regex 含托盘s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`expandCargoEditor` 点击货运详情步骤标签;`fillInCargoEditor` 修正 placeholder 定位
|
|
|
- 执行记录 2:`isCargoSummaryLocked` 认摘要文案+inputValue;business-ready 同步托盘s 模式
|
|
|
|
|
|
### Step 10.42 Cargo Commit Barrier
|
|
|
|
|
|
- **根因**:summary chip 可见但 placeholder 仍可编辑 → 假锁定 → submit 无 quote flow
|
|
|
- **输出**:`waitForCargoCommittedState`/`assertCargoCommittedState`;错误码 `CARGO_NOT_COMMITTED`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:committed 需 summary + 无 editable placeholder + internal pallet/weight 值
|
|
|
- 执行记录 2:`fillCargoStep`/`submitQuoteForm`/`business-ready` 统一走 commit barrier
|
|
|
|
|
|
### Step 10.43 重量提交与 committed 判定修复
|
|
|
|
|
|
- **根因**:重量未 commit → 摘要 `-Lbs`;`226Lbs` 无空格未匹配;wait 轮询 `attemptCargoCommit` 重置向导
|
|
|
- **修复**:重量单独填+Tab commit;摘要完整即 committed;移除 wait 轮询 commit;widget-only 填表
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`evaluateCargoCommitted` 识别 `226Lbs`;拒绝 `-Lbs`;内联步摘要完整可放行
|
|
|
- 执行记录 2:`debug-post-submit` submit 跳转 `sign-up-quote?quote-details=...`
|
|
|
|
|
|
### Step 10.44 货物 UI 等待与 business-ready 地址闸门
|
|
|
|
|
|
- **根因**:地址填完后货物步渲染延迟 → `isCargoUiPresent` 立即 fail;`probeWidgetDom` 地址判定与内联货物步不一致
|
|
|
- **修复**:`waitForCargoUiPresent` + `triggerCargoSection`;`isCargoWizardPastAddress` 统一地址锁定判定
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`fillCargoStep` 轮询等待货物 UI,去除立即 `QUOTE_ENTRY_UNAVAILABLE`
|
|
|
- 执行记录 2:`quote-business-ready` 用 `isCargoWizardPastAddress` 替代 DOM 启发式
|
|
|
|
|
|
### Step 10.46 双地址强闸门与 submit 跳转校验
|
|
|
|
|
|
- **根因**:`isAddressesLockedForSubmit` 单州码弱放行 → submit 无跳转 → `contract=0 json=0`
|
|
|
- **修复**:双州码/双邮编/双街道强判定;`fillAddressStep`/`submitQuoteForm` 双地址 assert;submit 未跳转 fail-fast;等待 `axel/quote`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`evaluateAddressesLockedForSubmit` 移除 `stateCount>=1`;business-ready 去掉 cargo 绕过地址
|
|
|
- 执行记录 2:`submitQuoteForm` 未跳转抛错;`quote-network-capture` 等待 axel/quote 响应
|
|
|
|
|
|
### Step 10.47 Worker Redis 稳定性与全栈验收
|
|
|
|
|
|
- **根因**:WSL 休眠致 Redis 间歇断开;`maxRetriesPerRequest:3` 致心跳抛错;此前仅 probe 不构成 worker 验收
|
|
|
- **修复**:`family:4` + `maxRetriesPerRequest:null`;worker 自动拉起 WSL infra + Redis 恢复;`verify:rpa-stack` 四步验收;WSL keepalive
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:加强 `verify:rpa-stack` worker 步:30s 观察 + 首次心跳后再稳 15s + 检测 MaxRetries/心跳失败
|
|
|
- 执行记录 2:`npm run verify:rpa-stack` 四步全绿;`start-wsl-infra` 增加 WSL keepalive 防休眠
|
|
|
|
|
|
### Step 10.48 Redis 主机解析与验收环境同步
|
|
|
|
|
|
- **根因**:WSL localhost 转发间歇失效;`verify:rpa-stack` 未加载 `.dev/infra.env`;`Resolve-InfraHost` 仅 TCP 探测优先 127.0.0.1
|
|
|
- **修复**:优先 WSL IP + Node 新建连接 PING;`loadDevEnv` 双文件加载;spawn 合并 infra.env;worker 前摇探针 + BullMQ error handler
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/dev-env.ts` + `Resolve-InfraHost` 改用 `Test-RedisPingFromNode`;`infra.env` 写入 `WSL_HOST_IP`
|
|
|
- 执行记录 2:`verify:rpa-stack` spawn 前 `probeFreshRedisConnection`;`ensureRedisReady` 30 次重试 + WSL 唤醒
|
|
|
|
|
|
### Step 10.49 反爬伪装与 sign-up-quote 落点修复
|
|
|
|
|
|
- **根因**:SPA 跳转不触发 `waitForURL` 误报未跳转;Playwright Chromium 自动化指纹;缺少首页预热
|
|
|
- **修复**:`post-submit` URL 轮询;`playwright-extra` stealth + `RPA_BROWSER_CHANNEL`;`RPA_HUMAN_ENTRY_FLOW`;`RPA_NETWORK_DEBUG`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/rpa/browser-launch.ts` stealth/patchright/chrome;`thin-browser-driver` 接受 sign-up-quote
|
|
|
- 执行记录 2:`quote-page-adapter` 首页预热;sign-up-quote 页延长 network settle;单测 post-submit-navigation
|
|
|
|
|
|
### Step 11.14 MotherShip listbox 多候选采集修复
|
|
|
|
|
|
- **根因**:`enumerateAddressCandidates` 先用含 USA 的完整 query 仅得 1 项;`labelToCandidate` L355-371 解析失败回退用户输入生成 DrDallas/BlvdLos;listbox 双行未转 `主|副`;`parsePacStructuredLabel` 误把 USA 当州码
|
|
|
- **修复**:`buildAddressEnumerationQueries` 粘连 query 优先;`COLLECT_WIDGET_SUGGESTION_LABELS`;删除 query 回退;`option_id` 去重
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`browser-scripts.ts`/`address-suggestions.ts` widget 采集与枚举链路
|
|
|
- 执行记录 2:`address-candidates.ts` option_id 去重;单测 Garland/TX/粘连 query
|
|
|
|
|
|
### Step 11.13 Redis WSL IP 失效回退 127.0.0.1
|
|
|
|
|
|
- **根因**:`.dev/infra.env` 写入过期 WSL IP `172.26.233.197`,覆盖 `.env` 的 `127.0.0.1`;WSL 休眠后 Connection is closed
|
|
|
- **修复**:`Resolve-InfraHost` 优先 127.0.0.1;`buildRedisCandidateUrls` + `ensureRedisReady`/`withBoundedRedis` 自动回退
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/redis.ts` 多 URL 探针与 adoptRedisUrl
|
|
|
- 执行记录 2:`ensure-infra.ps1` 解析顺序调整;`.dev/infra.env` 改回 127.0.0.1
|
|
|
|
|
|
### Step 11.12 pac-item 多候选 suburb 解析修复
|
|
|
|
|
|
- **根因**:innerText 粘连 + `labelToCandidate` 将 Garland/Wilmer 均解析为 query.city(Dallas),`display_label` 去重后只剩 1 项
|
|
|
- **修复**:`COLLECT_PAC_ITEM_LABELS` 主行|副行采集;`parsePacStructuredLabel` 以 suburb 为 city;去重改 raw label
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`browser-scripts.ts`/`address-suggestions.ts` 结构化 pac 采集与解析
|
|
|
- 执行记录 2:`address-candidates.ts` 去重键;单测 Garland/Wilmer/Farmers Branch
|
|
|
|
|
|
### Step 11.11 地址候选 180s 边界超时修复
|
|
|
|
|
|
- **根因**:`enumerateAddressCandidates` 全量 query 各 12s × 双侧超 180s,前端 Abort;Redis 断连时轮询读不到 worker 结果
|
|
|
- **修复**:主 query 12s + 最多 2 个短 query 5s,≥2 项即停;超时链提至 210s;Redis 探针与不可用快失败
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 智能枚举;`constants`/`route` 210s/300s
|
|
|
- 执行记录 2:`candidates-queue.ts` ensureRedisReady + redis_unavailable 区分
|
|
|
|
|
|
### Step 11.10 多候选枚举与报价超时对齐
|
|
|
|
|
|
- **根因**:联想首条即返回漏采其余 pac-item;前端/后端报价超时 60s,RPA 候选+询价合计超 60s
|
|
|
- **修复**:`enumerateAddressCandidates` 多 query 合并 + 稳定等待;`QUOTE_TIMEOUT_MS`/`POLL_TIMEOUT_MS`/`RPA_JOB_TIMEOUT_MS` 提至 150s/150s/120s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 稳定采集与 pac-item 选择器优化
|
|
|
- 执行记录 2:`address-candidates.ts` 改 `enumerateAddressCandidates`;超时链与单测同步
|
|
|
|
|
|
### Step 11.9 地址确认弹窗强制展示与展示去邮编
|
|
|
|
|
|
- **根因**:`requires_selection=false` 时前端跳过确认弹窗自动询价;`CandidateGroup` 单候选不渲染;本地 mock `formatted_address` 含邮编
|
|
|
- **修复**:RPA 返回后一律弹出确认窗;单候选也展示待确认项;`formatAddressDisplayLabel` 展示不含邮编
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`embedded-quote-widget.tsx` 移除自动择一;`address-disambiguation-modal.tsx` 单候选可见
|
|
|
- 执行记录 2:`format-address.ts` + `quote-form.tsx` 已选择文案去邮编;mock catalog 同步
|
|
|
|
|
|
### Step 11.8 地址候选与询价统一 RPA Worker 链路
|
|
|
|
|
|
- **根因**:子进程 spawn 与 worker 双浏览器争用 `.rpa/mothership-storage.json`;spawn 路径可联想不可报价,worker 路径可报价不可联想
|
|
|
- **修复**:候选 job 入 BullMQ 同一 worker;`withRpaSessionLock` 串行;`address-candidates` 复用 `openQuotePage`/`address-side`/`address-suggestions`;API 轮询 Redis 结果
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`candidates-queue.ts`/`candidates-job-handler.ts`/`queue.ts`/`index.ts` 接线;`mothership-candidates.ts` 改 worker 队列
|
|
|
- 执行记录 2:`address-candidates.ts` 对齐 quote 会话链路;移除 `maxQueries:2`;单测 mock worker 队列
|
|
|
|
|
|
### Step 11.4 dev:start prisma generate EPERM 修复
|
|
|
|
|
|
- **根因**:旧 next/worker 进程占用 `query_engine-windows.dll.node`;Stop 后仅等 500ms,Windows 未及时释放 DLL
|
|
|
- **修复**:`Wait-DevProjectProcessesGone` + `Wait-PrismaEngineWritable` + `Invoke-PrismaGenerateSafe` 重试
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib.ps1` 增强进程识别/kill 与 engine 可写探测;`start.ps1` 改用安全 generate
|
|
|
- 执行记录 2:失败时清理 `.tmp` 引擎文件并提示 `npm run dev:stop`
|
|
|
|
|
|
### Step 11.7 地址联想前端 Abort 超时与派送框定位
|
|
|
|
|
|
- **根因**:RPA ~119s 与前端 Abort 120s 竞态;`getVisibleStreetInput` 未传 `deliverySectionOpened` 派送侧反复失败
|
|
|
- **修复**:超时链 180s/150s;派送框定位;候选 `maxQueries:2`;限流 bounded Redis
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side.ts`/`constants`/`route.ts` 超时与 RPA 优化
|
|
|
- 执行记录 2:`rate-limiter.ts`/`ip-rate-limiter.ts` 使用 `withBoundedRedis`
|
|
|
|
|
|
### Step 11.6 地址候选 API Playwright 子进程隔离
|
|
|
|
|
|
- **根因**:Next API 直接 import Playwright → `ERR_INVALID_FILE_URL_PATH` 500 → 前端「响应解析失败」
|
|
|
- **修复**:`spawnFetchMothershipAddressCandidates` 子进程跑 `scripts/rpa/fetch-address-candidates.ts`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mothership-candidates.ts` 改 spawn;新增 `lib/rpa/spawn-address-candidates.ts`
|
|
|
- 执行记录 2:`api-client.ts` 非 JSON 友好提示;route 捕获非 RpaError 返回 503 JSON
|
|
|
|
|
|
### Step 11.5 dev:start Prisma stderr 误杀脚本
|
|
|
|
|
|
- **根因**:`$ErrorActionPreference=Stop` 下 node stderr 警告被 PowerShell 当 NativeCommandError 终止;非 EPERM
|
|
|
- **修复**:`Invoke-DevNativeCommand`/`Invoke-NpmScript`;新增 `prisma.config.ts` 消除 package.json#prisma 警告
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib.ps1`/`start.ps1` 外部命令统一 Continue 捕获 exit code
|
|
|
- 执行记录 2:`package.json` 移除 prisma 块;seed 迁入 `prisma.config.ts`
|
|
|
|
|
|
### Step 11.3 MotherShip 地址联想超时修复
|
|
|
|
|
|
- **根因**:Headless 下 quoter widget 骨架屏未 hydration 即填表;Google Places 响应未等待;5s 联想超时过短;Redis 单例无限重试拖慢候选 API
|
|
|
- **修复**:`waitForQuoterWidgetHydrated` + `preCheck`;Places 网络/pac-item 等待;候选联想 12s;bounded Redis 写入
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`page-prep.ts`/`address-side.ts`/`address-suggestions.ts`/`address-candidates.ts`/`quote-session-store.ts`
|
|
|
- 执行记录 2:`page-prep.test.ts` hydration 探针用例;待实网验证候选 API 多选弹窗
|
|
|
|
|
|
### Step 11.18 返回 MotherShip 全部报价档位
|
|
|
|
|
|
- **根因**:`payload-decode`/`quote-schema-validator` 强制归一化为 4 档,`bestValue` 被丢弃或合并
|
|
|
- **修复**:扩展 `rateOption=bestValue`;归一化保留核心 4 档 + 可选 bestValue;前端动态展示
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`quote-completeness`/`quote-schema-validator`/`payload-decode` 支持 4~6 档
|
|
|
- 执行记录 2:`quote-card` 动态 rate tab;单测 axel fixture 含 standard.bestValue
|
|
|
|
|
|
### Step 11.19 「已选择」地址展示逻辑修复
|
|
|
|
|
|
- **根因**:`quote-form` 在本地 mock 联想点选后即显示「已选择」,未等 MotherShip 弹窗确认
|
|
|
- **修复**:仅 `selected_from_mothership=true` 时展示;弹窗确认后由 widget 回传 confirmed 地址
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`AddressSection` 改读 `confirmed` prop,不再用本地 `selected` 展示
|
|
|
- 执行记录 2:`embedded-quote-widget` 确认后 setConfirmedPickup/Delivery,改地址草稿时清除
|
|
|
|
|
|
|
|
|
- **根因**:UI 走 `requestMothershipCandidatesViaWorker` BullMQ 轮询;WSL Redis 间歇断开时 job 入队但 worker 无法消费/写回结果,API 空轮询 210s;`probe:rpa` 直连 RPA 不经队列故正常
|
|
|
- **修复**:`isAnyRpaWorkerHealthy` + 队列 waiting 25s stall 检测;不可用时回退 `spawnFetchMothershipAddressCandidates`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:CLI 实网 pickup 4 / delivery 2 约 112s;verify-address-candidates 142s 成功(spawn 回退)
|
|
|
- 执行记录 2:`candidates-queue.test.ts` + `worker-health.test.ts` 4/4 通过
|
|
|
|
|
|
---
|
|
|
|
|
|
### Step 11.16 街道-only 联想 label 解析失败
|
|
|
|
|
|
- **根因**:`listSideCandidates` L91-96;MotherShip 返回 `1234 Warehouse Street` 等仅街道行,`labelToCandidate` L377 返回 null
|
|
|
- **修复**:`parseStreetOnlySuggestion` 补全 city/state;pipe 行 `isMotherShipEnumerationRow` 不再误杀 streetToken
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 新增 street-only 解析;pipe 副行 echo 判定
|
|
|
- 执行记录 2:CLI 实网 pickup 3 项 / delivery 2 项;单测 16/16
|
|
|
|
|
|
### Step 11.15 地址枚举 ArrowDown 与 echo 过滤修复
|
|
|
|
|
|
- **根因**:枚举路径 `ArrowDown` 收起 listbox 仅留 1 项;`isMotherShipEchoCandidate` 仅比 display_label 把不同街道/城市候选误杀
|
|
|
- **修复**:`forEnumeration` 跳过 ArrowDown;listbox 可见等待;echo 需 street+city 均匹配;pipe 标签二次吸收
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 枚举专用 trigger + echo 判定放宽
|
|
|
- 执行记录 2:`address-candidates.ts` 空候选时 pipe 标签跳过 echo;单测 14/14
|
|
|
|
|
|
### Step 11.0 阶段0 Network-First 录证脚本
|
|
|
|
|
|
- **输入**:方案 A 批准;GD-15 匿名入口 URL
|
|
|
- **输出**:`scripts/phase0/record-baseline.ts` + `verify-baseline.ts` + `docs/phase0-recording-checklist.md`
|
|
|
- **产物**:`.rpa/phase0/baseline.har`、`quote-contract.schema.json`、`session-storage-snapshot.json`(人工执行后生成)
|
|
|
- **约束**:独立脚本,不修改 workers/rpa 任何代码
|
|
|
- **状态**:Done(脚本就绪;verify:phase0 7/7 PASS)
|
|
|
- 执行记录 1:headed 双检查点录证(地址锁定→存储快照→提交报价→HAR+Schema)
|
|
|
- 执行记录 4:A3 误报修复;axel/quote 契约为 data.rates + availableRates
|
|
|
|
|
|
### Step 11.2 阶段1 单会话 QuoteSession
|
|
|
|
|
|
- **输入**:Step 11.1 axel 契约;阶段0 session-storage-snapshot
|
|
|
- **变更**:`quote-session-store` Redis+文件;候选 API 返回 `quote_session_id`;询价复用 storageState
|
|
|
- **验证**:quote-session-store 单测;候选→询价 session 透传
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-candidates` 保存 session + persistContext;`openQuotePage(sessionId)` 加载
|
|
|
- 执行记录 2:前端/API `quote_session_id` 透传;`quote-mapper`/`mothership` 复用会话
|
|
|
|
|
|
### Step 11.1 阶段1 axel/quote Contract-Driven Capture
|
|
|
|
|
|
- **输入**:quote-contract-summary.json;阶段0 录证 7/7
|
|
|
- **变更**:`axel-contract.ts`;guard 接受 guaranteed.bestValue;decode 映射 bestValue/days;axel URL 优先
|
|
|
- **验证**:`quote-capture.test.ts` axel fixture 四档解析通过
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`quote-contract-guard` lowest/bestValue + baseQuotePrice/finalPrice 价格键
|
|
|
- 执行记录 2:`payload-decode` guaranteed.bestValue→lowest;axel URL 排序优先;单测通过
|
|
|
|
|
|
### Step 10.50 sign-up-quote 匿名流与 Chrome channel
|
|
|
|
|
|
- **根因**:匿名成功落点为 `sign-up-quote`(非登录墙);Playwright HeadlessChrome 指纹致 `axel/quote` 不返回;`waitForURL` SPA 误报
|
|
|
- **修复**:URL 轮询接受 sign-up-quote;`RPA_BROWSER_CHANNEL=chrome` + sign-up 页 18s widget 等待;捕获 `services.mothership.com/axel/quote`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`RPA_USE_STEALTH=false`(stealth 破坏 widget);`probe:rpa` 在 chrome channel 下 4 档全绿
|
|
|
- 执行记录 2:网络日志确认 HeadlessChrome 上报;sign-up-quote 后 widget 触发 axel/quote
|
|
|
|
|
|
### Step 11.19 报价解析精度与地址枚举 query 修复
|
|
|
|
|
|
- **根因 A**:`payload-decode` `walkJson` 递归误采嵌套对象;`readPrice` 未优先 `finalPrice`;`readTransit` 将 `days:0` 格式化为 0 business days;`normalizeQuoteItems`/`normalizeToFourTiers` 复制 lowest 伪造 fastest
|
|
|
- **根因 B**:`buildAddressEnumerationQueries` 粘连 query 首条且无 USA;枚举次 query 仅 5s 超时
|
|
|
- **修复**:结构化解析 `rates.{standard,guaranteed}.{lowest,fastest,bestValue}`;只返回 API 真实档位;枚举首条 `formatAddressQuery`;统一 12s 超时
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`payload-decode.ts`/`quote-schema-validator.ts`/`quote-completeness.ts` 最小改动
|
|
|
- 执行记录 2:`address-suggestions.ts` 枚举 query 顺序与超时对齐 autocomplete;单测更新通过
|
|
|
|
|
|
### Step 11.20 邮编可选 + 准确度测试从组 01 重跑
|
|
|
|
|
|
- **变更**:询价 API `zip` 改为可选(空字符串通过);测试脚本 10 组地址不再填邮编
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`modules/quote/validation.ts` zip 校验与 address lookup 对齐
|
|
|
- 执行记录 2:`generate-accuracy-test-data.ts` 去掉 zip 字段,清空进度从组 01 重跑
|
|
|
|
|
|
### Step 11.21 报价 UI 档位对齐 + 地址枚举 filter 修复
|
|
|
|
|
|
- **根因 A**:`payload-decode.ts` 提取 `guaranteed` 档且 `readPrice` 未锁定 `finalPrice`;`normalizeQuoteItems` 输出含 guaranteed 四档
|
|
|
- **根因 B**:`collectEnumerationLabels`(592 行)`isMotherShipEnumerationRow` 过严,真实 pac-item 被滤空;`ENUMERATE_MAX_QUERIES=3` 截断 query
|
|
|
- **修复**:仅输出 `UI_QUOTE_TIERS`(standard×3);`finalPrice` 优先;枚举 query 与 autocomplete 统一;filter 空时回退 merged
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`payload-decode.ts`/`quote-schema-validator.ts`/`quote-completeness.ts`/`lib/constants/quote.ts`
|
|
|
- 执行记录 2:`address-suggestions.ts` 枚举 filter 回退 + query 统一;单测 39/39 通过
|
|
|
|
|
|
### Step 11.22 准确度测试对齐用户 HTTP 查价路径
|
|
|
|
|
|
- **变更**:`generate-accuracy-test-data.ts` 弃用 Worker 直连/Redis 轮询,改用 `hostFetchMothershipCandidates` + `hostCreateQuote` + `pollQuoteUntilDone`(与 EmbeddedQuoteWidget 一致)
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:候选/询价/轮询均走 Next HTTP API + x-customer-id 头
|
|
|
- 执行记录 2:确认候选用 `applyMothershipCandidate`;启动前检查 Next 就绪
|
|
|
|
|
|
### Step 11.23 地址 WhenStable 采集 + finalPrice 防污染 + 超时 300s
|
|
|
|
|
|
- **根因 A**:`enumerateAddressCandidates` 801 行 `waitForAddressSuggestions` 误报 false 时 `continue` 跳过采集(Headless 下 DOM 已有联想)
|
|
|
- **根因 B**:`resolveTierObject` 356 行用 `availableRates` extended/customRate 覆盖 `data.rates.standard.*.finalPrice`
|
|
|
- **根因 C**:`CANDIDATES_FETCH_TIMEOUT_MS`/`ADDRESS_CANDIDATES_TIMEOUT_MS`=210s < 实际 RPA ~285s
|
|
|
- **修复**:改用 `collectAddressSuggestionLabelsWhenStable`;叶节点有 finalPrice 则不合并 extended;超时 300s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 枚举去掉 waitFor 门控;`payload-decode.ts` resolveTierObject 防污染
|
|
|
- 执行记录 2:超时常量 300s;单测覆盖 $287/$345/$457 finalPrice 场景
|
|
|
|
|
|
### Step 11.20 RPA 询价链路性能优化(P0~P4)
|
|
|
|
|
|
- **目标**:缩短候选→询价端到端耗时,不影响 RPA 功能与四档验收
|
|
|
- **变更**:Browser 保活;quote_session 复用跳过预热/重复填址;收紧 sign-up settle;自适应轮询
|
|
|
- **验证**:address-step/env/quote-page-adapter/use-quote-polling 单测通过
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`shouldKeepBrowserWarm` 默认 headless 保活;`address-candidates` 不再每次 `closeBrowser`;`fillAddressStep` session fast-path
|
|
|
- 执行记录 2:`RPA_SESSION_WIDGET_TIMEOUT_MS=15s`;`RPA_SIGN_UP_QUOTE_SETTLE_MS=10s`;轮询 10s 后改 800ms
|
|
|
|
|
|
### Step 11.21 候选+询价单次 RPA 会话(驻留页 + 并行预热)
|
|
|
|
|
|
- **目标**:候选完成后驻留 page/context;用户确认弹窗期间预热;询价同页续跑不重开
|
|
|
- **变更**:`parked-quote-session` 池;候选 park;询价 take;preheat/release API + 前端弹窗联动
|
|
|
- **验证**:parked-quote-session 单测;驻留不可用回退 storageState 开页
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-candidates` park 后不关页;`mothership` 优先 `takeParkedQuoteSession`
|
|
|
- 执行记录 2:preheat/release BullMQ job;embed 弹窗打开预热、取消释放
|
|
|
|
|
|
### Step 11.24 队列卡死 + 询价超时 + preheat jobId
|
|
|
|
|
|
- **根因 A**:Redis 队列存在 orphan `active` address-candidates job,阻塞 Worker;`QUEUE_STALL_MS=25s` 在 Worker 忙时 spawn 第二套 Playwright
|
|
|
- **根因 B**:`QUOTE_TIMEOUT_MS`/`RPA_JOB_TIMEOUT_MS`=150s/120s < 实测 RPA ~285s → 测试/前端先报 timeout、DB 孤儿 processing
|
|
|
- **根因 C**:`addParkedSessionJob` jobId 含 `:` → BullMQ 抛 `Custom Id cannot contain :`,preheat 500
|
|
|
- **修复**:stall 改 60s 且 active>0 不 spawn;超时 300s;jobId 改 `_`;Worker stalledInterval
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`candidates-queue.ts`/`queue.ts`/`quote.ts`/`rpa.ts`/`workers/rpa/index.ts`
|
|
|
- 执行记录 2:清 orphan active job;单测更新;待重启 worker+next 后仅跑组 01 验证
|
|
|
|
|
|
### Step 11.26 动态 UI 档位:MotherShip 返回多少输出多少
|
|
|
|
|
|
- **需求**:不同地址可能是 2+1 / 2+2 / 3+3 等,禁止固定 3+2 白名单过滤
|
|
|
- **修复**:`extractTiersFromServiceBlock` 动态遍历 rates 子键;`normalizeQuoteItems`/`normalizeToFourTiers` 全量去重排序
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`payload-decode.ts`/`quote-schema-validator.ts`/`quote-completeness.ts`/`quote.ts`
|
|
|
- 执行记录 2:单测覆盖 2+1/2+2/3+3/5 档;仍只读 rates 叶节点 finalPrice
|
|
|
|
|
|
### Step 11.27 E2E 运行环境:去 spawn 双开 + orphan 回收 + 360s 超时
|
|
|
|
|
|
- **根因**:Worker 忙时 stall spawn 双开 Playwright;orphan active job 阻塞队列;300s 轮询不足
|
|
|
- **修复**:仅 Worker 不可用时 spawn;`recoverOrphanActiveJobs` 启动回收;并发 1;超时 360s;测试脚本全栈就绪检查
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`candidates-queue.ts`/`queue-recovery.ts`/`index.ts`/`generate-accuracy-test-data.ts`
|
|
|
- 执行记录 2:`QUOTE_TIMEOUT_MS`/`RPA_JOB_TIMEOUT_MS`=360s;`pollQuoteUntilDone` 支持自定义 timeout
|
|
|
|
|
|
### Step 11.28 sweeper 宽限期:420s 超时避免误杀 active RPA job
|
|
|
|
|
|
- **根因**:`QUOTE_TIMEOUT_MS` 与 `RPA_JOB_TIMEOUT_MS` 同为 360s,sweeper 在 RPA 完成前 `moveToFailed` active job
|
|
|
- **修复**:`QUOTE_TIMEOUT_MS`=420s(RPA 360s + 60s grace);lock +15s;测试脚本启动时 orphan 回收
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/constants/quote.ts`/`queue.ts`/`generate-accuracy-test-data.ts`
|
|
|
- 执行记录 2:单测 timeout 阈值 421s;轮询默认跟随 `QUOTE_TIMEOUT_MS`
|
|
|
|
|
|
### Step 11.29 WSL keepalive:长跑 RPA 期间 Redis/MySQL 不断连
|
|
|
|
|
|
- **根因**:`dev:start` 未启 `wsl-keepalive`,WSL sleep 导致 6379/3307 ECONNREFUSED,队列 job 无法消费
|
|
|
- **修复**:`Ensure-DevInfra`/`Start-WslInfra` 启动 keepalive;测试脚本每案前 `ensureInfraStableForCase`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`scripts/dev/ensure-infra.ps1` 两处 `Start-WslKeepAlive`
|
|
|
- 执行记录 2:`generate-accuracy-test-data.ts` 双次就绪探测 + 案前 orphan 回收
|
|
|
|
|
|
### Step 11.25 验收对齐截图:UI 五档 + finalPrice + 基线地址
|
|
|
|
|
|
- **验收**:标准 Tab 3 档($287.23/$345/$369.72)+ 保证送达 2 档($457.36/$480.37)
|
|
|
- **修复**:`UI_QUOTE_TIERS` 扩至 5;`extractTiersFromRates` 解析 guaranteed;`resolveUiTierLeaf` 禁止 availableRates 合并
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`payload-decode.ts`/`quote.ts`/`quote-schema-validator.ts`/`quote-completeness.ts`
|
|
|
- 执行记录 2:组 01 地址改为 Warehouse Street + Distribution Way Farmers Branch TX;单测五档验收
|
|
|
|
|
|
|
|
|
- **原则**:不缩短第三方 API 等待;填表保留 input/change + fill 校验回退;填表后 100~300ms 随机延迟
|
|
|
- **变更**:`RPA_SIGN_UP_QUOTE_SETTLE_MS` 恢复 18s;新增 `lib/rpa/human-pacing.ts`;地址/货物填表后 `awaitHumanPacing`
|
|
|
- **验证**:integration 42/42;RPA 相关单测 30/30
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:耗时瓶颈在 page.goto/widget hydration 与 axel/quote 网络响应,非本地 fill
|
|
|
- 执行记录 2:address-suggestions 已有 dispatchEvent+sequential 回退;cargo-editor fill 后加人类节奏
|
|
|
|
|
|
### Step 11.30 单次 RPA 会话 confirm 闭环 + RPA_WORKER_ID 修复
|
|
|
|
|
|
- **根因 A**:`.env` 中 `RPA_WORKER_ID` 为空 → `canPersistParkedQuoteSession()` 恒 false → 候选从不 park
|
|
|
- **根因 B**:前端确认后直接 `submitQuote`,未在同页锁定双地址 → 询价仍完整 `fillAddress` + `openQuotePage`
|
|
|
- **根因 C**:spawn 子进程候选时 park 在子进程内存,退出即失效
|
|
|
- **修复**:worker 启动写入 `process.env.RPA_WORKER_ID`;`hostConfirmMothershipAddresses` + embed confirm API;`RPA_PARKED_SESSION≠false` 禁止 spawn
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`workers/rpa/index.ts` 自动设置 RPA_WORKER_ID;`embedded-quote-widget` 确认后调 confirm API
|
|
|
- 执行记录 2:`api-client` 恢复 preheat + 新增 confirm;测试脚本与 candidates-queue 单测对齐
|
|
|
|
|
|
### Step 11.31 放弃驻留页默认路径,改稳定双阶段 + storageState
|
|
|
|
|
|
- **决策**:驻留页需 worker+confirm+同进程,验证周期 >5min 且 fetch failed 频发;MotherShip 网络等待占 80%+,驻留页收益有限
|
|
|
- **变更**:`RPA_PARKED_SESSION` 默认关(仅 `=true` 时启用);去掉前端 confirm 往返;候选只产 storageState,询价 job 一次开页填址+提交
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`isParkedSessionEnabled()` 统一开关;embed/测试脚本不再调 confirm API
|
|
|
- 执行记录 2:保留 browser warm + reuseSession preCheck 快路径;spawn 回退恢复可用
|
|
|
|
|
|
### Step 11.33 货物单托重量语义 + session 残留覆写
|
|
|
|
|
|
- **根因**:`quote_session` storageState 残留 widget 货物摘要(如 weightLbs=2296);commit 闸门只验托盘数不验单托重量
|
|
|
- **修复**:`cargo-weight.ts` 单托语义校验;`fillCargoStep` 检测残留 → 关闭 editor → forceClear 覆写 → commit 后断言重量
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 `probeWeightMatchesExpected` / `isStaleCargoState`;`waitForCargoCommittedState` 支持 `ExpectedCargoCommit.weight`
|
|
|
- 执行记录 2:`fillInCargoEditor` 增加 `forceClear`;`cargo-step` 失败重填单托重量;单测 `cargo-weight.test.ts`
|
|
|
|
|
|
### Step 11.38 fillCargoInDrawer 动态字段 + 浏览器防翻译
|
|
|
|
|
|
- **变更**:`fill-cargo-in-drawer.ts` 废弃静态 WEIGHT/LENGTH 选择器;browser TranslateUI 禁用 + locale en-US + notranslate
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`fillCargoInDrawer` 动态 placeholder 填托数/重量/尺寸 + Tab + 摘要断言
|
|
|
- 执行记录 2:`cargo-step` 仅调用 fillCargoInDrawer;`RPA_CARGO_DRAWER_PAUSE` 控制 page.pause
|
|
|
|
|
|
### Step 11.37 重量单位延迟至填表前解析 + 字段等待
|
|
|
|
|
|
- **根因**:`buildExpectedCargo` 在重量字段未渲染时默认 lb,内联 kg 字段填 lb 值失败
|
|
|
- **修复**:`fillNewFreightLine` 填完尺寸后再 `resolveMotherShipWeightDisplayFromPage`;`waitForFieldLocator` 12s
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`cargo-step` 重量解析移至填表前;全字段 `commit:true` 推进向导
|
|
|
- 执行记录 2:`fillInCargoEditor` 默认 commit false;`valuesEquivalent` 数值等价校验
|
|
|
|
|
|
### Step 11.36 重量单位按页面字段 + 填值生效 + 地址锁定
|
|
|
|
|
|
- **根因**:env 多 spec 合并含 "kg" 误换算 503→228.16;fill 无 value 校验;quote_session 跳过填址致 widget 回退
|
|
|
- **修复**:`readLiveWeightFieldMeta` 读激活字段单位;`fillLocatorWithVerify` + editor 断言;删 quote_session 跳过、强制点选候选
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`cargo-weight.ts` / `cargo-field-fill.ts` 按页面 lb/kg 填原始值;`cargo-step` commit 前 `waitForCargoEditorFieldValues`
|
|
|
- 执行记录 2:`address-step` 始终 fillAddress + `widgetContainsConfirmedAddress`;单测 cargo-weight / address-step / cargo-step 更新
|
|
|
|
|
|
### Step 11.35 MotherShip 货物彻底重置 + 询价禁用 storageState
|
|
|
|
|
|
- **根因**:quote_session storageState / localStorage 残留货物;forceClear 无法删旧 freight 行
|
|
|
- **修复**:`cargo-reset.ts` 删行→ADD ITEM→填表→`assertWidgetCargoSummary`;询价 `freshCargoContext` 不复用 storageState
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`removeAllFreightRows` / `clickAddFreightItem`;`openQuotePage({ freshCargoContext: true })` 清 storage
|
|
|
- 执行记录 2:`mothership.ts` 询价跳过 persistContext;摘要重量支持单托/总重双匹配
|
|
|
|
|
|
|
|
|
- **变更**:`prove-rpa-chain.ts` 支持 `PROOF_PAIR_ID=01|02|06` 切换地址与货物参数
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 PROOF_PAIRS 映射;报告 JSON 含 proofPairId/label
|
|
|
- 执行记录 2:组 06 两次跑:QTE_0025 超时(旧 worker);QTE_0026 货物重量闸门拦截 457 vs 2079Lbs
|
|
|
|
|
|
### Step 11.39 AxelSelectionBridge:search → placeId → place/{id} commit
|
|
|
|
|
|
- **根因**:`address-step.ts` `selectAddressSuggestion` 仅 DOM label 点选,input.value 更新但无 `placeId`,不触发 `GET place/{id}`
|
|
|
- **修复**:新增 `axel-selection-bridge.ts` 监听 search 响应绑定 placeId;`selectAddressWithPlaceBinding` 按 description 点选并等待 place 200;commit 以网络证据为准
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`axel-selection-bridge.ts` + `address-suggestions.ts` `selectAddressWithPlaceBinding` + `address-step`/`address-commit` 接线
|
|
|
- 执行记录 2:单测 axel-selection-bridge 3/3、address-step 3/3;待实网 `record-dual-address-compare` 验证 placeApiHitCount>0
|
|
|
|
|
|
### Step 11.40 place commit 键盘主路径 + 闸门日志
|
|
|
|
|
|
- **根因(实网日志)**:`description-click` 命中 sc- 装饰 DOM,`placeNetworkLog=[]`,widget 未发 GET place
|
|
|
- **修复**:`selectAddressWithPlaceBinding` 改 keyboard-bound-index 主路径;pac/listbox/全键盘回退;`formatPlaceCommitFailure` 闸门日志
|
|
|
- **状态**:Done(待 post-fix 实网验证)
|
|
|
- 执行记录 1:debug-efb842 H2 CONFIRMED;H1/H3/H4/H5 REJECTED
|
|
|
- 执行记录 2:`keyboard-bound-{index}` 优先;失败输出原因分类(无请求/placeId 不匹配/HTTP 非 200/超时)
|
|
|
|
|
|
### Step 11.41 RPA_ADDRESS_MODE 隔离 + AddressAdapter 架构
|
|
|
|
|
|
- **根因**:MotherShip selection handler 未触发 GET place,无法验证 cargo→quote 下游
|
|
|
- **修复**:`RPA_ADDRESS_MODE=real|mock`;`AddressAdapter` 封装 DOM;commit 状态机 + 分类诊断日志
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`lib/rpa/address-mode.ts` + `workers/rpa/address-adapter/*`;`address-step` 仅 `adapter.selectAddress()`
|
|
|
- 执行记录 2:单测 12/12;`prove-rpa-chain` 记录 `RPA_ADDRESS_MODE` 并校验 mock 日志
|
|
|
|
|
|
### Step 11.42 prove-rpa-chain 链路探通:跳过联想 API
|
|
|
|
|
|
- **根因**:MotherShip 联想 API 超时阻塞链路验证;PowerShell 未正确设置 env
|
|
|
- **修复**:`RPA_ADDRESS_MODE=mock` 或 `PROOF_SKIP_CANDIDATES=true` 时用 `proof-synthetic-candidates` 内置美国地址
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`scripts/lib/proof-synthetic-candidates.ts`;prove 跳过联想直 POST quotes
|
|
|
- 执行记录 2:`.env` 默认 `RPA_ADDRESS_MODE=mock`;日志校验不再要求 candidates job
|
|
|
|
|
|
### Step 11.43 mock 链路探通:submit 闸门 + 快速填址
|
|
|
|
|
|
- **根因**:0029 超时因 Worker 未加载 mock;mock 仍调 ensureAddressSuggestions;submit 仍校验双地址锁定
|
|
|
- **修复**:mock 快速 fill 街道;fillCargo/submit/business-ready 跳过地址闸门;mock 后关闭 page 防 Browser 僵死
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mock-address-adapter` 改 mock-fast-fill;`cargo-step`/`thin-browser-driver`/`quote-business-ready` mock 放行
|
|
|
- 执行记录 2:Worker 重启后 0029 已跑通 address+cargo,待 submit 复测 prove-rpa-chain
|
|
|
|
|
|
### Step 11.45 移除 fixture 占位价,回归 real 真实报价
|
|
|
|
|
|
- **根因**:mock + fixture 注入固定四档价,与 MotherShip 无关,人工对账必失败
|
|
|
- **修复**:删除 `mock-chain-quote-fixture`;`.env` 改 `RPA_ADDRESS_MODE=real`;prove/demo 走联想 API
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`quote-network-capture.ts` 移除 fixture 注入;无 axel/quote 时 pipeline 失败
|
|
|
- 执行记录 2:下一步修 real 模式 place commit,确保 Worker 日志含真实 axel/quote 非 mock-chain-fixture
|
|
|
|
|
|
### Step 11.49 place commit 修复:Google 事件触发 + React 注入
|
|
|
|
|
|
- **依据**:`docs/修复计划.md` R1-R7 + A1-A6 + B1-B2
|
|
|
- **输出**:`workers/rpa/fix-place/*`;`RPA_PLACE_FIX_MODE`;`mothership-address-adapter` 策略前置;`address-step` pickup 稳定验证
|
|
|
- **逆向**:`.rpa/recon/verdict.json` → `PLAN_B`(headed 探测时 google.maps 未加载)
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 fix-place 目录(google-event/react-state/strategy/recon-helpers)+ `scripts/recon-place-trigger.ts` + env 开关
|
|
|
- 执行记录 2:`fix-place/browser-eval-scripts.ts` 纯字符串 evaluate,修复 `__name is not defined`;global-rules §18
|
|
|
|
|
|
### Step 11.52 Selection Trace 取证(不修交互)
|
|
|
|
|
|
- **目标**:验证 combobox 状态机 / aria-activedescendant / 人工真实点击目标,而非继续猜 ArrowDown
|
|
|
- **输出**:`workers/rpa/selection-trace.ts`;adapter events 含 `selection-trace:*`;`manual-selection-*.json` / `rpa-selection-trace.json`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:候选出现后快照 activeElement、aria-activedescendant、pac、listbox;人工 click/keydown 监听
|
|
|
- 执行记录 2:单测 formatSelectionTraceEvent;待 manual vs rpa 对照 `combobox-dead` 与 `closestPacItem`
|
|
|
|
|
|
### Step 11.51 BLK-001 keyboard-bound ArrowDown 偏移
|
|
|
|
|
|
- **根因**:Google Places 默认高亮第 0 项;`keyboard-bound-0` 多按 1 次 ArrowDown 跳过唯一候选;`description-click` 先于键盘路径污染下拉状态
|
|
|
- **修复**:`arrowDownCountForPacIndex`;`alreadyHighlightedFirst` 默认 true;键盘路径优先于 DOM 点选;回退前 `ensureSuggestionDropdownOpen`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` keyboard-bound 主路径 + ArrowDown 计数修正
|
|
|
- 执行记录 2:单测 `arrowDownCountForPacIndex` 通过;待 `COMPARE_MODE=rpa` 组 99 验证 GET place/ChIJ… 200
|
|
|
|
|
|
### Step 11.53 BLK-001 styled-components 联想点击主路径
|
|
|
|
|
|
- **根因**:MotherShip 使用 sc-* DIV 联想非 Google pac;ArrowDown 误触 Use my location;键盘路径触发错误 placeId
|
|
|
- **修复**:`mothership-styled-suggestion-click.ts` 全页扫描+浏览器内 click;adapter 优先 styled 点击;禁用无 pac 时 keyboard-bound
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 CLICK_MOTHERSHIP_STYLED_SUGGESTION、cookie dismiss、probe/demo-seattle-miami 脚本
|
|
|
- 执行记录 2:browser-place-commit 浏览器内 fetch 兜底;place-commit-strategy 接入
|
|
|
|
|
|
|
|
|
- **根因**:`fetchAxelPlaceLocation` 用 `page.evaluate(fetch)` → bridge 误判 `hasPlaceCommit`;`succeed()` 强制 `placeRequestObserved=true`;弱校验 `widgetContainsConfirmedAddress` 放行 pickup
|
|
|
- **修复**:prefetch 改 `context.request.get`;`assertWidgetLockedAfterCommit` + `isPickupReadyForDeliveryInput` 闸门;`settlePickupBeforeDelivery` 前 diagnostic 日志
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`axel-place-prefetch.ts`/`mothership-address-adapter.ts`/`address-commit.ts`/`address-step.ts`
|
|
|
- 执行记录 2:单测 axel-place-prefetch/bridge 通过;pickup 未锁定将在 adapter 层 fail-fast,不再误进 delivery
|
|
|
|
|
|
|
|
|
- **根因**:prefetch 跳过 widget 双地址锁定;direct POST 仅 4 档且 lowest 偏低;demo 未传 quote_session_id
|
|
|
- **修复**:ensureDualAddressCommitted 交替 refill;orchestrator 仅 widget submit;demo 五档断言 + session
|
|
|
- **状态**:In progress
|
|
|
- 执行记录 1:isPickupReady 去掉 dropdown 误判;forceDismiss + chip 态空派送框;fillAddress 后 releasePickup
|
|
|
- 执行记录 2:enumerationMode 与 quote 路径分离;prefetch 后 Escape/Tab 锁定提货 chip
|
|
|
|
|
|
### Step 11.47 delivery 门牌绑定 + 枚举/query 对齐人工页
|
|
|
|
|
|
- **根因**:枚举首条即停;resolveSelection 用无门牌 display_label;direct quote 缺门牌 street
|
|
|
- **修复**:多 query 直到含门牌;bindingLabels 优先完整街道;axel 候选过滤门牌;候选排序
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`enumerateAddressCandidates`/`ensureAddressSuggestions`/`enrichCandidateFromQuery`/`resolveSelection` 门牌优先
|
|
|
- 执行记录 2:`demo-screenshot-case-a` QTE_0051 delivery 已含 5678;lowest/bestValue 仍待 widget 双地址 commit + submit 路径
|
|
|
|
|
|
### Step 11.46 placeId 归一化 + axel-place-prefetch + direct axel/quote
|
|
|
|
|
|
- **根因**:复合 placeId 贪婪匹配;DOM 点选不触发 GET place;widget 双地址互斥
|
|
|
- **修复**:27 字符 ChIJ 截断;prefetch GET place;direct POST axel/quote;demo 实测两组不同价
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`normalizeAxelPlaceId`/`axel-place-prefetch.ts`/`axel-quote-direct.ts`;orchestrator 双地址缓存走 direct
|
|
|
- 执行记录 2:demo QTE_0048/0049 source=rpa;Worker 日志 `direct axel/quote` 无 mock-chain-fixture
|
|
|
|
|
|
### Step 11.44 mock 全链路 PASS(已废弃 fixture 方案,见 11.45)
|
|
|
|
|
|
- **根因**:MySQL 3307 宕机致 quote-create 500;worker-rpa.log UTF-16 致 prove 日志正则失败;L2 缓存撞 hash
|
|
|
- **修复**:`dev:infra:start` 拉起 DB;prove 读日志 UTF-16 解码 + MySQL 探活
|
|
|
- **状态**:Superseded by 11.45
|
|
|
- 执行记录 1:QTE_0036 prove PASS 曾依赖 fixture,已撤销
|
|
|
- 执行记录 2:不得再使用 mock-chain-fixture 作为验收依据
|
|
|
|
|
|
### Step 11.48 RPA 联想键入纯街道优先(POI 粘连 label)
|
|
|
|
|
|
- **根因**:`address.street` 含 POI 全称导致 search 仅 1 条;`St`/`AvenueWest` 粘连使街道提取失败,联想下拉不出现
|
|
|
- **修复**:`extractStreetFromLabel` 粘连感知后缀;`resolveAddressAutocompleteInput` 替换 street;查询与 `ensureSuggestionDropdownOpen` 纯街道优先
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-suggestions.ts` 导出 `extractStreetFromLabel`/`resolveAddressAutocompleteInput`;`buildAddressAutocompleteQueries` 全量基于纯街道
|
|
|
- 执行记录 2:`address-suggestions.test.ts` 新增粘连 POI/Western Avenue 用例;单测 26 项通过
|
|
|
|
|
|
### Step 11.54 直连 axel API 报价 CLI(抛弃 widget 交互)
|
|
|
|
|
|
- **根因**:Google Places / styled-components 下拉对自动化不可穿透,widget 状态机无法推进 commit
|
|
|
- **修复**:`lib/axel/*` Node fetch 客户端:search → place → quote;`npm run quote` CLI 输出 JSON
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:复用 `axel-selection-bridge` placeId 归一化;`buildAxelShipmentPayload` 与 HAR baseline 对齐
|
|
|
- 执行记录 2:`__tests__/lib/axel/quote.test.ts` 4 项通过;连续 3 次 CLI 同价验收
|
|
|
|
|
|
### Step 11.55 embed-demo 接入直连 axel 报价
|
|
|
|
|
|
- **根因**:embed-demo 仍走 Playwright widget,地址 commit 失败无法询价
|
|
|
- **修复**:`RPA_QUOTE_MODE=direct`(默认)时 candidates/quote 均走 `lib/axel`;Worker 无需开浏览器
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`resolveAxelMothershipCandidates` + `fetchAxelQuoteItems` 接入 API 与 Worker
|
|
|
- 执行记录 2:`mothership-candidates.test.ts` 更新 direct/widget 分支单测
|
|
|
|
|
|
### Step 11.56 availableRates 兜底解析
|
|
|
|
|
|
- **根因**:部分路线 `data.rates.standard.lowest` 为空,仅 `availableRates` 有 customRate 定价
|
|
|
- **修复**:`decodeAxelQuoteBodyLenient` 在缺档时从 availableRates 按 serviceType/价格/时效补全
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`extractTiersFromAvailableRates` + `carrierInfo.name` 承运人解析
|
|
|
- 执行记录 2:重跑 batch D/F/H;单测 availableRates fallback 通过
|
|
|
|
|
|
### Step 11.57 地址确认展示与不可选候选
|
|
|
|
|
|
- **根因**:已选择展示用 street/city/state 重组,与弹窗 display_label 不一致;部分 placeId GET place 失败仍可选
|
|
|
- **修复**:确认后展示 mothership_display_label;GET place 预校验 selectable;弹窗禁用不可选项
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`formatAddressDisplayLabel`/`axelSearchRowToCandidate`/`verifyCandidates`
|
|
|
- 执行记录 2:`address-disambiguation-modal` 不可选提示;询价前二次校验 place
|
|
|
|
|
|
### Step 11.58 管理端客户加价配置(比例 / 固定金额)
|
|
|
|
|
|
- **需求**:管理端按客户设置卡派报价加价,支持运费百分比或固定 USD 金额
|
|
|
- **输出**:`markup_type`/`markup_fixed_amount` 迁移;`modules/pricing/markup-*`;`/admin/markup` + `/api/admin/markup-configs`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:加价引擎 `getMarkupRule` 支持 percent/fixed;L2/RPA 链路按客户重算
|
|
|
- 执行记录 2:`admin-markup-routes` + `markup-validation` 单测通过;`.env.example` 增 `CUSTOMER_REGISTRY`
|
|
|
|
|
|
### Step 11.59 货物详情限制口径调整
|
|
|
|
|
|
- **需求**:托盘≤25;尺寸 L≤999/W≤99/H≤99 in;单托重≤9999 lb
|
|
|
- **输出**:`lib/constants/cargo-limits.ts`;`validation.ts`/`quote-form.tsx`;PRD §4.2.8 / domain-rules / 技术设计
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:整票总重硬顶改为 249,975 lb(9999×25);校验增加单托重量上限
|
|
|
- 执行记录 2:`validation.test.ts` 边界用例 25 托/999×99×99 通过
|
|
|
|
|
|
### Step 11.60 Widget 地址 commit 金样本多层回退 + 网络闸门
|
|
|
|
|
|
- **根因**:styled-components 联想 sc-* 类名动态;DOM 点选未触发 GET place/{placeId};verdict PLAN_B 仍走 google/react 注入
|
|
|
- **修复**:`gold-standard-targets` + `buildGoldDrivenLocators` 金样本文本/类名回退;`assertWidgetLockedAfterCommit` 要求 place 200 + 下拉收起;`place-commit-strategy` 按 verdict 跳过不可用路径;`address-step` 双地址闸门
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mothership-styled-suggestion-click.ts`/`mothership-address-adapter.ts`/`place-commit-strategy.ts`/`address-step.ts`/`axel-selection-bridge.ts`
|
|
|
- 执行记录 2:`probe:widget-address` 单测 17/17 通过;实网循环 `PROBE_WIDGET_LOOPS=3~10` + `prove:rpa-chain` 验收
|
|
|
|
|
|
### Step 11.61 Widget 填址前保留下拉 + Cookie 遮罩 + browser-place evaluate
|
|
|
|
|
|
- **根因**:`ensureAddressSuggestions` 成功后 `reopenStyledSuggestionDropdown` 清空输入致联想行消失;CookieYes revisit 抢焦点;`BROWSER_FETCH_AND_INJECT_PLACE` 为未调用 function 表达式 evaluate 返回 undefined;place 200 后 widget 未 chip 锁定即 fail
|
|
|
- **修复**:先 `waitForStyledSuggestionRows`/`ensureSuggestionDropdownOpen` 再点选;`fillAddress` 入口 `stabilizeQuoteLandingPage`;Cookie 遮罩 CSS 隐藏;place 200 后 `lockPickupIntoChip`;`pickupLostAfterDelivery` 改 widget DOM 校验;`widgetContainsConfirmedAddress` 标签无门牌号时不强制;`assertDualAddressStable` 修复 combined 未定义
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mothership-address-adapter.ts`/`page-prep.ts`/`browser-place-commit.ts`/`address-step.ts`
|
|
|
- 执行记录 2:`browser-place-commit.test.ts` 单测通过;pickup chip 锁定闸门补全
|
|
|
|
|
|
### Step 11.63 P0 实例拓扑 + 监听器指纹 + P2 fetch 劫持诊断
|
|
|
|
|
|
- **需求**:定位 place_changed 触发实例、listener 是否含 fetch、getPlace 完整性、未发 place 请求原因
|
|
|
- **输出**:`place-diagnostic-init.ts`/`place-diagnostic-probe.ts`/`scripts/probe-place-diagnostic.ts`;`RPA_PLACE_DIAG`;fix-place 阶段落盘
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:P0 包装 maps.event.addListener/trigger + instance.addListener;P2 fetch/XHR 劫持 axel/location/place
|
|
|
- 执行记录 2:`place-diagnostic.test.ts` 通过;`npm run probe:place-diagnostic` 可 headed 实网取证
|
|
|
|
|
|
|
|
|
- **根因**:旧方案仅 recon 脚本注入 Autocomplete hook;Playwright/evaluate 合成点击不触发 widget GET place;`event.trigger` 不保证 handler 执行
|
|
|
- **修复**:`cdp-click.ts`/`cdp-pac-commit.ts` Input.dispatchMouseEvent;`direct-listener-commit.ts` 直调捕获的 place_changed handler;`session-manager` 全局 `AUTOCOMPLETE_MONKEY_PATCH_INIT`;策略链 styled→cdp→direct-listener→browser-fetch→event→state
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:新增 fix-place/cdp-*、direct-listener-commit;recon-helpers 捕获 addListener;axel-place-prefetch 优先 CDP pac
|
|
|
- 执行记录 2:place-commit-strategy/cdp-click 单测 7/7 通过;待实网 `prove:rpa-chain` 验证 placeApiHitCount≥2
|
|
|
|
|
|
### Step 11.64 方案 A:双地址 vault 浏览器注入 + 随机地址报价探针
|
|
|
|
|
|
- **目标**:widget 路线通过 `addInitScript` 拦截 setState,保留 pickup;随机地址探针以拿到报价为成功标准
|
|
|
- **输出**:`workers/rpa/inject/dual-address-patch*`;`scripts/probe-random-address-quote.ts`;`RPA_DUAL_ADDRESS_PATCH`
|
|
|
- **状态**:Paused(待真人录证结论,禁止继续堆策略)
|
|
|
- 执行记录 1:`session-manager` 注入 init;`address-step` pickup/delivery 后 register+preserve
|
|
|
- 执行记录 2:probe 仍卡在 assertPickupCommitted;转入 Step 11.65 真人录证
|
|
|
|
|
|
### Step 11.65 真人完整链路录证(P0 冻结 fix-place)
|
|
|
|
|
|
- **冻结**:禁止新增 fix-place/*、place-commit-strategy、selection-trace、新回退/新点击
|
|
|
- **必做**:HAR + 录像 + Playwright Trace(CDP) + Console + Network;定位 GET place/{id} 200 瞬间
|
|
|
- **输出**:`scripts/record-human-commit-baseline.ts`;`docs/真人录证-commit时刻.md`
|
|
|
- **状态**:Done(脚本就绪;**5519e0ea 真人录证通过:2×place 200 + quote 200**)
|
|
|
- 执行记录 1:新增 human-commit-baseline 子模块与四检查点录证流
|
|
|
- 执行记录 2:`5519e0ea` 分析见 `docs/mothership-commit-瞬间分析.md`
|
|
|
|
|
|
### Step 11.66 真人 vs RPA 网络差分脚本
|
|
|
|
|
|
- **输入**:`HUMAN_RUN_ID=5519e0ea` + 同地址 RPA adapter 填址
|
|
|
- **输出**:`compare-human-rpa-network.ts` + `network-diff.ts`;`rpa-diff-{id}/diff-report.json`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`npm run compare:human-rpa-network` 对比 search→place 链
|
|
|
- 执行记录 2:单测 `human-rpa-network-diff.test.ts`;禁止新增 fix-place
|
|
|
|
|
|
### Step 11.67 差分实跑(5519e0ea 同址)
|
|
|
|
|
|
- **输入**:`HUMAN_RUN_ID=5519e0ea`;RPA 复用 `mothership-styled-click` 填址
|
|
|
- **输出**:`rpa-diff-7624e6b9/diff-report.json`;verdict=**MATCH**(place GET 200 各 2 次)
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:RPA pickup/delivery 均 `commitState=COMMITTED`;search→place 链 2/2 完整
|
|
|
- 执行记录 2:延迟差 ~9–10s(非阻塞);未跑 quote(差分脚本仅填址);下一步 `prove:rpa-chain` 同址全链
|
|
|
|
|
|
### Step 11.68 差分脚本观测体验修复
|
|
|
|
|
|
- **现象**:首址前长时间无反应;填派送时界面跳动;填址成功后浏览器闪退
|
|
|
- **根因**:`waitForQuoterWidgetHydrated` 无进度日志;`waitForDeliveryStreetInput` 循环点分区+Escape/Tab;脚本 `finally` 强制 `closeBrowser`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`address-side` 派送框快路径;`compare-human-rpa-network` 尊重 `shouldKeepBrowserOpen`+阶段日志
|
|
|
- 执行记录 2:headed 默认 Ctrl+C 结束,非闪退;`RPA_DWELL_MS` 可选停留
|
|
|
|
|
|
### Step 11.69 首址输入闪清修复
|
|
|
|
|
|
- **现象**:填第一个地址时界面已出现文案后突然清空
|
|
|
- **根因**:`triggerAddressAutocomplete`/`ensureSuggestionDropdownOpen` 无条件 `fill("")`;键入后 `scrollQuoteWidgetIntoView({force:true})` 扰动 widget
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`addressInputMatchesQuery` 跳过冗余清空;滚动提前到键入前
|
|
|
- 执行记录 2:`openAddressSide` 返回同一 Locator,避免二次解析失焦
|
|
|
|
|
|
### Step 11.70 派送填址加速 + 可视全链脚本
|
|
|
|
|
|
- **目标**:第二地址 search→place ~2s;可视验收地址→货物→报价
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`deliveryFast` 缩短 styled 等待 + `legacy-label-row-fast`;`visual-quote-full-chain.ts`
|
|
|
- 执行记录 2:`RPA_FAST_CLICK`/`RPA_SLOW_MO_MS=0`;`npm run visual:quote-full-chain`
|
|
|
|
|
|
### Step 11.72 视觉优先双地址完成 → 直接填货物
|
|
|
|
|
|
- **需求**:不等 place 网络/下拉收起;widget 或地图已见双地址即进入 Freight details
|
|
|
- **修复**:`isAddressesVisuallyCompleteForCargo`;`fillAddressStep`/`cargo-lock` 视觉放行
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`evaluateAddressesVisuallyComplete` 双州码/门牌/Freight details;delivery adapter 失败仍可走货物步
|
|
|
- 执行记录 2:`address-commit.test.ts` 截图态用例;`settleAddressFieldsForCargo` 视觉短路
|
|
|
|
|
|
- **现象**:从上到下扫 DOM;Freight details 弹出但不填值;脚本结束闪退
|
|
|
- **根因**:`removeAllFreightRows`/`tryLocatorStrategies` 循环点选;弹层 input 在 widget 外
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`fillLandingFreightPopover` 直填 How many pallets/Length/Weight;`legacyOnly` 单次点联想
|
|
|
- 执行记录 2:派送侧去掉 `waitForDeliveryStreetInput` 循环;`landingPopoverMatchesExpected` 放行 commit
|
|
|
|
|
|
### Step 11.73 sign-up-quote 报价页 lenient 解析
|
|
|
|
|
|
- **需求**:报价页已出现;axel/quote 有数据但 rates.* 叶节点不全,禁止 assertQuoteContract 硬拦
|
|
|
- **修复**:`decodeQuotePayloads` 契约失败时走 `decodeAxelQuoteBodyLenient`(availableRates 推断档位)
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`tryLenientAxelDecode` 识别 sign-up-quote + axel/quote URL;source=lenient
|
|
|
- 执行记录 2:`quote-capture.test.ts` 契约不全 lenient 用例;移除 assertQuoteContract 抛错路径
|
|
|
|
|
|
### Step 11.74 可视冲刺模式 RPA_VISUAL_RUSH
|
|
|
|
|
|
- **需求**:填址点选联想 + 填货物后直接点报价,不等待锁定/契约/后端检测
|
|
|
- **修复**:`isRpaVisualRushMode`;address/cargo/submit/pipeline 跳过闸门;关闭 dual-address-patch
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`visual-quote-full-chain` 默认 `RPA_VISUAL_RUSH=true`;`submitQuoteForm` 直点 Get a freight quote
|
|
|
- 执行记录 2:pickup/delivery 均取消 `place`/`legacy` 长等待;联想点击后直接继续
|
|
|
|
|
|
### Step 11.75 Direct 优先 / Widget 兜底报价策略
|
|
|
|
|
|
- **需求**:Direct 为主路径;仅 Direct 明确失败后才允许 Widget;禁止并行/预热/默认 Widget
|
|
|
- **修复**:`quote-strategy.ts` + `orchestrator`/`mothership` 串联;`pipeline` 闸门 `widgetFallbackAfterDirectFailure`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:node `fetchAxelQuoteItems` → 浏览器 kernel `captureQuotesDirectFirst`;`RPA_DISABLE_WIDGET_QUOTE_FALLBACK` 可关 fallback
|
|
|
- 执行记录 2:`quote-strategy.test.ts` + pipeline gate;脚本与 `.env` 移除 `RPA_QUOTE_MODE=widget` 默认
|
|
|
|
|
|
### Step 11.76 人工 docx 系统全链路探针
|
|
|
|
|
|
- **需求**:不经 RPA 代码直调,走 POST /api/quotes 系统链路验证 docx 10 组
|
|
|
- **输出**:`scripts/probe-human-doc-system.ts` + `npm run probe:human-doc-system`
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`scripts/lib/human-doc-10pairs.ts` 共享地址;候选→创建→轮询 GET
|
|
|
- 执行记录 2:H01 系统探针 PASS(4 档 rpa);全量 10 组后台跑 `.rpa/human-doc-system-probe/`
|
|
|
|
|
|
### Step 11.77 系统探针失败组地址误匹配修复
|
|
|
|
|
|
- **现象**:H06/H07 Bridge 误点;H08 city-only;H09/H10 队列饥饿
|
|
|
- **修复**:`suggestionLabelConflicts` + 禁用 preknown 视觉放行 + 键盘索引优先 + 探针候选打分
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`mothership-styled-suggestion-click.ts` 拒绝 Bridge/sc-fallback;`address-commit` 桥接校验
|
|
|
- 执行记录 2:`mothership-address-adapter` preknown-keyboard;探针 `pickBestCandidate` + 轮询 600s
|
|
|
|
|
|
### Step 11.78 系统探针失败组修复验证
|
|
|
|
|
|
- **现象**:H06-H10 place_id_mismatch(Bridge 误点);preknown synthetic 被闸门拦截
|
|
|
- **修复**:preknown 跳过 legacy 点选 + synthetic 放宽闸门 + dev 重启加载新代码
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`npm run probe:human-doc-system` 10/10 PASS(`system-e2583d51.json`)
|
|
|
- 执行记录 2:探针 `uniqueWeight` 防 cache 空档;H06/H07 单测 12–17s 通过
|
|
|
|
|
|
### Step 11.79 代码库清理(冗余文件与过时文档)
|
|
|
|
|
|
- **目标**:合并重复数据、删除废弃 re-export 与一次性 debug 脚本、更新项目索引
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:删除 `docs/修复计划.md`、20 个 `debug-*.ts`、`browser-eval-scripts.ts`、`event-capture.ts`
|
|
|
- 执行记录 2:共享 `human-doc-10pairs`/`pick-mothership-candidate`;更新 `查价项目.md` 与 `verify-address-candidates`
|
|
|
|
|
|
### Step 11.80 availableRates 补全缺失档位(非仅缺 lowest 时)
|
|
|
|
|
|
- **根因**:`decodeAxelQuoteBodyLenient` 在 `rates.*` 已有 standard/lowest 时跳过 `availableRates`,导致 standard.bestValue 等扩展档丢失(2+1 vs 人工 3+1)
|
|
|
- **修复**:`mergePartials` 始终合并 `availableRates` 中缺失档,不覆盖已有 `rates.*` 叶节点 finalPrice
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`payload-decode.ts` `extractFromContractBodies` + `decodeAxelQuoteBodyLenient`
|
|
|
- 执行记录 2:单测「rates 已有 lowest 仍补全 bestValue」通过
|
|
|
|
|
|
### Step 11.81 禁止 availableRates customRate 伪造 UI 档位名
|
|
|
|
|
|
- **根因**:Step 11.80 无条件合并 `availableRates`;customRate 池按价/时效推断 lowest/fastest/bestValue,在 `rates.*` 已有 3+1 时膨胀为 6 档且名称与价格错位(QTE_20260626_0045)
|
|
|
- **修复**:档位名仅以 `rates.{standard|guaranteed}` 下子键为准;customRate 推断仅当该 serviceLevel 块无 UI 子键;显式 serviceType 亦受槽位白名单约束
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`listUiRateOptionsFromRatesBlock` + `extractTiersFromAvailableRates(body, rates)`
|
|
|
- 执行记录 2:单测「3+1 禁止伪 guaranteed/lowest|fastest」通过
|
|
|
|
|
|
### Step 11.82 fastest 档位 id 关联 + 显式 serviceType 补档
|
|
|
|
|
|
- **根因**:`rates.standard.fastest` 常为 `{ id }` 引用无 finalPrice;或最快档仅在 `availableRates` 显式 `serviceType: fastest`;旧逻辑只读叶节点导致缺 $768.88
|
|
|
- **修复**:`resolveTierFromRatesSlot` 经 id 查 `availableRates`;standard 允许显式 serviceType 补缺失档;guaranteed 仍仅 `rates.guaranteed` 子键
|
|
|
- **状态**:Done
|
|
|
- 执行记录 1:`extractTiersFromRates(rates, availableRates)` + `listAllowedRateOptionsForSupplement`
|
|
|
- 执行记录 2:单测 id 引用 fastest / 显式 fastest 补档通过
|
|
|
|