[B-09][high] 连接生命周期没有串行化:握手与断线交错时断开的连接被标成在线,在线判定多处来源互相不一致 #16

Closed
opened 2026-09-30 13:56:53 +08:00 by nixevol · 1 comment
Owner

编号:B-09 严重级:high 工作线:broker(internal/broker) 来源:审查 I-04、I-05、M-10
依赖:B-05 (#12)(同改 OnDisconnect) 被依赖:B-10 (#17)(同改 handleHello)

结论与统一方案

审查 I-04、I-05 与 M-10 描述的是同一组竞态,统一由本 issue 修,避免三方各改一半:

  1. connState 增加 closed;hooks.OnDisconnect 在删除连接表条目的同一临界区置位,并在同一处直接停掉该连接的握手计时器(审查 P-22 第一点:现在先删表再按代号找计时器,永远找不到)。
  2. Session 增加按编号的生命周期锁:handleHello 从"标在线"到 OnHandshakeComplete 持锁,持锁后先检查 closed,已关闭直接返回;HandleDisconnect 持同一把锁做离线处理。备选方案是把断线事件放进该端上行队列顺序处理,二选一,推荐加锁。
  3. "是否当前连接"只在 hooks 判断一次并传给 appUplink(类型断言调扩展方法,不改 port 接口);appUplink.OnHandshakeComplete 只在 memConns 当前仍是本代号时才更新;appUplink.OnDisconnect 先从 memConns 移除本连接,再调用 msg.OnDisconnect(审查 M-10 第 1 点)。
  4. presence 使用的连接表改为基于 broker 的已握手状态(IsHandshook、当前代号);注入连接表后 presence 只信连接表,内存表只用来取 since;SetOffline 代号不符且该端已无已握手连接时,清掉残留条目并发下线通知。消息线分发仍按 DEVELOPMENT 7.4 把"握手中"算在线,不受影响。
  5. online_since / offline_since 只保留一个写入方:保留消息线在同一事务里的写入,删除 Login 与 presence 的重复写(DEVIATIONS N3 第 3 条本来就要求避免重复写)。

审查 M-10 的消息侧兜底(分发时参考 online_since、每秒补宽限)在 C-03 做。

改动文件

internal/broker/session.go、hooks.go、broker.go;cmd/nixmsg/uplink.go(OnSessionEstablished、OnHandshakeComplete、OnDisconnect 三个函数);internal/app/presence/app.go。

与其他问题的交互 / 冲突说明

  • 与 B-05(hooks.go OnDisconnect)、B-10(handleHello)同文件:本条在 B-05 之后、B-10 之前合入。
  • uplink.go 只改三个生命周期函数;C-05 改 HandleUplink、B-06 改 publishResp,互不重叠。
  • C-01 的推送 worker 由 msg.OnHandshakeComplete / msg.OnDisconnect 启停,本条保证这两个回调的调用顺序与"当前连接"判断正确。

验收与测试

  • 注入会阻塞的 PresenceSink,发 hello 后立即断开再放行:presence 为离线、memConns 无此连接、库里 offline_since > online_since、不保留投递的 expire_at 非空。
  • 顶号版:A 的 hello 被阻塞时 B 连上,放行后 memConns 仍为 B。
  • 会话已建立但未发 hello 时,presence.get 返回离线。
  • 断线后握手计时器已停止。

问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[I-04] 握手和断线没有串行,断开的连接会被重新标成在线,不保留的消息也不再按宽限作废

  • 严重级:high
  • 分类:并发 / 数据
  • 现象与影响
    • hello 在端的上行 worker 里依次处理:发 resp → 写 online_since(等提交)→ presence.SetOnline(再写一次)→ 置 handshook → OnHandshakeComplete。中途不复查连接是否还在。断线处理则同步跑在 mochi 的连接 goroutine 里。
    • 如果客户端在这段时间断开(弱网、刚连上就退出、握手中被顶号),断线处理会先跑完,然后 hello 继续执行:
      • 库里 online_since 被写成更晚的时间;
      • presence 内存表记为在线,并给订阅者发「上线」;
      • appUplink.OnHandshakeComplete 无条件执行 conns.Set,把已经死掉的连接登记回 memConns;
      • 消息线清空该端不保留投递的 expire_at。
    • 结果:
      • 该端在 presence.get、directory.list、group.get 里一直显示在线,订阅者最后收到的是「上线」。
      • 消息线认为它「有连接」,不保留的消息既不会在 60 秒宽限后丢弃,也推不出去。
      • 推送循环每秒对这个死连接执行一次「占标记→发布失败→清标记」,每次写两次库,直到该端下次成功上线。
    • 顶号时,A 迟到的 conns.Set 还会覆盖 B 的登记。如果 B 在 hello 前断开,状态同样卡死。
  • 证据
    • hello 处理:broker/session.go:237-263。
    • 断线钩子:broker/hooks.go:196-238。
    • 无条件登记:cmd/nixmsg/uplink.go:38-50;断线时 uplink.go:52-62 又用 memConns 重新判断是否当前连接,可能和 broker 的判断不一致。
    • 清空 expire_at:message/session.go:12-26。
    • 标在线:presence/app.go:205-221。
    • 推送循环的空转:message/push.go:88-99、190-231。
  • 文档依据:DEVELOPMENT 6.1(339)「握手完成才算在线」;第 5 节(270)新旧连接互不影响;7.5(675-682);PRD F03、D4。
  • 为何不是故意设计:没有相关偏差记录。
  • 解决方案
    1. connState 增加 closed 字段;hooks.OnDisconnect 在删除连接表条目的同一临界区里置 closed=true,并记下是否已握手。
    2. Session 增加按编号的生命周期锁:
      • handleHello 从「标在线」到 OnHandshakeComplete 这一段持锁;持锁后先检查 closed,已关闭就直接返回。
      • HandleDisconnect 持同一把锁做离线处理。
    3. 「是否当前连接」只在 hooks 里判断一次,由 Session 传给 appUplink(用类型断言调用扩展方法,不改 port 接口),appUplink 不再用 memConns 重新判断。
    4. appUplink.OnHandshakeComplete 只在 memConns 当前仍是本代号时才更新。
    • 备选方案:把断线事件放进该端的上行队列,让 worker 顺序处理。
  • 改动文件:broker/session.go、broker/hooks.go、broker/broker.go、cmd/nixmsg/uplink.go。
  • 与其他模块的交互/冲突风险
    • 这把锁只在同一编号的新旧连接之间竞争。
    • 调用 OnDisconnect 时 mochi 没有持有客户端锁,不会形成锁环。
    • 和 #3 对 broker 的改动在同一批文件里,合并时需要协调。
  • 需补测试
    • 注入一个会阻塞的 PresenceSink,发 hello 后立即断开,再放行。断言:presence 为离线、memConns 里没有这条连接、库里 offline_since > online_since、不保留投递的 expire_at 非空。
    • 顶号版:A 的 hello 被阻塞期间 B 连上;放行后 memConns 仍然是 B。
  • 置信度:代码阅读确定。触发取决于时序,窗口大约是两三次写库提交的时间。

[I-05] 在线状态的几个来源不一致:内存表残留、握手中也算在线、时间戳写三遍

  • 严重级:medium
  • 分类:逻辑 / 协议一致性
  • 现象与影响
    • (a) presence.SetOffline 发现内存表里记的连接代号不是本次断开的代号,就直接返回。
      • 顶号时,旧连接 A 的断线晚于 B 建立会话是常见情况,这时 A 的断线不算当前连接断开,表里仍记着 A。
      • 如果 B 在 hello 前断开,SetOffline(B) 会因为 A≠B 提前返回。
      • 表里就永久残留 A;连接表查不到时,resolveOnline 会回退到这张表,于是一直显示在线,订阅者也收不到下线。
    • (b) serve 接给 presence 的连接表用的是 memConns,它在会话建立时就登记,早于 hello。
      • 握手中的连接、连上 30 秒都不发 hello 的连接,都会被报为在线。
      • 返回的 since_ms 还是上一次会话的时间。
      • 这违背了 presence.Service 自己的约定「IsOnline 查询端是否有已握手连接」。
    • (c) 已经接了连接表时,仍然回退到内存表和库字段,这只会产生「假在线」。
    • (d) 每次握手或断线,Login、presence、message 三处各写一次 online_since/offline_since:三次提交、三个略有差异的时间戳。N3 偏差第 3 条本来要求接线时避免重复写。
  • 证据
    • presence 的判定与回退:presence/app.go:224-249、252-270、285-305。
    • 接线:cmd/nixmsg/uplink.go:30-36、333-349。
    • 约定:presence/service.go:49-50。
    • 重复写:broker/session.go:130-141、241-251;presence/app.go:209-212、240-243;message/session.go:15-17、37-40。
  • 文档依据:PRD F03(173)「在线以服务器上的真实连接为准」、F04;DEVELOPMENT 6.1(339);7.4 里「含握手中」只用于分发;DEVIATIONS N3 第 3 条。
  • 为何不是故意设计:I2/I3/I4 第 2 条说明回退只是 N3 合入前的过渡;L-UPLINK 没有提到要把握手中的连接算作在线。
  • 解决方案
    1. presence 用的连接表改为基于 broker:IsOnline 用 brk.IsHandshook(ep),CurrentConn 只返回已握手的当前代号。消息线仍用 memConns 做分发,保持不变。
    2. 注入了连接表时,presence 只信连接表,内存表只用来取 since。SetOffline 遇到代号不符时,如果连接表显示该端已没有已握手连接,就删掉残留条目并发下线通知。
    3. 上下线时间戳只保留一个写入方,建议保留消息线在同一事务里的写入。
  • 改动文件:cmd/nixmsg/uplink.go、presence/app.go、broker/session.go。
  • 与其他模块的交互/冲突风险:后台列表读的是库字段,合并写入方后仍然一致;要和 I-4 一起改。
  • 需补测试
    • SetOnline(ep,A) 后,SetOffline(ep,B) 且连接表为空:Get 返回离线,订阅者收到下线。
    • 会话已建立但还没发 hello 时,presence.get 返回离线。
  • 置信度:代码阅读确定。

[M-10] 断线写库与分发之间有竞态,会产生永不过期的「僵尸」不保留投递

  • 严重级:medium
  • 分类:并发 / 逻辑
  • 现象与影响:
    • appUplink.OnDisconnect 先调用 msg.OnDisconnect(入队写操作并等待提交),然后才从连接表里移除这个连接。
    • 在这段时间里执行的分发操作仍会把接收端判为在线,插入一条 expire_at 为空的不保留投递。
    • 断线写操作只修正在它之前已存在的投递,这一条漏了。
    • 清理只处理 expire_at IS NOT NULL 的投递,所以它永远不会被丢弃。接收端不再上线的话,消息永远停在 dispatched,正文永不删除,还占着双方配额。
  • 证据:uplink.go:56-61(先写库、后移除);dispatch.go:148(事务内查内存连接表);recover.go:49(清理条件)。
  • 文档依据:PRD F10「宽限结束仍不在则丢弃」;F18。
  • 为何不是故意设计:DEVIATIONS 里没有相关说明。
  • 解决方案:
    1. OnDisconnect 先计算 isCurrent,再从连接表移除,最后调 msg.OnDisconnect。会话层写 offline_since 发生在此之前,所以之后的分发能按新的离线时刻正确计算宽限。
    2. dispatchFullTx 的离线分支同时读 online_since:如果数据库里仍显示在线,就按「刚断线」处理,expire_at = now + grace。
    3. 每秒到期处理里加兜底:把没有就绪连接、不保留、未推送且 expire_at 为空的投递补上宽限截止时间。
  • 改动文件:cmd/nixmsg/uplink.go;internal/app/message/dispatch.go、recover.go。
  • 交互/冲突风险:与 M-03 的就绪标记一起实现。
  • 需补测试:保持连接表里还有该连接时调 OnDisconnect,随后提交一条不保留消息,再移除连接;运行兜底后投递得到宽限,宽限过后变为 dropped。
  • 置信度:代码阅读确定(时间窗口约为一个写批次加一次落盘)。

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:B-09 **严重级**:high **工作线**:broker(internal/broker) **来源**:审查 I-04、I-05、M-10 **依赖**:B-05 (#12)(同改 OnDisconnect) **被依赖**:B-10 (#17)(同改 handleHello) ### 结论与统一方案 审查 I-04、I-05 与 M-10 描述的是同一组竞态,统一由本 issue 修,避免三方各改一半: 1. `connState` 增加 `closed`;`hooks.OnDisconnect` 在删除连接表条目的同一临界区置位,并在同一处直接停掉该连接的握手计时器(审查 P-22 第一点:现在先删表再按代号找计时器,永远找不到)。 2. Session 增加按编号的生命周期锁:`handleHello` 从"标在线"到 `OnHandshakeComplete` 持锁,持锁后先检查 `closed`,已关闭直接返回;`HandleDisconnect` 持同一把锁做离线处理。备选方案是把断线事件放进该端上行队列顺序处理,二选一,推荐加锁。 3. "是否当前连接"只在 hooks 判断一次并传给 appUplink(类型断言调扩展方法,不改 port 接口);`appUplink.OnHandshakeComplete` 只在 memConns 当前仍是本代号时才更新;`appUplink.OnDisconnect` 先从 memConns 移除本连接,再调用 `msg.OnDisconnect`(审查 M-10 第 1 点)。 4. presence 使用的连接表改为基于 broker 的已握手状态(`IsHandshook`、当前代号);注入连接表后 presence 只信连接表,内存表只用来取 since;`SetOffline` 代号不符且该端已无已握手连接时,清掉残留条目并发下线通知。消息线分发仍按 DEVELOPMENT 7.4 把"握手中"算在线,不受影响。 5. online_since / offline_since 只保留一个写入方:保留消息线在同一事务里的写入,删除 Login 与 presence 的重复写(DEVIATIONS N3 第 3 条本来就要求避免重复写)。 审查 M-10 的消息侧兜底(分发时参考 online_since、每秒补宽限)在 C-03 做。 ### 改动文件 `internal/broker/session.go`、`hooks.go`、`broker.go`;`cmd/nixmsg/uplink.go`(`OnSessionEstablished`、`OnHandshakeComplete`、`OnDisconnect` 三个函数);`internal/app/presence/app.go`。 ### 与其他问题的交互 / 冲突说明 - 与 B-05(hooks.go `OnDisconnect`)、B-10(`handleHello`)同文件:本条在 B-05 之后、B-10 之前合入。 - uplink.go 只改三个生命周期函数;C-05 改 `HandleUplink`、B-06 改 `publishResp`,互不重叠。 - C-01 的推送 worker 由 `msg.OnHandshakeComplete` / `msg.OnDisconnect` 启停,本条保证这两个回调的调用顺序与"当前连接"判断正确。 ### 验收与测试 - 注入会阻塞的 PresenceSink,发 hello 后立即断开再放行:presence 为离线、memConns 无此连接、库里 offline_since > online_since、不保留投递的 expire_at 非空。 - 顶号版:A 的 hello 被阻塞时 B 连上,放行后 memConns 仍为 B。 - 会话已建立但未发 hello 时,`presence.get` 返回离线。 - 断线后握手计时器已停止。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [I-04] 握手和断线没有串行,断开的连接会被重新标成在线,不保留的消息也不再按宽限作废 - **严重级**:high - **分类**:并发 / 数据 - **现象与影响** - hello 在端的上行 worker 里依次处理:发 resp → 写 online_since(等提交)→ `presence.SetOnline`(再写一次)→ 置 handshook → `OnHandshakeComplete`。中途不复查连接是否还在。断线处理则同步跑在 mochi 的连接 goroutine 里。 - 如果客户端在这段时间断开(弱网、刚连上就退出、握手中被顶号),断线处理会先跑完,然后 hello 继续执行: - 库里 online_since 被写成更晚的时间; - presence 内存表记为在线,并给订阅者发「上线」; - `appUplink.OnHandshakeComplete` 无条件执行 `conns.Set`,把已经死掉的连接登记回 memConns; - 消息线清空该端不保留投递的 expire_at。 - 结果: - 该端在 `presence.get`、`directory.list`、`group.get` 里一直显示在线,订阅者最后收到的是「上线」。 - 消息线认为它「有连接」,不保留的消息既不会在 60 秒宽限后丢弃,也推不出去。 - 推送循环每秒对这个死连接执行一次「占标记→发布失败→清标记」,每次写两次库,直到该端下次成功上线。 - 顶号时,A 迟到的 `conns.Set` 还会覆盖 B 的登记。如果 B 在 hello 前断开,状态同样卡死。 - **证据** - hello 处理:`broker/session.go:237-263`。 - 断线钩子:`broker/hooks.go:196-238`。 - 无条件登记:`cmd/nixmsg/uplink.go:38-50`;断线时 `uplink.go:52-62` 又用 memConns 重新判断是否当前连接,可能和 broker 的判断不一致。 - 清空 expire_at:`message/session.go:12-26`。 - 标在线:`presence/app.go:205-221`。 - 推送循环的空转:`message/push.go:88-99`、`190-231`。 - **文档依据**:DEVELOPMENT 6.1(339)「握手完成才算在线」;第 5 节(270)新旧连接互不影响;7.5(675-682);PRD F03、D4。 - **为何不是故意设计**:没有相关偏差记录。 - **解决方案** 1. connState 增加 `closed` 字段;`hooks.OnDisconnect` 在删除连接表条目的同一临界区里置 `closed=true`,并记下是否已握手。 2. Session 增加按编号的生命周期锁: - `handleHello` 从「标在线」到 `OnHandshakeComplete` 这一段持锁;持锁后先检查 `closed`,已关闭就直接返回。 - `HandleDisconnect` 持同一把锁做离线处理。 3. 「是否当前连接」只在 hooks 里判断一次,由 Session 传给 appUplink(用类型断言调用扩展方法,不改 port 接口),appUplink 不再用 memConns 重新判断。 4. `appUplink.OnHandshakeComplete` 只在 memConns 当前仍是本代号时才更新。 - 备选方案:把断线事件放进该端的上行队列,让 worker 顺序处理。 - **改动文件**:`broker/session.go`、`broker/hooks.go`、`broker/broker.go`、`cmd/nixmsg/uplink.go`。 - **与其他模块的交互/冲突风险** - 这把锁只在同一编号的新旧连接之间竞争。 - 调用 OnDisconnect 时 mochi 没有持有客户端锁,不会形成锁环。 - 和 #3 对 broker 的改动在同一批文件里,合并时需要协调。 - **需补测试** - 注入一个会阻塞的 PresenceSink,发 hello 后立即断开,再放行。断言:presence 为离线、memConns 里没有这条连接、库里 offline_since > online_since、不保留投递的 expire_at 非空。 - 顶号版:A 的 hello 被阻塞期间 B 连上;放行后 memConns 仍然是 B。 - **置信度**:代码阅读确定。触发取决于时序,窗口大约是两三次写库提交的时间。 #### [I-05] 在线状态的几个来源不一致:内存表残留、握手中也算在线、时间戳写三遍 - **严重级**:medium - **分类**:逻辑 / 协议一致性 - **现象与影响** - (a) `presence.SetOffline` 发现内存表里记的连接代号不是本次断开的代号,就直接返回。 - 顶号时,旧连接 A 的断线晚于 B 建立会话是常见情况,这时 A 的断线不算当前连接断开,表里仍记着 A。 - 如果 B 在 hello 前断开,`SetOffline(B)` 会因为 A≠B 提前返回。 - 表里就永久残留 A;连接表查不到时,`resolveOnline` 会回退到这张表,于是一直显示在线,订阅者也收不到下线。 - (b) serve 接给 presence 的连接表用的是 memConns,它在会话建立时就登记,早于 hello。 - 握手中的连接、连上 30 秒都不发 hello 的连接,都会被报为在线。 - 返回的 `since_ms` 还是上一次会话的时间。 - 这违背了 presence.Service 自己的约定「IsOnline 查询端是否有已握手连接」。 - (c) 已经接了连接表时,仍然回退到内存表和库字段,这只会产生「假在线」。 - (d) 每次握手或断线,Login、presence、message 三处各写一次 online_since/offline_since:三次提交、三个略有差异的时间戳。N3 偏差第 3 条本来要求接线时避免重复写。 - **证据** - presence 的判定与回退:`presence/app.go:224-249`、`252-270`、`285-305`。 - 接线:`cmd/nixmsg/uplink.go:30-36`、`333-349`。 - 约定:`presence/service.go:49-50`。 - 重复写:`broker/session.go:130-141`、`241-251`;`presence/app.go:209-212`、`240-243`;`message/session.go:15-17`、`37-40`。 - **文档依据**:PRD F03(173)「在线以服务器上的真实连接为准」、F04;DEVELOPMENT 6.1(339);7.4 里「含握手中」只用于分发;DEVIATIONS N3 第 3 条。 - **为何不是故意设计**:I2/I3/I4 第 2 条说明回退只是 N3 合入前的过渡;L-UPLINK 没有提到要把握手中的连接算作在线。 - **解决方案** 1. presence 用的连接表改为基于 broker:`IsOnline` 用 `brk.IsHandshook(ep)`,`CurrentConn` 只返回已握手的当前代号。消息线仍用 memConns 做分发,保持不变。 2. 注入了连接表时,presence 只信连接表,内存表只用来取 since。`SetOffline` 遇到代号不符时,如果连接表显示该端已没有已握手连接,就删掉残留条目并发下线通知。 3. 上下线时间戳只保留一个写入方,建议保留消息线在同一事务里的写入。 - **改动文件**:`cmd/nixmsg/uplink.go`、`presence/app.go`、`broker/session.go`。 - **与其他模块的交互/冲突风险**:后台列表读的是库字段,合并写入方后仍然一致;要和 I-4 一起改。 - **需补测试** - `SetOnline(ep,A)` 后,`SetOffline(ep,B)` 且连接表为空:`Get` 返回离线,订阅者收到下线。 - 会话已建立但还没发 hello 时,`presence.get` 返回离线。 - **置信度**:代码阅读确定。 #### [M-10] 断线写库与分发之间有竞态,会产生永不过期的「僵尸」不保留投递 - 严重级:medium - 分类:并发 / 逻辑 - 现象与影响: - `appUplink.OnDisconnect` 先调用 `msg.OnDisconnect`(入队写操作并等待提交),然后才从连接表里移除这个连接。 - 在这段时间里执行的分发操作仍会把接收端判为在线,插入一条 `expire_at` 为空的不保留投递。 - 断线写操作只修正在它之前已存在的投递,这一条漏了。 - 清理只处理 `expire_at IS NOT NULL` 的投递,所以它永远不会被丢弃。接收端不再上线的话,消息永远停在 `dispatched`,正文永不删除,还占着双方配额。 - 证据:`uplink.go:56-61`(先写库、后移除);`dispatch.go:148`(事务内查内存连接表);`recover.go:49`(清理条件)。 - 文档依据:PRD F10「宽限结束仍不在则丢弃」;F18。 - 为何不是故意设计:DEVIATIONS 里没有相关说明。 - 解决方案: 1. `OnDisconnect` 先计算 isCurrent,再从连接表移除,最后调 `msg.OnDisconnect`。会话层写 `offline_since` 发生在此之前,所以之后的分发能按新的离线时刻正确计算宽限。 2. `dispatchFullTx` 的离线分支同时读 `online_since`:如果数据库里仍显示在线,就按「刚断线」处理,`expire_at = now + grace`。 3. 每秒到期处理里加兜底:把没有就绪连接、不保留、未推送且 `expire_at` 为空的投递补上宽限截止时间。 - 改动文件:`cmd/nixmsg/uplink.go`;`internal/app/message/dispatch.go`、`recover.go`。 - 交互/冲突风险:与 M-03 的就绪标记一起实现。 - 需补测试:保持连接表里还有该连接时调 `OnDisconnect`,随后提交一条不保留消息,再移除连接;运行兜底后投递得到宽限,宽限过后变为 dropped。 - 置信度:代码阅读确定(时间窗口约为一个写批次加一次落盘)。 --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P1-highlane/brokerreview-2026-09-30 labels 2026-09-30 13:56:53 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 91e887b fix: 完成 broker 复审 B-03 至 B-12 (#16)。

已合入 origin/main `0c9b459`。落地提交 `91e887b` fix: 完成 broker 复审 B-03 至 B-12 (#16)。
Sign in to join this conversation.