ISSUE-037: Phase 1 守恒模型与拆分模式的设计层限制 (Phase 2 重做)
发现日期:2026-06-05
状态:已修复 (Phase 2A 完成:F001 跨边界 cbfc_residue + F002 重做拆分用标准 mod + must_use 防御)
修复日期:2026-06-05
类型:建模误差 + 实现 bug (代码本身正确,但探测能力 / 拆分模式与 plan 设计意图有偏差)
影响范围:G5 测试加固 Phase 1 完成后的两类已知限制。不影响当前代码功能,仅影响 Phase 1 守恒断言层的探测覆盖范围与代码组织品质——Phase 2 plan 必须吸收。
问题现象
来自 Phase 1 完成后 in-flow code-review (opus, 2026-06-05) 报告的两个非阻塞 finding:
1. Credit 守恒等式由构造保证 (F001,原 P0)
rc_link_tx.rs Task 1 守恒断言 credit_imbalance() = credits_issued - credits_consumed - sum(credits.values()) 设计为"held 派生不维护字段",初衷防漏插桩——任何对 credits map 的新写入路径都会让 held 变化,从而让守恒等式破坏。
实际效果:credits map 字段是 private,所有 mutation 都强制经过 3 个现有更新点(init/return/consume),每个更新点同时更新 credits 和 issued/consumed。守恒等式由构造满足:
$\sum credits = issued - consumed \quad \text{恒成立}$
即使 try_arbitrate 整体逻辑有 RX 端未返还 credit 类型的 "CBFC 黑洞" bug,只要走的还是那三个 TX 内部更新点,守恒等式仍为 0。
真实探测覆盖:当前实现只能 detect"未来开发者新增第 4 个 credit 路径但忘了同步更新 issued/consumed"——即 TX 内部簿记错位。不能 detect Task 1 立项时想抓的真 CBFC 黑洞 (例如 RX 路径漏 emit CreditReturn)。
2. include! 宏注入子文件 (F002,原 P1)
rc_link_tx.rs:536 用 include!("rc_link_tx_diag.inc.rs") 把第二个 impl RcLinkTx 块从主文件注入。Task 8 subagent 选这种做法的理由:用 mod rc_link_tx_diag; 需要把 RcLinkTx 内部字段改 pub(super),会泄露 module 边界。
问题:include!是 Rust 反模式:
- 文件无法独立编译,IDE 跳转/格式化/lint 行为退化
cargo check报错定位指向 include!行而非真实位置- 仅做"行数搬家"——同一编译单元,私有字段访问仍跨文件,没有真正的 module 语义边界
是 worst-of-both:既没真拆分语义边界,又付出 IDE/工具链体验代价。
调查过程
- [in-flow code-review, opus, 2026-06-05] 独立 reviewer 指出 F001 Task 1 守恒等式由构造保证,F002 include!反模式两个问题
- [查代码] F001:
rc_link_tx.rs:393-398credit_imbalance 实现 +rc_link_credit.rs三个更新点确认 reviewer 论断正确 - [查代码] F002:
rc_link_tx.rs:536include!调用 +rc_link_tx_diag.inc.rs内含两个 impl 块,确认是 module 边界绕过
根因分析
F001 根因
Task 1 plan 设计时假设"派生 held 自动 catch 漏插桩"——但忽略了 credits map 是 private 这一前提。所有 mutation 必经现有 3 个 helper,守恒由构造恒成立。真正的 CBFC 黑洞发生在跨边界路径 (TX 扣 credit 但 RX 漏返还),Phase 1 没有跨边界对账机制。
F002 根因
Task 8 拆分时未充分讨论 Rust module 系统的 abstraction trade-off。include! 看起来是"最少改动"方案 (不动字段可见性),但牺牲了模块系统的全部价值。
Spec / 文档依据
- Phase 1 plan(已归档)Task 1 + Task 8 拆分方案
- code-review report (本 ISSUE 创建者通过 in-flow code-review skill 触发)
- Rust 社区惯例:
include!主要用于 build-script 生成代码 (OUT_DIR模式),不用于源码拆分
解决方案 (Phase 2 plan 必须覆盖)
F001 修复方向
采用方案:Phase 2 plan 加独立 RX 对账层。例如:
- 在 RX 侧维护
credits_returned_total计数 (每次 CreditReturn 发出时 ++) - 在全局守恒断言里加
TX.credits_consumed == RX.credits_returned_total + TX.credits_held_outstanding跨边界对账 - 或:TX 维护
sent_bytes/credit_size比例 推导独立 consumed 路径,与现有 consumed 计数对账
F002 修复方向
采用方案:Phase 2 plan 重做 rc_link_tx 拆分。两选项:
- A:把内部字段改
pub(super),用mod rc_link_tx_diag;正常子模块,接受微小可见性 leak - B:把 diag 方法收回 rc_link_tx.rs,接受适度超 600 行 (在
code-style.md加项目级例外条款给特定文件)
验证 (Phase 2A 完成实测)
F001 跨边界 Credit 对账:
- 新增 stat
multi_chip.cbfc_residue = tx_consumed_total - rx_returned_total - 收尾时严格 == 0 (守恒等式)
- 3 单测验证:
test_cbfc_residue_normal_run_converges_to_zero:正常路径 PASStest_cbfc_residue_detects_rx_omission: mock RX 漏返还 → 守恒断言 panic ✓test_cbfc_residue_detects_tx_undercounting: mock TX 少计数 → 守恒断言 panic ✓
- 3 mutation test 验证 RX 3 个 CreditReturn 决定点 (accept/nak/dup) 漏插桩都被探测到 ✓
- 全 413 个 cargo test 在 Phase 2A 完成后均未触发 cbfc_residue 真 bug — 说明 Phase 1 by-construction 守恒在 G5 当前实现下没掩盖隐藏 CBFC 黑洞
F002 重做拆分:
rc_link_tx_diag.inc.rs删除,新rc_link_tx_diag.rs作为tier6子模块用标准mod rc_link_tx_diag;引入- 7 个字段改
pub(super)(不泛 pub),接口签名不变 - bit-identical 验证通过:406 baseline 测试全保留,集合无差异
- 覆盖率 51% → 94% (远高于 ≥85% 目标),整体覆盖率 80.53% → 81.27% (微升不回退)
#[must_use] 防御:
- 7 个方法加 must_use:
paxi::{submit, on_ack, on_credit_return, on_tx_done}+RcLinkTx::{try_arbitrate, on_tx_done, submit_frame} - 编译期 warning 收集:生产代码 0 warning (ISSUE-036 修复后生产事件链 100% schedule 正确)
- 测试模块 52 warning 通过模块级
#![allow(unused_must_use)]+ SAFETY 注释处理 (测试无 kernel, fire-and-forget 合理)
修复后状态 (Phase 2A 完成)
| 维度 | Phase 1 | Phase 2A |
|---|---|---|
| cargo test | 377/0/3 (修复 ISSUE-036 后) | 413/0/4 (+36 新测试) |
| 整体覆盖率 (regions) | 80.53% | 81.27% |
| 整体覆盖率 (lines) | 83.88% | 84.54% |
| rc_link_tx_diag 覆盖率 | 51% (反模式) | 94% (标准 mod + inline 单测) |
| Credit 守恒 detect 能力 | 由构造保证 (不能 detect 真黑洞) | 跨边界对账 detect RX 漏返还 / TX 少计数 / 静默丢帧 |
| ISSUE-034/036 类回归编译期防御 | 无 | 7 方法 #[must_use] |
遗留问题
- 守恒探测器的 mutation testing 机制未通用化:仅 Phase 2A Task 1 反例测试一次性出现,未沉淀到 testing-conventions。Phase 2B 可考虑写入 docs/issues 或新建测试公约文档。
- Phase 2B 还需补的部分:P1 缺口 (Switch 优先级 / SwitchTick 自驱动) + P2 缺口 (诊断 stat 字段) + 蜕变 MR + proptest + golden + CI 三级分层