From cad5dcd021c6614ed28513e71bba33fbddcefcb2 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 19 Sep 2026 14:16:32 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#7442=20=E4=B8=89=E4=BB=BD?= =?UTF-8?q?=E4=BA=A4=E6=8E=A5=E4=BB=B6=E8=A1=A5=E7=9C=9F=E5=AE=9E=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E5=AE=9E=E6=B5=8B=EF=BC=8C=E5=B9=B6=E8=AE=A2=E6=AD=A3?= =?UTF-8?q?=20batchStatus=20=E5=A5=91=E7=BA=A6=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三份文档此前 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) --- ...½¦分组写口reconfigure补登-新增接口-管理后台.md | 41 +++++++++- ...控重开窗口计划刷新状态收口-新增接口-管理后台.md | 39 +++++++-- ...°±绪回写带身份两级判定补登-修改接口-管理后台.md | 81 +++++++++++++++++-- 3 files changed, 145 insertions(+), 16 deletions(-) diff --git a/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md b/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md index 3c802d53..2fc26916 100644 --- a/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md +++ b/changelogs-v2/2026-09/19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md @@ -12,7 +12,7 @@ frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" -status_note: "本文档是补登,不是新上线通知。PR-A(#7844,merge commit 8eb8e13cdfd4a8cc004f95cfb53f7e4b81904461,2026-09-16 合入 dev-v3)首次交付本端点时漏写了交接件,导致 #7442 AC-22(要求『5 个新增接口 + 4 个改造接口的完整契约』)按字面一直不可能达成;本单是对这个缺口的补登。backend_status=deployed 的依据:8eb8e13cd 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于 8eb8e13cd 与后续 c6aa1224f,均已实测回读 commit 确认在链上)。gateway_status 先记 pending,网关实测由 #7442 取证车道另行补。本文档按端点当前(2026-09-19)的完整契约撰写,其中 requirementId/requirementVersion/demands[].assignments[].groupId 等分组相关字段是 PR-A 首次引入的核心内容;reconfigureWindowToken 字段与 602011/602012/602013 三个错误码是随后 PR-C2(#7957)追加到同一端点的字段,本文档一并如实标注,避免把 PR-C2 之后的完整契约错当 PR-A 原状描述。" +status_note: "本文档是补登,不是新上线通知。PR-A(#7844,merge commit 8eb8e13cdfd4a8cc004f95cfb53f7e4b81904461,2026-09-16 合入 dev-v3)首次交付本端点时漏写了交接件,导致 #7442 AC-22(要求『5 个新增接口 + 4 个改造接口的完整契约』)按字面一直不可能达成;本单是对这个缺口的补登。backend_status=deployed 的依据:8eb8e13cd 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于 8eb8e13cd 与后续 c6aa1224f,均已实测回读 commit 确认在链上)。gateway_status=verified 的依据:2026-09-19 已完成本端点的网关实测,正负各一次,见第八节;⚠️ 本文档此前 gateway_status 字段写的是 verified 而 status_note 与第八节都写着 pending,头身不一致,那时的 verified 是假状态——现在它是真的,见第八节的请求/响应原文与数据库交叉读数。本文档按端点当前(2026-09-19)的完整契约撰写,其中 requirementId/requirementVersion/demands[].assignments[].groupId 等分组相关字段是 PR-A 首次引入的核心内容;reconfigureWindowToken 字段与 602011/602012/602013 三个错误码是随后 PR-C2(#7957)追加到同一端点的字段,本文档一并如实标注,避免把 PR-C2 之后的完整契约错当 PR-A 原状描述。" updated_at: "2026-09-19" base: "dev-v3" --- @@ -326,7 +326,44 @@ POST /admin/fleet/group-dispatch/batches/1934567890123456800/reconfigure ## 八、测试环境已验证 -> ⚠️ 本节暂无网关实测数据,PR-A 虽已部署但本文档撰写时未另行发起网关请求;gateway_status 待 #7442 取证车道回填。 +✅ **2026-09-19 已完成网关实测**(走网关,非直连)。部署基线:order-v3 与 fleet 均在 `31fd5b5e6`。 + +本文档只覆盖一个端点,**正负各测一次**,两次都达到「HTTP 200 + `success`/预先点名的错误码 + 至少一个本次改动相关字段并经数据库交叉核实」。 + +### 1. 正常路径 + +夹具 `groupBatchId=2099951310525673474`。 + +``` +POST /admin/fleet/group-dispatch/batches/2099951310525673474/reconfigure +→ HTTP 200,success=true +``` + +| 断言字段 | 读数 | 说明 | +|---|---|---| +| `planVersion` | **2 → 3** | 真实递增,非固定值 | +| `idempotentShortCircuit` | `false` | 本次确实走了写路径,不是幂等短路 | +| `coverage.wholeBatchSatisfied` | `true` | 覆盖判定通过 | + +落库交叉核实:`fleet_group_dispatch` 该团 **3 条**活跃行。 + +### 2. 负向路径(**调用前即写明期望 602002**) + +夹具 `groupBatchId=2100667751600148481`(BUS + SUV 双组团),故意只提交 BUS 组、漏掉 SUV 组。 + +``` +POST /admin/fleet/group-dispatch/batches/2100667751600148481/reconfigure +→ HTTP 200,code=602002 + message="以下乘车分组整组未排车: SUV" +``` + +**消息点名了具体漏掉的组**,不是笼统失败。数据库交叉核实:调用前后 `fleet_group_dispatch` 行数**不变**(纯校验失败,**零写入**)。 + +> 🔴 **给下一个要复现 602002 的人**:**单组需求下 602002 在语法上不可达**。只要 `demands` 非空就必然映中至少一个组码;而若该组码不在权威清单里,会**先**触发 602001(`unknownGroupCodes` 的检查排在 `missingGroupCodes` 之前)。⇒ 复现 602002 必须用**双组及以上**的夹具。 + +> 🔴 **另一个坑**:旧夹具里遗留的 `vehicle_id=1001` 是 `fleet_vehicle` 表里**不存在的占位 ID**,拿它重提会被 **600006**(车辆被占,源头在 `fleet_assignment`)拦下。换成库里真实存在且当日空闲的车/司机才走得通。 + +**副作用**:主夹具全程经真实写口驱动、**零 SQL 直改**,终态与起始态结构等价,无需复位。 --- diff --git a/changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md b/changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md index 09f3f0b5..d561fc66 100644 --- a/changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md +++ b/changelogs-v2/2026-09/19_7442_团期配车受控重开窗口计划刷新状态收口-新增接口-管理后台.md @@ -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`、无悬挂,**无需复位**。 + --- ## 十、相关文档 diff --git a/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md b/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md index 3e988b7c..144d8f66 100644 --- a/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md +++ b/changelogs-v2/2026-09/19_7442_团期配车就绪回写带身份两级判定补登-修改接口-管理后台.md @@ -12,7 +12,7 @@ frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" -status_note: "本文档是补登。PR-C1(#7923,merge commit 6f5b1b679f6b534081ca7b136e338cd3fffc1155,2026-09-18 合入 dev-v3)首次交付本次改动时同样漏写了交接件——这是排查 #7442 交接件缺口时顺带发现的第二处(第一处是 PR-A 的 reconfigure 端点,见 19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md)。backend_status=deployed 依据:6f5b1b679 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于本提交,链上包含)。gateway_status 先记 pending。frontend_status 记 not_required:本文档涉及的两个端点都是仅限内部 Feign 调用的接口,不面向 hl-ui,前端无需改动。⚠️ 本文档只覆盖 PR-C1(#7442)原始交付的两级判定部分;#7444 PR-1 随后在 vehicle-ready 端点同一事务内又追加了一步(团级正式需求 DISPATCHED→DONE),那部分内容已在 19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md 交付,本文档不重复。" +status_note: "本文档是补登。PR-C1(#7923,merge commit 6f5b1b679f6b534081ca7b136e338cd3fffc1155,2026-09-18 合入 dev-v3)首次交付本次改动时同样漏写了交接件——这是排查 #7442 交接件缺口时顺带发现的第二处(第一处是 PR-A 的 reconfigure 端点,见 19_7442_团期配车分组写口reconfigure补登-新增接口-管理后台.md)。backend_status=deployed 依据:6f5b1b679 已在 dev-v3,测试服 2026-09-19 01:15 已滚动部署到 4cbccc26b(晚于本提交,链上包含)。gateway_status=verified 依据:2026-09-19 已对本文档覆盖的两个端点各做真实调用并做数据库交叉核实,见第八节;⚠️ 本文档此前 gateway_status 字段写着 verified 而 status_note 与第八节都写着 pending,头身不一致,那时的 verified 是假状态。⚠️ 同轮实测还查出并订正了一处契约错误:batchStatus 在 applied=true 时恒为 null,而本文档原先的两个成功响应示例给的是非 null 值,照旧稿写的前端会读到一个永远为空的字段——已在第三节两处订正。frontend_status 记 not_required:本文档涉及的两个端点都是仅限内部 Feign 调用的接口,不面向 hl-ui,前端无需改动。⚠️ 本文档只覆盖 PR-C1(#7442)原始交付的两级判定部分;#7444 PR-1 随后在 vehicle-ready 端点同一事务内又追加了一步(团级正式需求 DISPATCHED→DONE),那部分内容已在 19_7444_团期配车就绪门禁与同团车辆共用关系-新增接口-管理后台.md 交付,本文档不重复。" updated_at: "2026-09-19" base: "dev-v3" --- @@ -94,7 +94,13 @@ fleet-service 整团配车完成后调用本端点回填 `vehicle_ready=true`。 | currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null | | currentRequirementVersion | Integer | 提供方当前活跃需求版本 | | currentPlanVersion | Long | 提供方已应用的最高计划版本 | -| batchStatus | String | 回填后的团期状态(可能已被四 ready 闸门推进) | +| batchStatus | String | 🔴 **`applied=true` 时恒为 `null`**,只有丢弃分支(`applied=false`)才回填团期状态。见下方订正。 | + +> 🔴 **2026-09-19 契约订正(实测发现,本文档此前写错了)**:`batchStatus` 在**成功分支上永远是 `null`**。 +> 源码依据:`GroupBatchService#appliedResp`(`hl-order-service-v3/.../groupbatch/service/GroupBatchService.java:946-954`)在 `applied=true` 分支**从不设置该字段**,其 javadoc(`:974-977`)写明「判定通过的那一支刻意不读」;只有 `discardedResp()` 才回填。 +> **实测佐证**:2026-09-19 两次 `applied=true` 的真实调用(`vehicle-ready` 与 `vehicle-ready-reset` 各一次),响应里 `batchStatus` 均为 `null`。 +> ⚠️ **本文档原先的两个成功响应示例给的是非 null 值**(`MATERIAL_PREPARING` / `RESOURCE_PREPARING`),照它写的前端会读到一个恒为 `null` 的字段。下方示例已按实测值更正。 +> **想拿团期状态,请另查团期详情接口,不要依赖本响应的这个字段。** #### 请求示例 @@ -121,15 +127,17 @@ POST /v3/internal/group-batch/1934567890123456800/vehicle-ready "currentRequirementId": "1934567890123456789", "currentRequirementVersion": 3, "currentPlanVersion": 7, - "batchStatus": "MATERIAL_PREPARING" + "batchStatus": null }, "success": true } ``` +> ⚠️ `batchStatus` 在 `applied=true` 时**就是 `null`**(不是示例省略),见上方订正。 + #### 空数据 / 降级响应 -身份不一致时**一律返 200**,不是错误响应: +身份不一致时**一律返 200**,不是错误响应(下例的 `batchStatus` 有值是对的——丢弃分支才回填): ```json { @@ -219,7 +227,7 @@ fleet-service 整团清零或取消成团/流团释放占用后调用本端点 | currentRequirementId | String(雪花 ID) | 提供方当前活跃需求 ID;无活跃需求为 null | | currentRequirementVersion | Integer | 提供方当前活跃需求版本 | | currentPlanVersion | Long | 提供方已应用的最高计划版本 | -| batchStatus | String | 重置后的团期状态 | +| batchStatus | String | 🔴 **`applied=true` 时恒为 `null`**(同上方 `vehicle-ready` 的订正,同一份 `appliedResp()` 逻辑),只有丢弃分支才回填 | #### 请求示例 @@ -246,15 +254,17 @@ POST /v3/internal/group-batch/1934567890123456800/vehicle-ready-reset "currentRequirementId": "1934567890123456789", "currentRequirementVersion": 3, "currentPlanVersion": 10, - "batchStatus": "RESOURCE_PREPARING" + "batchStatus": null }, "success": true } ``` +> ⚠️ `batchStatus` 在 `applied=true` 时**就是 `null`**(不是示例省略),见上方订正。 + #### 空数据 / 降级响应 -重放一条落后的 `plan10` reset(当前已推进到 `plan11` 且已就绪)会被丢弃,**不会把就绪状态打回 false**: +重放一条落后的 `plan10` reset(当前已推进到 `plan11` 且已就绪)会被丢弃,**不会把就绪状态打回 false**(下例的 `batchStatus` 有值是对的——丢弃分支才回填): ```json { @@ -453,7 +463,62 @@ hl-common/hl-common-core/src/main/java/com/hulalv/common/dto/fleet/GroupBatchVeh ## 八、测试环境已验证 -> ⚠️ 本节暂无网关实测数据(两个端点为内部接口,不走网关,网关实测本就不适用);内部调用链的验证归属 fleet↔order-v3 集成测试,本文档撰写时未另行发起手工调用,gateway_status 记 pending 供 #7442 取证车道处理。 +✅ **2026-09-19 已完成实测,本文档覆盖的两个端点各验一次,并各配负向对照。** + +> 🔴 **走位**:两个端点都是 `/v3/internal/**`,**不走网关也不能走** —— `JwtAuthFilter.isInternalPath()`(`hl-gateway/.../filter/JwtAuthFilter.java:290-294, :326-329`)对任何匹配 `/internal/*` 或 `/v3/internal/*` 的请求**无条件 403**(路由是配了的,但请求到不了后端),带不带 admin token 都一样。⇒ **直连 order-v3 `127.0.0.1:8086`,带 `X-Internal-Token`**。 + +夹具 `groupBatchId=2099951310525673474` / `requirementId=2099951397100302337`。**全程零 SQL 直改业务状态**,唯一的 SQL 是 SELECT 读数。 + +**基线读数**:`vehicle_ready=1`、`vehicle_plan_version=5`、`status=CONFIRMED`、`version=5`、`blocked_stage=NULL`、`plan_refresh_state=DONE`。 + +### 1. `POST /v3/internal/group-batch/{id}/vehicle-ready-reset` + +```json +// 请求 +{"requirementId":2099951397100302337,"requirementVersion":5,"planVersion":6,"sourceRefNo":"ac21-verify-reset"} + +// 响应 HTTP 200 +{"code":200,"message":"成功","success":true, + "data":{"applied":true,"discardReason":null,"discardCode":null, + "currentRequirementId":"2099951397100302337","currentRequirementVersion":5, + "currentPlanVersion":"6","batchStatus":null}} +``` + +**DB 交叉核实**:`vehicle_ready` **1 → 0**、`vehicle_plan_version` **5 → 6**;`status`/`blocked_stage`/`plan_refresh_state` 不变。 +(`batchStatus` 为 `null` —— 这正是本文档第三节订正的那条契约,此处是它的实测读数。) + +### 2. `POST /v3/internal/group-batch/{id}/vehicle-ready`(还原) + +```json +// 请求 +{"requirementId":2099951397100302337,"requirementVersion":5,"planVersion":7,"sourceRefNo":"ac21-verify-restore"} + +// 响应 HTTP 200,applied=true,currentPlanVersion="7",batchStatus=null +``` + +**DB 交叉核实**:`vehicle_ready` **0 → 1**、`vehicle_plan_version` **6 → 7**;其余与基线一致。 + +### 3. 两条负向对照(证明 `applied` 不是恒真字段) + +在 reset 之后、还原之前的中间态上做: + +| 构造 | 响应 | DB 复读 | +|---|---|---| +| 重放同一 `planVersion=6`(身份不变) | `applied=false`、`discardReason=ALREADY_APPLIED` | `vehicle_ready` 仍 0、`vehicle_plan_version` 仍 6,**未再变化** | +| 故意传 `requirementVersion=99` | `applied=false`、`discardReason=IDENTITY_MISMATCH`、**`discardCode=809205`** | **零写入** | + +> 🔴 **为什么必须做这两条**:只看成功那一次,`applied=true` 与「这个端点无论如何都返回 true」观测完全相同。两条负向让它翻成 `false` 并给出不同的丢弃原因和错误码,才证明两级判定在**判**而不是在**报**。 + +### 4. 契约逐字段核对 + +入参 4 字段 + 出参 7 字段共 **11 个字段**逐条对源码核过(`GroupBatchVehicleReadyReqDTO` / `GroupBatchVehicleReadyRespDTO` / `GroupBatchResourceController` / `GroupBatchService#applyReadyCallback`、`#appliedResp`、`#discardedResp` / `GroupVehicleRequirementService#decideVehicleReadyCallback`): + +- **10/11 一致**(类型、必填性、`required=false` 的 legacy 兼容窗口、两级判定顺序、`discardReason` 四常量、`discardCode` 只在 809205/809206 有值、丢弃场景一律 HTTP 200); +- **1 处不一致已订正**:`batchStatus`(见第三节)。 + +### 5. 残留 + +`vehicle_plan_version` 由基线 5 前进到 **7**(reset 用 6、mark 用 7)。这是两级判定 CAS 语义的必然结果 —— 该列**只能单调递增**,且**不允许用 SQL 回改**(回改会造出系统不可能自然产生的状态,下一个人会把它当真实缺陷去追)。`vehicle_ready`/`status`/`blocked_stage`/`plan_refresh_state` 均已还原到与基线一致。**这是真实写口产生的合法状态,不是损坏。** ---