Files
MobileGL/docs/Disaggregated/notes/p7/ph-f.md
T

24 KiB
Raw Blame History

P7 wave 2-F:Ph 小件(分支 p7/ph-f)

计划见 PLAN-PH-P34B-P7.md §1.1(PH-7、PH-8、D11、门行)与 §3 wave 2-F; 裁定见 INTEGRATOR-DECISIONS-P7.md ID-P7-3(令牌足够、不做 TLS、 Welcome.dataNonce);规范见 CONTRACT-P7.md §0 规则 I / J、§9 簇 F、§10 第 3 条。基线 ec46a550。

主机口径:WSL Arch + lavapipe,build-split 与集成者同配置(integration tests ON)。flatc 取 3rdparty/flatbuffers 子模块构建的钉版本(scripts/gen_protocol.py)。

基线门读数(~/w7/logs/p7-gate-f-base.log):G1 .text 0xa52203、符号 0/0;普查 79 处 abort、 0 未标记;棘轮 173;车道 unit 2429 / split 303 / spawn 219 / tcp 222 / magma-tcp 74。

0. 每片一条提交,每片一次 red-once

下表的 SHA 都是包树(分支 p7/ph-f)上的;集成者 cherry-pick 到 pipe 后以那边的为准。

片 项 提交 状态
1 PH-7 (1)(2)(3):常量时间令牌合一、≥16 字节(TCP 监听处)、无令牌只 loopback、smoke 入 CI 2975a337 落地
2 PH-7 (4):Welcome.dataNonce 把数据连接绑定到已认证的控制连接 596b34fd 落地
3 PH-8:Hello 要 64 GiB → server 忽略请求、按自己的尺寸建段且不分配 a746417a 落地
4 D11 五处上限 + PH-2 — 未做,见 §4
5 PH-6 drop-with-latch — 未做(顺序在 4 之后)
6 PH-1 (3)(4) — 未做(顺序在 4 之后)
F-a 审查修复 1+4:绑定后关闭 hand-off / 单会话监听者,hand-off 发送不阻塞 3370833f 落地,见 §6.1
F-b 审查修复 2:sha256(protocol.fbs) 钉住 MOBILEGL_PROTOCOL_CONTROL_REVISION 75f24db0 落地,见 §6.2
F-c 审查 nit 6/8/9/10(代码) 2b2a0ae0 落地,见 §6.3
F-d 审查 nit 3/5/7/11 + 两条 §12 债(文档) 本提交 落地,见 §6.4

red-once 原始行汇总在 ~/w7/logs/p7-ph-f-redonce.txt。


1. 片 1:令牌策略合一(PH-7 (1)(2)(3),ID-P7-3)

1.1 机制

MOBILEGL_IPC_TOKEN 原来有三种读法:SocketTransport.cpp 的 ListenTcp 只问「非空」,所以一个字节 的令牌也能打开 tcp://0.0.0.0;ServerSession::Accept 用 std::strcmp;ServerMain::RunSession 用 std::string::operator==,且在 server 没配令牌时反而要求对端送空令牌。两处比较都在第一个不同字节处返回。

  • 新文件 MG_Remote/Transport/AuthToken.h:ConfiguredAuthToken()(空值 = 未配置)、 kMinimumAuthTokenBytes = 16(只在 TCP 监听处问,见下条;unix 端点既不要求令牌也不问长度)、 ConstantTimeTokenMatch(F 修复轮起循环固定 64 字节、两端越界读零,更长的令牌比到最后一字节;长度差并入 累加器,嵌入的 NUL 参与比较)。
  • Handshake.h AuthenticatePeerToken:唯一的策略函数,ServerSession::Accept 与 supervisor 子进程 (ServerMain::RunSession)都调它。不配置令牌时不因对端送了令牌而拒绝——策略由监听侧的 loopback 限制负责。
  • ListenTcp:配置了但不足 16 字节的令牌让 TCP 监听失败(具名 Refuse{Authentication} ... minimum is 16, 进程退出 72),而不是悄悄退化成 loopback server。令牌值从不进日志。unix 监听者不检查这条:那条路上令牌 不是访问控制(§6.4 的本地对端债)。
  • Refuse{Authentication} 是 protocol.fbs RefuseCode 的枚举值,fatal_census.py 规则 4 的词表门 直接认它;普查前后都是 3 个 refusal 词,无需新增 .def 行(Refuse 词不是 Fatal 家族词)。
  • CI:TcpLane.SupervisorProtocolControls 从 wave 0 起挂在 integration-tcp 上却因 CI 没有 flatc 而恒为 SKIP。test.yml 的 integration-split 作业现在从子模块构建钉版 flatc(gen_protocol.py --check, 不改已提交头文件),导出 MOBILEGL_FLATC_EXECUTABLE,并用 junit_tally.py --require-entry-passed TcpLane.SupervisorProtocolControls 断言它跑了且过了——SKIP 在这里等于红(tcp_lane_tools_test.py 覆盖 passed / skipped / notrun / failed / absent 五态)。
  • smoke 新增两条监听控制:无令牌 + 通配地址 → 具名拒绝;13 字节令牌 + loopback → 具名拒绝、行里不含令牌。

