# 流式中段沉默:计量口径与对冲(已批准设计) - 状态: **已批准(2026-09-11 人类拍板 §9 五项,见 §9 决策记录)**;本文件只做设计,不含实现 - 基线: main `a28d944` / 1.3.7(已发布,含单路并发对冲与 `CallStats.generation_ms/hedges/hedge_won`) - 输入: `findings/2026-09-11-streaming-stall-hedging-research.md`(业界范式与来源,下称"调研",引用处标注 §x.y);下游 Terminal-Bench 实测 4407 次成功调用遥测(需求来源);1.3.7 设计 `designs/2026-09-10-24-hedged-requests-design.md`;远端 issue #27(P1)/#28(P2) - 关联: ARCH §6.4 取消语义、§7.2 重试;既有流式看门狗 `streaming.py`;1.3.7 对冲机制(本设计直接站在其编排与记账语义上) ## 1. 背景与问题陈述 两个问题都是**口径/能力缺口,不是实现 bug**:1.3.7 行为与其设计文档(§4.5 H8、对冲触发判据)完全一致,但下游实测暴露了设计时未覆盖的病态形态。 ### P1:中段沉默计入 `generation_ms`,"裸生成时间"名不副实(issue #27) `CallStats.generation_ms` 的口径是赢家 transport 调用的完整墙钟:`middleware/retry.py:~335` `gen_started` 紧贴 `transport.complete()` 调用前,`:~346` sink 在返回后立即记录。对流式,`complete()` 返回 = 整个流读完,故中段沉默全部计入——这是 1.3.7 设计明写的口径,但"裸生成时间"这个名字误导了下游把它当活跃生成时间用。 下游实测(Terminal-Bench,4407 次成功调用): | 指标 | 数值 | 含义 | | --- | --- | --- | | ttft 中位 | 1.63s(仅 3 次 >20s) | 首 token 几乎从不慢 | | 慢调用 `generation_ms` 中位 | 69.4s | 墙钟长尾严重 | | 其中 `max_inter_token_ms` 中位 | 54.9s | 长尾的主体是**一次中段沉默** | | 沉默占全部模型时间 | 71.3% | "在等不在生成"是主要成本 | | 沉默与输出/输入量相关性 | r = 0.05 / −0.06 | 沉默与生成量无关 | | reasoning_tokens 上报次数 | 129 / 4407 | 沉默**不是**模型在思考,是渠道/服务端排队(调研 §A4:prefill/decode 互踩是结构性现象) | 结论:下游需要一个"扣除沉默段的活跃生成时间"口径,且带归因(沉默几次、共多长),不是只给一个净数(Gregg off-CPU 分解原则,调研 §B2)。 ### P2:中段沉默无对冲,现有两道闸门都接不住(issue #28) | 既有机制 | 证据 | 为何接不住中段沉默 | | --- | --- | --- | | 首 token 对冲 `HEDGE__AFTER_S` | `middleware/retry.py:542-562` `_past_hedge_window` 返回 `not primary.done() and not first_token.is_set()`;非对冲模式 `__call__` 传 `first_token_event=None` 根本不观测 | 首 token 到达后对冲窗**永不打开**;实测 ttft 中位 1.63s,`AFTER_S=20` 对流式调用恒不触发 | | 看门狗 `INTER_TOKEN_TIMEOUT_S` | `streaming.py`(budget = ttft_s 首 chunk / inter_token_s 后续);`openai_compat.py:573` `inter_token_s = source.inter_token_timeout_s or source.timeout_s`;配置 `config.py:46` | 能掐中段沉默,但**掐掉即丢弃全部已生成内容**、从头重试;重试可能再卡;每步成本 = 一次完整重新生成 | 调研结论(§A3):业界对中段沉默的落地形态收敛为"间隔看门狗 → 抛错 → 重试",**"中段沉默触发并发副本"在任何主流网关/库中均无公开先例**——本设计的 P2 方案 2B 属超业界现状能力,须诚实标注。 ## 2. 业界范式摘要(压缩自调研,不重查网络) | 范式 | 出处(调研节) | 对本设计的对应 | | --- | --- | --- | | 中段沉默对冲**无公开先例**;主流 = 看门狗 → Transient → 重试 | 调研 §A3(LangChain `stream_chunk_timeout` 默认 120s 已合并;Codex CLI 静默 300s 整路重试;LiteLLM 提案默认关) | P2 的 2A = 业界基线;2B 若做须自证语义 | | 沉默扣除唯一先例 pi-tps:gap ≥500ms 判 stall,**减所有超阈值段**,连续停顿合并一次,样本不足返回 null | 调研 §B3 | P1 的 `stall_ms/stall_count` 口径直接照此;"只减最大一段"无任何先例,否决 | | Gregg off-CPU:墙钟分解须**成对报告 + 归因**,只报净数无法行动 | 调研 §B2 | 1B' 下由 `generation_ms`(净)+ `stall_ms`/`stall_count` 成对报告满足,墙钟 = `generation_ms + stall_ms` 可还原 | | NVIDIA TTFT/ITL/E2EL:排队计入 TTFT,基准口径**不扣除**沉默 | 调研 §B1 | 扣沉默是库自造口径,必须显式声明、不与基准口径混写 | | vLLM `generation_time_ms` 排除条款:token 数 <2 时间隔类字段 null;`n>1` 无法归因即抑制 | 调研 §B3 | 三新字段的 None 条款照抄:内容 chunk <2 / 非流式 → None | | Envoy/gRPC:判赢在**响应头**即 commit 不回头;per_try_timeout 只在响应开始前有效,中段停顿不重新对冲 | 调研 §A2 | P2 赢家裁定与"不回头"语义的参照;约束未来流式透传(§5.2) | 另:配置命名先例对照见调研 §3.3,本设计键名与其对齐。 ## 3. 共享观测原语:内容 chunk 单调时间戳序列 P1(计量)与 P2(中段触发)共用同一份底层观测,避免两套计时逻辑漂移: | 项 | 设计 | | --- | --- | | 记录点 | transport 层(`openai_compat.py:605` 现算 `max_gap` 处已逐 chunk 循环,时间戳序列在此追加记录,增量成本近零) | | 记录内容 | 每个**内容 chunk** 的单调钟时间戳;空 chunk / SSE keepalive 注释**不计时**(LangChain 语义,调研 §A3——keepalive 字节会重置 httpx read timeout,对"有字节无内容"的卡顿失效,我们的序列天然免疫) | | 派生原则 | 一切计量(`stall_ms` 等)与中段触发信号(§5.2)都从此序列/其增量事件派生,**不在热路径做判定**(调研 §3.2-1);热路径只做"记一个时间戳 + set 一个 Event" | | chunk ≠ token | chunk 边界是传输/解析缓冲产物,非 token 边界(vLLM #27485 陷阱,调研 §B1);一切间隔类口径按 chunk 声明,不假装是 ITL | P2 触发所需的活体信号由此派生:transport 每记一个内容 chunk 即刷新一个 `asyncio.Event`(语义 = "自上一内容 chunk 以来未再沉默"),编排侧以"等该事件超时"实现 stall 计时器——与 1.3.7 首 token 事件同构,零新机制类别。 ## 4. P1 方案:沉默计量口径(1B' 已批准) ### 4.1 方案 1A:不改库,下游自算 —— 已否决 | 项 | 内容 | | --- | --- | | 语义 | 下游用既有 `LLMResponse.max_inter_token_ms`(`types.py:417-418`)自算 `generation_ms − max_inter_token_ms`,即**只减最大一段**;库仅澄清文档 | | 改动面 | README/CHANGELOG 口径澄清,零代码 | | 优点 | 零实现、零契约变更;`max_inter_token_ms` 已在遥测表(`telemetry/schema.py:43/85`),存量数据即可分析(本次实测正是用它定位的病) | | 缺点 | 多段沉默场景**系统性高估**活跃时间(调研 §B3:只减最大一段无任何先例);口径责任散落每个下游,各家算法漂移后跨项目不可比;无 `stall_count`,违反 Gregg 归因原则 | **否决理由(人类裁决,2026-09-11)**:口径必须库内收敛,不得散落各下游自算。 ### 4.2 方案 1B'(已批准):`generation_ms` 口径重定义 人类原话:"如果 generation_ms 与 max_inter_token_ms 本质是一样的那能不能改一下 generation_ms,减去所有大于 stall_ms 的中段沉默得到一个纯净的时间"。PM 澄清两字段本质不同(完整墙钟 vs 单次最大沉默段)后,人类意图收敛为:原草案 1B 的 `active_generation_ms` 净口径**直接写进 `generation_ms` 本身**,不新增派生字段。 | 项 | 内容 | | --- | --- | | 语义 | `generation_ms` 重定义为**净生成时间** = 赢家 transport 墙钟 − Σ(所有 > 阈值的相邻**内容 chunk** 沉默,pi-tps 式口径);`CallStats` 新增两归因字段(默认 None):`stall_ms: int \| None`(沉默总额)、`stall_count: int \| None`(超阈值段数,连续停顿合并一次);**不新增 `active_generation_ms`**——`generation_ms` 本身就是净口径 | | 归因与还原 | 墙钟 = `generation_ms + stall_ms`;净数与沉默总额/次数成对报告,Gregg 成对报告原则(调研 §B2)在净口径下依然满足 | | 排除条款(照 vLLM,与原案一致) | 非流式 / 内容 chunk <2 → `stall_ms`/`stall_count` 为 None 且 `generation_ms` **不退减**(= 原墙钟);对冲输家的沉默**不进**赢家快照(输家数据留在其 attempt 遥测行,可 join 还原) | | 阈值 | 新配置 `{SCOPE}__STREAM__STALL_THRESHOLD_MS`,**默认 1000ms**(§9-③ 拍板);人类附加要求"需要是一个可以调整的参数,对外提供调整接口"——调整接口 = 既有装配路径(env 键 / `GatewaySettings` 字段 / `GatewayClient` 构造 kwarg 三层均可调),**不提供 per-call 覆盖**(§8 非目标 F);**不做自适应**(防与对冲阈值耦出反馈环,调研 §3.2-3) | | 遥测 | 遥测表新增 `stall_ms`/`stall_count` 两列(§9-⑤ 拍板):`telemetry/schema.py` PG/SQLite 各一次可空 DDL + recorder/`middleware/telemetry.py` 透传;`generation_ms` **仍不进遥测列**(YAGNI——下游要墙钟用 `latency_ms` 或 `generation_ms + stall_ms` 还原) | | 改动面 | `types.py:417-418` 邻域(`CallStats` 增两字段);`ports.py` `TransportResult` 增 `stall_ms`/`stall_count: float \| None` 默认 None;`openai_compat.py:605` 邻域从 chunk 时间戳序列计算两值(沿用 §3 原语);`middleware/retry.py:~335/~346` 邻域(赢家记账改为 `record_generation`(净值) + context 存 stall 两值进快照,口径与 H8 同款);`telemetry/schema.py:43/85` 邻域(PG/SQLite 各一次可空 DDL)+ recorder + `middleware/telemetry.py` 透传;`config.py:46` 邻域(新键 + loader);`client.py` 构造透传 | #### 契约变更的正当性与披露 语义突变在一般情形下违反"只增不删不改名"精神(§4.3 拒绝理由),本次由人类以"窗口期最小代价"裁决放行,依据: | 依据 | 事实 | | --- | --- | | 字段极新 | `generation_ms` 是 1.3.7(2026-09-10)引入的新字段,发布仅 2 天 | | 消费者唯一 | 唯一消费者是提出本需求的下游 Terminal-Bench;三参考项目均未消费该字段 | | 名实自相矛盾 | 字段名"裸生成时间"原本承诺的就是净口径,现实现(墙钟)与名字矛盾,本次重定义是**兑现名字**而非推翻承诺 | | 无账本断裂 | 遥测 DB 从未存过 `generation_ms`(已验证 `telemetry/schema.py` 与 `middleware/telemetry.py` 均无此字段),库内无历史账本纵向比较断裂 | 披露义务:CHANGELOG 设独立"行为变更"段显著标注 1.3.7→1.3.8 口径变化 + README 口径表更新 + 本设计存档。 ### 4.3 方案 1C:改 `generation_ms` 口径为扣沉默 —— 原拒绝理由被人类裁决覆盖(留档) | 理由 | 依据 | | --- | --- | | 已发布契约突变:1.3.7 设计 §4.5 H8 明写墙钟口径并已被下游消费 | 铁律"字段只增不删不改名";语义突变等同改名 | | 历史账本不可比:遥测 DB 存量 `generation_ms` 全是墙钟,口径切换后纵向比较断裂 | 遥测必录铁律的对账前提 | | NVIDIA/vLLM 基准口径均不扣沉默(调研 §B1),库把墙钟改掉会与业界定义脱钩 | 口径分层应靠新增字段,不是靠改旧字段 | **人类裁决(2026-09-11)**:上述理由在一般情形下成立,但人类以"窗口期最小代价"覆盖之(字段发布仅 2 天、消费者唯一、遥测无账本,见 §4.2 正当性表)——净口径以 1B' 形式落地;原 1B 三字段并存案随之被取代。 ### 4.4 P1 对比与决策 | 维度 | 1A 下游自算(已否决) | 1B 原三字段案(已被取代) | **1B' 口径重定义(已批准)** | 1C 改原口径 | | --- | --- | --- | --- | --- | | 多段沉默准确性 | 差(系统性高估) | 好(减所有超阈段) | 好(减所有超阈段) | 好 | | 归因能力 | 无(只有最大段) | 有(总量 + 次数) | 有(`stall_ms`/`stall_count` 成对) | 无(只有净数) | | 口径唯一性 | 散落各下游 | 库内收敛 | 库内收敛 | 库内但破坏存量 | | 契约风险 | 零 | 只增字段 + 遥测 DDL | 语义突变,经人类以"窗口期最小代价"裁决放行(§4.2 正当性) | **违反只增不删精神,拒绝** | | 实现量 | 零 | 中(五文件邻域) | 中(六文件邻域,含端口) | 小但不可接受 | **决策:1B'(§9-① 拍板)**;1A 因"口径必须库内收敛"被人类否决;1B 原案被 1B' 取代;1C 拒绝理由被人类裁决覆盖(留档 §4.3)。 ## 5. P2 备选方案:中段沉默的应对 ### 5.1 方案 2A:不做中段对冲,看门狗链 + 文档 | 项 | 内容 | | --- | --- | | 语义 | 维持业界主流:中段沉默超 `INTER_TOKEN_TIMEOUT_S` → 掐流 → Transient → 既有重试/换源链;库补文档与配置指引(各源默认值、与 `timeout_s` 的回落关系 `openai_compat.py:573`、建议按源沉默分布取值) | | 改动面 | README/wiki 配置指引,零代码 | | 优点 | 零实现零契约变更;有 LangChain(默认 120s)与 Codex CLI(300s)生产先例(调研 §A3);掐死即 Transient,四分类不新增语义 | | 缺点 | 掐掉即**丢弃全部已生成内容**、从头重试;重试可能再卡同一类排队;每步成本 = 一次完整重新生成;实测沉默 >20s 占 19.0%,这条链会频繁点火 | ### 5.2 方案 2B(已批准):中段沉默对冲 | 项 | 内容 | | --- | --- | | 触发 | 新配置 `{SCOPE}__HEDGE__STALL_AFTER_S`(**独立于 `HEDGE__AFTER_S` 生效**,§9-④ 拍板"否,独立生效";调研 §3.3 命名);语义:主路**相邻内容 chunk 间隔**超此阈值 → 触发。机制 = §3 原语派生的"内容 chunk Event 等待超时",与首 token 事件同构;`_past_hedge_window`(`retry.py:542-562`)扩为"首 token 未至走 AFTER_S,首 token 已至走 STALL_AFTER_S" | | 对冲执行 | 触发后主路**不取消**;并发异源副路走**完整准入 + 熔断**,无配额静默放弃(1.3.7 设计 §4.3 语义原样复用);`FIRST_COMPLETED` 赛跑;输家取消,est 保留不喂熔断、遥测 `hedge_cancelled`(1.3.7 设计 §4.4/§4.5 全部复用,零新记账语义) | | 赢家计量 | 赢家路为准:`generation_ms`(1B' 净口径)/`stall_ms`/`stall_count`(1B' 同批落地,§9-①)全部取赢家路;`hedge_won` 语义不变(赢家是否对冲路) | | 无拼接污染 | `chat()` 是**库内聚合流**——调用方拿完整 `LLMResponse`,流在库内消费完毕才返回;中段切换赢家时两路内容从未交付调用方,**无拼接问题**。这与调研 §3.1 基于"流透传"假设的 commit 语义不同。**约束明写**:未来若做流式透传,commit 点须重审——一旦任一路首 chunk 已透传下游,即按 Envoy"判赢不回头"锁定该路,中段对冲对该次调用永久关闭(§8 非目标:本版不做透传) | | 与看门狗的交互 | 两参数语义独立:若主路沉默超 `inter_token_timeout_s` 被看门狗掐死,**对冲路已在跑 → 主路之死不算失败,等副路**;副路未起 → 照旧 Transient 重试。装配守卫:`stall_after_s ≥ inter_token_timeout_s` 时 warning(对冲窗永不开,形同虚设);建议 `stall_after_s < inter_token_timeout_s` | | 成本披露 | 实测沉默 >20s 占 **19.0%**——触发率远高于首 token 对冲(ttft 仅 3/4407 超 20s);每触发一次最多 +1 并发请求 + 一份 est 预扣滞留(1.3.7 §4.4 口径);**默认必须关闭**,文档明写"对冲 = 用配额换延迟" | | 改动面 | `retry.py:542-562` 邻域(双阈值触发窗 + 与看门狗掐死的汇合规则);`ports.py` + `openai_compat.py:569-575/605` 邻域(`complete` 加内容 chunk Event 参数——公共端口签名第二次变更,须进批准项);`config.py` 邻域(新键 + 交叉守卫);`client.py` 透传;遥测复用 `hedge_cancelled`,零新列 | **方案 2C 不单列,作为 2B 的裁定细节**:看门狗掐死瞬间原子地并发补一路(掐与对冲合一)。评估:省一个参数但把"观测阈值"与"掐死阈值"耦合,掐死时副路从零开始(TTFT 全付),总延迟 = 沉默阈值 + 完整生成,严格劣于 2B 的"沉默中提前并发";且与 2A 链叠加时行为难推理。否决,仅留档。 ### 5.3 P2 对比与决策 | 维度 | 2A 看门狗链 + 文档 | **2B 中段沉默对冲(已批准)** | | --- | --- | --- | | 已生成内容 | 掐掉全丢 | 主路成果保留到最后一刻,输家才弃 | | 长尾延迟 | 沉默阈值 + 完整重试耗时 | ≈ 沉默阈值 + min(两路剩余) | | 业界先例 | 有(LangChain/Codex CLI) | **无公开先例**(调研 §A3),属超业界现状能力 | | 成本 | 每次沉默 = 一次完整重新生成 | 触发率 19% 量级 × 一次额外请求 | | 公共面变更 | 零 | 端口签名再变 + 新配置键 | | 实现量 | 零(文档) | 大(但编排/记账/遥测全复用 1.3.7) | **决策:2B(§9-② 拍板,独立于 `HEDGE__AFTER_S` 生效)**。诚实标注保留:它超出业界现状能力(调研 §A3),人类已明确接受;2A 链(看门狗 → Transient → 重试)作为不启用对冲时的默认行为继续存在,文档指引义务不变;P1(1B')同批落地后下游可直接量化沉默代价。 ## 6. 配置汇总(新增两键,默认关闭/合理默认) | 键 | 值域 | 缺省 | 守卫与既有键关系 | | --- | --- | --- | --- | | `{SCOPE}__STREAM__STALL_THRESHOLD_MS` | 正整数毫秒 | **1000**(§9-③ 拍板,可调) | 仅计量口径,不改任何行为;不设 = 用缺省;调整接口 = env 键 / `GatewaySettings` 字段 / `GatewayClient` 构造 kwarg 三层,无 per-call 覆盖;命名对齐调研 §3.3(pi-tps 500ms 先例,但场景不同) | | `{SCOPE}__HEDGE__STALL_AFTER_S` | 有限正数秒(复用 `ensure_call_deadline` 同款校验) | **未设 = 关闭** | 独立于 `HEDGE__AFTER_S`;`stall_after_s ≥ inter_token_timeout_s` → 装配 warning(§5.2);单源 scope 设阈值 → warning 允许(1.3.7 §5 同款);与 `call_deadline_s` 组合守卫沿用 1.3.7 §5/§6 | `STREAM` 段须加入 `_RESERVED_SEGMENTS`(`config.py:57` 邻域,1.3.7 为 `HEDGE` 做过同款);3 段键天然跳过 `_load_sources`(`config.py:401-407`)。装配路径与 1.3.7 对冲同款:`GatewaySettings` 字段 + `__post_init__` 守卫 + `GatewayClient.__init__` keyword-only 入口即校;不提供 per-call 覆盖(阈值是源/渠道特性,1.3.7 §5 同款判据)。 ## 7. 与既有契约的关系 | 契约 | 处理 | | --- | --- | | `CallStats`/`LLMResponse` 只增不删不改名 | 两新字段(`stall_ms`/`stall_count`)全 `int \| None`、默认 None;1.3.7 及以前构造的位置调用不炸(1.3.7 §4.5 同款论证);`generation_ms` 语义重定义为净口径(**已批准例外**,§4.2 正当性与披露),`max_inter_token_ms` 原语义一字不动 | | 遥测迁移 | `stall_ms`/`stall_count` 两列可空,PG/SQLite 各一次 DDL;旧行 null;迁移纪律照 issue #12/#13/#15 教训与 1.3.7 §4.5 备选调先例;**加列已拍板**(§9-⑤) | | 公共端口签名 | `Transport.complete` 随 2B 第二次加参(内容 chunk Event,必填 kw),与 1.3.7 `first_token_event` 同款约定;另 `TransportResult` 随 1B' 增 `stall_ms`/`stall_count`(默认 None) | | 文档登记义务 | README 能力表 + CHANGELOG + ARCHITECTURE.md(口径段与对冲触发段)三处同步,发版按 CLAUDE.md §4.4.1 清单执行 | | 版本归口 | **1.3.8 确认**(人类拍板 P1 + P2 同批发);P1 含 `generation_ms` 口径变更,披露义务照 §4.2;P2 纯增量(新键 + 默认关闭行为) | ## 8. 非目标 | # | 不做 | 理由 | | --- | --- | --- | | A | 不做墙钟/净双口径并存字段(如 `active_generation_ms`) | 1B' 拍板净口径直接写进 `generation_ms`;墙钟由 `generation_ms + stall_ms` 还原(§4.2),并存字段徒增口径漂移面 | | B | 不做自适应分位对冲阈值(bhope/hedge DDSketch) | 列未来方向:per-source 滚动分布跨进程要进 Redis,且与沉默计量阈值耦合有反馈环风险(调研 §3.2-3);本版绝对阈值已够用 | | C | 不做流式透传 | 透传引入 commit 点与"判赢不回头"约束(§5.2),是独立大设计 | | D | 不做内容拼接/续写 | 增量文本不可拼接,缝合即结果污染(调研 §3.1) | | E | embedding/OCR 不动 | 无流式中段概念,沿用 1.3.7 非目标 A | | F | 不做 per-call 沉默阈值覆盖 | 人类拍板(§9-③):调整接口走装配层(env 键 / `GatewaySettings` 字段 / `GatewayClient` 构造 kwarg 三层),per-call 覆盖超本版范围 | ## 9. 决策记录(2026-09-11 全部拍板) | # | 决策点 | 拍板结果 | 人类原话 / 选项 | | --- | --- | --- | --- | | ① | P1 选型 | **1B'**:`generation_ms` 口径重定义为净时间(原 1B 的 `active_generation_ms` 口径直接写进 `generation_ms` 本身,不新增派生字段);新增 `stall_ms`/`stall_count` 归因字段 | "如果 generation_ms 与 max_inter_token_ms 本质是一样的那能不能改一下 generation_ms,减去所有大于 stall_ms 的中段沉默得到一个纯净的时间"(PM 澄清两字段本质不同后,意图收敛为口径重定义) | | ② | P2 选型 | **2B**:中段沉默对冲 | 选定 2B(已明确接受"超业界现状能力"标注) | | ③ | `STREAM__STALL_THRESHOLD_MS` 默认值 | **1000ms,可调** | "需要是一个可以调整的参数,对外提供调整接口"——接口 = 装配三层(env 键 / `GatewaySettings` 字段 / `GatewayClient` 构造 kwarg),不提供 per-call 覆盖 | | ④ | 中段对冲是否以 `HEDGE__AFTER_S` 已启用为前提 | **否,独立生效** | 原话"否,独立生效" | | ⑤ | 遥测是否加 `stall_ms`/`stall_count` 两列 | **加** | 选定"加"(归因进 DB,可对账) | 另(§9 之外拍板):P1 + P2 **同批发 1.3.8**(§7)。 ## 10. 自查记录 - 行数 210 ≤400;无超 15 行代码块(全文无代码块,仅表格与伪语义表述)。 - 方案改动面均列具体文件:行号(1B'、2B);行号沿用 PM 已验证事实,未重新验证。 - 调研结论引用处标注(调研 §x.y);未重查网络。 - 2026-09-11 批准确认:§9 五项 + 同批发 1.3.8 全部落地本文;1A/1C 保留为否决留档,1B 原案标注被 1B' 取代。