docs: hand off segment confirmation contract (#5364)
所有检测均成功
changelog-filename-gate / validate (pull_request) Successful in 2s

这个提交包含在:
wx 2026-08-03 01:55:36 +08:00
父节点 6b86db55b1
当前提交 884b585261

查看文件

@ -0,0 +1,174 @@
---
schema: "hl-changelog/v2"
ticket: "5364"
title: "连续执行段司机通知、回复与整组确认隔离"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "not_required"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: ""
status_note: "后端 PR #5420 已 squash 合并到 dev-v3e543a41035;最终本地 Fleet 2820 项与 Order 7261 项 reactor verify 均通过。本工单冻结范围不要求运行时部署或网关验证;前端尚未领取,保持 pending。"
updated_at: "2026-08-03"
base: "dev-v3"
---
# 连续执行段司机通知、回复与整组确认隔离(#5364
> **服务**`hl-fleet-service``hl-order-service-v3`
>
> **后端 PR**[#5420](https://git.1814.love:8443/wx/HL/pulls/5420)
>
> **Backend Issue**[#5364](https://git.1814.love:8443/wx/HL/issues/5364)
>
> **Frontend tracking**[#5366](https://git.1814.love:8443/wx/HL/issues/5366)
## 变更接口
| 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|
| 查询车务派车订单详情 | `GET` | `/admin/fleet/board/orders/:orderId` | additive 响应字段 |
| 登记司机确认 | `POST` | `/admin/fleet/assignments/:assignmentId/driver-confirmation` | additive 请求字段与段级约束 |
| 用车需求整组原子确认 | `POST` | `/admin/fleet/assignments/requirements/:requirementId/confirm` | 新增接口 |
| 旧单派车确认 | `POST` | `/admin/fleet/assignments/:assignmentId/confirm` | multi-group 行为收紧,singleton 兼容 |
| 司机 H5 行程单 JSON | `GET` | `/app/h5/itinerary/:token` | additive/versioned 响应字段与段级裁剪 |
## 1. Board 详情字段
`GET /admin/fleet/board/orders/:orderId` 的既有字段保持不变,新增字段均为 additive/null-safe。
### 1.1 顶层需求快照
| 字段 | JSON 类型 | 可空 | 说明 |
|---|---|---|---|
| `requirementId` | `String(Long)` | 是 | 当前有效用车需求 ID |
| `requirementVersion` | `Integer` | 是 | 当前需求版本 |
| `requirementSha256` | `String` | 是 | 当前需求 canonical SHA-256;整组确认时原样回传 |
| `driverConfirmationSummary` | `Object` | 否 | 当前 generation 的服务端守恒汇总 |
`driverConfirmationSummary`
| 字段 | JSON 类型 | 说明 |
|---|---|---|
| `dispatchPlanGeneration` | `String(Long)` / `null` | 当前最终派车方案代际 |
| `requiredSegmentCount` | `Integer` | 当前需司机确认的 HOLD 执行段数 |
| `confirmedSegmentCount` | `Integer` | 已满足有效确认谓词的执行段数 |
| `pendingSegmentCount` | `Integer` | 待确认且非歧义段数 |
| `rejectedSegmentCount` | `Integer` | 当前拒绝且待改派段数 |
| `ambiguousSegmentCount` | `Integer` | 通知结果歧义段数 |
| `allDriverConfirmed` | `Boolean` | 所有 required 段是否均确认 |
| `allExecutionConfirmed` | `Boolean` | 全部实际用车切片是否均确认执行 |
守恒规则:`confirmedSegmentCount + pendingSegmentCount + rejectedSegmentCount + ambiguousSegmentCount = requiredSegmentCount`。消费者不得使用 `currentAssignment` 或任一代表司机自行计算整组完成。
### 1.2 `activeAssignments[]` 执行段字段
每项按独立 `assignmentGroupId` 返回;同槽多段、同司机非连续多段不得合并。
| 字段 | JSON 类型 | 可空 | 语义 |
|---|---|---|---|
| `assignmentGroupId` | `String(Long)` | 否 | 当前连续执行段身份 |
| `assignmentSlotId` | `String(Long)` | 否 | 稳定车辆槽位身份 |
| `serviceDates[]` | `String(date)[]` | 否 | 本段精确服务日,不补日期洞 |
| `holdNotificationGeneration` | `String(Long)` | 是 | 当前段通知代际 |
| `holdNotificationStatus` | `String` | 否 | `NOT_REQUIRED/PENDING/SENT/FAILED/AMBIGUOUS/INVALIDATED` |
| `holdSentAt` | `String(date-time)` | 是 | 有可信发送成功事实时才返回 |
| `driverConfirmedAt` | `String(date-time)` | 是 | 当前段司机确认时间 |
| `driverReplyNote` | `String` | 是 | 回复摘要 |
| `driverReplySource` | `String` | 是 | `NOTIFICATION/MANUAL_CONTACT`;历史未知保持 `null` |
| `driverReplyOperatorId` | `String(Long)` | 是 | 人工登记操作人;历史未知可空 |
| `driverReplyRecordedAt` | `String(date-time)` | 是 | 回复事实登记时间 |
| `driverConfirmationEvidencePresent` | `Boolean` | 否 | 是否存在确认凭证 |
| `driverConfirmationEvidenceCount` | `Integer` | 否 | 凭证数量,不新增文件元数据 |
`SENT` 只表示系统发送成功,不表示通道已提供送达回执;前端不得展示为 `DELIVERED`
## 2. 登记司机确认
`POST /admin/fleet/assignments/:assignmentId/driver-confirmation` 请求新增:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `driverReplySource` | `String` | 否 | 仅允许 `NOTIFICATION``MANUAL_CONTACT`;人工电话/微信确认必须显式传 `MANUAL_CONTACT` |
既有 `requestId``driverReplyNote``evidenceFileIds[]` 保持兼容。登记只更新 `assignmentId` 所属当前 group;旧 group、旧 generation、已改派/取消或资源快照漂移均 fail closed,不得跟随 slot 更新替代组。
## 3. 用车需求整组原子确认
新增 `POST /admin/fleet/assignments/requirements/:requirementId/confirm`
请求体:
```json
{
"orderId": "2080000000000000001",
"requestId": "requirement-confirm-5364-001",
"expectedRequirementVersion": 7,
"expectedRequirementSha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"expectedPlanGeneration": "12",
"groups": [
{
"assignmentGroupId": "2080000000000000101",
"sendItinerarySms": true
}
]
}
```
- `groups[]` 必须是 Board 当前 generation 中全部 used execution groups 的精确集合;按 `assignmentGroupId` 去重。
- 服务端重读 exact slot×service-date topology 并一次提交;失败不产生部分状态推进、receipt 或重复 Outbox。
- `(requirementId, requestId)` 是 durable 幂等键:同 canonical 请求返回首次稳定结果;同键不同载荷返回 `605059`
- 旧 `/:assignmentId/confirm` 在 multi-group 当前 generation 返回 `605057`;singleton 继续委托同一原子引擎。
主要业务错误:
| code | 含义 |
|---|---|
| `605055` | 派车方案 generation 已变化 |
| `605056` | 执行段精确集合已变化 |
| `605057` | multi-group 必须使用需求级整组确认 |
| `605058` | 至少一个当前执行段不满足最终确认条件 |
| `605059` | 幂等 requestId 已用于不同确认载荷 |
## 4. H5 段级最小化
`GET /app/h5/itinerary/:token` 新增/versioned 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `contractVersion` | `String` | 当前为 `V2` |
| `segmentDays` | `Integer` | token 所绑定执行段天数 |
| `serviceDates[]` | `String(date)[]` | 本段精确服务日期 |
| `segmentRoute` | `String` / `null` | 仅由本段逐日标题生成 |
既有 `days``route``daily``transport` 字段不删除且原语义不静默改写。V2 token 同时绑定 group、slot、plan generation、notification generation、driver、vehicle、精确日期摘要和 audience snapshot;任一漂移都返回失效,禁止按 slot 跳转到新司机或新 group。
## 5. 前端消费动作
1. Board 按 `activeAssignments[].assignmentGroupId` 渲染独立执行段,不以 `currentAssignment`、司机 ID 或连续日期猜测合并。
2. 整组确认提交 Board 返回的 `requirementVersion/requirementSha256/dispatchPlanGeneration` 与全部 group 精确集合;收到 `605055/605056` 后刷新,不做部分重试。
3. 人工确认显式传 `driverReplySource=MANUAL_CONTACT`;展示通知状态与确认来源为两个独立事实。
4. H5 使用 `serviceDates/segmentDays/segmentRoute` 展示本段范围;保留对既有字段的兼容读取。
5. 所有 JSON Long 按字符串处理,禁止转 JavaScript `Number`
## 6. Internal Feign / shared Java
Fleet 与 order-v3 新增内部 reservation/release 契约:
- `POST /v3/internal/order/orders/:orderId/requirement/vehicle/final-confirmation-reservations`
- `POST /v3/internal/order/orders/:orderId/requirement/vehicle/final-confirmation-reservations/release`
共享 DTO 采用 additive 字段,Fleet reservation、事务内 durable receipt、release Outbox 与 Order holder/fence 共同保证失败回滚和可重放释放。oasdiff 与 Spring Cloud Contract 均未配置,不能标记为工具 PASS;本次以 Controller JSON、producer/consumer 源码对比及两个 reactor verify 作为人工回退证据。
## 验证证据
- Fleet`mvn -pl hl-fleet-service -am verify`,2,820 tests,0 failures,0 errors,5 skipped,BUILD SUCCESS。
- Order`mvn -pl hl-order-service-v3 -am verify`,7,261 tests,0 failures,0 errors,36 skipped,BUILD SUCCESS。
- Fleet SpotlessBUILD SUCCESS;`git diff --check dev-v3`PASS。
- 最终独立 P0–P2 reviewGO,无可复现阻断项。
- 后端 PR #5420 已 squash 合并到 `dev-v3@e543a41035`;按 changelog 发布门禁记录 `backend_status=deployed`,本工单不包含运行时测试部署。
- 本工单冻结范围不执行部署或网关调用,`gateway_status=not_required`
- 前端未领取,`frontend_status=pending`;changelog 发布不代表前端已实现、发布或页面验证。