1.2 red-once(两进程,TcpLane.SupervisorProtocolControls)

把 SocketTransport.cpp 还原到 ec46a550、只重链 libMobileGL.so:

RED   AssertionError: (None, '... MG_Remote server: pid=42256 listening on tcp://127.0.0.1:46447')
GREEN short_token_listen exit 72: MG_Remote: Refuse{Authentication} MOBILEGL_IPC_TOKEN is 13 bytes and the
      minimum is 16 (PH-7 (3)); refusing to listen on tcp://127.0.0.1:46545 rather than authenticating with
      a guessable secret

即:改前 13 字节令牌起了一个活的监听者;改后进程以名字拒绝。单测 SocketTransportTest.TcpRefusesToListen WithATokenShorterThanTheMinimum 与 TheTokenComparisonAnswersLengthAndPrefixTheSameWay 是廉价的那一半。


2. 片 2:Welcome.dataNonce(PH-7 (4),ID-P7-3)

2.1 现场故障

设备窗口 #1:经 Windows adb forward 时一个会话的两条连接乱序到达;AcceptPair 规定「先到的是控制、 后到的是数据」,于是数据连接被当成控制读 Hello,每个子进程等 10 s 后以 67 退出(日志里伴随 only 1 of 2 connections arrived within 250 ms)。同一条规则也意味着任何能连到端口的人都能把一条连接 插进窗口、成为别人会话的数据面。

