docs(net): N-32 P1 台架验收记录 + 验收腿否定结论

P1 真机验收(@build Code=197096 / 基线 +12B,回读 No difference found):
@test PASS;ftp_regression 11/11;Trap23 abort/graceful 各 5/5;
:21 0ms×50 与 100ms×50 各 50/50;:8888 0ms×20 20/20;
:7003 300ms×10 10/10;设备侧 orphan listener 增量 0。

**否定结论(重要,勿重复踩)**:原计划的"抢先 relisten"验收腿在
真机**外部无法构造**——外部唯一可注入形态(A 发完数据立刻 FIN +
立即 B 抢占)下,P1 版与对照组(撤掉 g_client != g_listen 护栏)
**双双 PASS**、设备侧 accept/relisten/disconnect 簿记与 orphan 计数
完全一致 ⇒ 探针无判别力,测不到它声称要测的东西。
机理:真实"抢先"要求 relisten 发生在消费者下一次 recv 之前;对账④
周期 200ms + 去抖,ui_net_sink 轮询 100ms ⇒ 正常时序消费者总先拿到
EOF。只有拖住 uiTask 才可能出现,而那是设备内部状态。
处置:按 Trap 25「不留未证明的复杂度」删除探针;该场景登记为残余
验证项(需设备内注入点,参照 TEST_LATENCY_PROBE 的编译开关模式)。

故 P1 正确性依据 = ① 代码级论证(边沿→电平、置位/清零完备、两处
relisten 补状态门)② 全量回归矩阵不回归。前瞻场景**未真机证明**,
如实标注,不冒充已验收。

台架观测改用 serial-assistant skill(daemon 独占串口、日志即接口,
设备侧簿记 log --grep 取增量);自建 pyserial 客户端必撞
PermissionError(daemon 独占绑定串口)。

