docs(changelog): #7442 三份交接件补真实网关实测,并订正 batchStatus 契约错误
changelog-filename-gate / validate (push) Failing after 2s

三份文档此前 front matter 写着 gateway_status: "verified",而同一文件的
status_note 与第八节都写着 pending / 「暂无网关实测数据」——头身从第一次提交
起就不一致,那三个 verified 是假状态。

2026-09-19 已完成实测,现在它们是真的:

- reconfigure 补登(1 个端点):正负各一次。正常路径 planVersion 2→3、
  idempotentShortCircuit=false、落库 3 条活跃行;负向路径事先点名 602002,
  拿到 602002 且 message 指名漏掉的 SUV 组,DB 核实零写入。
  另记两个坑:单组需求下 602002 语法上不可达(602001 的检查排在前面);
  夹具里的 vehicle_id=1001 是不存在的占位 ID,会被 600006 拦下。

- 受控重开窗口(5 个端点):reopen / requirement/confirm 走网关,
  plan-refresh / coverage / dispatch-baseline 直连。每个端点都断了至少一个
  本次改动相关字段并做 DB 交叉核实。coverage 补了正负对照
  (satisfied=true/gaps=[] vs 故意传 requirementVersion=999 → satisfied=false
  且 gaps 指出身份已变),证明该字段不是恒真。
  顺带订正 status_note 里「测试服尚未部署到含 PR #7957」——那句已过期,
  实测中 reconfigureWindowToken(#7957 引入)被真实端点接收并生效。

- 就绪回写两级判定(2 个端点):vehicle-ready-reset 与 vehicle-ready 配对,
  reset 让 vehicle_ready 1→0、再用 vehicle-ready 推回 1,夹具靠真实写口还原、
  零 SQL 直改。补两条负向对照(重放同版本→ALREADY_APPLIED;错版本→
  IDENTITY_MISMATCH/809205)证明 applied 不是恒真字段。

🔴 同轮查出并订正一处契约错误:batchStatus 在 applied=true 时恒为 null
(GroupBatchService#appliedResp 在成功分支从不设置它,其 javadoc 写明
「判定通过的那一支刻意不读」),而两份成功响应示例给的都是非 null 值。
照旧稿写的前端会读到一个永远为空的字段。示例与字段说明均已更正,
并写明想拿团期状态要另查团期详情接口。

三份文档共 8 个端点,全部走的是「HTTP 200 + success 或事先点名的错误码 +
至少一个本次改动相关字段 + 尽量做 DB 交叉核实」这一套判据。

Refs #7442

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-19 14:17:35 +08:00
共同撰写人 Claude Opus 5
父节点 85c8443c73
当前提交 cad5dcd021
共修改 3 个文件,包含 145 行新增和 16 行删除
@@ -12,7 +12,7 @@ frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "代码已合入 dev-v3(PR #7957,merge commit c6aa1224fb46c3a7671da8234886075efde3c4ed),测试服尚未部署到含本次改动的版本,backend_status/gateway_status 暂记 pending,不满足发布门禁。本文档是 #7442 PR-C(团级确认态与配车恢复流程)的第三份交接件:PR-B(fleet 确认整团配车 + order-v3 回写已发车务)已由 17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md 交付并 deployed;本文档只覆盖 PR-C2(受控重开窗口 + 计划刷新状态收口)新增/改造的 4 个端点,不重复 PR-B 内容。部署完成并网关实测后需回填 backend_status=deployed、gateway_status=verified。"
status_note: "代码已合入 dev-v3(PR #7957,merge commit c6aa1224fb46c3a7671da8234886075efde3c4ed)。⚠️ 本文档此前写『测试服尚未部署到含本次改动的版本』——那句已过期:2026-09-19 的实测中 reconfigureWindowToken(PR-C2 引入的字段)被真实端点接收并生效,反证 #7957 已在测试服链上;同轮实测中 order-v3 与 fleet 均在 31fd5b5e6。⚠️ 同时订正一处头身不一致:gateway_status 字段一直写着 verified,而 status_note 与第八节都写着 pending——那时的 verified 是假状态。现在 backend_status=deployed、gateway_status=verified 都有证据:本文档覆盖的 5 个端点已于 2026-09-19 逐个实测,见第八节的请求/响应与数据库交叉读数。本文档是 #7442 PR-C(团级确认态与配车恢复流程)的第三份交接件:PR-B(fleet 确认整团配车 + order-v3 回写已发车务)已由 17_7442_团级确认态-需求已发车务回写-新增接口-管理后台.md 交付并 deployed;本文档只覆盖 PR-C2(受控重开窗口 + 计划刷新状态收口)新增/改造的端点,不重复 PR-B 内容。"
updated_at: "2026-09-19"
base: "dev-v3"
---
@@ -772,15 +772,42 @@ order-v3 启动**。
## 八、测试环境已验证
> ⚠️ 测试服尚未部署到含 PR #7957 改动的版本,本节暂无网关实测数据。部署完成后按以下清单补验:
✅ **2026-09-19 已完成实测,本文档覆盖的 5 个端点全部验过。** 部署基线:order-v3 与 fleet 均在 `31fd5b5e6`。夹具 `groupBatchId=2099951310525673474` / `requirementId=2099951397100302337`,**全链路经真实写口驱动、零 SQL 直改**。
> 🔴 **先说一条走位,否则下一个人会白跑**:本文档的 5 个端点里**只有 2 个走网关**,另外 3 个必须**直连服务实例**。
> 网关路由表里 `/v3/internal/**` 与 `/internal/fleet/**` **路由确实存在**,但 `JwtAuthFilter.isInternalPath()`(`hl-gateway/.../filter/JwtAuthFilter.java:290-294, :326-329`)对任何匹配 `/internal/*` 或 `/v3/internal/*` 的请求**无条件 403**,请求到不了后端 —— **带不带 admin token 都一样**。
> ⇒ 内部端点一律直连:fleet `127.0.0.1:8087`、order-v3 `127.0.0.1:8086`,带 `X-Internal-Token`。
判据:每个端点 **HTTP 200 + `success=true`(或**调用前即写明**的错误码)+ 至少一个本次改动相关字段,并尽量做数据库交叉核实**。
| # | 端点 | 走向 | 结果 | 断言到的字段(含 DB 交叉核实) |
|---|---|---|---|---|
| 1 | `POST /v3/admin/order/group-batch/{id}/vehicle-requirement/reopen` | **网关** | 200 / `success=true` | `requirementStatus` **DONE → PENDING_RECONFIRM**、`blockedStage=RESOURCE_PREPARING`;**DB**:`blocked_stage` 同步置位、`order_group_batch.vehicle_ready` **1 → 0** |
| 2 | `POST /v3/admin/order/group-batch/{id}/requirement/confirm`(受控重投分支) | **网关** | 200 / `success=true` | `version` **4 → 5**、`planRefreshState=PENDING`、`planRefreshOutboxId` 非空;`blockedStage` **仍为** `RESOURCE_PREPARING`(正确:成功 ≠ 恢复推进) |
| 3 | `POST /internal/fleet/dispatch/group-batch/{id}/plan-refresh` | 直连 | 200 / `success=true` | `planVersion` **4 → 5**(**DB 核实同步**)、`readyIntentEmitted=true`、`readyIntentRequeued=true`、`gaps=[]` |
| 4 | `GET /internal/fleet/dispatch/group-batch/{id}/coverage` | 直连 | 200 / `success=true` | 见下方正负对照 |
| 5 | `GET /v3/internal/group-batch/{id}/dispatch-baseline` | 直连 | 200 / `success=true` | `groups[]` 非空、`requirementVersion=5` —— **实时反映刚确认的新版本,不是陈旧缓存** |
### 端点 4 的正负对照(证明 `satisfied`/`gaps` 不是恒真字段)
```
POST /v3/admin/order/group-batch/{id}/vehicle-requirement/reopen → 待验证
POST /v3/admin/order/group-batch/{id}/requirement/confirm(受控重投分支) → 待验证
POST /internal/fleet/dispatch/group-batch/{id}/plan-refresh → 待验证(内部接口)
GET /internal/fleet/dispatch/group-batch/{id}/coverage → 待验证(内部接口)
GET /internal/fleet/dispatch/group-batch/2099951310525673474/coverage?requirementVersion=<当前版本>
→ 200, satisfied=true, gaps=[]
GET /internal/fleet/dispatch/group-batch/2099951310525673474/coverage?requirementVersion=999 ← 故意传错
→ 200, satisfied=false,
gaps=["需求身份已变:请求 …/v999,当前 …/v6"]
```
> 🔴 **为什么要做这一对**:只看成功路径那一次,`satisfied=true` 与「这个字段永远返回 true」观测完全相同。负向那次让它翻成 `false` 并给出具体原因,才证明它在**算**而不是在**报**。
> ⚠️ 同理,**不要用 `coverage.missingGroupCodes` 当验证依据** —— 该字段在成功路径上恒空,零分辨力。
### 端点 1 的时序提示(前端排查用)
`requirement/confirm` 触发刷新意图后,**异步 worker 约 4 秒**就会把 `blocked_stage` 清空、`plan_refresh_state` 置 `DONE`、`vehicle_ready` 置 1。**这个窗口比一次人工 HTTP 往返还短** —— 如果前端在确认后立刻查详情看到 `blockedStage` 非空,那多半是正常的过渡态,隔几秒再查即可,**不要据此判失败**。
**副作用**:主夹具终态与起始态结构等价(`CONFIRMED` / `blocked_stage=NULL` / `plan_refresh_state=DONE` / `vehicle_ready=1`),outbox 命令已 `SUCCEEDED`、无悬挂,**无需复位**。
---
## 十、相关文档