2.2 机制

  • protocol.fbs:Welcome 末尾追加 dataNonce: [ubyte];新表 DataBind { nonce: [ubyte]; } 追加到 CtrlMsg 末尾。头文件由钉版 flatc 重新生成。
  • wire 指纹随之改变。 原指纹的输入全是数据面的结构尺寸与编解码摘要,控制 schema 改形状不会动它。新增 MOBILEGL_PROTOCOL_CONTROL_REVISION(mg_protocol_base.h,本次 = 1)作为 AbiFingerprintInputs. ControlSchemaRevision 混入;SessionHandshakeTest.TheAbiFingerprintChangesWhenAnyOfItsInputsDoes 钉住 该输入并加扰动行。实测 wireFingerprint 由 0x7d4470f241d23539 变为 0x03c4c293a54b3e08。 设备侧后果——两个方向不对称(审查 nit 3):
    • 新 server + 旧 client:旧 client 仍开两条连接、送旧指纹,新 supervisor 读到 Hello 后以 Refuse{WireFingerprint} 具名拒绝——这是有帧的那个方向。
    • 旧 server + 新 client(= 手机上的 server 没有随本提交重装的情形):新 client 只开一条连接; 旧 supervisor 的 AcceptPair 等第二条连接 2000 ms,记 only 1 of 2 connections arrived within ... ms, 把两条都关掉、继续监听——没有子进程、没有读 Hello、没有任何 Refuse 帧。client 侧的症状是 MG_Remote client: no Welcome ... (rc=6)(MOBILEGL_ERR_TRANSPORT_CLOSED,对端 EOF),不是一条 具名拒绝。看到这条就先查设备上的 APK 是否是本次构建(device-window-1-runbook.md §2 第 3 步)。
    • 指纹随 MOBILEGL_PROTOCOL_CONTROL_REVISION 变,而 revision 是手改的整数:F 修复轮把 sha256(protocol.fbs) 钉在它旁边(§6.2),改了 schema 不 bump 在单元车道与 flatc-check 里都是红。
  • 流程(仅 tcp://;unix 端点的第二条连接是 SCM_RIGHTS 套接字,保持 AcceptPair——那是本地对端债, §6.4):
    1. client SocketTransport::ConnectControl 只连控制连接,发 Hello;
    2. server 子进程在令牌通过后用内核 CSPRNG(getrandom,退回 /dev/urandom,绝不退回 PRNG)铸 128 位 nonce,StreamLink::AttachOwnedDeferred 先建好私有内存(Welcome 要宣告其尺寸)但不带 fd、不起 reader 线程,发 Welcome(带 nonce);
    3. client 收到 Welcome 后 ConnectDataConnection 开数据连接,第一帧是 DataBind{nonce},随后才挂 StreamLink;
    4. supervisor 一次只 AcceptOne:无活会话时新连接是控制连接、fork 子进程(带一对 hand-off socketpair);有活会话时读新连接的恰好一帧(ReceiveOneFrame 不多读一个字节——DataBind 后面 紧跟 StreamLink 自己的帧),是 DataBind 就把 fd 连同所呈 nonce(sideband)经 FdPassing::SendFd 交给子进程,否则照旧 Refuse{Busy};
    5. 子进程里 ServerSession::BindDataConnection 是唯一比较点(ConstantTimeNonceMatch):匹配者 StreamLink::BindDataFd 起 reader;不匹配者在那条连接上回 Refuse{Authentication} data connection nonce mismatch 并关闭,等待继续——陈旧或抢插的连接结束不了它没能加入的会话。截止时间 (5 s)到则在控制连接上回 Refuse{Authentication} no data connection presented the session nonce; 控制对端先走则立即结束等待(子进程按 0 退出,不计入 sessionsFaulted)。
    6. 无活会话时到达的 DataBind 由子进程具名拒绝:Refuse{Authentication} data connection names no live session,干净退出 0。
  • 250 ms / 2000 ms 的「两连接到达窗口」在 TCP 上不复存在;取而代之的是 nonce 匹配。非 --serve 的单会话 TCP 模式保留监听者,由会话自己 accept 并读 DataBind(ListenerSource),比较仍在同一处;F 修复轮起, 数据连接绑定后监听者随即关闭(§6.1)。
  • 仍然没有做的:ID-P7-3 说「认证在 fork 之前完成」——本片的令牌检查与 nonce 铸造仍在 fork 出的会话子 进程里(每会话一进程,不影响绑定的正确性);把 Hello 读取与认证移进 supervisor 属于 PH-7 (5)「有界的预 认证工作 + 失败退避」,未做。

2.3 red-once(两进程)

探针只跑「数据连接先到、控制连接后到」这一条(新 smoke 的 (a)),server 分别是片 1 的头与本片:

RED   (片 1 server) stray: RuntimeError: control channel closed before a complete frame
                    control: ConnectionResetError: [Errno 104] Connection reset by peer
                    supervisor: session pid=1705895 reaped exit=67 sessionsFaulted=1      ← 现场故障原样
GREEN stray: Refuse{Authentication} 'data connection names no live session'(子进程 exit=0)
      control: Welcome,nonce 16 字节

smoke 常驻控制(TcpLane.SupervisorProtocolControls):data_connection_first(上面这条)与 stale_nonce(会话活着时先呈一个取反的 nonce → 那条连接收到 Refuse{Authentication} data connection nonce mismatch,日志 MGPipe: Refuse{Authentication} data connection nonce mismatch,随后正确的 nonce 绑定、子进程打出 ready)。单测 SocketTransportTest.TheDataBindIsReadExactlyAndTheStreamBehindItIsLeft OnTheSocket 钉住「恰好一帧」与 16 字节宽度。

integration-tcp(真 GL client 走新流程)222/222、integration-magma-tcp 74/74 保持全绿。


3. 片 3:PH-8(server 钳制尺寸)

3.1 机制

没有改代码:ServerSession::Accept 只看 Hello linkTerms 的 dataPlane / wireForm,段按 server 自己的 SetSegmentSizes 建,Welcome 宣告的也是这组。准确地说这不是「钳制」而是忽略、按 server 尺寸:四个计数 根本没被读去定尺寸(审查 nit 8);F 修复轮起,请求超过所授条款时 Accept 记一条 MGLOG_W(§6.3),生产 client 的 Hello 不填这四个数,所以正常会话不会打这条。本片把它钉住:

  • 单测 SessionHandshakeTest.AHelloAskingFor64GiBIsClampedToTheServersTermsAndAllocatesNothingOfIt: Hello 四个窗口各要 64 GiB,Welcome 必须回 server 的 4 KiB 条款,四个 SegmentRef 各 ≤ 64 KiB。
  • smoke hello_asks_64_gib:两进程下同样的请求,Welcome 的条款 < 1 GiB,且回答它的会话子进程(Welcome serverPid)在数据连接绑定后(即全部段已存在)VmPeak < 64 GiB。实测:maxReply 2097136、cmd 8 MiB、 stage 32 MiB、event 256 KiB,子进程 VmPeak 237264896。

3.2 red-once(变异:Accept 改为按 Hello 的条款定尺寸,已还原)

RED   unit  SessionHandshakeTest.cpp:294  Accept rc 2 != 0(64 GiB 私有段建不出来)
RED   smoke hello_asks_64_gib: RuntimeError: control channel closed before a complete frame(子进程死于该请求)
GREEN 如 3.1

4. 片 4–6 未做,以及为什么停在这里

计划的停止规则:「某片超过一天就停在上一片」。片 4(D11 五处 + PH-2)的代码改动本身不大,但每一处都要求 一条在两进程臂上、字节来自对端的负控,而树上没有任何车道能把一条越过 client 自身守卫的畸形记录 送进一个 spawn / TCP server:

  • create_render_state 的 Cso.Slot、read_pixels 的 W×H(client 在发射前按回复槽尺寸自检)、程序归档 里的 ArchiveVector 计数、StagedTextureStore 的 run 与 level extent、SlotTables 的 handle slot/gen—— 全都由 client 的编码器生成且被它自己的检查挡住;要到达 server 必须有一个能按需伪造记录的对端(fuzz 臂 2 「超界计数」一行正是这件事,计划里记在 P7 之后)。
  • 另外两处落在簇 F 之外、且会碰 pull 构建:ProgramArtifactsCodec.cpp 编进 pull 构建(G1 .text 要求新界限 只能在 MOBILEGL_BUILD_DISAGGREGATED 内),SlotTables.h 在簇 D 的 MG_Backend/DirectGLES/ 下,且 MGPipeFatalFamily 目前只有 UnmigratedVerb / RoleViolation,PH-2 的具名拒绝需要先加一个 ProtocolCorruption 族并在 hook 映射里接上。
  • 已核实的一点备查:MG_Pipe/PipeApply.cpp 不在 pull 构建里(build-linux/compile_commands.json 无该文件),D11 第一行在那里改不影响 G1。

所以片 4 连同其后的片 5(PH-6)、片 6(PH-1 (3)(4))按规则未开工。建议下一包先落「对端伪造记录」的两进程 驱动(它同时是 fuzz 臂 2 的骨架),再逐处上界限,每处一条经它红一次的负控。


5. 门(片 1–3 的头 a746417a,包树 SHA;F 修复轮的头见 §7)

门 基线 ec46a550 头
G1 pull .text / 符号增删 0xa52203 / 0/0 0xa52203 / 0/0(build-linux 无需重编:簇 F 文件不在 pull 构建)
fatal_census.py 79 abort、44 家族词、3 refusal 词、0 未标记 同左
link_ratchet.py --assert-monotone 173 173(不变)
spawn_lane_parity.py 0 错 0 错
unit 2429 2433(+4:片 1 两条、片 2 一条、片 3 一条)
integration-split 303 303
integration-spawn 219 219
integration-tcp 222 222
integration-magma-tcp 74 74

6. F 修复轮:片 1–3 的 fable 审查(裁定 land with fixes)

审查的 11 条里 should-fix 五条(1–5)、nit 六条(6–11);下面按提交分。每条都有一次 red-once,原始行在 ~/w7/logs/p7-ph-f-redonce.txt 的 F fix round 段。

6.1 修复 1 + 4(3370833f):绑定之后,数据连接的来源随即关闭

缺陷 1(回归)。 子进程只在 BindDataConnection 里读 hand-off socketpair,数据连接绑定后再也不读;而 supervisor 的 RouteWhileBusy 对此后每一条 DataBind 仍然 SendFd 进去。后果:每个 fd 在子进程的接收 队列里躺着、对端没有任何回复(不是具名拒绝;改前基线对第二条连接还会回 Refuse{Busy}),那条 TCP 连接一直 挂到会话结束;队列到 net.unix.max_dgram_qlen(手机内核默认 10;本机 512,但发送端 sndbuf 在 ~288 个 datagram 处先满)后 supervisor 阻塞在 sendmsg 直到会话结束——不再 accept、不再回 Busy、不再 reap。

缺陷 4。 非 --serve 的单会话 TCP 模式为 ListenerSource 留着监听者,数据连接绑定后没人关它:内核继续 完成三次握手进 backlog,第二个 client / 重试要等满 kSpawnConnectTimeoutMs(20 s)而不是像改前那样立即 ECONNREFUSED。

修法(同一个钩子)。 RunSession 多收一个 sourceFd——--serve 下是子进程那端 pair[1],单会话下是 监听者——session.Accept() 一返回就 close 并清掉 DataConnectionSource。于是 (1) supervisor 再转发时 sendmsg 得到 ECONNREFUSED/ENOTCONN(FdPassing::SendFd 把「对端已走」当答案而不是错误日志),已有的 Refuse{Authentication} data connection could not be handed to the live session 按名字打出;SendFd 加 dontWait(MSG_DONTWAIT),队列因别的原因满也拒绝而不是卡住;(4) 单会话的第二个连接立即 ECONNREFUSED。

red-once(server 还原到 da313010,只重建 .so):

RED   smoke data_bind_after_bound: TimeoutError: timed out(第一条多余 DataBind 的 receive 就超时——没人回)
RED   probe f1probe.py(qlen+2 条 DataBind 只发不收): extras opened=288 of 514 后 connect/send 超时;
      answered by name=0 silent=288;此后新开控制连接: CONNECT FAILED TimeoutError(supervisor 卡在 sendmsg);
      supervisor alive=True
RED   smoke single_session_listener: 绑定后的第二个 connect 成功(监听者还开着)
GREEN smoke data_bind_after_bound: refused=12,每条 Refuse{Authentication} 'data connection could not be
      handed to the live session';随后新开控制连接 → Refuse{Busy}
GREEN probe: 514/514 具名拒绝、silent=0、新控制连接 → Busy、supervisor alive
GREEN smoke single_session_listener: second_connect=ConnectionRefusedError(111)

smoke 常驻控制两条:data_bind_after_bound(会话 ready 后 12 条多余 DataBind 各得具名拒绝、再开控制连接仍 Busy)与 single_session_listener(无 --serve 的 supervisor,ready 后 connect 必须 ECONNREFUSED)。 held_session / pair 共用 connect_control(启动期 ECONNREFUSED 重试 5 s)。

6.2 修复 2(75f24db0):sha256(protocol.fbs) 钉住 revision

revision 是手改整数,改了 protocol.fbs 不 bump 就回到「新旧对端指纹相同、字段静默缺失」。 scripts/ci/protocol_revision_pin.py 持有表 {revision → sha256(protocol.fbs)} (protocol_revision_pins.json,行尾折成 LF 再哈希),当前 revision 没有行、或行与文件摘要不符都红;--write 在 bump 后记新行,且拒绝把已有 revision 改钉到另一个摘要(那正是「改 schema 没 bump」的样子)。注册为 ProtocolSchema.RevisionPinsDigest(label unit,--self-test 先证明两种扰动都红才说绿),并作为 CI flatc-check 作业的一步——正是每次 schema 改动都要重新生成头文件的那个作业。

RED   protocol.fbs 末尾加一行注释、revision 仍为 1:
      ::error::protocol.fbs has sha256 a7b5c7fb888d2926... but MOBILEGL_PROTOCOL_CONTROL_REVISION is still 1,
      which is pinned to 7fc859f7fa756818...   rc=1
GREEN 还原: protocol revision pin: revision 1 holds (sha256 7fc859f7fa756818..., 1 revision(s) pinned,
      self-test red on both perturbations)   rc=0

6.3 nit 6 / 8 / 9 / 10(2b2a0ae0)

  • nit 6 AuthToken.h:改前循环长度 = 本端令牌长度,响应时间正比于秘密的长度而头注释写得像什么都不泄。 现在循环固定 kAuthTokenCompareBytes = 64(更长的令牌延到其长度,只泄露「长于 64」),两端越界读零、长度差 仍并入累加器、不延到对端长度(不让对端选我们的工作量)。SocketTransportTest.TheTokenComparisonAnswersLength AndPrefixTheSameWay 加 70 字节令牌最后一字节判定的用例。
  • nit 8 ServerSession::Accept:请求超过所授条款 → 一条 MGLOG_W:Hello asked for windows the server does not grant (cmd=68719476736 ...); the ask is ignored and the session is server-sized (cmd=8388608 stage=33554432 event=262144 maxReply=2097136) - PH-8。单测 §3.1 那条现在要求这一行(改前红:行不存在)。
  • nit 9 ServerSession::Accept:MintNonce 失败先在控制连接上回 Refuse{LinkTerms} stream data plane unavailable: the server could not mint a data nonce,再返回原错误(仍是 67、仍计入 sessionsFaulted—— CSPRNG 给不出字节是 server 的故障不是对端的条款)。没有驱动:不注入故障造不出 getrandom + /dev/urandom 同时失败,记录于此。
  • nit 10 RouteWhileBusy:有活会话时首帧 magic 错或超 1 MiB(ReceiveOneFrame 回 PROTOCOL_MISMATCH) 改回 Refuse{MalformedHello} first frame is not a control frame(Accept 对不可验证 Hello 用的同一个词), 关闭前先把对端已发的字节读掉免得 RST 冲掉拒绝帧;格式正确但不是 DataBind 的帧仍是 Busy。smoke 常驻控制 garbage_first_frame_while_busy(magic XXXX → code 8;改前红:code 7)。普查仍 3 个 refusal 词(新拒绝 走枚举值,源码里没有新字面量)。

6.4 记录债(本轮补记;CONTRACT-P7 §12 同步)

  • unix 端点仍按到达顺序配对(AcceptPair 的 250 ms / 2000 ms 窗口)。SocketTransport.h 把它写成 「同用户 rendezvous、别人够不着」——但抽象命名空间套接字没有 mode bits,「只有本 app 能连」这条性质靠的是 Android 的 SELinux 域隔离,不是传输层自己。本地对端债,不在 Ph 的远端威胁模型里,未做。
  • 两处 wave 2-F 之前就存在的预认证暴露(审查核实、归 PH-7 (5)):(a) supervisor 对每条 TCP 连接先 fork 再读 Hello,未认证对端每连接可让 server 付出一次 fork + 最长 10 s 的 Hello 等待;(b) ValidatePeerHandshake 在 AuthenticatePeerToken 之前跑,Refuse{ProtocolVersion|WireFingerprint|BuildFingerprint} 把本端 wire 指纹与 build stamp 送给了未认证的对端。(P7 F2 包 f2-auth 已关 (a)(b):supervisor 在 fork 前自己读首帧、 先验令牌,待认证工作有期限、有上限、按地址公平让位,失败按地址退避;unix 端点的「先 fork 再读 Hello」仍在, 归本地对端债。旋钮与留下的债见 CONTRACT-P7 §12 末条、MG_Remote/Server/PreAuthGate.h。)
  • 令牌最小长度只在 TCP 监听处检查(§1.1)。

7. 门(F 修复轮的头 2b2a0ae0,包树 SHA;F-d 只改文档,二进制同此头)

读数 ~/w7/logs/p7-ph-f-fgate.log、车道日志 ~/w7/logs/p7-ph-f-ffinal-*.log。

门 片 1–3 头 a746417a F 修复轮头
G1 pull .text / 符号增删 0xa52203 / 0/0 0xa52203 / 0/0(build-linux 无工作;compile_commands.json 里 MG_Remote 条目 0——簇 F 文件不在 pull 构建,mg_protocol_base.h 只改注释)
fatal_census.py 79 abort、44 家族词、3 refusal 词、0 未标记 同左(新拒绝走枚举值,无新字面量)
link_ratchet.py --assert-monotone 173 173
spawn_lane_parity.py 0 错 rc 0(split 193 / spawn 190 / tcp 190)
protocol_revision_pin.py --self-test — revision 1 holds,两种扰动均红
G14 名集合 — 对 ~/w7/p7-before 基线 removed=1(CapsMirrorTest.APlaceholderMirrorConsumesNothing,在本包基线 da313010 上已不存在,是更早的 P7 波次退的)、added=2819;本轮只增 ProtocolSchema.RevisionPinsDigest
unit 2433 2434(+1:ProtocolSchema.RevisionPinsDigest;SessionHandshakeTest / SocketTransportTest 的新断言在既有用例内)
integration-tcp 222 222(TcpLane.SupervisorProtocolControls Passed,evidence 17 项含 data_bind_after_bound / garbage_first_frame_while_busy / single_session_listener)
integration-spawn 219 219
integration-magma-tcp 74 74