⚠ SKIP=doc-links:基线预存在问题(另一在途任务未提交文件),非本次引入。
This commit is contained in:
2026-10-12 13:34:03 +08:00
parent a8442410d1
commit 15bffce52f
@@ -267,16 +267,53 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
- [x] `ui_net_sink.c:563` 加 `g_client != g_listen` 护栏;EOF/错误后不再自行 `bind_listen`
- [x] 新增 `net_listener_armed(fd)`(LISTENING 或 CLOSED+auto_relisten),替换 `ui_net_sink.c:512` 的 `net_can_accept` 门控(MCU + `net_socket_sim.c` 双端实现)
- [x] **超出清单的必要扩展(设计时未预见)**:`net_pending_tick` ③ 断连判据同步改 `CLOSED || session_dead`。不改则 lftpd 的**阻塞** recv 仍会被抢先 relisten 饿死——①要求 ESTABLISHED、③要求 CLOSED,抢先 relisten 后两头落空,只能干等 deadline 超时。判据放宽后,两处 relisten 调用均须补内层 `state == CLOSED` 门:`net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**(U12 同源事故)
- [ ] 验收:新增"抢先 relisten"腿(连上 :7003 → 拖住 uiTask → 断连 → 断言 sink 仍得 EOF **且下一个客户端被服务**)+ 全量回归矩阵(§7.1 那一套)
- [ ] `@test` PASS → 回读校验 → 停下等提交指令
- [x] 全量回归矩阵(§7.1 那一套)—— 2026-10-12 真机全绿,见下「P1 台架验收记录」
- [!] 「抢先 relisten」腿 —— **外部注入不可构造,已放弃**:见下「验收腿的否定结论」
- [x] `@test` PASS → 回读校验(`No difference found`)
**P1 台架验收记录(2026-10-12,`@build` Code=197096 / 基线 197084,+12 B)**
| 腿 | 结果 | 说明 |
|---|---|---|
| `@test` 启动闭环 | **PASS** | 启动锚点齐、无 `[ERR]`、reset cause ×1、7.2 s 到 net ready |
| `ftp_regression` | **11/11** | 含 STOR 32KB —— 「发完即 FIN」链路,`session_dead` 直接作用面 |
| Trap 23 `--check-abort`(RST) | **5/5** | 450 + 5xx 正确回报,无误报 226 |
| Trap 23 `--graceful`(FIN) | **5/5** | 226 正常 |
| `:21` 快速复连 0 ms ×50 | **50/50** | Trap 22 基线不回归 |
| `:21` 快速复连 100 ms ×50 | **50/50** | 同上 |
| `:8888` 0 ms ×20 | **20/20** | 对时服务不回归 |
| `:7003` 300 ms ×10 | **10/10** | B10 原始失败场景(修复前 0/10) |
| 设备侧孤儿对账 `orphan listener` | **0 次** | 探针时段经 serial-assistant `clear` 划界后统计 |
> 台架观测用 **`serial-assistant` skill**(daemon 独占串口、日志即接口)——
> 设备侧簿记(`standalone accept` / `auto-relisten ok` / `DISCONNECTED, releasing` /
> `orphan listener`)直接 `log --grep` 取增量,无需自建串口客户端。
> ⚠ 自建 pyserial 客户端会撞 `PermissionError(拒绝访问)`:daemon 独占绑定串口。
**验收腿的否定结论(重要,勿再重复踩)**:设计文档原计划的新增腿是
「连上 :7003 → 拖住 uiTask 使 relisten 先发生 → 断连 → 断言 sink 仍得 EOF」。
2026-10-12 实现后发现**真机外部无法构造**,且据此写的探针**无判别力**:
- 外部唯一可注入的是「A 发完数据立刻 FIN + 立即 B 抢占」。实测该形态下
**P1 版与对照组(撤掉 `g_client != g_listen` 护栏)双双 PASS**,设备侧
`accept/relisten/disconnect` 簿记与 `orphan` 计数完全一致 → 探针测不到它声称要测的东西。
- 机理:真实「抢先」要求 relisten 发生在**消费者下一次 recv 之前**;而对账④ 周期
200 ms + 去抖(`NET_RECONCILE_STABLE_N`),`ui_net_sink` 轮询 100 ms ——
正常时序下消费者总是先 recv 到 EOF,对账抢不到。**只有 uiTask 被拖住**才可能出现,
而那是设备内部状态。
- 处置:按 Trap 25「不留未证明的复杂度」**删除该探针**;该场景登记为**残余验证项**,
需设备内注入点(参照 `TEST_LATENCY_PROBE` 的编译开关模式)才能在真机复现。
- 因此 P1 的**正确性依据**是:① 代码级论证(判据由边沿改电平、置位/清零完备、
两处 relisten 补状态门);② 上表全量回归矩阵**不回归**;③ 不变量 I 的结构性论证。
「抢先」的前瞻场景**未被真机证明**,如实标注,不冒充已验收。
**P1 落成状态(2026-10-12)**:代码已实现、`@build` 0 错 0 警(`Code=197096`,较基线 +12 B)、
`mem-numbers` 快照已 `--write` 刷新、除下述基线问题外静态门禁全绿。
⚠ **已知基线问题(非本改动引入)**:`doc-links` 在**任何从当前 `v2-on-ui` 切出的工作树**上都会报
2 处失效引用——`RAM使用分析与优化方案.md:277/:585` → `scripts/ram_baseline.tsv`、
`docs/内存管理/RAM预算台账.md`。这两个文件属另一在途任务、尚未提交,主工作树磁盘上有故主线
门禁呈绿(见 `docs/环境与部署/并行任务工作树方案.md` §10.2)。待该任务合入后自动消解,
**不应**由本分支代为提交他人半成品。
2 处失效引用——`RAM使用分析与优化方案.md` 引用了两个**尚未提交**的文件(RAM 基线 `.tsv` 与
RAM 预算台账 `.md`,属另一在途任务)。这两个文件在主工作树磁盘上存在故主线门禁呈绿,
工作树里没有故必报红(机理见 `docs/环境与部署/并行任务工作树方案.md` §10.2)。
待该任务合入后自动消解,**不应**由本分支代为提交他人半成品。
**P2(单一所有者)**
- [ ] 新增 `net_listener_pump()`,与 `net_reconcile_tick` 同节拍,成为**唯一**调 `net_relisten_try` 的监听重建入口