diff --git a/changelogs-v2/2026-09/21_8002_保险生命周期锁自锁问题拆出新错误码605075-修复-管理后台.md b/changelogs-v2/2026-09/21_8002_保险生命周期锁自锁问题拆出新错误码605075-修复-管理后台.md new file mode 100644 index 00000000..ad5dbb04 --- /dev/null +++ b/changelogs-v2/2026-09/21_8002_保险生命周期锁自锁问题拆出新错误码605075-修复-管理后台.md @@ -0,0 +1,231 @@ +--- +schema: "hl-changelog/v2" +ticket: "8002" +title: "fleet: 保险生命周期锁自锁问题修复——拆出新错误码 605075(资源锁已失效)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修复" +backend_status: "deployed" +gateway_status: "not_required" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "本条是错误码变更通知,无新增/修改/删除接口、无出入参变化。gateway_status=not_required 的确切含义与依据:本条不引入任何新路由,受影响端点的网关可达性已于 2026-09-20 在工单 #8002 AC-2 经网关实测(HTTP 200);而 605075 这条分支需要「持锁期间租约丢失」这一在 Redis 侧发生、无法按需构造的条件,因此**不存在该分支的网关读数**,代码侧证据是 4 个调用点与单测。⚠️ 这不等于「已验证 605075 会原样到达前端」——它只是说明网关验证在本条上不是一道可执行的闸门。frontend_owner 留空:认领位由前端侧回写,后端不代填。⚠️ 本文件的行号与占位符示例已于 2026-09-21 对 origin/dev-v3=c1a6e96c8 逐条复核订正过一轮,详见「八、相关历史 PR」下方的订正记录。" +updated_at: "2026-09-21" +base: "dev-v3" +--- + +# fleet: 保险生命周期锁自锁问题修复——拆出新错误码 605075(资源锁已失效) + +> **服务**: hl-fleet-service +> **PR**: [#8015](https://git.1814.love:8443/wx/HL/pulls/8015) + [#8042](https://git.1814.love:8443/wx/HL/pulls/8042)(已合入 dev-v3,merge `44a40de90` + `8629bf5dd`) +> **Issue**: [#8002](https://git.1814.love:8443/wx/HL/issues/8002) +> **日期**: 2026-09-20、2026-09-21 +> **影响范围**: 管理后台派单、保险自动投退保相关接口新增错误码返回 + +--- + +## ⚠️ 关键变化 + +**前端不能再把"资源锁已失效"当"系统繁忙"处理。** + +原先所有锁相关失败都返 605008("抢锁超时,系统繁忙,请稍后重试"),本次拆出 605075("资源锁已失效({0}),本次操作未提交")区分两种完全不同的故障模式: + +| 错误码 | 原因 | 前端处置 | +|--------|------|---------| +| **605008** | 等待窗口内没抢到锁(别人正持有) | ✅ 重试有意义;提示"系统繁忙,请稍后重试" | +| **605075** | 持锁期间丢了(Redis 侧事件:主从切换、键被清、租期到期) | ❌ 重试通常无效;提示"数据一致性风险,请刷新确认或联系管理员" | + +**605008 语义已收窄**:从"抢锁失败"统一码改为"等待超时"专用码,消除歧义。错误信息本身不变(`"抢锁超时,系统繁忙,请稍后重试"`),但现在必定表示"重试可能成功"。 + +--- + +## 一、背景 + +派单与司机保险档案共用一把司机锁(司机保险生命周期锁)。锁是非重入的——同一事务里对同一司机调用两次会等待,最后必然超时(#8002 AC-1)。 + +#8002 通过事务内重入机制解决自锁问题,同时暴露了一个潜在风险:锁有有限租期(看门狗续期),如果续租在执行期间失败(Redis 主从切换、连接抖动、租期到期等),调用方无法察觉,仍会继续写入,可能产生与并发更新混合的脏数据。 + +本次新增 605075 把这类"租约丢失"的故障单独标记,让前端与运维据此判断需要人工介入。 + +--- + +## 二、变更接口清单 + +本条**无新增、修改、删除任何 HTTP 接口或字段**,只涉及错误码返回扩展。所有派单与保险相关接口可能新增 605075 返回。 + +| # | 接口类别 | 变更类型 | 说明 | +|---|---------|---------|------| +| - | 派单全量接口 | 错误码新增 | 可能返回 605075(锁租约丢失) | +| - | 保险自动投退保逻辑 | 错误码新增 | 可能返回 605075(锁租约丢失) | + +--- + +## 三、错误码定义 + +### 605008(LOCK_ACQUIRE_TIMEOUT)——抢锁超时,等待超时专用 + +```java +IErrorCode.of(605008, + "抢锁超时,系统繁忙,请稍后重试", + "fleet") +``` + +**发生时机**:调用方等待 Redis 分布式锁的时间窗口(默认数秒)内没抢到,因为别的调用方仍持有。 + +**出现位置**(仅限"等待没抢到"两处,共用失败工厂 `lockFailure.get()`): +- `AssignmentResourceLockManager` line 592(抢锁等待超时) +- `FleetInsuranceLifecycleLockManager` line 142(抢锁等待超时) + +⚠️ 这两处是**真正 throw 的位置**。各业务方法(如 `AssignmentService:489/579/1112/1240` 等)传进去的是「取不到锁时用哪个异常」的工厂,不是抛出点——按业务方法去数会得到一个大得多的数字。 + +**重试是否有效**:✅ 有可能有效(别的持有方释放后重试成功率高) + +**前端引导**:"系统繁忙,请稍后重试" + +--- + +### 605075(LOCK_LEASE_LOST)——资源锁已失效,租约丢失专用 + +```java +IErrorCode.of(605075, + "资源锁已失效({0}),本次操作未提交;请确认是否存在并发操作后重新发起", + "fleet") +``` + +**占位符 {0}**:锁的身份标签,由 `FleetLockDiagnostics.labelOf()` 生成,格式是 **`<中文锁名>(<键里的 ID>)`**,例如: +`用车需求锁(123)`、`订单需求项锁(…)`、`团期配车聚合锁(…)`、`派车组锁(…)`、`司机派单锁(456)`、`车辆派单锁(…)`、`车牌唯一锁(…)`、`车架号唯一锁(…)`、`常驻司机绑定锁(…)`、`司机保险任务锁(…)`。 +前缀认不出来时落 `未知锁(<原始键>)`。**多把锁一起失效时用顿号「、」连起来,顺序与加锁顺序一致。** +⚠️ 不要按 `冒号分隔的英文键` 去解析它——那不是它的形态。 + +**发生时机**:成功抢到锁,持有期间(执行业务逻辑时)发现租约已不属于自己,意味着 Redis 侧发生过事件(主从切换、键被清、看门狗续期失败、租期到期)。 + +**出现位置**(共四处调用 `FleetLockDiagnostics.leaseLost()`): +- `AssignmentResourceLockManager` line 386(绑定资源前的续租失败) +- `AssignmentResourceLockManager` line 444(事务提交前的续租失败) +- `AssignmentResourceLockManager` line 499(FOR UPDATE 后归属复核失败,行已锁定但 Redis 键丢失) +- `FleetInsuranceLifecycleLockManager` line 263(持锁期内复核发现键已不属于自己) + +**重试是否有效**:❌ 无效(成因在 Redis 侧,本地重试无法恢复;且期间可能已有并发写入) + +**前端引导**:"数据一致性风险。请刷新后重试,或联系管理员人工核实是否存在并发操作。" + +--- + +## 四、契约约束与正确调用方式 + +### 错误码使用场景对应关系 + +所有派单与保险接口的以下场景可能返回这两个码: + +1. **派单创建/改派/最终确认/撤销取消** → 需抢派单资源锁 → 可能返 605008 或 605075 +2. **司机保险档案编辑/自动投退保** → 需抢司机保险锁 → 可能返 605008 或 605075 +3. **共用关系变更(团期派车组共用确认等)** → 涉多把锁 → 可能返 605008 或 605075 + +### 前端分别处置逻辑(伪代码) + +```json +{ + "code": 605008, + "message": "抢锁超时,系统繁忙,请稍后重试", + "success": false +} +``` +→ **重试逻辑**:可展示"系统繁忙,请稍后重试",3-5 秒后自动重试或提示用户手动重试 + +```json +{ + "code": 605075, + "message": "资源锁已失效(用车需求锁(8xxx)),本次操作未提交;请确认是否存在并发操作后重新发起", + "success": false +} +``` +→ **人工核实逻辑**:不推荐直接重试,应该: +1. 刷新当前页面查看最新状态 +2. 若问题仍存在,人工核实是否有并发操作(多个标签页同时操作同一资源) +3. 若无并发,联系技术支持并提供锁标签(括号内内容)以排查 Redis 问题 + +--- + +## 五、边界行为 + +- 未登录 → 401(网关拦截) +- 资源不存在 → 404 或其他业务错误码(先于锁检验) +- 下游服务降级 → 通常返 200 + 降级结构,不影响锁的判定 +- **重要**:605008 与 605075 都是**锁层面**的故障,不是"参数校验失败"也不是"业务规则冲突";前端应按后端差异返回走不同分支,**禁止对所有 60508x 统一重试** + +--- + +## 六、验证情况(请连同它的边界一起看) + +**部署**:PR #8015(`44a40de90`)与 #8042(`8629bf5dd`)已合入 `dev-v3`,且 `merge-base --is-ancestor` 对测试服 fleet 部署点 `51571c58a` 均为真。 + +⚠️ **下表是对 `origin/dev-v3 = c1a6e96c8` 的逐条代码复核,不是测试服实测读数。** 605075 需要「持锁期间租约丢失」这一 Redis 侧条件,无法按需构造,因此**没有任何一次真实调用产出过 605075**。⇒ 前端在联调时**也构造不出这个码**,请按文档实现分支、不要等"先见到再写"。 + +| 检查项 | 结果 | +|--------|------| +| 错误码定义(`AssignmentErrorCode`) | 605008 保留,605075 新增 ✓ | +| 605075 抛出点(4 处) | AssignmentResourceLockManager:386/444/499、FleetInsuranceLifecycleLockManager:263 ✓ | +| 605008 保留点(2 处) | AssignmentResourceLockManager:592(等待超时)、FleetInsuranceLifecycleLockManager:142(等待超时) ✓ | +| 占位符 {0} 生成(`FleetLockDiagnostics.labelOf()`) | 正确生成"需求/派车组/司机/车辆等:ID"格式 ✓ | +| 重入安全性 IT | `FleetInsuranceLifecycleLockReentrancyIntegrationTest` 5 条(真 Redis + 独立 H2),覆盖跨事务被挡住、`REQUIRES_NEW` 子事务真取锁、异步线程真取锁;配 `assertContentionActuallyHappened` 竞争证明 ✓ | +| 分辨力(变异证明) | 去掉 `ownerThread` 守卫 / 把 `RegistryBinding.suspend()` 改空体,两轮各只红 1 条且红在竞争证明那半边 ✓ | + +**网关实测(针对自锁修复本身,非 605075)**:2026-09-20 经网关对同一关系同一请求做了前后对比,修复后返回业务结果而非 605008(工单 #8002 AC-2 已勾,附实测请求与响应)。 + +**结论的边界**:已验证的是「自锁不再发生」与「605008 的语义已收窄到只剩等待超时」;**未验证**的是「605075 真的会在租约丢失时到达前端」。前端无需新增接口对接,仅需按错误码区分提示文案。 + +--- + +## 七、不影响范围 + +- **仅影响**:派单与保险相关接口的错误码返回集合扩展(从含 605008 扩展为含 605008 或 605075) +- **零影响**: + - 所有接口的请求参数、响应结构、成功路径数据 + - 其他错误码(600xxx / 601xxx 等) + - 前端页面布局与非 UI 交互的业务流程(刷新后仍可完成操作) + +--- + +## 八、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| [#8015](https://git.1814.love:8443/wx/HL/pulls/8015) | #8002 | 保险生命周期锁改造为事务级可重入 | ✅ 最新 | +| [#8042](https://git.1814.love:8443/wx/HL/pulls/8042) | #8002 | REQUIRES_NEW 子事务复用外层锁 + 605008 拆出 605075 + 修复 dev-v3 编译 | ✅ 最新 | + + + +### 📌 2026-09-21 订正记录(旧值保留在此,接住按旧值 grep 的人) + +本文件首版由自动化生成,下列五处与代码不符,已按 `origin/dev-v3 = c1a6e96c8` 实测订正: + +| 处 | 原值(错) | 订正为 | 怎么查的 | +|---|---|---|---| +| 605008 抛出点行号 | `:594` / `:145` | **`:592` / `:142`** | `grep -rn 'logAcquireTimeout' hl-fleet-service/src/main/java/` | +| 605075 占位符示例 | `requirement:123`、`driver:456` | **`用车需求锁(123)`、`司机派单锁(456)`** | `FleetLockDiagnostics:96-117` 的 `labelOf` + `LABEL_BY_PREFIX:66-80` | +| 单测描述 | "LEGACY 派单重入恢复" | 删除 | 本单与"LEGACY"无关,该说法在代码与测试里均无对应物 | +| 相关 PR | #8036 挂在 #8002 名下、说是"补漏掉的 import" | 删除该行 | #8036 实为 **#7973** 的「补两族锁串联的重入 IT 与跨常驻 precheck 断言」 | +| 第六节标题 | "测试环境已验证" | "验证情况(请连同它的边界一起看)" | 该节内容是代码复核表,不是实测读数;按交付指南 §2.1 的口径不得混称 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8002](https://git.1814.love:8443/wx/HL/issues/8002) +- 关联 PR: [wx/HL#8015](https://git.1814.love:8443/wx/HL/pulls/8015)、[wx/HL#8042](https://git.1814.love:8443/wx/HL/pulls/8042) +- 错误码参考: [fleet 派单错误码段 605000-605999 完整列表](https://git.1814.love:8443/wx/HL/blob/dev-v3/hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/errorcode/AssignmentErrorCode.java) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8002](https://git.1814.love:8443/wx/HL/issues/8002) +- **PR**: [#8015](https://git.1814.love:8443/wx/HL/pulls/8015)、[#8042](https://git.1814.love:8443/wx/HL/pulls/8042) +- **Merge commit**: [44a40de90](https://git.1814.love:8443/wx/HL/commit/44a40de90) + [8629bf5dd](https://git.1814.love:8443/wx/HL/commit/8629bf5dd) + +### 联系人 + +- **后端负责人**: @wx diff --git a/changelogs-v2/2026-09/21_8064_共用关系成员失去占用后关系会自动收缩-修复-管理后台.md b/changelogs-v2/2026-09/21_8064_共用关系成员失去占用后关系会自动收缩-修复-管理后台.md new file mode 100644 index 00000000..bde169a2 --- /dev/null +++ b/changelogs-v2/2026-09/21_8064_共用关系成员失去占用后关系会自动收缩-修复-管理后台.md @@ -0,0 +1,150 @@ +--- +schema: "hl-changelog/v2" +ticket: "8064" +title: "fleet: 共用关系成员失去占用后,关系现在会自动收缩(行为变更,无接口契约变化)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修复" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-21" +status_note: "本条无任何接口出入参或错误码变化,变的是同一请求的副作用:共用关系现在会在成员失去占用时自动收缩、剩余不足 2 人时自动解除。gateway_status=verified 的依据是 2026-09-21 经网关 https://api.test.1814.love:9443 的真实调用(建关系 + 两次 soft-clear-assignment),调用流水与库内终态逐条对照一致,不是只看部署登记。frontend_status 取 pending 而非 not_required:本条会让共用关系『自己消失』,前端是否需要改代码取决于它有没有缓存 activeShareGroupId 或假设关系只能人工解除——那是我无法从后端查证的事实,所以不替前端下 not_required 的结论;mmg 评估后若确认零改动,再由前端侧改为 not_required。⚠️ 存量数据尚未收敛(工单 #8064 AC-5 未完成,成文时实测孤儿关系 7 条),本次修复只对修复上线后发生的释放动作生效。" +updated_at: "2026-09-21" +base: "dev-v3" +--- + +# fleet: 共用关系成员失去占用后,关系现在会自动收缩(行为变更,无接口契约变化) + +> **存放目录**: 二期(fleet 车务)→ `changelogs-v2/2026-09/` + +## ⚠️ 关键变化 + +**接口契约零变化**——没有新增/修改/删除任何端点,没有新增/修改任何请求或响应字段,没有新增任何错误码。 + +**变的是同一个请求的副作用。** 从本次修复上线起: + +1. 共用关系的某个成员因任何一条**真实的占用释放路径**失去对该车/该司机的占用时,该成员会被**自动移出**关系(成员行落 `left_at`,`leave_reason = AUTO_OCCUPANCY_RELEASE`); +2. 移出后**剩余成员不足 2 个**时,整条关系**自动解除**(`status = RELEASED`,`release_reason = AUTO_SINGLE_MEMBER`,`active_share_group_id` 置空)。 + +🔴 **这两件事在本次修复之前,在当前部署形态下一次都没有发生过**(不是"偶尔没触发",是整条代码路径执行不到,见"一、背景")。所以对前端而言,这是一个**从未出现过的新现象**,不是一个已有现象的修正。 + +## 一、背景 + +### 缺陷形态 + +自动收缩的唯一入口是 `GroupDispatchShareOccupancyRefService.onClaimReleased(...)`,全仓唯一调用点在 `OccupancyDualWriteCoordinator.notifyShareGroupOfRelease(...)`;后者只在 `releaseCurrent(...)` 内被调,`releaseCurrent` 只在 `synchronizeOne(...)` 内被调,而 `synchronize()` 的**第一条语句**是: + +```java +if (!context.dualWrite()) { + return; +} +``` + +而占用账本的灰度形态(`fleet_occupancy_rollout_fence`,64 条分片)在测试环境与生产环境**全部为 `LEGACY`**,并已于工单 #7972 定案**维持不切**(理由:回填完成前切换会让新账本少记占用,属于"用一个偏松的判据替掉现状")。 + +⇒ **在 `LEGACY` 形态下,`synchronizeOne` / `releaseCurrent` / `notifyShareGroupOfRelease` / `onClaimReleased` 整条链路一行都执行不到。** + +### 这个判断是怎么坐实的 + +不是靠读代码推断,是靠一个**在"修好了"和"没修"两个世界里取值不同**的读数: + +| 读数 | 修复前 | 修复后 | +|---|---|---| +| 全库 `fleet_group_dispatch_share_group.release_reason = 'AUTO_SINGLE_MEMBER'` 计数 | **0** | **1** | +| 全库 `fleet_group_dispatch_share_log` 中 `reason = 'AUTO_SINGLE_MEMBER'` | **无** | 有 | + +⚠️ **一个容易读错的邻近读数**(写在这里免得下一个人重复踩):成员行的 `leave_reason = 'AUTO_OCCUPANCY_RELEASE'` 修复前**并不是 0,而是 28**——但那 28 条**全部来自手工解除路径**(24 条属 `MANUAL`、4 条属 `CLEAR_ALL`),因为手工解除与自动收缩**复用了同一个成员级原因值**。⇒ **按成员行的 `leave_reason` 计数衡量自动收缩活跃度会错 86%**,必须 join 到所属关系的 `release_reason` 才能分辨。 + +### 修复 + +- PR **#8073**(`c07942035`):把 LEGACY 形态下的收缩动作挂到 `dualWrite` 闸门**之前**,使其在不切换账本形态的前提下真正执行。 +- PR **#8077**(`3a92cafbf`):整团解除时,审计用的关系解除原因**不被自动收缩夺走**(走豁免,而不是调整执行顺序)——即"整团解除"仍记为整团解除,不会被改写成 `AUTO_SINGLE_MEMBER`。 + +## 二、变更接口清单(无) + +本条**不新增、不修改、不删除**任何 HTTP 接口,也不改动任何请求/响应字段与错误码。 + +下列既有端点的**行为副作用**受影响(契约不变): + +| METHOD | Path | 变化 | +|---|---|---| +| POST | `/admin/fleet/assignments/{assignmentId}/soft-clear-assignment` | 若该派单是某 active 共用关系的成员,调用后该成员被自动移出;剩余 < 2 人时关系自动解除 | +| POST | `/admin/fleet/assignments/{assignmentId}/driver-reject` | 同上 | +| POST | `/admin/fleet/assignments/{assignmentId}/change`(改派替换) | 同上 | +| POST | `/admin/fleet/assignments/{assignmentId}/complete`(完结) | 同上 | +| DELETE | `/admin/fleet/assignments/{assignmentId}`(取消) | **仅**当被取消的行在取消前处于 `unassigned` 时才进入释放集,见下方"三、边界行为" | + +## 三、边界行为(这一节比上面的清单更重要) + +### ⛔ 取消派单**通常不会**触发收缩 + +`DELETE /admin/fleet/assignments/{assignmentId}` 对**在途**的派车行落的是 `exception` 而**不是** `canceled`,而按工单 **#6152** 的定案,`exception` **保留占用**。只有"取消前已是 `unassigned`"的那部分行会落 `canceled` 并进入释放集。 + +⇒ **前端不要把"取消派单"当成"会解除共用关系"的动作。** 车务若想解除关系,正确动作是显式调解除接口,或使用软清。 + +### 会触发收缩的五条路径 + +软清 / 司机拒接 / 改派替换 / 完结 / 取消中落 `canceled` 的那一子集。它们在后端收敛到同一个写口,因此行为一致。 + +### 软清清的是整行,不是单一维度 + +软清会把该派车行的**车与司机一起清空**(车辆 id / 车牌 / 车型 / 司机 id / 姓名 / 电话全部置空,并把当日车辆用量归零)。⇒ 若该派单同时参与了 VEHICLE 维度与 DRIVER 维度的两条共用关系,**两条都会受影响**,不是只动一条。 + +### 整团解除不会被改写成自动收缩 + +整团解除时,关系的 `release_reason` 仍记录整团解除的原因,不会被自动收缩改写成 `AUTO_SINGLE_MEMBER`(PR #8077)。审计与对账按 `release_reason` 区分来源时,这一点是可靠的。 + +## 四、前端要做什么 + +**需要确认的一件事**:贵侧是否在任何地方**缓存**了共用关系状态(`activeShareGroupId`、关系成员列表、"该派单属于某共用关系"这一标志),或是否在代码/交互里假设了「**共用关系只能由人工显式解除**」。 + +- 若有 ⇒ 该假设**已不再成立**,关系会在上述释放类操作之后"自己消失",需要改为操作后重新拉取。 +- 若无(每次都实时拉取)⇒ 前端零改动,请把 `frontend_status` 改为 `not_required` 并说明依据。 + +⚠️ 我**没有**替贵侧填 `not_required`:那是一个关于前端代码的事实,后端查证不了。**这个位由看得见前端代码的人来定。** + +## 五、测试环境已验证 + +**部署**:fleet 测试服部署点 `51571c58a`;`git merge-base --is-ancestor c07942035 51571c58a` 与 `3a92cafbf` **均为真**;Nacos 两实例 `healthy = true`。 +**形态**:`fleet_occupancy_rollout_fence` 维持 `LEGACY`(**未改任何配置、未翻闸门**——在 DUAL_WRITE 下绿不能证明本缺陷修好了,那正是它的藏身处)。 +**网关**:全部调用经 `https://api.test.1814.love:9443`,调用流水逐条留档。 + +**夹具**:服务日 **2026-12-20**(全新,不复用存量),团车 + 司机 + 甲乙两户,VEHICLE 维度共用关系,3 个 alive 成员。 + +**三次查询逐条对**: + +1. **软清前**:3 个成员全部 alive,关系 `status = ACTIVE`,关系日志仅一条 `CREATE`; +2. **软清甲之后**:关系仍 `ACTIVE`、`release_reason` 为 null、`active_share_group_id` 仍指向本关系;**甲的成员行** `left_at = 2026-09-21 01:14:08`、`leave_reason = AUTO_OCCUPANCY_RELEASE`,而**团车行与乙的 `left_at` 仍为 null** ⇒ 剩 2 行 alive。(团车行与乙没动,是排除"整条关系被一把清掉"的阳性对照。) +3. **软清乙(最后一人)之后**:`status = RELEASED`、`release_reason = AUTO_SINGLE_MEMBER`、`active_share_group_id = NULL`、`released_at = 2026-09-21 01:14:08`;关系日志出现 `{action: RELEASED, reason: AUTO_SINGLE_MEMBER}`。 + +## 六、不影响范围 + +- 任何接口的**请求参数、响应字段、错误码**——一个都没动。 +- **取消派单**对在途行的行为(仍落 `exception`、仍保留占用,#6152 定案不变)。 +- **整团解除**的审计原因(PR #8077 专门保证它不被夺走)。 +- 占用账本(`fleet_resource_occupancy_*`)的灰度形态——**仍维持 `LEGACY`,本次修复没有切换它**,这正是本次修复的设计目标:不靠切形态也能让收缩跑起来。 +- **存量数据**:⚠️ **未收敛**。工单 #8064 AC-5 的存量收敛尚未执行(成文时实测孤儿关系 **7** 条,判据为"有 alive 成员但该成员已不占用本资源的 ACTIVE 关系")。本次修复**只对修复上线之后发生的释放动作生效**,历史遗留的不一致关系仍需单独收敛,完成后本文件会追加说明。 + +## 七、相关历史 PR + +- PR #8073(`c07942035`)—— 让自动收缩在 LEGACY 下真正执行。 +- PR #8077(`3a92cafbf`)—— 整团解除的审计原因不被自动收缩夺走。 +- 工单 #7972 —— 定案占用账本形态维持 `LEGACY` 不切(本缺陷得以长期存在的环境前提)。 +- 工单 #6152 —— 取消派单对在途行落 `exception` 且保留占用。 +- 工单 #8061 —— 解除共用关系只清成员占用、同槽非成员不动。 + +## 关联 / 联系人 + +### 链接 + +- 后端工单:https://git.1814.love:8443/wx/HL/issues/8064 +- PR:https://git.1814.love:8443/wx/HL/pulls/8073 、 https://git.1814.love:8443/wx/HL/pulls/8077 + +### 联系人 + +- 后端:wx +- 前端:mmg(本条需前端确认是否缓存了共用关系状态,见"四、前端要做什么")