v2-on-ui #4
Reference in New Issue
Block a user
Delete Branch "v2-on-ui"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
改动的代码合并到主分支
- README:新增"实施状态"表(9ac9879/ae17d36/4c0a0af/b8c6a3d);§2 逐机制 裁决表补"✅ 已实施"与关键坑位索引 - 实验方案:新增 E10 —— 阶段 0/1 转正验证全记录,含阶段 1 首版台架失败的 根因链(armed 漏放行 ESTABLISHED → i104 整场饿死 18/18 FAIL → 修复后 18/18 PASS、127s→8.3s)、M3 无辜旁证、time_sync 连续 6 轮实测、 两处实施偏差(lftpd 未走 listener_try;阶段 0 提交合并) - 实施方案:阶段 0/§2、阶段 1/§3 标注完成与验证判据;P0 转正标注 - 盘点:F-NET-2/3/4/13/14 补实施结论;⚠ F-NET-4 补"门控迁移必须给 net_listener_armed 补 ESTABLISHED 放行"的坑位说明;net_listener_try 调用点计数 5→4;§2/§4 的缺口表述改闭环状态 - 顺带修正两条既有 doc-links 违规(实验方案 §4.4 → 实施方案 §4 第 4 项; 盘点 §5.29 → docs/芯片手册/CH395F.md §5.29),该 4 份文档 doc-links 现 Passed 无代码改动(纯文档)。实施方案 docs/网络/N34-自愈审计/实施方案.md §4 五原语,形态按 E2/E3/E4 定案(实验方案.md): 1. 类型化状态回读 + 每 Socket 缓存(open=0 时 tcp_state 哨兵 0xFF;sn>7 边界返 {00,FF} 计 fault); 2. 事件护栏 ch395f_sock_events:只读本 Socket SINT,全局寄存器 唯一读者=net_poll(F-NET-7 契约); 3. ch395f_sock_reset_begin/poll 非阻塞状态机:读空 RX -> CLOSE -> 排空事件(quiescent 2 轮) -> 轮询 byte0=00H;GRACEFUL/ QUARANTINE 最短等待 100/400ms(Trap 23);WAIT_CLOSE 内置 LISTEN 态二段 CLOSE 补发(TC1502b 实证 ~180ms 落定); 4. ch395f_cmd_done 状态回读判据(E3:GET_CMD_STATUS 对 OPEN/LISTEN/CLOSE 无判别力); 5. 链路态缓存 ch395f_glob_event_feed + link_up/变化计数 (B9 判据 PHY!=DISCONN)。 Phase 15 板测(硬件层模式,Socket 7,无需 PC):20/20 PASS, 含二段关闭语义断言(一段 05/00、二段 byte0=00)。 RAM +56B < 64B(g_sock_state 16B + g_rst 32B + 链路/fault 8B); 产品 Code 与阶段 2 前逐字节同 209780(无调用者被链接器裁剪)。 @test PASS;relisten_stress 20/20、n30 3/3 零回归。 RAM 台账同步:RW_IRAM1 97952B 74.7% / RW_CCM 57376B 87.9%。- BSD_Socket_API_使用指南:五层自愈→四层(合一 reconciler 形态①~⑤+U11, 分级去抖②1/③④⑤2/①4轮+1s冷却);新增 net_drop_conn 小节(拆会话保 监听/幂等/EOF 不得调);变更记录追加 2026-10-13 行 - BSD_Socket_API_函数调用关系:pump 节点删除、net_drop_conn 链路新增、 驱动接口表补 sock_reset/cmd_done/缓存行、常量清单更正 - 网络Socket监听生命周期设计说明:新增 §10.10 合并说明,历史文本保留+注记 - Net层阻塞模型架构设计:主循环 3 tick 口径 + 附录 B 追加 - RAM使用分析与优化方案:netTask 链尾改 retry_tick→relisten(1944 B 不变) - CH395F_Trap_Records:Trap 07 补充(close 无 RST:对端内核存活不 close → FIN_WAIT_2 长尾,兜底 E13);Trap 09 补充(LISTEN 态二段关闭) - 实施方案 §5:项1/2/3/5 ✅、项4 受 E6 暂缓、§5.1 E-02 收口、§5.2 延后项 - 实验方案:E-02 收口台架小节(FIN_WAIT_2/TIME_WAIT ~150ms/双 PASS) - N34 README 裁决行更正;Test_Guide TC-NET-1101 pump→reconciler 形态③④ 历史变更记录与历史事故描述一律原文保留、只追加;check_doc_links --sections-strict 对改动文件 0 问题;乱码 0、无 BOM 变化。- 登记簿删 N-32 行:P1(`session_dead` 电平)/P2(单一重建所有者)均已 实施转正;pump 于 N34 阶段 3 并入 `net_reconcile_tick` 形态③④ - 结论沉淀核对齐备:设计文档 §10(已执行)+ D1~D10、N34 实施方案阶段 0~3、 陷阱记录 B10 行(✅ 已修 + `net_drop_conn` 根治出口) - 设计文档勾掉两条 N-32 时代遗留清单项:U11~U15 实际落点=N34 机制盘点 + E6 观察项;B10 行 2026-10-13 复核在位 - N34 README 提交表补终笔 `925058a` 行 - 正文其余 N-32 引用均为架构溯源名(pump/不变量 I),不动 - 门禁:`check_doc_links --all` 149 文档通过;ci_local L0+L0.5 全绿现象:每轮 agent 会话在 .dsh/ 根目录留下草稿(提交信息草稿、门禁输出、台架 日志、一次性改名脚本、故障注入 patch),全部以 ?? 挂在 git status 里,混在 真改动中间。本次清掉 17 个:196 KB 门禁输出、台架日志、3 份提交信息草稿、 4 个注入 patch、2 个已合并实现的过程 diff、1 个一次性改名脚本。 规则:`.dsh/*` 忽略 + `!.dsh/skills/` 放行 —— skills 是**入库的项目知识** (子域方法论),30 个已跟踪文件不受影响。 判据写进注释而非只写"忽略草稿",命中任一就不值得留: ① 能重跑的(门禁输出、日志); ② 内容已在 git 历史里的(已合并实现的过程 diff —— 如 T0/T1 E17 与 sock_reset,主干已有 ch395f_sock_reset_begin/poll); ③ 离开已丢弃的任务分支就跑不通的(依赖 NET_FAULT_INJECT 的注入产物)。 第 ③ 条是关键:这类东西提交进去是"看着有证据、实际不可复现"的**假证据**, 比没有更坏;方法应沉淀成文档文字 —— docs/网络/网络自愈架构设计.md §5.2 E18/E19 已完整记录注入点、触发方式、判据与实测结果,patch 不是唯一载体。 与 MDK-ARM/retracted_bench_20260922 归档先例的区别(避免下次误套):那次 归档的 probe_*.py 独立可运行,归档理由是"判据是错的,但要立牌警示后人别 再用"。归档的前提是**还能跑、结论错**,不是"根本跑不起来"。 验证:git check-ignore 双向确认(草稿命中 .dsh/*、skills 路径不被忽略); 删除前逐个 git ls-files --error-unmatch 确认未跟踪,**未用 git clean**(会 误伤 skills 下可能存在的未跟踪文件);skills 跟踪数 30 不变;.gitignore 无 BOM / 无 U+FFFD / 无 CR。启动 docs/任务规划/README.md 的 W6 UI 扩展簇(用户裁定,覆盖 2026-10-11 的"本轮不排期")。四个工作包,全部验证通过。 ## U1 设计令牌层(零行为变化) 新建 Drivers/BSP/UI/ui_tokens.h(20 个宏),框架 + 7 个页面 + App/task/ ui_task.c 全部接入。屏幕几何**复用 ui_port.h 既有 UI_SCREEN_W/H**,不重复 定义;令牌头自身只 include ui_port.h,不把 LVGL 拖进全部消费者。 补收了报告没列但代码里真重复的量:UI_TEXT_BAND_H 15(7 次)、列表左右边界 与每页行数(各 4 个页面逐字复制)、推流协议几何与显示几何同源(此前硬编码 3200 且注释写着"须与 UI_FB_SIZE 一致"——典型漂移隐患)、UI_TASK_TICK_MS (ui_port_step 与 osDelay 必须同值,原先靠注释维系)。 故意不收页面私有几何与"同形不同义"的值(device_info 的 pitch=15 与列表 UI_ROW_PITCH=14 合并即改像素)。 ## U3 页面注册表与生成器(删手工分发) ui_page_registry.{c,h} 由生成器产出,**手工 menu_content_page_for() 已删 除**(仓库内仅存 2 处历史说明注释)。密码页按代码实况**单独出口** (ui_page_registry_password())——它非节点绑定,任何 #P 叶子进同一页。 顺带落地此前判定"U1 阶段做不了"的共享 flat 样式 ui_style.{c,h}。 生成器除产注册表外**自动把新 .c 写进 uvprojx 与 CMakeLists.txt**,且 **双向对账**:加页打印 registered、删页打印 pruned,不留死条目;并双向 校验(#page 找不到实例报错、实例未被任何 #page 引用也报错)。 ## U4 门禁补强(35 个钩子) 新增 font-charset 钩子(scripts/check_font_charset.py):复用 tools/font_gen 的同一套提取与 cmap 解析规则(importlib 直载,零第二套 规则),A 段查源码在用 vs 字符集、B 段查字符集 vs 字体 cmap。 registration-check 补第 ④ 类:实测原规则对"进了两端构建但没接描述符表" 的页面**全绿**——desc 被链接器剥离、编译链接全过、菜单永远打不开的死代码。 仍照不出的两项已如实写进文档(枚举漏加归 @build、菜单树漏挂归 golden)。 ## 字体子集瘦身(D 案,4 留 3 删) ui_cn_chars.txt 122→119 字(恰删 上/电/重,零新增)。异/常/片/芯 进 KEEP_EXTRA 带理由白名单:它们曾在9eb1bd63被写进中文自检描述落盘 /log/selftest.log(定长 64B 记录跨固件持久),删字形会使历史故障记录 渲染空白且不可逆。保留字按**必需**处理,反向有牙——谁只跑 extract_cn 把它们静默洗掉,钩子直接 FAIL 并逐字打印理由。 --strict 口径为"未知冗余 = 0"而非"冗余 = 0"。 ## 验证 @build 0 错 0 警;ER_IROM1 232,052 B(较 HEAD −228 B)、RW_IRAM1 75,232 B **零增量**、RW_CCM 87.9% 未变。golden:菜单 8 状态全像素一致 + 内容页 11 页逐字节一致,**参考图 sha256 差异 0 条**(未用 --update 刷 基线);池水位菜单基线 5368 B、开页-关页往返 5448 B 与 U1 时相同。 38 个在途修改文件逐个核对 BOM/CRLF/U+FFFD 全部与 HEAD 一致。 L0 33 钩子 + L0.5 16 runner / 1104 断言全绿;check_doc_links --all 142 篇、无法判定 0;mem-budget / mem-numbers 已回填。 ## 单位成本实测(U5 排期依据) 临时页 zzprobe 绑到"遥信量"叶子 → 只跑生成器 → 带页 @build 0/0 → 注册表出现 {5u, &ui_page_zzprobe_desc} → 回滚后 6 个产出文件 zzprobe 残留计数均 0、重编回基线。 **结论:新增一页 = 3 处人工(新 .c + 新 .h + 菜单树那一行加 #page=<名>)+ 1 条生成器命令,手工编辑注册文件 0 处。** 据此修正报告原口径"8 处降到 1 处"——那个 1 只在"1 处对既有文件的修改" 意义下成立,页面自身 2 个文件本就要写,真实处数是 3。 ## 文档同步与两处自我修正 §0.1 UI 行 B → B+;§6.1「内容页」「设计令牌」两行按实况回填;§6.5 U1/U3 状态更新;§6 顶部加"建议栏不可照抄"总警示(本轮四次证伪:ui_port.h 已有 同名宏、LV_COLOR_BLACK/WHITE 不存在且极性反了、27 页实为目标页数现存 7 个、生成器草图漏密码页非节点绑定)。 两处改口:① §6.5 U4 从我上一版**误写的 3/3 改回 2/3**——"golden 脚本早已 存在"不等于"已进自动门禁",两个 golden 脚本至今未挂 pre-commit,该缺口 单列为待办;② 原计划"分三笔提交"**作废**,U1 与 U3 改同一批文件、文件内 交叠,硬切会产生不可编译的中间态,故整波一笔。补上我上一轮自己标记为 2/3 的那块缺口:两个 golden 脚本早已存在且一直在 人工跑,但**从未进自动门禁**——"有基线"不等于"有门禁"(§6.1 原文即自认)。 ## 先实测成本再定形态 改一个 UI 源文件后「增量重编模拟器 + 两条 golden」合计 **2~4 秒**,支持走 自动门禁。若实测要几分钟,正确做法是明确不挂、改为文档化人工门禁——一个 逼人 --no-verify 的门禁等于没有门禁,故这个数字是本决定的前提而非附注。 ## 落地 新增 scripts/check_ui_golden.py:串「增量构建 → 菜单 8 状态 → 内容页 11 页 + 池水位」。**有意忽略 argv 且 pass_filenames: false**——两条 golden 按整棵 菜单树 + 全量参考图工作,不接受文件参数,pre-commit 传的变更名对它无意义。 files: 覆盖 UI 源 + ui_task.{c,h} + 模拟器 tests/sim/src + main.c + CMakeLists + lv_conf.h + 包装器与本配置自身(pre-commit 4.x 无 always: true, files 是唯一触发手段)。 ## 端到端验的是钩子,不是脚本 干净树连跑 3 次 rc=0 全 Passed;给 ui_page_dc.c 的 .top_y 注入 +5 位移 → **钩子 Failed** 并点名 `[dc] FAIL 与参考页不一致(1882 px 差异)`;还原后 git diff 干净、simulator/tests/ref 的 git status 为空——**全程未用 --update 刷基线**(那正是本钩子要防的行为)。 诚实记录瑕疵:首次实测在"连续注入/还原的紧邻重建"之间偶发失败一次,疑似 MSBuild 节点争用,此后 3 次连跑均绿。已把"单次失败先复跑、连续两次才当真、 不许直接 --no-verify 绕过"写进钩子注释。 ## 文档同步 AGENTS.md 钩子数与文末索引 35→36 + 钩子清单补 ui-golden; pre-commit使用说明.md 概览计数同步;报告 §6.5 U4 由 2/3 → **3/3**(这次是 "真进了自动门禁",不是"脚本存在")、§6.1 golden 行转 ✅、§0.1 缺口描述更新。 ## W6 剩余 U2 合并两页记录、U5 落地目标页(须先对齐"目标页 vs 现存 7 个 ui_page_*.c" 的差集——报告的"27 页"是目标数而非现状数)。ui_page_runlog.c 与 ui_page_selftest.c 各 102 行,逐行比对后差异全是命名: 唯一的功能变量是日志 ID(SYSLOG_RUN / SYSLOG_SELFTEST),计数、每记录 2 行、 同记录读盘缓存、空态伪行的实现两边逐字相同。是真重复,不是推测出的抽象。 ## 落地 新建 Drivers/BSP/UI/ui_w_records.{c,h}(已在 uvprojx + CMakeLists 双端注册; 生成器只自动管 ui_page_*.c,故这两处手工加)。两页各留一个实例与两个薄跳板, .count_cb/.refresh 名字不变 => 注册表与生成器零改动。 净效果:两页 90 删 / 28 增(−62 行),重复逻辑收成一份。 ## 合并唯一的真陷阱:缓存归属 count_cb/refresh 签名里没有 user context,最容易写的版本是把 s_cache_valid / s_last_rec / s_ts / s_desc 留成组件级静态——那样两页共用一份 缓存,从 A 页切到 B 页会因记录号相同而直接命中缓存,显示另一页的陈旧行, 是静默错数据而非崩溃。故缓存内嵌进实例(ui_w_records_t),每页各一份。 ## 验证 @build 0 错 0 警;golden 经真实 ui-golden 钩子跑通:菜单 8 状态 + 内容页 11 页(含这两页)逐字节一致,未用 --update 刷基线 => 语义搬迁行为等价有证。 ER_IROM1 232,052 → 231,936 B(−116 B)。RW_IRAM1 75,232 → 75,240 B(+8 B), 原因如实记录:原先 4 个文件级静态零初始化落 ZI,现在 s_rec 带 .id 初值整个 结构体落 .data(RW),两页合计净 +8 B;要压回 0 需把 .id 留零再加 init 钩子, 为 8 字节引入运行时初始化顺序不值得,故保留并把账写在此处。 stack_report FAIL 行数 0(间接层未顶破栈预算);mem-budget / mem-numbers 已 按 @build 后规定回填台账。 ## 过程中的两处自我失误(已修复,记录以免重犯) ① 用 stem[:2] 生成函数前缀,对 selftest 得到 se_ 而非 st_,锚点失配。 ② 撤销脚本里 ${UI_DIR} 被 PowerShell here-string 插值吞掉,导致"边校验边写" 把树留在引用组件但未注册的链接错误态。教训:回滚脚本必须比正向脚本更保守, 全部校验通过后才允许写盘。批 2(用户授权改名 + 「新名要一眼看懂」裁定) - 16 个有歧义模块码单词化,共 1,146 处随 scripts/rename_ids.py 改写: AI→ADCIN、AH→ADCHEALTH、PS→PROTSET、PE→PROTELEM、PH→PHASOR、 TS→TIMESYNC、VL→VOLLOCK、NP→NANDPORT、CFG→CONFIG、CLK→SYSCLK、 WD→WATCHDOG、STO→STORAGE、SH→SHA256、FA→FTPAUTH、FR→FREQ、104→IEC104 - 不动 NET/GD5F/RTC/SOE/UI/ADC/LOG/RS485/TOOL(已是单词、芯片名或业界通用词, 改了纯属 churn) - 别名按全局登记:这批字面量已完全限定(TC-<码>-<nnn>),不存在裸形多义 - 映射表存档 scripts/rename_maps/batch2.json 作审计轨迹;§6 模块码表同步 连带修门禁(关键教训) - 模块码涨到 9 位(ADCHEALTH/WATCHDOG)后,候选正则与族正则的 {0,4} 上界 会让改名后的号**整批从门禁眼里消失**:实测编号字面量从 8271 掉到 7315、 悬空假降 490 处。已放宽到 {0,9} 并写进 §6 注记 - 结论:改名批次必须连带复核识别面,只改数据不改判据 = 静默放行; 这次是靠「可判定比例跌破基线即失败」那条线拦下来的 现状与遗留 - 校验 1 未登记前缀 0 处;可判定比例 82.71%(基线 82.6%,只涨不跌成立) - 遗留:7 个模块的「单元测试说明.md」仍缺(PROTSET 116、NANDPORT 109、 PROTELEM 65、FREQ 48、FTPAUTH 45、SHA256 25、SOE 14 处悬空)——改名不解决 缺文档,处置方式待议 - 遗留:T-2/T-3/T-4/T-6 历史任务号悬空(别名表未收全,属本工具批次漏项) - 未改任何 C/H 可编译语义(仅注释与宏名内的编号),故不需 @build为什么改(第 1 件,用户裁定的顺序) - B 层已认 test/host/<模块>/* 为 TC 号属主登记处,但 check_test_traceability.py 仍按老规矩要求"每个号都必须出现在文档" → 两处判据互相打脸 - 实测矛盾面 284 处里有 173 处属于该口径:照老口径升 --strict 会当场拦死这些 本不该写文档的号,唯一出路是 --no-verify——门禁公信力就是这么丢的 追溯差异改为三档(口径与编号门禁一致) - DOC-ONLY:文档声明了、代码没有 → 真断链,38 处 - CODE-ONLY:代码有、母号不在登记处、**且号旁边没写人话意图** → 真未登记,7 处 - IN-CODE:靠"号旁写着意图"放过 → 238 处,**弱于文档的保证,单列示数不算过关**, 对外举证仍需 C 层模块级说明(脚本里写明这条边界,禁止把它读成"已登记") - 小写子项后缀(TC-TIMESYNC-003b)按母号算,与 check_ids 的 subitem_ok 同口径 - 真差异从 284 降到 45(38 + 7) 顺带修两个自己的 bug - TC_RE 模块码上界停在 {0,4}:批 2 模块码单词化(ADCHEALTH 9 位)后代码侧集合 从 556 悄悄缩到 217(339 个号隐形)→ 放宽到 {0,8}。**同一个坑第二次踩**, 第一次是 check_ids.py 的候选正则——改名必须复核**所有**识别面,已写进 §8.1 - _scan_malformed 的豁免条件写成 `(skip and name != "空格形 Trap")`,语义正好反了: 带 [示例] 的行反而专门被 Trap 规则咬。改为示例行**全部**形态规则不判, 并给引用示例号的两行文档补 [示例] 标记 验证 - 编号门禁:错误级 712 → 710,可判定比例 82.73%(基线 82.6% 未跌破),校验 1/4/5 仍 0 - 追溯:代码侧 578 / 文档侧 298 / 交集 260 / 差异 45;--board-only 回退口径不变 - check_doc_links.py --all 文档路径与章节引用均通过;字节卫生复查 BOM=否、CR=0 - 未改 C/H,不需 @build 文档:docs/编号规范.md §8「顺带收编」条目改写 + §8.1 口径修正记录补两行 + 变更记录; docs/代码规范/pre-commit使用说明.md §2.25「顺带收编」行同步三档口径手法与上一批 TC-NET-1101 相同:只把号挂到已有实现体的注释上,不改任何语义 - 第一轮第 1 项处补挂 TC-NET-101(附录 B 的 ✅ 由此可走到实现体) - 第一轮第 6 项处补挂 TC-NET-102,并注明条目何时删、汇总行为何遗留 顺带修一处被门禁当场咬住的写法:注释里原写「P1-01~P1-09」这个范围式, 让校验 6 多出 2 类台账外新账 → 按既定规矩改写成平实语言,**不往债务台账塞条目** 验证:@build 0 错误 0 警告且 Program Size 与改前逐字节相同 (Code=213356 RO=18580 RW=1184 ZI=131480)→ 证实只动注释; id-check --strict 通过(无台账外新账);doc-links --all 143 篇 0 失效; 追溯交集 259 → 261(101/102 两侧现在都有号,是真对上,不是靠注记压掉); BOM / CR / U+FFFD 与 HEAD 逐项一致- 新建 docs/算法/采样有效性校验设计说明.md(15 章):判据推导 (100 ms 门限的三条独立依据)、电平/锁存/事件三种位语义与 位号对齐的收益代价、闭锁与 IWDG 兜底责任矩阵、观测盲区、 主机侧验证分工(11 组 TC / 31 断言映射)、现场判读手册、 自测问题与改前必读清单 - 配图走 draw-diagram 生成器路线(脚本 + 产物一并提交): tools/arch_diagram/gen_adc_health_flow_svg.py 等三个脚本 → docs/images/ 下三张 SVG;文内另有 2 张 Mermaid(判定主干 flowchart、静默位生命周期 stateDiagram-v2,沿用仓内既有方言) - 登记与指针:docs/README.md §5.2 加一行 + 变更记录; docs/算法/采集与算法层设计说明.md §4 与 §13 加深读指针; 本文只写「为什么」,接口表仍由该文档属主,不重复 - 读码确认并如实登记三项(本提交不改代码,待裁): 1) adc_task_init() 全库无调用点,adc_health_init() 因此不在 产品路径,判定正确只依赖 .bss 清零 + 首拍建基线 2) tgap 计帧点数(d-1 加权),adc1_gap 计次数——两侧单位 不对称,心跳里不可直接对比 3) TIM3 停摆时判定层随消费者一起停,最坏约 800 ms + 2498 ms 窗口内 protectTask 仍以旧相量判据(细节见 §10 / §14) - 门禁自查:pre-commit 目标集全绿(doc-links / id-check / 空白 / EOF / 大文件);主机单测 adc_health 31/31 复跑通过; 9 个新增或改动文件 BOM=无、CR=0、U+FFFD=0- check_ids.py 扫描循环:位号([RCDJXUT][0-9]{1,3} 无连字符)在 refs_total 之前 continue, 既不产生结论也不灌分母;add() 里的同一道判定保留作兜底 - 注册表 excluded_paths 移除 docs/硬件平台/(docs/芯片手册/ 仍排除) - 首次纳入暴露 28 类先前不可见的存量 → 按规矩逐条写 reason 入台账(**不重打快照**, 台账只增带理由的条目);错误级 626 → 661,可判定 82.84%(基线 82.6 仍守住) - 遗留噪声已在 designator_rule 记明:引脚名 PB12-15 / PG0-15 / DB0-15(两字母+数字+区间) 不是编号,但位号规则不覆盖这种形状,先记账、待加 pin-name 类型规则后清除 - 验证:--strict 通过(无未登记的台账外新账);未改 C/H 不需 @build规则扩展(scripts/check_ids.py,两处共用同一正则) - 原:^[RCDJXUT][0-9]{1,3}(单字母位号) - 现:再加 ^[A-Z]{2,3}[0-9]{1,2}(-[0-9]{1,2})? → 收掉硬件语料里的**引脚名 / 总线名 / 网名 / 引脚区间**:PB12-15、PG0-15、PF13-15、DB0-15、OS0-2、KEY1-3、AIN1 这类 - 关键边界:带连字符的一字母形态(C-10 / A-9 / N-18 / D-7)与 TC-206、TC1003 **不落在 新形状里**,照旧判定 → 报告自有条目号这类真编号没被顺手挡掉(实测台账仍留 21 类真债) - 位号与引脚名都不进 refs_total:错误级 661 → **651**,refs_total 8807 → 8797(同步 −10), 可判定比例 82.84% → **82.94%**(分母没被噪声灌水,涨幅来自噪声出清而非真进展) 修自己的 bug:--prune-backlog 会静默丢掉 reason 字段 - 症状:收账一次后,那 28 类硬件存量条目的「这笔债为什么该存在」说明全部消失 - 根因:prune 用 dict 字面量重建条目,只写了 file/code/literal/count 四个键 - 修法:改为从原始 JSON 取整条再覆盖 count,其它字段原样保留;已实测幂等 (再跑一次「还清 0 条、剩 313 条、带 reason 仍 21 类」) - 21 类真债的 reason 已重新写回,内容含一条判断:器件料号 FOC-817 **不做**形状规则, 因为 [A-Z]{3}-\d{3} 与真家族 DEF-* 撞形,宁可留一条记账也不制造盲区 验证:id-check --strict 通过(存量 651 全在台账内、无未登记新账); doc-links --all 失效与待确认均 0;未改 C/H 不需 @build;台账只减不增用户裁定:报告很久没更新,过时的删除、重新写。697 → 440 行(−563 / +306)。 取数:旧报告里的数字一个都不沿用,全部按今天的仓库重测——`.map`(ER_IROM1 231,768 B / 22.1%,RW_IRAM1 75,264 / 57.4%,RW_CCM 57,376 / 87.9% 余 7,904 B)、 `.htm` + 同日真机 `[STACK]`(9/9 PASS、min_ever 8,464)、`git ls-files` 逐个 stat (2,498 文件 / 80.0 MB,docs 45.3 MB 中芯片手册占 39.5 MB=87%,.git 248 MB)、 37 个钩子、16 套主机单测、TC 593∶271 交集 265、登记簿在策 18 条。 删除:六维评分旧表与 §0.4 的 12 条已闭环行;§6「UI 8 人日方案」正文(四层设 问、逐日排期、27 页清单);§7 交付物清单的已完成行与人日估算;§1~§3 的 2026-09-11 / 10-14 旧快照数字与附录 A 旧证据行;§4 各节的设计复述(同一事实 只在一处,细节归各自设计文档),改为"今天复核到的状态 + 仍在册问题 + 证据"。 刻意保留三层(删了会出事): ① 新增 §9 编号族登记处——本文是 id_registry.json 中 N-RPT / D-RPT / S-RPT / R-RPT(游标 R-9) / F-RPT / U-RPT(U5) 六族的属主登记处,5 族开 check_dangling, 且外部存在「报告 N-14」「报告 F-4/F-5/F-6」「报告 D-1」式紧邻引用,删定义位 会被编号门禁当"定义缺失"拦下;每个存量号一行状态 + 现去处。 ② §1.2 / §1.3 复现命令与口径定义——保证每个数字可重跑。 ③ 文末变更记录与已销项去处表 52 行原样冻结(登记簿销项纪律的落点)。 被外部点名的锚点 §0.3 / §3.1 / §4.1~§4.3 / §5.3×9 / §5.4 / §5.5×4 / §6.2 / §6.3 / §6.5 逐个留位 → 全库 68 处入站引用零改动。 重审计改判三条: · §6 原写 U1/U3/U4「❌ 未建」是错的:ui_tokens.h(69 行 L0 令牌)、 ui_page_registry.c(描述符注册表,已取代手写 case)、ui-golden + font-charset 两枚钩子都在;U2 只有 ui_w_records 一个组件;"27 页未落地"实为 7 页。 · "新增一页 ≤30 行"目标未达(最省 ui_page_dc.c 40 行,重页 275/370 行),如实 记为未达项。 · §0.4 与登记簿已漂开 → 本版把待办清单降级为快照 + 指向,权威只留一处。 门禁:check_doc_links.py --all --sections-strict → 146 文档 0 问题(章节引用 2,515、可判定 1,483);check_ids.py --strict 增量拦截通过、存量债 650 → 641 (−9)、可判定比例 82.98% → 82.80%(基线 82.6% + 0.1pp 容差仍通过,缓冲从 0.38pp 缩到 0.20pp——被删引用 99% 可判定,属预期副作用);mem-numbers、 ram_baseline --gate、task-table、cubemx-survival 全 0;字节卫生 BOM/CR/U+FFFD/ 行尾空格全清。未改任何 .c/.h,未推送。推荐清单第 5 步的拆分执行:反转依赖(5a)暂缓,**只把"防扩大"的门禁先上**。 **为什么门禁比搬迁值钱**:AGENTS.md 与分层图早就写着"六层单向依赖",可从来没有 机械检查——于是同一件事的记载从 2026-09 审查报告的"NETCFG 一处",漂到 2026-10-22 实测的 **8 个文件 9 处**,全靠人读代码才发现。边界的静默腐蚀只能靠门禁止住。 **机制(与 `id-check` 的增量拦截同构)**:`scripts/check_layer_deps.py` 扫 `Drivers/BSP/**.{c,h}` 的 include,按 `App/**` 头文件索引解析反向边(同名头按字面 路径消歧),命中分三类: · ALLOW 永久豁免 3 条横切叶子头——`App/log/console_log.h`(日志后端,BSP 的 dbg_log.h 就建在它上面)、`App/util/sys_clock.h`(时间源)、`App/util/sha256.h` (纯算法、无状态)。它们是"任何层都要用的叶子能力",不是上层服务;新增豁免 必须先回答"它为什么不是上层服务"。 · BASELINE 钉住存量 6 条:`cfg_file.h`×2(NETCFG)、`vol_lock.h`×1(lftpd_io)、 `sys_log.h`×3(UI 两处 + `ui_w_records.h`)。**只减不增**(脚本没有 `--write`)。 · 新出现的 (BSP 文件, App 头) 组合 → FAIL,并给出三条出路(注入回调 / 上层传参 / 确属叶子才可豁免)。 `--prune` 在某条存量还清时点名"可从基线删除",于是"还欠几条"成为可见进度,而不是 文档里的一句口号。 **当场自证**:落地时临时给 `Drivers/BSP/RS485/rs485.c` 加了一行 include `cfg_file.h `,钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉后转绿——没被触发过的门禁 等于没有门禁。 **边界要说清(免得被误读成缺陷)**:本钩子只拦**静态 include 方向**。运行时的属主 纪律本来就没破——UI 只投递 `net_cfg_save_request()`,真正写盘在 defaultTask,模拟器 也早在主机侧跑整条配置链。 **口径同步**:钩子数 37 → 38 共 6 处(`AGENTS.md` 两处、`pre-commit使用说明.md` 两处 并新增 §2.26 详解、`并行任务工作树方案.md`、报告 §0.1/§1.1); `项目架构总览.md` §1 分层原则处补"此方向已机械检查";报告 §0.4 的 S-1 计数由 "7 文件 15 处"更正为脚本口径的 8 文件 9 处,§9 状态改 🟡。 **暂缓项(都已在报告 §0.4 记因)**:5a 反转依赖——没有功能在等它,且要过"改 IP 免重启生效"整条真机链,留给下次真动 NETCFG/UI 时顺带做;5b 目录搬迁——uvprojx 326 条 + IncludePath + CMakeLists + 42 个 UI 文件 + 全库引用改写,收益只是"目录名好看", 而远程 CI 仍刻意不跑 `@build`,兜底未成立。 门禁:`layer-deps` 自身通过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题;`check_ids.py --strict` 增量拦截通过、可判定 82.84%;`mem-numbers` 0; `py_compile` 通过。本轮无 `.c/.h` 改动,未重跑 `@build`。未推送。接上一笔(FTP 凭据)之后,`@test --require-selftest-pass` 仍不可用——它其实是**三个 bug 叠 在一起**,这次一并收口。 **① 抓取失败被当成"设备干净"**:`_fetch_selftest_log()` 把"设备侧无此文件"(正常路径, "仅故障才写"语义下干净设备本就没有该文件)和"连不上/登录被拒/FTP 故障"(结果**未知**) 都返回 `""`,调用方一律按"无记录 → PASS"处理。叠加上一笔修掉的匿名登录 530,这条门禁 **从写下那天起一次都没真正读到过那个文件**。现在 fetch 返回 `(路径, 状态)`,状态 ∈ {ok, absent, error},三者分开判。 **② 判据本身判不了"一次启动"**:`/log/selftest.log` 是 **512 条环形累计历史**(设计文档 §4.2 容量表),而旧判据是"文件非空即 FAIL"——在任何做过故障注入或长跑的设备上**永远不 可能 PASS**。本次实机抓到的 29 条记录,时间戳全是 2026-10-10 ~ 10-14,没有一条来自本次。 新判据 `--selftest-scope this-run`(默认):**烧录之前**先取基线,启动后再取一次,只把 "基线最后一条之后"的记录交给判定(差集落成临时副本,命名落在 `selftest_log_*.log` 里, 才会被 `--keep-out` 的保留规则收走)。 **③ 取基线本身会污染本次启动日志**(我这次实测踩到的):抓基线是一次真实 FTP 会话—— 设备把 `USER/PASS/RETR` 打到串口上,客户端退出时还多打一行 `[ERR] [FTP] error reading next command`。放在 `open/clear`(采集武装)之后,这些行就落进本次启动日志:Forbidden `[ERR]` 命中 → 本次 FAIL,开机耗时从 612 ms 被拉到 **7997 ms**。所以基线固定在 open 之前, 并把这条写进文档当规避口径。 **判定表**(`docs/测试说明/自动化测试流程.md` §自检结果判定整段重写):无此文件=PASS; 本次无新增=PASS;本次有新增=FAIL;连不上/登录被拒/FTP 故障=**FAIL**;既无 `device_ip` 又无 `--selftest-log`=**FAIL**(要求判定却没有数据源,不能再"视为无记录");基线最后一条 被 512 条环覆盖(更新量 ≥ 环容)= **FAIL**(不拿"看不到"当"没有")。后四种"判不了"由调用 方在 `verify()` 之后强制收口成 RC=1——否则标记全中就把"没判"读成"判过了、没问题"。 `--selftest-scope all` 保留旧的累计口径(审计 / 第一次建基线用);给了 `--selftest-log` (本地副本)就没有基线可比 → 自动降级为 all 并打印一行说明;FTP 凭据 `--ftp-user`/ `--ftp-pass`(config `ftp_user`/`ftp_pass`,默认出厂 admin/admin)。退出码 1 的含义、 `verify_log.py` 的语义条目、SKILL.md 参数表(+2 行)、config.example.json(+3 键)同步。 **真机三跑,三条路径各自验过**: · 默认 this-run + `--device-ip` → **PASS / RC=0**:`基线 29 条 → 台账不变`、boot banner ×1、 `net init: OK` **612 ms**(证明污染已消除)、Forbidden 零命中。 · `--selftest-scope all` → **FAIL / RC=1**:`29 fault record(s)`(累计口径仍可用)。 · 不给 `--device-ip` → **FAIL / RC=1**:`verify_log` 那侧显示 `no fault record → PASS`, 调用方收口为 `RUN_TEST_VERDICT: FAIL —— 自检台账判定未能执行`(正是要的行为)。 门禁:`py_compile`(run_boot_test + verify_log)通过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题;`check_ids.py --strict` 增量拦截通过(可判定 82.82%, 在地板之上);6 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。 **仍待办(本笔未做)**:设备侧"FTP 客户端中途退出"按 ERROR 级打日志(要不要降级);lftpd 的 LIST 传完回 550 而非 226;设备 `/` 残留约 24 个旧探针文件;`MDK-ARM/` 下 4 个一次性 探针脚本仍写匿名。未推送。前一笔立契约与门禁(`Drivers/BSP/bsp_err.h` + `bsp-err` 钩子 + RS485 试点);这一笔按用户 指定顺序 C3 → C1 → C2 把三个模块真正收编,并让存储失败第一次变得可读、可分类。 **C3 · SD2506 整模块(11 个公共 API + 6 个内部 helper)**:三枚私有宏 `SD2506_OK`/ `SD2506_ERROR`/`SD2506_I2C_ERROR` **删除而不是留别名**——留别名等于新代码还能用旧形状, 把迁移洗掉;编译器替我们找全所有调用点。新增 `sd2506_err_from_hal()`,**把 I2C 超时从 "和没人应答共用一个 −2"里拆出来归成 `BSP_ERR_TIMEOUT`(可重试)**——SD2506 是启动链第一站、 I2C1 上电时序敏感,这是本波唯一真正的语义改善,不是改名。`sd2506_init` 的 charge 寄存器 **回读校验不符**原返参数类错误,改归 `BSP_ERR_IO`(那不是调用方的错)。 **C1 · 同形两义**:`GD5F_BUSY_TIMEOUT` → `GD5F_WAIT_EXPIRED`(**值 −2 不变**,纯消"名字叫 TIMEOUT 其实是返回码")。**GD5F 私有码集整体收编刻意没做**:169 处引用里 48 处在板级套件、 还要动 Trap 记录用词,属另一波带真机+套件回归的工作,不夹带。TPAFE5160 收编 23 处: `BUSY_TIMEOUT → BSP_ERR_TIMEOUT`、`ERROR → BSP_ERR_PARAM`(**同值 −1,行为零变化**)、 `FRSTDATA_ERR → BSP_ERR_NO_DEVICE`(E-44 的本义就是"芯片没驱动 DB 总线",归到器件无响应比 私有 −3 有用,且避开与 `BSP_ERR_TIMEOUT` 撞值)。实测 `tpafe5160.o`/`gd5f2gq5ue.o` 的 Code 增量为 **0**——改名不产生一条指令。 ⚠ **`tpafe5160_snapshot()` 刻意不套成败码**:它是 1=有新帧 / 0=无新帧 / −1=参数错 的三态 **状态**返回,0 不是错误而是"这一拍没有新数据",强行收编会把慢消费者的正常路径变成失败。 顺带订正我上一版在 `bsp_err.h` 的断言——"本层只有 `net_*` 把数量与错误挤同一返回值"是错的, `snapshot`/`adc_input_poll` 就是第二族;两族现都已写明并各注保留理由。 **C2 · FTL 坍缩点分类**:`ftl_flush_cache`/`ftl_read_page` 不再把 dhara 失败压成 `GD5F_ERROR`, 按**处置差异**分三类,并在最早同时知道 LPN 与原始码的位置就地打印:`ECC`/`TOO_BAD` → `BSP_ERR_CRC`(页级,重试或换页,不代表卷坏);`JOURNAL_FULL`/`MAP_FULL` → `BSP_ERR_NO_SPACE`(介质健康但块池/日志见底,此时重格式化只是把问题推回去); `BAD_BLOCK`/`RECOVER`/`CORRUPT_MAP`/越界 → `BSP_ERR_IO`(完整性故障,保卷 + 上报,不许自愈 重格式化)。这与 **E-31** 是同一条纪律的两端。可读名用自建短表:**本仓 dhara 没有编 `error.c`,用 `dhara_strerror()` 会直接链接失败**。`disk_read`/`disk_write` 仍回 `RES_ERROR` (FatFs 契约);要让 defaultTask 卷健康探针按类分支还差跨调用存活的快照 → C4 待还。 **两次靠核实拦住的自我错误**:① 我一度认定 `DBG_ERROR("... err=%d", err)` 是把结构体塞进 varargs 的格式 bug——核 `Lib/dhara/error.h` 后确认 `dhara_error_t` 是 `enum`(标量),那行日志 本来合法,改了反而是引入错误;② `bsp-err` 一次 `replace_all` 越界把 8 处 **GD5F 码空间**的 判据改成 `!= BSP_OK`(数值同为 0、无行为变化),但那正是"把两个码空间混起来",已逐一还原。 `cppcheck` 亦抓到 `cls` 声明作用域过宽,已收进失败分支。 **验证**:`@build` 0 错 0 警告(`Code` 213,648、RO 18,860、`ER_IROM1` 232,508 B / 22.2%、 `ROM 合计` 233,696;**RAM 三项零增量**);`@test` PASS(692 ms,烧入戳 `Oct 23 01:35:32`) 且逐条读到被改模块的锚点:`SD2506 init: OK (ret=0)` 与 ID/时间/温度/电池四条读回正常、 `ADC init: OK`、`NAND init: OK`、`Map resumed: 14094/97504`、`adc=` 帧计数持续递增(证明 snapshot 三态未被改坏)、`[ERR]`/Fault/FAIL 0 条。主机单测:系统时钟 100/100、dhara NAND 移植层 149/149(两者都因本次改动需补 `Drivers/BSP` include 路径,补后在钩子内实跑 Passed)。 `pre-commit run` 全量 RC=0(20 个实跑钩子 Passed);`bsp-err` 基线 **19 → 17**(还掉两个 `*_BUSY_TIMEOUT = -2`)、`--prune` 干净;`doc-links --all --sections-strict` 147 篇 0 问题; `id-check --strict`、`layer-deps`、`mem-numbers`、`mem-budget`、`ui-golden` 全过;33 个文件 字节卫生 0 异常(无 CR / 无 U+FFFD / 无行尾空格 / 均有尾换行)。 **口径与数字联动**:新增 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.4(三类码与处置 理由);`pre-commit使用说明.md` §2.27 与 `AGENTS.md` 基线计数 19 → 17;TPAFE README、 GD5F 测试指南与 Trap 记录均**保留旧名留痕**以便对旧日志;配图桶按 .map 对象级增量重算 (`dhara/FTL` 10,931 → 11,222,+291 全部落在 `nand_ftl.o`;应用代码/UI +36 落在 `app_main.o`;其余散量入"其他"7,557;桶和 = Code+RO 断言通过、文本溢出 0); `ram_baseline --update/--write`、`ram_report_20261023.md` 快照、报告 §1.3/§5.1/§5.4 与 部署文档 §3.3 数字全部同步;报告 §0.4/§9 的 S-5 状态改为 🟡 主体已落地。P1 的 NET 四块开了第一块:新增 test/host/net_select/,21 条断言全部 MSVC 编译 通过并 PASS。net_select.c 一个芯片函数都不调,所以它是四块里唯一不需要假芯片的, 先把它拿下,顺带把 NET 域的桩骨架立起来。 钉住的契约: - 就绪位回写:入参集合里未就绪的 fd 必须被清掉,只留就绪的(POSIX 语义);未请求 的集合不被写回、可以传 NULL。 - 状态准入:LISTENING 即使 readable 也不算可读(accept 语义不在这层);ESTABLISHED + send_ready 才可写;CONNECTING 时 send_ready 不作数。 - 边界:nfds 为 0 / 负 / 超过 NET_MAX_SOCKETS 都返回 -1(不许扫出 socket 表外)。 - 超时算术:tv_usec 是**整除截断**(1999us 当 1ms 而不是 2ms);timeout=NULL 时 无限等并由对端事件唤醒;poll 的 timeout=0 首轮即退、每轮先清 revents。 用可控 tick(每问一次 +1)+ 可控 osDelay(计数、可指定轮数后造一个就绪 socket) 把这些变成确定性轮数,台架上根本构造不出"没人来连时该怎么退"。 照出两处**死路径**(只登记,未改语义;全库唯一调用点 test/board/net_test_task.c 传的是 NULL 与非零 tv,今天没有受害者): 1. net_select 的 timeout = {0,0} 换算出 timeout_ms = 0,而退出条件写的是 if ((timeout_ms > 0) && (已用时 >= timeout_ms)) → **超时 0 永不触发退出**,退化成 无限等;POSIX 在这里必须立刻返回 0。同文件 net_poll_events 写的 timeout >= 0 是对的,两条并排放着正好是对照。风险面:谁拿它当"非阻塞查一遍",netTask 就 回不到主循环,而 netTask 在 IWDG required_mask 里。 2. exceptfds 是恒为空的死参数:循环体开头 p_sock == NULL → continue 先跳过, check_socket_error 的 NULL 分支从 select 里到不了。按 POSIX 直觉用它收异常的人 会永远什么都收不到。 两处都用"现状桩"钉成改了会响(用例名里写明不是期望行为)。另修掉我自己写错的一条 用例:给 timeout=NULL 又没有就绪 fd,直接死循环——已在假 osDelay 里加了 500 轮 保险(跑飞就 exit(2)),因为挂死的测试会把 exe 文件名占住、让后续构建报 LNK1104。 桩的边界写死在文件头:net_select.h / net_types.h / net_config.h 用真实头(net_sock_t 字段名与 NET_SOCK_STATE_* 一改即编译期报错);main.h / cmsis_os.h / FreeRTOS.h / task.h 是本目录桩;新建的 ch395f.h 是**空壳**,只服务不调芯片函数的文件—— net_pending / net_events / net_listener 直接调十余处 ch395f_*,要的是可脚本化返回的 假芯片,不要把这个空壳当成"NET 域桩已就绪"。 覆盖率:套件 21 → 22 全绿;files_tested 25 → 26;未触及 .c 41 → 40;分母 3,332 → 3,446 行;net_select.c 97/114(85.1%)。恒等式更新为 40 = 6(P1 剩) + 5(P2) + 29(P3)。 编号按 B 层口径补建 TC-HOST-NETSEL(游标 019),TC-HOST 族现共 16 族;注册表仍只 做增量插入。 自查:test-cppcheck Passed;check_ids --strict 通过;doc-links = 35 处(= 存量); BOM/CR/U+FFFD/行尾逐项净。本增量零生产代码改动,未跑 @build。--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填)与 ui-golden (ref/*.bin 不入库,新 worktree 天生没有黄金参考)三条失败,与本分支无关。三处都由本轮 P1 新增的主机单测照出,用户逐条裁定后实施。共同性质:都不是改动引入 的回归,而是**从没被执行到的分支**——生产路径上要么没有调用者、要么没走边界,所以 编译器(0 错 0 警)、cppcheck、真机日志对它都无话可说。 1) 缺陷一(选 a:改名,行为零变化) lftpd_string_trim 的尾部循环起点落在 strlen(p) 那一格 NUL,isspace(0) 为假 → 循环体一次都不执行,"尾部空白从未被裁",而名字承诺两头都去。改名 lftpd_string_ltrim + 删掉那条死循环 + 在 .h/.c 写明"只去前导"。不去尾才是对的: RFC 959 以命令终止符 CRLF 界定命令,参数(PASS 口令、含空格的文件名)的尾随 空格属于参数本体,去掉等于静默改数据;CRLF 已由 lftpd_inet_read_line 截断。 调用点 lftpd.c 同步改名。全库无遗留真引用(余 6 处旧名均为解释改名历史的散文)。 2) 缺陷二(按推荐修) net_select 的 timeout = {0,0} 换算出 timeout_ms = 0,而退出条件写着 if ((timeout_ms > 0) && (已用时 >= timeout_ms)) → 超时永不生效,退化成死等; POSIX 语义是"只查一轮立即返回"。去掉 timeout_ms > 0 这个错误前置即可。就绪 检查在本判断之前,故有就绪时仍立刻带计数返回;同文件 net_poll_events 的 timeout >= 0 本来就对,现在两条对齐。风险面:拿它当"非阻塞查一遍"会让 netTask 回不到主循环,而 netTask 在 IWDG required_mask 里(串行 socket 栈唯一执行者)。 3) 缺陷三(选 ①:按 POSIX 置位) exceptfds 是恒为空的死参数:循环开头 p_sock == NULL → continue 先跳过,而 check_socket_error() 只有"表项为空 / fd 越界"才返回 1,越界的 i 又不在本循环 范围内 → 调用方按 POSIX 直觉用它收异常,永远什么都收不到,且返回码不报错。 把异常判定提到 continue 之前:表项不存在的 fd 计入 exceptfds 与返回计数,并更新 函数注释。选 ① 会改变返回计数语义,故补一条"未请求异常集时不计数"的用例, 确保只查可读的老调用方不被误伤(TC-NETSEL-012b)。 测试同步翻转:原 TC-NETSEL-008 / TC-NETSEL-012 是"现状桩、改了会响",现在改成正向 契约,并新增 TC-NETSEL-012c(异常位只落到被请求的那个集合);TC-LFTSTR-005 从 "尾部不裁"的现状登记改为正向契约(尾随空格必须保留)。net_select 22/22、 lftpd_io 33/33 全绿。 @build:0 错误 0 警告。实测体积 ER_IROM1 232,512 B(快照 232,536 → **−24 B**)、 Flash 233,704 B(−20 B)、RW_IRAM1 75,272 B(+8 B 是上一提交 net_cfg 那个字节, 本批零新增)、**RW_CCM 57,376 B 不变**;四档百分比 22.2/57.4/87.9/22.3 全不变。 Code 减少 36 B 即删掉的死循环。 覆盖率台账:门禁如实报了一次 lftpd_string.c 已覆盖行 8 → 7(**代码少了一行**,不是 覆盖退化),已 --update-baseline 重登;现 22/22 套件、line=3003/3446(87.1%)、 func=236/259(91.1%)、tested_files=26、untested_files=118。 文档:主机单测覆盖率口径 §9 新增"三处死路径的处置"小节(裁定、改法、影响、钉住 它的用例一一对应),并把两处"等裁定"的登记改为已处置。 自查:coding-standard / test-cppcheck / id-check --strict(无台账外新账)/ doc-links (35 处 = 存量)全过;八个改动文件与 HEAD 逐项比对 BOM/CR/U+FFFD/行尾无变化 (这几个文件本就不带 BOM,未被动过)。pre-commit --all-files 仍只有三条失败且与 本分支无关:mem-numbers / mem-budget(.map 与 docs/内存管理 受管快照漂移,增量见 上,按并行分支约定不在分支里 --write,等 Lead 合并后统一回填)与 ui-golden (simulator/tests/ref/*.bin 在 .gitignore 内,新建 worktree 天生没有黄金参考)。起因:上一提交修掉 net_select 两处死路径后,发现排期登记里写着"C1 消化 E-25 (net_select 语义)",想核对 E-25 到底还列了哪些子项,结果查不到条目正文。 调查结论(三条独立证据,不是"我没找到"): 1. scripts/id_registry.json 里 E 系列族 E-AUD:pattern \bE-[0-9]{1,2}\b、 frozen=true、游标 E-63,而 **owner_docs_glob = None** —— E 系列是当年审计报告 的冻结快照号,仓库里从来没有属主登记处; 2. 全库(含代码与文档,排除构建产物)E-25 只有 3 处命中,且全部是"挂 C1"这一句话 的交叉引用,没有任何条目正文、判据或用例; 3. 债务台账 id_known_backlog.json 里 E-25 命中 0 条 —— 它落在族游标 E-63 之内, 被门禁当作"在册号"放行,于是既不报悬空也不报越界。 结论:这是一个**号在册、正文已随当年销项消失、却仍被当作在册依据**的编号,正是 编号规范 §0 与销项纪律里点名的那类漏清引用(同类先例:N-31 因销项删行后引用未清 而被复用撞号)。用户指示:查无相关内容即删除。 处置(E-25 全库归零): - 排期详述.md §1 的 C1 行:编号列改为 E-27 / E-28;描述去掉 E-25,但**不丢信息** —— 改用平实语言写明"net_select 的两处死路径(timeout={0,0} 退化成死等、 exceptfds 恒为空)已于 2026-10-23 由主机单测照出并修掉",并指向 docs/测试说明/主机单测覆盖率口径.md §9 的处置表(裁定、改法、用例都在那里)。 - 任务规划/README.md 两处(§0 冻结号说明、W4 结构债行)同步去掉 E-25。 - 冻结历史快照族本身不动(E-AUD 仍 frozen、游标仍 E-63),只清引用;E-27/E-28 照旧挂在 C1 下继续排期。 门禁:check_ids --strict 通过且**可判定比例 82.6% → 83.12%**(删掉的是"族已登记但 属主登记处不存在"的不可判定引用,分母下降所以比例升,符合只许涨不许跌); check_doc_links --all = 35 处(= 存量基线,本次改的两个文件 0 命中);task-table Passed;两个文件 BOM/CR/行尾与 HEAD 一致。 pre-commit --all-files 仍只有 mem-numbers / mem-budget(.map 与受管快照漂移,等 Lead 合并后统一回填,分支内不 --write)与 ui-golden(ref/*.bin 不入库,新 worktree 天生没有黄金参考)三条失败,与本分支改动无关。按 §9 规格做假芯片的第一批消费者。新增 test/host/common/ch395f_fake.{h,c}:15 个 函数的**真实原型逐字照抄**(含 ch395f_sock_state_t{open,tcp_state} 与 ERR_SUCCESS/ PROTO_TYPE_TCP/SOCKET_CLOSED/TCP_CLOSED/TCP_LISTEN 的真实取值),脚本化模型沿用本仓 既有假件风格:读函数按调用序号取返回队列(耗尽回落最后一个值)、写函数记次数与 最近入参、外加一条**调用顺序日志**让时序契约可断言。两条芯片怪癖必须复刻: ① get_sock_int_status **整体读即清**、无按位清除手段(AGENTS 模块约定 B3 实证坑, 不复刻就照不出"顺手全量清一遍把别的在用 Socket 门焊死");② *_cached 是**缓存值** 而非现场值,"监听还活着"的判据是 (open != CLOSED && tcp_state == LISTEN)。 各模块 stub/ch395f.h **只做转发** include 共享件(规格里拍板的方案):三份假芯片各自 漂移会让"假件与被测代码同时错"再也报不出来,签名与常量只能有一份定义。 新增 test/host/net_listener/,34 条断言,net_listener.c 覆盖 125/126 = **99.2%**。 钉住的契约(真机上一闪而过、台架凑不齐的组合): - 四道互相牵制的时间闸门:30ms 退避(20ms 不重试、30ms 重试)、隔离级 400ms 与 优雅级 100ms(399ms/99ms 不放行、400ms/100ms 放行)、**800ms 上限只留痕且不放弃 请求**(留痕只打一次,到 2000ms 预算耗尽才报 ERROR 交对账器)。排期登记里那句 "沉降窗 800ms 上限只留痕"至此第一次有了可执行的验证。 - 该不该重建:芯片仍报 LISTEN 就不重建且清空请求;有挂起 recv/send 时让路但**保留 请求**(挂起结束自动补做);五种条件静默放弃(非 LISTENING/CLOSED 态、 auto_relisten=0、非 STREAM、local_port=0、in_use=0)。 - 失败不留半成品:open 失败关回去并回 BUSY;listen 失败也必须 close_socket。 - 重建后簿记:转 LISTENING、清 send_ready/recv_bytes/remote_ip(_arr)/remote_port、旧 remote_port 记进 last_peer_port、清 lost_conn_seen/released_abnormal;源端口与协议 取自表项本身(监听端口不能漂)。 - 芯片时序:清接收 → 关断 → 读即清 → 读状态(顺序不可换);清理接收区单次读钳到 256B、最多 16 轮、读到空即停。越界 fd 全程静默且不打扰芯片;请求去重保留首个 reason。 测试自查修了两处(都不是生产问题但值得记): ① reset_all 只复位假世界、**没复位被测模块自己的 static**(s_relisten_req/_next/_dead/ _quarantine/_settle_*/_capped),一上来红了 9 条;改用模块自带的 net_listener_clear() 清,顺带验证了 clear 的完整性。 ② 沉降窗边界必须在**独立场景**测:放行判定与 30ms 退避共用 s_relisten_next,同一条 链里"恰好 400ms"那轮 tick 会被上一轮设的退避槽挡掉。这本身是个该知道的事实—— **hold 到点后最坏还要再等一个退避周期(实测 ≤30ms)才真正放行**,已写进用例注释。 另留痕一处死分支:net_relisten_locked 的 NET_ERR_BADF 从 tick **不可达**(tick 在调用 前就因表项为空放弃),TC-NETLIS-026 记着,新增调用点时要重新审视。 桩的口径:net_listener.h/net_types.h/net_config.h/net_pending.h 用**真实头**(net_sock_t 字段与各枚举一改即编译期报错);net_socket.h 是桩,但 net_set_errno 与 NET_ERR_RETURN_1 照抄真实头同名宏(net_socket.h:528/532);真实 net_pending.h 原型带 TaskHandle_t,故 宿主 FreeRTOS.h 补一个 void* typedef(只出现在签名里,被测代码从不传它)。 覆盖率与台账:套件 22 → 23 全绿;files_tested 26 → 27;未触及 .c 40 → 39;分母 3,446 → 3,572 行;line=3128/3572(87.6%,本批净涨因为新进来的文件几乎全绿)、 func=243/266(91.3%)。恒等式 39 = 5(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETLIS(游标 026),族数 16 → 17;注册表仍只做增量插入。 自查:test-cppcheck Passed(连假芯片一起过);check_ids --strict 通过;doc-links 35 处 (= 存量);新增文件 BOM/CR/U+FFFD/行尾逐项净。**本增量零生产代码改动**,未跑 @build。 --all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填) 与 ui-golden(ref/*.bin 不入库,新 worktree 天生没有黄金参考)三条失败,与本分支无关。P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界): net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片) sys_log_mcu 75 条 / 310-313=99.0%、12-12 console_log 104 条 / 188-189=99.5%、15-15 cfg_file_store_mcu 34 条 / 97-97=100%、10-10 soe_store 26 条 / 78-79=98.7%、4-4 合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。 账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29, 恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母 4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件 全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对: 下降 0 个文件。 基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只 定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED 可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开 一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正 两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04), 另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。 存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、 nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。 编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27, 先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK; id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。 文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、 12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。 本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。 10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、 net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示 全部修复,另开一轮(含测试字面量统一纯 ASCII)。 gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱); mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0 (纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。用户裁定「全部修」。第 1 轮修复跨三名队友、写范围互斥、零越界; 11 条登记现状 → 10 条已处置 + 1 条撤销(非阻塞 recv 「看不见 EOF」是误报: net_recv_locked:1885 的非阻塞分支已把 CLOSED||session_dead 判成 EOF(0), 动手前读码才发现,误报比漏报更贵)。 存储与日志(App/log/sys_log_mcu.c、soe_store.c、cfg_file_store_mcu.c、sys_log.h) A 卷忙整批丢日志:批量路径补 rc==-2 分支整批回投(不计故障/不打日志/ 保序);此前一批最多 8 条既不落盘也不回投,而文档写着「条目不丢」 B rc==-1 文案与行为相反:改 write fail, requeue + 正常→故障节流 C 删永不可达的截断支;容量契约写死 sys_log.h(不足一律 -1、不写调用方字节) D 自愈触发集违规(比原报告更严重):新增代码侧唯一入口 s_is_selfheal_code, 触发集收敛为 {DISK_ERR,INT_ERR,NO_FILESYSTEM,DENIED},移出名字/参数类码—— 旧集合在 8.3 同名同因下每轮删一次自己的历史日志且永不自愈,正是存储架构 §10.1 第 10 条严禁的归集;soe_store 侧同源取更保守子集(打开失败绝不 unlink) E 注释里的 FR_NO_SYSTEM 不存在,改 FR_NOT_READY / FR_NO_FILESYSTEM F syslog_post 满态留痕节流(A 修好后「队列常驻满」成必然场景,不修等于用 新刷屏换掉静默丢);丢弃语义一字未改 NET(net_socket.c/.h、net_types.h、net_socket_sim.c、lftpd.c) IP 字节序统一到 POSIX 网络序:改 net_inet_addr(&s_addr[0]==第一段)+ 出向 IP 数组按内存字节顺位拆装(进与出同时换族→芯片侧数组净效果不变,550h 钉住) + 模拟器逐字同步;ntoa 函数体未动,只改对它注释里取了废弃族值的例子—— 那正是两套约定长期共存不被发现的原因。lftpd 两处算术核对确认无需改 UDP 绕过 netTask:两入口加执行任务上下文守卫,新码 NET_ERR_NOT_IN_NETTASK (-12,重试永远无意义);结论口径=危险面已结构性关掉、串行化改造仍未做。 附带关掉第二条更隐蔽的路:非 netTask 自旋 recvfrom 期间控制块被 close 并 把同一 sockfd 分给新会话→旧自旋者消费新会话数据报 控制台(console_log.c/.h):冷却改独立 s_con_cooling 标志(旧写法在 tick==0 那 拍冷却等于没记);con_append 的判空位+推进 head+整行拷贝收进同一临界区(旧 为区外 check-then-act,环 >95% 满时可静默覆盖且既不打 [ERR] 也不计 dropped); Instance==NULL 与僵尸通道分诊(未初始化绝不 reinit);三本账分离 + 新读口 dbg_console_tx_timeout();[ERR] 行改为发成功才销账 测试:net_socket 452、console_log 127、sys_log_mcu 104、soe_store 27、 cfg_file_store_mcu 34;回归 net_listener 34 / net_events 34 / net_pending 43 / net_select 22 / lftpd 35 / lftpd_inet 28 / lftpd_io 33 / iec104_serv 28 / nand_port 149 / vol_lock 33 / soe 32 / cfg_file 151 全绿。原「按现状钉住」的 用例全部翻正向;console_log 用「逐条退回旧实现」做变异自证,失败集合单调增长 6→11→18→22(把一条本来空转的断言逼了出来)。 游标抬升:TC-NETSOCK-545→560、TC-SYSLOG-100→107、TC-CONLOG-135→167。 文档:控制台设计说明 §4.1 口径与 §13-O2 销项 + v1.5;运行与自检记录 §4.4/§4.5 四条;存储架构 §10.1 第 10 条推论(指向 s_is_selfheal_code);TRAP-NET-21 后续 条目;BSD 指南新增 §9.0 s_addr 唯一约定;覆盖率口径 §9「第 1 轮修复处置」表 + 后续项 5 条 + 台架复验 7 条 + 新坑 8 条 + 变更记录。 后续项(本轮刻意不做,理由见 §9):UDP 改走 netMsgQueue(需 per-task 结果槽, net_msg_t 按值拷贝、禁止加回传字段,且要真机回归);sim_syslog.c 同源不可达 支(本轮编译验证不到,不交没编译过的改动);syslog_post 丢弃计数; dbg_console_tx_timeout() 读端接线(STALL 行是被解析的证据行,加字段要连解析端 与台架判据一起改);104 来源地址日志(现网无输出点)。 gate: @build 0 errors 0 warnings(Code=213720 RO=18860 RW=1204 ZI=131500)。 pre-commit run --all-files -> 39 hooks, 36 Passed; failed = mem-numbers + mem-budget(受管快照与本树首次构建产物的漂移,非本批增量)+ id-check(已修, 现 0 台账外新账,可判定 84.63% > 基线 82.6%)。实测水位 RW_IRAM1 57.4%、 RW_CCM 87.9%,均低于 92% 红线;RAM/Flash 本批增量为数个 static 变量。 test-coverage / cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed;全量覆盖率 33/33 套件、line 89.3%、func 92.3%、逐文件下降 0。 按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。待发条目 syslog_entry_t 原为 {id, const char *desc},只存调用方指针, 隐含「desc 必须活到落盘」的契约,但契约既未写进 sys_log.h、也无机械 检查。台架一次可控实验照出后果:testTask 临时钩子复用同一块缓冲连投 12 条带序号记录、并与 8 MB FTP STOR(占住卷锁)重叠,落盘的 12 条变 成「1~5 + 第 12 条七份」——滞留条目全部别名到该缓冲最后一次写入。 叠加卷忙整批回投(条目可滞留到卷空闲),投栈缓冲即成悬垂读。 改法(B 方案):条目内嵌 char desc[SYSLOG_DESC_MAX + 1u],syslog_post 投递时按 SYSLOG_DESC_MAX 截断拷贝并补终止符;sys_log.h 参数说明同步 写明「投递即拷贝、不要求生命周期覆盖到落盘」。 R1 台架正/负双向实证(非破坏性,钩子与临时改动已全量反向还原): - 修复在位:12/12 全落、序号严格递增、无重无缺(768 B = 12x64) - 临时把 -2 支路改成 rc == -2 && 0u:只落 5/12、6~12 全丢,两轮独立 复现一致,且该轮无 queue full 留痕 => 丢的路径确为「卷忙整批丢」 RAM/Flash 前后(scripts/ram_report.py 同口径,同一 .map 来源): - ZI 131,500 -> 131,852 B(+352 B;s_preboot/s_batch_src 各 8 条 由 8 B 变 32 B);RW_IRAM1 75,280/57.4% -> 75,632/57.7% - Code 213,720 -> 213,780 B(+60 B);RW 1,204 B 不变 - RW_CCM 静态不变(57,376 B / 87.9%);队列存储多要 192 B 取自 FreeRTOS 堆池,烧后串口水位 [STACK] heap free=15624 min_ever=8464 余量充足;总量仍远低于 mem-budget 92% 红线 测试:sys_log_mcu 套件 104 -> 106 条全绿,新增 TC-SYSLOG-108(投出 后改写调用方缓冲,落盘必须仍是投递时原文)与 TC-SYSLOG-108b(超长 在条目内钳位)。假消息队列护栏 MQ_MAXMSG 由 24 抬到 40:条目变大后 它拒收一切 Put、从既有用例起整片失败,属「尺寸变了就响」的正向记录。 主机侧此前照不出该类问题,是因为用例传字面量、静态存储期无限。 门禁 pre-commit run --all-files:39 项中 37 通过,含 test-coverage (已覆盖行只增不减)、id-check(0 条台账外新发现、游标 TC-SYSLOG-108)、 doc-links --sections-strict、coding-standard、cppcheck/test-cppcheck、 bsp-err、layer-deps、no-mojibake、ui-golden、font-charset。仅 mem-numbers 与 mem-budget 失败:docs/内存管理/ 受管台账仍记改前 131,500/213,720。按分支约定不在分支上 --write 受管台账,留待合回 主干时统一回填,故 --no-verify 提交。 真机:产品构建 0 错 0 警告,重烧 + 回读校验 OK(suite=NONE dstr=off)、 启动标记齐全、无 [ERR]、VERDICT PASS。规格:docs/任务规划/测试与架构专项.md §2.1(模块级轻量断言宏,必须零生产开销)。 本提交只做道C-1:新增断言宏 + 2 处试点,不开 net 层大口子、不新增 test/host 目录。 改动 - 新增 Drivers/BSP/app_assert.h:APP_ASSERT 两级裁剪。 · 关闭态(默认,产品构建):编译期消失、零符号零代码;条件仍进 sizeof 的 不求值上下文,写错变量名/比较类型会当场报错,不会出现"裁掉=从没被编译器看过"。 · 打开态:失败交 configASSERT 用的同一个钩子 vApplicationAssertFailed() (裸控制台打 [ERR] ASSERT FAILED file:line 后软复位);宿主端(模拟器 / 主机单测)落 stderr + abort(),非零退出码不会被一键回归静默吞掉。 - 开关 APP_ASSERT_ENABLE 默认注释,策略与 ENABLE_DESTRUCTIVE_TESTS 同级。 - 纯宏、无函数、无数据,不新增 .c → 不涉及双端注册(.h 靠 include 路径找到)。 - Drivers/BSP/NET/net_socket.c:2 处试点,都落在"函数头写了限定条件、函数体 一项都没检查"的缺口上。 · net_close_locked:控制块必须落在 s_net_socks 数组内且 in_use——此前给出 越界或已释放指针会一路走到 memset(sizeof(net_sock_t)),直接踩脏 fd 表。 · net_drop_conn_locked:进入时必须 in_use + TCP 流 + 会话处于 ESTABLISHED 或 CONNECTING——函数体无条件置 CLOSED 并登记重建,将来绕过 NET_MSG_DROP_CONN 分派门的新调用路径会拆掉一个健康监听器(症状与 N-30 一族同源难查),断言把这条状态机不变量钉成机器可查。 两处均 netTask 上下文、非中断、非高频;条件纯读无副作用;既有错误返回分支 一条都没改(断言不替代错误码,见 app_assert.h 第四节纪律)。 - test/host/net_socket/run_unit_test.py:include_dirs 补 Drivers/BSP 一行,使该 套单测编 net_socket.c 时用真 app_assert.h,不造桩——假件与真头漂移正是本 runner「常量闸」要防的那类自洽假绿;stub 目录仍在最前,同名头命中不受影响。 - Drivers/BSP/README.md:根级文件表补 app_assert.h 一行。 两处有意偏离示意稿(理由已写进头注释) 1) 断言点不再额外发一条 DBG_ERROR:钩子本身已打印 file:line,而每条 DBG_* 调用点约 90 B Flash + 216 B 栈(AGENTS「日志不是免费的」);断言点又常在 「内核状态已不可信」的现场,走裸控制台比走日志行缓冲稳。 2) 头落 Drivers/BSP(与 dbg_log.h、bsp_err.h 同级)而非示意稿的 App/util:它是 纯宏叶子能力,落 App/util 会让 BSP 新增一条反向 include App 层头的边,而 layer-deps 存量基线只减不增、加不进去;落 Drivers/BSP 已在 MCU 与模拟器双端 include 路径内 → 双端零工程文件改动(uvprojx / CMakeLists 一字未动)。 验证(工作树 _wt/S12-assert,分支 task/S12-assert) - @build 产品态:BUILD_OK,0 错误 0 警告。 - 体积与 RAM:Code=213780 RO=18860 RW=1204 ZI=131852,与 scripts/ram_baseline.tsv 的 2026-10-24 基线行完全相同 → Flash/RAM 增量 0 B;.map 中 app_assert 相关 符号 0 条(规格完成判据"无新增符号")。gate 全绿,无需 mem-budget 例外。 - 打开态实测(临时取消注释编译,跑完已还原成注释态):0 错 0 警, Code=213916(2 处断言共 +136 B ≈ 68 B/处,无每调用点栈代价),RW/ZI 不变。 - 宿主 MSVC 三态实测:关闭态断言不求值但条件仍做类型检查(错误标识符报 C2065);打开态真条件不触发、假条件 stderr 打 [ASSERT] FAILED 后 abort; net_socket 主机单测 452/452 通过(用真头,开关默认关)。 - 门禁实跑:cppcheck(warning/perf/portability/style)、no-mojibake、coding-standard、 layer-deps、bsp-err、id-check、doc-links、registration-check、mem-budget 全 0。 - 台架 @test 未跑:bench 锁归 Lead;产品态本改动零行为差异,无需烧录回归。 遗留 - 打开态未在台架上真跑一轮(需 bench 锁 + 一个故意造假的注入点):待台架实测。 - 文档销项(测试与架构专项.md §2.1、并行分道方案.md 对应行、AGENTS 任务表) 归主工作树统一处理,本分支未碰 docs/任务规划/**。背景:A-5 残余(docs/任务规划/测试与架构专项.md)说得很直白——mem-numbers 在 CI 被跳过(runner 无 Keil/无 .map),上一轮只把"跳过"声明成一条 ::warning::, "盲区本身未消除":看到 CI 全绿仍会以为体积也守住了。 复核时本地侧同样漏:ram_baseline.py --gate 在无 .map 时自适应 SKIP 且返回 0; mem-budget 甚至没进 CI 的 SKIP 名单——它在 runner 上以"Passed"的面貌出现,实际 什么都没检查。两处共同点是**"没跑 = 通过"**。 做法:给体积数字配一条可提交、CI 可复核的**同码凭证**,而不是要求 runner 装 Keil。 - 新增 scripts/check_size_gate.py(口径与边界的唯一正文在其模块 docstring) · --stamp:只在三条都成立时才盖章——① .map 存在且解析得出执行区与 Grand Totals;② .map 不早于指纹范围内任何源码的 mtime;③ 受管区占比不超过 mem-budget 用的同一常数。凭证因此不是随手可写的。 · --local:工作树指纹必须等于凭证指纹,否则 FAIL(="改了代码但体积从没测过") · --ci:不需要 Keil 也能判的两条——同码性 + 按凭证记录的数字复核 92% 红线 · --show:排障用。指纹 = git ls-files 范围内逐文件 sha256(路径+内容) 再聚合, 覆盖 App/ Drivers/ Src/ Inc/ Lib/ Middlewares/ test/board/ 与 MDK-ARM 的 .sct/.s - 新增 scripts/size_attestation.json:本树的凭证(由本树一次真实 @build 盖戳) - scripts/ram_baseline.py:--update 顺手盖戳——"每次 @build 后跑 --update"是既有 纪律,不新增人工步骤;在函数内 import 防成环;盖戳失败只告警、不改 --update 返回值(新鲜度由门禁自己 fail-closed 兜)。_util/_read_tsv 提为公开名 region_pct / read_tsv_rows 供其复用(体积口径禁第二处定义);--gate 的无 .map 文案改成明说"SKIP 不等于 PASS"并指向兜底路径。 - scripts/ci_local.py:新增 L0c 体积门禁步(fail-closed、无条件照跑、约 0.3 s) 与 --size-only 开关;docstring 里把"无 .map 时跳过两根钩子"的语义补全。 - .gitea/workflows/ci.yml:L0 作业末尾加「体积门禁」步(if: always(),无需 Keil); 主干 push 拦红、其余事件 --warn-only(分支上代码尚未定型,拦红只造噪音,还会 诱发多条道同时抢写 scripts/ 台账);SKIP 名单补 mem-budget,让它诚实显示 Skipped 而不是假 Passed;文件头的"门禁盲区声明"改写为"已关闭为强制路径 + 仍守不住的三项"。run: 块全部纯 ASCII(PS 5.1 无 BOM 按 GBK 解码的实测坑)。 - scripts/README.md:登记新脚本与凭证文件,补用法行。 验证(工作树 _wt/A5-size-gate,分支 task/A5-size-gate) - @build 0 错 0 警:Code=213780 RO=18860 RW=1204 ZI=131852——与 ram_baseline.tsv 的 2026-10-24 行逐项相同(本改动不碰固件源码,指纹不变,零体积增量)。 - 门禁实跑全 0:check-yaml / trailing-whitespace / end-of-file-fixer / check-added-large-files / no-mojibake / id-check / doc-links / mem-budget。 - 负向 8 例全过:改凭证指纹 → --local 与 --ci 都 rc=1;凭证占比改成 95% → --ci rc=1,加 --warn-only 则 rc=0;凭证写成非法 JSON → rc=1;用 mtime 回拨的 .map 去 --stamp → 拒绝且不动凭证;把区 max 改小到必然超线再 --stamp → 同样拒绝。 - 正向:--stamp 之后 --local / --ci 均 rc=0,并与 tsv 末行互证同源。 - --update 的盖戳联动单独验(把 TSV_PATH 与凭证指到仓库内已 gitignore 的 ci-artifacts/ 探针文件,跑完清理,本树台账未被测试污染)。 - ci_local 全量:L0.5 33 runner / 2227 断言全绿、gitlint 10/10、L0c rc=0。L0 里 test-cppcheck 与 test-coverage 各报过一次"files were modified"——单独重跑两条 均 rc=0 且工作树无改动,判定为三条道并行跑主机单测时 %TEMP% 里按套件名派生 的 obj/exe 互相踩踏(harness 产物名全机共用一个 TEMP 目录),非本改动引入。 RAM 增量:0 B(未动固件源码,凭证数字与本树一次真实链接一致)。 gate:全部通过,未使用 --no-verify。 遗留 / 需要 lead 裁定 1. 合入后主干 push 会要求"同码测量":done 流程在 @build 0/0 之后要多跑一条 python scripts/ram_baseline.py --update,并把 scripts/size_attestation.json (数字变了则连 scripts/ram_baseline.tsv)一并提交。这就是"强制"的含义,但 确实新增半步——若你想先只在本地强制(L0c),我把 CI 那步改成 --warn-only。 2. 文档口径同步我没做(docs/任务规划/**、docs/门禁说明/**、docs/CI/README.md 归主树):A-5 销项时建议把"CI 不守体积"改为"CI 守同码性 + 红线,绝对数字仍 由本地真实构建产生",正文引用 check_size_gate.py 的模块 docstring。 3. 编译器选项(uvprojx)刻意不进指纹:UV4 每次构建改写它且不入库,进指纹会让 门禁在"刚刚构建完"的那一刻自己变红;代价是改优化等级/宏不触发本门禁,需人工 重测——已写进边界正文。7feeea6a9ba8ec745结论先说:wd_required_mask() 里那条"硬件层测试构建剔除 netTask"的分支,自它被写进 仓库那天起**从未生效过**——不是逻辑错,是本编译单元根本看不见判据宏。台架一轮 (2026-10-25,boot log 20261025_050539)把它照出来了: - 468 条 [ERR][WD] task stall 里,**431 条是 mask=0x1F5 / req=0x1F7**; - 0x1F5 恰好等于那条死分支本该要求的集合(default|ftp|ui|timesync|adc|iec104| protect,NET 位不在内)——该动的任务全动了,只是"要求方"看不见自己的例外, 于是按产品掩码 0x1F7 追 netTask 的心跳位;而 netTask 在 ENABLE_HW_LAYER_TESTS 下按设计入口自挂起(CH395F 归 testTask 独占),永远追不到。 根因:ENABLE_HW_LAYER_TESTS 定义在 test/board/ch395f_test.h(经 test_config.h 拉入), 而本文件没有 include 任何一条通往它的路径 → #if defined(...) 在本 TU 恒为假 → 死代码。 同一个需求早有既有做法:App/task/net_task.c:39 就为这个宏 include 了 test_config.h, 注释还写着"产品构建全未定义,无作用"——本文件只是漏了这一步。 修法:补 #include "test_config.h",并把根因与 req/mask 证据写在包含点上方(同一事实 不另开文档;销项时 docs 引这里)。写域扩到 App/util 由 lead 背书(看板 task-9)。 零产品影响(实测,不是推演): - 产品态(TEST_SUITE_* 全注释、宏未定义 → 走原分支)@build 0 错误 0 警告, Code/RO/RW/ZI = 214104 / 20388 / 1204 / 131852,与主干 2026-10-25 台账逐项相同。 - 测试态 hw_nopc 尺寸也与修复前逐项相同:改的只是"选哪个常量",没有新代码路径。 - 修复后净效果(下一轮台架复核):431 条 net 类 stall 消失,剩下的是 uiTask 间歇 缺位的 37 条(0x1E7 / 0x1E5)——那是另一件事,不在本提交里掩盖。 顺手记两条**没在本提交改**的发现(判据语义,归 lead 裁): 1) 抽样判定是**精确匹配**(注释自述:"掩码必须恰等于会置位的任务集合")。而 netTask 自挂起前会喂一次位 → 那一瞬间 mask=0x1F7 又不等于剔除后的 0x1F5 → 照样报 stall。 即"从掩码里剔一位"与"精确匹配"天然打架;要不要放宽成"观测 ⊇ 要求即可"值得单定。 2) 本轮 miss 一路涨到 1971 而**没有停喂、没有复位**:说明"连续未达标即停喂"的阈值 路径在 HW 层测试构建里未被触发(或 IWDG 尚未启动)。这条对产品侧更重要,值得单查。 随附 .cubemx_survival_baseline.json:本文件在存活审计里是 kind="generated" 的整文件 哈希项 watchdog-module("非 CubeMX 管辖,但须确认未被误删"),有意改动按脚本自述用 --write 重建基线。**先证明漂移 100% 来自本笔**:把我的 include 临时撤掉再跑 --check → rc=0(基线与 HEAD 逐项相符),因此不存在"别人早就改了没登记"的情况。 --write 顺带补出一条**基线里原本缺失**的条目 net-msgq-adopt(Src/freertos.c 的 RTOS_QUEUES 区,须含 net_msg_queue_adopt):它此前不在基线里 → --check 一直在静默 跳过这一项。本笔把它一并登记,属于意外收获,不是本笔的目的。 gate:cppcheck / coding-standard / bsp-err / id-check / no-mojibake / trailing-whitespace / end-of-file-fixer / mem-numbers / mem-budget / cubemx-survival(已 --write 重建基线)全 0; 未用 --no-verify。台架第一轮(boot log 20261025_050539)判 VERDICT FAIL,命中 469 条 [ERR],两类: 1) [ERR][CH395] ch395f P2 boundary fault @get_state sn —— **设计内**:Phase 15 的 sn 越界负向用例故意命中驱动守卫(Drivers/BSP/CH395F/ch395f.c 的 ch395f_p2_fault_note():首次 DBG_ERROR、之后只计数),用例本身 PASS; 2) [ERR][WD] task stall ×468 —— **设计外**,已在a9ba8ec单独修(掩码死分支), 不用豁免去掩盖它。 本笔只管判据:套件回归这条路要豁免的是"设计内守卫 [ERR]",不是崩溃。 为什么不能只是把 [ERR] 从 forbidden 里删掉(核过 verify_log 源码与固件崩溃行): 四种 Fault 的崩溃行是 [ERR] HARDFAULT / MEMFAULT / BUSFAULT / USAGEFAULT (Src/stm32f4xx_it.c 的 hard_fault_report 先打 "[ERR] " 再打标签),而 verify_log 的 forbidden 是**大小写不敏感的子串匹配**(fl in ln.lower())→ 默认集合 HardFault;assert;[ERR] 里的 HardFault 只兜得住硬Fault,**MEM/BUS/USAGE 三种平时 完全靠 [ERR] 兜**。据此: - 新增 FORBIDDEN_SUITE = HardFault;MEMFAULT;BUSFAULT;USAGEFAULT;assert;bring-up FAIL - CH395F 用它(原来吃默认值,含 [ERR]); - GD5F 也用它——旧值 "HardFault;assert" 正是上面那个漏法(当初只为豁免 TC-GD5F-704 的设计内 [ERR] 就把整条 [ERR] 摘了,等于顺手放走了三种 Fault), 现在既保留豁免意图,又把崩溃判定补齐; - STORAGE / RTC 不动(它们没有设计内 [ERR],继续吃含 [ERR] 的默认判据)。 边界写死在注释里:产品 @test 的判据(.serialtest/config.json 的 forbidden 含 [ERR]) **一个字节没动**,豁免只活在"套件回归"这条路上,渗不回产品路径。 test/board/ch395f_test.h: - Phase 15 汇总实测 21/21(旧注释写"台架 20/20 PASS"是历史记录,已按本轮改正); - 记一条台架档案:镜像 ch395f_hw_nopc_v2.hex 单次开机跑完 1/12/15,汇总行 9/9、1/1、21/21,failed=0,全程无 Fault;并记下同轮暴露的掩码死分支结论与它的 证据位置,后来人切阶段时不必再踩一遍。 验证: - py_compile 通过;test/run_all_suites.py --check-build --suites CH395F 走**官方编排** 实跑 PASS(BUILD_OK 0 错 0 警,Code=217468 RO=21452 RW=1268 ZI=134076),脚本自己 还原 test_config.h 并重建产品固件;这个 Code 与上一笔实测表里"默认阶段 11/14"一行 逐项相同 → 两个独立工具互证同一组数字。 - gate:id-check / no-mojibake / trailing-whitespace / end-of-file-fixer / doc-links / cppcheck(test/) / test-suite-switch(产品态 Skipped)全 0; 未用 --no-verify。 - RAM/Flash 增量:0 B(Python 与注释,不进固件;test/board/*.h 的注释不改代码)。 下一轮台架等 lead:f2 镜像我会在 sync 到 A-3/B-3 之后重编(现在这份 ci-artifacts/p110_fw/ch395f_hw_nopc_f2.hex 戳 05:23:03,若中途主干动过固件就要重编), 判据用上面的 FORBIDDEN_SUITE;请顺手带回两个数:[ERR][WD] 总条数、mask 含 bit4(ui) 缺位的条数。按 docs/UI设计/UI_数据供给设计说明.md 的 D 方案把 App→UI 数据通道落地: App 组帧、BSP 存帧、页面只读;UI 侧零 App include,薄包装只在两端适配层。 本笔是纯增层、不接任何页面 → 零像素变化(菜单 8 状态 + 内容页 11 页黄金 参考逐字节不变,simulator/tests/ref 无 diff,未刷基线)。 新增 - Drivers/BSP/UI/ui_data.{c,h}:实时量 rt / 参数 par / 装置信息 info 三通道存储 + 发布序号盖章 + 只读访问。零 App include、零 LVGL include(分层叶子); 读写同在 uiTask 上下文串行 → 无锁、无双缓冲、无 seqlock(契约 §5.1)。 - App/app/ui_data_feed.{c,h}:快照唯一填充者。rt/info 每 200 ms 一帧,par 只在 prot_set_checksum() 变化时重发;首轮立即发布,免启动初期闪一下 "--"。 - Drivers/BSP/UI/ui_w_soe.{c,h}:SOE 二进制记录的显示名映射与注入式 reader 契约。未标定闭锁=7 / 标定复归=8 已进表——编号按 App/log/soe_rec.h 与 App/algo/prot_elm.h 现场数过(类型 1..8 + 哨兵 9、元件 0..8、ELM_NONE=0xFF、 FLG_IV=0x80),不采信文档与消息转述。reader 备好但两端未绑定:落盘侧还 没有只读 API(Lead 已裁该 API 归下一工作项),未注入按"无记录"空态渲染。 量纲诚实性(契约 §4.1 同步改成真值口径,不再抄旧码值表述) - eng 不写"0 = 原始码值":标定层 ROM 系数是引脚侧 LSB,采样环折算出来的就是 ADC 引脚电压(承诺边界 docs/任务规划/数据可信.md §2.6);电气 8 路 adc_cal_flags() 的 VALID 全置位才升 eng=1 二次值——整帧共用一个 eng,只要 还有一路是引脚电压就不能贴"二次值"标签,宁可整体保守。 - TEMP 不参与线性标定 → 恒带新增品质位 UI_DATA_Q_RAW=0x20,页面不挂单位。 - UB/UC/IB/IC/IN 无基波相量实例(V0.1 削顶)→ Q_IV|Q_OV;未满一周波 → Q_NT。 - 谐波另开 ph[] 相量实例槽带:yc 的语义通道下标容不下同通道的二次/五次谐波。 接线与双端注册 - App/task/ui_task.c:ui_menu_init 之前 ui_data_init + 首帧发布;主循环每轮 ui_data_feed_tick() 排在 ui_port_step 之前(本轮渲染用本轮刚发布的帧)。 - simulator/main.c:发布固定合成帧(无头与窗口两个初始化点,先于建页); 定值不手抄 40 个数,改为链入 App/algo/prot_set.c 取出厂默认(抄表等于造 第二真相);build_date 用固定串而非 __DATE__,否则跨天重建会把装置信息页 黄金参考刷掉,而黄金参考是判据本身。 - 注册:uvprojx 加 3 条(App 组 ui_data_feed.c + UI 组 ui_data.c/ui_w_soe.c)与 simulator/CMakeLists.txt;ui_data_feed.c 不进模拟器 → check_registration.py 的 SIM_EXEMPT 加一行(App/app/app_main.c 先例,已报备)。 主机单测与钩子 - test/host/ui_data:82 断言全绿。钉三类事:快照语义(首帧前读 -1、序号由本层 盖章、三通道互不串台、NULL 防御、init 幂等复位);跨层不透明下标现场一致 (yc 序 = adc_semantic_t、q10 序 = prot_set_id_t,同一 TU 里 include 两侧头); SOE 编号与名字表现场一致(八类事件都有名、采样/标定四个词互不相同、 名长不超列宽、越界回占位)。 - .pre-commit-config.yaml 加 ui-data-unit-test 钩子(第 43 个,Lead 已批自加)。 - scripts/id_registry.json 建族 TC-HOST-UIDATA(属主登记处 = 单测目录,同 TC-HOST-ADCCAL 先例;不建族则新号被编号门禁判"未登记前缀")。 体积(相对 HEAD 台账 Code 215744 / RO 21396 / RW 1224 / ZI 131984) - Code +1272 B、RO +108 B(字库 4 字,见上一笔)、RW +24 B、ZI +696 B → ER_IROM1 238520 B(22.7%)、RW_IRAM1 76504 B(58.4%,台账 57.8%)。 - .map 逐项:ui_data.o 488 B(Code 184/RW 12/ZI 476)、ui_data_feed.o 221 B (1034/12/209);LVGL 池 0 增量;ui_w_soe 未接线不进镜像(.map 无其符号)。 - 台架未跑(本笔零像素、零接线):首帧时刻、200 ms 刷新、eng 升档与遥信 位图真值全部留"待台架实测",接线完成后请 Lead 排窗。 遗留与风险(随回报进台账) - 写路径(契约 §6 的 param_request/state)不落:无消费者就是永不执行的死码, 随整定 4 页一起落;届时须先定"UI 整定 vs iec104Task 写 prot_set"的并发口径 (prot_set_set_sealed 的影子区非重入)。 - 公共地址 CA 未下发:固件里 CA 逐帧从 ASDU 头解析,无集中常量可取。 - 遥信位图与 App/protocol/iec104/iec104_pts.c 板端源是两处镜像(该文件位号 写在函数体内未导出,UI 不得 include 协议头),开入采集落地时应并成单源。 - 版本类字段是本文件内的宏(仓库无产品级版本登记表,值沿用页面既有显示 值);软件校验位在拿到构建期 CRC 步骤前一律显示 "--",不抄参考遗留值。 gate: 全量钩子(--all-files)40 项通过;cppcheck/test-cppcheck/coding-standard/ layer-deps(基线仍 3 条)/registration/bsp-err(存量 38 条不变)/font-charset/ id-check(增量无新账)/doc-links/ui-golden/主机单测与覆盖率全绿;唯一红项是 mem-numbers 与 mem-budget——两者只因 Flash/RAM 台账未回填(禁区,归 Lead)。 gate: only mem-budget/mem-numbers pending lead ledger backfill 自查:BOM(Drivers/BSP/UI/* 与 App/task/ui_task.c 保持 EF BB BF,新建 UI 两件 同规范;App/app 与 test/host 无 BOM 与本目录一致);CR = 0 全 LF;U+FFFD = 0; 字符串字面量纯 ASCII(中文只进注释)。道F 批 2 / task-17。定位 + 单点收口,不开 net 层跨上下文大口子。 【定位(链路,全部代码可复核)】 探针挂起 30s → time_sync.c:149 预算耗尽 → :187 net_drop_conn()。NET_MSG_ DROP_CONN 处理器(net_drop_conn_locked)内同步跑 net_sock_reset_sync (QUARANTINE)(2ms 节奏、400ms 底/600ms 顶,自家注释原记「等待期 netTask 有界占用 ~400-600ms」):netTask 的 1ms 主循环冻结在同一迭代 → NET 位 停摆;串行总线整窗停摆 → UI 推流/FTP/104 在途消息回执顺延;对时调用方 睡在 net_wait_result 等回执期间无分片心跳 → 五位同窗缺席 → 连续 8 个 100ms 抽样停喂 → 声明 [WD] task stall(V5 时代 req=0x37、快照只剩 bit0, 即日志 mask=0x00000001)。被动断连/EOF 走 net_listener_tick 异步沉降机 (D10 条令)故纯 FTP 负载 0 stall——只有主动 drop 留在同步原语里。 【修复】监听型 fd 的芯片拆除改为 net_listener_settle(QUARANTINE) + net_listener_request(REQ_DROP) 登记即返,拆链/排渣/等 CLOSED/重建全部 交 net_listener_tick 30ms 节奏——与被动释放同一台机器(E16 台架实证该 自建序列 RST 10/10、FIN 5/5)。非监听 client fd 无监听可保,同步形态 原样保留(全仓零生产调用者)。返回值语义显式改为「拆除与重建已登记」 (net_socket.h 注释按 lead 要求写明,防未来调用方按旧语义写)。 APP_ASSERT(S12 试点)分派门与断言位置未动,登记时序下仍成立。 【评估否决留档(lead 要求论证入文档)】「缩短重建重试 5×2s」不采: 该退避在 time_sync_open_listener() bind/listen 失败路径、已被 time_sync_delay_fed() 分片喂保护、不在缺口链上;缩短反撞 TRAP-NET-09 沉降窗。全文见 docs/网络/网络自愈架构设计.md §4.6(已改写)。 【竞态与信源核对】① 主路径置 CLOSED 后迟到的 DISCONNECT/TIMEOUT 被 net_events 状态门拦截(只认活会话态),不会把刚登记的 QUARANTINE 降级; ② 零新增读清点——全局寄存器唯一读者仍 net_poll,隔离窗两处本 Socket 读清是 net_listener_tick 原位,§4.10 三条准入判据不触发;③ E13 物理 边界(对端不配合 FIN_WAIT_2 长尾)逐字不变,只移动等待发生的位置。 【测试】net_socket 主机套件 457/457:TC-NETSOCK-240f/321b/321b2/460e 重锚到登记式(settle+REQ_DROP、rst 机零启动、tick 预算 ≤20、无 [ERR]), 新增 321g/h/i + 460f 钉住 client fd 旧同步形态。全量 --all-files 40 绿。 【体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行)】 ER_IROM1 237328→237344(+16 B);RW_IRAM1 75792(+0);ZI 0。 @build 0 错误 0 警告(Code=215948 RO=21396 RW=1232 ZI=131984)。 gate: only mem-budget/mem-numbers pending lead ledger backfill test-coverage 本条**绿**(已覆盖行净增,未动用裁决③的覆盖率例外—— 旧同步路径行经 client fd 新用例继续被覆盖,净差非负)。其余含 id-check/doc-links/全部主机单测绿。仅 mem-numbers/mem-budget 两红 = 台账 +16B 漂移待回填(lead 单写者禁区)。 【越界申报】① test/host/net_socket/{ns_s3_msg,ns_s4_state,ns_s5_io}.c (批准③同类:只动本任务相关用例,未碰他人行);② docs/网络/ 网络自愈架构设计.md §4.6 改写(lead 本任务指令明示)。登记簿 N-17 销项与 README 波次行归 lead;docs/内存管理 数字未碰(禁区)。 台架:V5 复跑判据(e02_probe + FTP ≥30min 零 stall / 零复位)属网络 对端类——待 lead 排窗,步骤清单随 done 消息发出。道F 批 2 / task-18(评估任务:结论即交付,零代码改动,未顺手重构)。 【结论(全文 = docs/子系统设计/控制台单写者日志设计说明.md 文末附录 A.1-A.4)】 A.1 S8(非阻塞日志;需求出处 = 链路延迟测试说明 §9.2/§10.2)在**生产者侧已由 单写者形态达成**:实测日志税 4402µs/行(L10)→ 亚微秒~µs 级整行入环; 溢出 = 有界 200ms + dropped 账 + [ERR];冲刷 = 有界裸发。S8 判**已实现**。 A.2 TX 中断/DMA(原案后半)维持 D4 否决;复议条件绑定 S-3(TX-only 根治 ORE 僵尸,批 2.5 独占轮):根治后 tx_timeout 实测增量 > 0 才复议——S-3 之前 动 TX 路径 = 给即将被移除的场景写两遍代码。 A.3 与 N-2 合并判定 = 同源不同题:判据②(丢弃可观测)已具备(v1.5 三本账 + syslog 丢弃账);判据① 运行期窗内静默随 N-17(merge 969fc5c)待复跑核验, 附录并给出「无源 vs 真停滞」机判口径(账目增量 / [ERR] dropped 行对照)。 销项条件 = V5 复跑(e02_probe + FTP ≥30min)运行期 stall 与非开机 MISS 双零; 销项动作与登记簿行清理归 lead,本提交未碰 docs/任务规划/**。 A.4 遗留面全部既有(O1/O2/O3/O4/S-3 归属不变),无新增。 【最小实现】判定为**无需**(三结论:已实现/否决/观察窗内)。唯一候选(O2 的 STALL 证据行接 tx_timeout)涉 App/util 与台架证据行解析端,超本卡写域,维持 O2 后续项登记。 【改动】纯文档 3 个:控制台单写者日志设计说明.md(附录 + §11 时间线 + 修订 记录 v1.6);链路延迟测试说明.md §10.2 第 9 条划状态 + 第 14 条现状互链; 实机测试实施指南.md §7.2 结论段互链 N-17 修复与销项条件。 【越界申报】docs/子系统设计 + docs/测试说明 3 篇——评估类任务的结论落文档 即交付物本身,同 n22 批准②的先例类别;未碰单写者/登记表以外他人文件。 【门禁】check_doc_links --all:本单 3 篇引用全绿(首轮照出附录 3 处跨行/裸形 小节引用已改紧邻完整路径写法;仓内余 32 条 = simulator/SDL2 第三方路径空格 既有噪音,与主干一致、非本单引入);id-check exit 0。零代码 → 无需 @build; soe_store/道E 窗口零交集(本卡零代码)。ui_w_soe 组件(task-14 落)此前两端都没绑 reader,只能显示"无记录"空态。本批补齐三件事: App 侧只读遍历、两端 reader 绑定、SOE 记录页与菜单注册。 App/log/soe_store 只读 API(Lead 已放行,本批独占这两个文件) - soe_store_count() / soe_store_read(idx, soe_rec_t*):与 syslog_count / syslog_get_record 同构——8 条一窗(512 B)整块读、命中窗口零 I/O、卷锁 try(0 超时) 非阻塞;定长记录逐条 open 等于同一扇区开 4 次,SPI NAND 上这个差距是肉眼可见的卡顿 - 读者不得看到半成品记录,三道保障缺一不可(规则写进 soe_store.h 头注,不只是提交信息): ① 条数只数整条 64 B 槽(写者留下的半条尾槽天然不计入);② 窗口代数:写者每次成功落盘 / 容量重建在**卷锁区间内**自增代数,读者比对填窗时记下的代数,不符即重填(对齐 32 位读写 在 ARM 上原子,跨任务无需临界区,最坏多读一轮);③ 单条硬门槛:soe_rec_parse() 校版本 + 校验和,撕裂/半写一律拒收 - 容忍的竞态只有一个:count 与 read 之间新落一条 → 本轮少看一条,下轮重绘(200 ms)自然 追上;所以边界一律用锁内重取的条数,不信调用方传进来的旧值 - API **不带类型掩码**(Lead 裁定 2026-10-25):5 个记录页怎么分组是界面语义,不进日志存储 层;环容量小、页渲染全扫无成本,真变大了再加带 mask 的优化 API(YAGNI) ⚠ 顺带修掉一颗定时炸弹:菜单生成器与生成物漂移 - 提交里的 Drivers/BSP/UI/ui_menu_tab.h 带着 5 个表查询 API 的规范注释块,而 tools/menu_gen/gen_menu.py 只会输出"声明 + 行尾短注释"——说明历史上有人**手改生成物** 补注释、没回灌生成器。我这次加 #page=soe 后重新生成,等于把那些注释抹掉,bsp-err 立刻 报 5 条 return-doc - 修法不是把生成物改回去(下次重新生成还会再抹一次),而是把注释块**回灌进生成器**; 回灌后重跑 gen_menu.py 产出的 ui_menu_tab.h 与 HEAD **逐字节一致**(git diff 为空), 漂移关闭。生成物里的手工优化 = 一定会被覆盖的优化,要么进生成器要么别改生成物 时标口径(跨模块陷阱,注释即防线) - 页面时标取记录内的 CP56(事件自己的时标,不是"现在")。它与运行/自检记录页同口径,靠两条 事实叠出来:sys_clock 的 epoch 域本身就是本地时间(App/util/sys_clock.h 的 sys_clock_set_unix 注明"Unix epoch time (秒,本地时间)"),而 soe_rec.c 的 epoch→CP56 分解不加任何时区偏移。**谁"顺手补 8 小时",SOE 页就会和文本记录页差 8 小时**;要引显式 时区必须两侧一起改 显示名回正:类型 7 用源词"未标定闭锁" - 组件名字表里原写作"标定闭锁"——缩掉一个"未"字后意思可以读成"把标定闭锁了",与"因未标定 而闭锁"几乎相反;源词见 App/log/soe_rec.h 与"数据可信"卡 5,UI 表现层无权改写事实词 - 模拟器的 3 条假记录刻意含一条 type 7 + ELM_NONE(非元件支路):这是 7/8 两个新类型 **真的接上屏**的第一个像素级证据——只有主机断言时,名字表写错也照样一片绿 - 主机测试的宽度护栏随之按"画到哪一列"分列:动作/返回进值列(4 汉字 = 12 字节),其余类型 名进名列(7 汉字 = 21 字节);原来一刀切 12 字节会把正常名字判成违规 页面与注册 - 新建 Drivers/BSP/UI/ui_page_soe.{c,h}:薄跳板,一条记录两行共用组件内单条读盘缓存, 实例独立(两页共用一份缓存会互相命中对方记录号,task-6 的教训) - 菜单树 SOE记录 加 #page=soe;重生成 4 个生成物,uvprojx / CMakeLists 两处注册由生成器 自动写入(本批 uvprojx 只含这一条新增,无 UV4 抖动) - reader 绑定在两端适配层:真机 App/task/ui_task.c(CP56 → UI 视图 + 容量钳位), 模拟器 simulator/main.c(固定 3 条、时标固定串,保证黄金参考可复现) 两处被门禁当场抓住的记录(都是有效拦截,不是噪音) - id-check 拦下注释里裸引"P2-10":编号引用必须可判定归属,代码注释也一样 → 改为指向 App/log/sys_log_mcu.c 与事实本身,不留裸编号 - 又一次 BOM 铁律现场复现:一次编辑剥掉 ui_w_soe.c 的 BOM,ARMCC 立刻报一串 #8/#7/#20 假语法错(missing closing quote 之类),而 MSVC 侧 0 警告完全照不出来;补回 EF BB BF 即 0/0。本批提交前逐文件比 HEAD 做了 BOM 漂移审计(临时脚本,不入库)——建议把这条 审计固化进 ui-verify skill 或门禁(不在本道写域,已回报 Lead) 验证 - @build:0 错误 0 警告;模拟器:0 警告 - 内容页回归 15 项 PASS:新增 soe 页 9 条文本断言(两条时标、过流Ⅰ段+动作、+返回、 未标定闭锁、页码),其余 11 页逐字节不变;菜单 8 状态 PASS、池 5368 B - soe 页刷新黄金参考前出图肉眼确认:6 行两两成对、19 字符时标在 94 px 名列内不越屏、 非元件行的类型名落在名列且值列为空 - test/host/ui_data 83/83、test/host/soe 32/32;13 个代码/文档/注册类钩子(含 coding-standard、bsp-err、id-check、cppcheck、registration-check、layer-deps、 font-charset、doc-links)全 Passed - 全量钩子红项只有同族三条:mem-numbers / mem-budget / size-gate(L0c),root cause 一条: Flash/RAM 台账与同码凭证回填归单写者 → 本批 --no-verify 体积(相对b746a1f台账 Code 217204 / RO 21504 / RW 1256 / ZI 132680) - 实测 Code 219712、RO 21388、RW 1288、ZI 134272 → Code +2508、RO −116、RW +32、ZI +1592 → Flash 合计 +2424 B、RAM +1624 B(含本批之前的三页回炉) - 本批新增里约 1064 B ZI 是 SOE 读窗口(512 B)+ static FIL(528 B),一次性投入、页面打开 才用得到;RAM 再紧张可把窗口降到 4 条(256 B)拿回一半,代价是跨窗记录多一次 I/O 待台架实测:真机 /log/soe.log 有记录时的翻页与尾部行为、卷忙时该行退化显示、SOE 页与文本 记录页时标一致性(跨 8 小时口径的现场核对)、记录增长时窗口代数失效后的重读节拍。 gate: only mem-budget/mem-numbers/size-gate pending lead ledger backfill (同族三条,root cause 一条:台账与同码凭证回填归单写者) 像素基线单独提交(下一笔新增 simulator/tests/ref/shot_soe.bin):本笔单独 checkout 时 ui-golden 会报"缺参考",属预期拆分;上述钩子结论取自"本笔 + 下一笔"的合并树。 自查:BOM 漂移审计 17 个改动文件全对齐 HEAD;CR = 0;U+FFFD = 0;字面量纯 ASCII。四个空叶子(参数定值 → 查看 → 参数/定值/控制字/软压板)接上 par 通道。页面薄、模型厚: 新建 Drivers/BSP/UI/ui_w_setview.{c,h} 收四张视图表 + Q10 换算 + 越界反显,四个页面各是 一份实例 + 四个跳板钩子;渲染与分页复用批 1 的 ui_w_setlist(查看态 commit=NULL:无保存行、 无编辑通路,§3.2.6 查看列口径)。 行表三项口径全部有真源,一项都不在这份文件里拟定 - id = App/algo/prot_set.h 的枚举序。BSP 不 include App 头(分层门禁),所以行表里是裸数字 + 行尾注枚举名——这份对不上就没人能在 BSP 侧编译期发现,于是从外面钉:见下面的新脚本 - min/max = App/algo/prot_set.c 的 g_prot_set_meta Q10 上下限按该行小数位换算的结果。整定页 下一批复用同一批表,界内界外一处一份,不会出现「查看看着合法、整定说越界」两套边界 - 显示名/小数位/单位 = docs/UI设计/页面扩充详细方案.md §3.2.2~§3.2.5 四张表 一处偏离文档(不是静默偏离,写进三处:代码注释 / 文档落地注 / 本正文) - 四个返回系数按 2 位小数显示,文档写 3 位。理由是量值真实性不是排版:Q10 存不下 0.95 (0.95×1024 截成 972 → 真存 0.94921875),按 3 位显示就多打一位根本不存在的数字;整定单 上写 0.95 的人看到屏上 0.949 会怀疑装置、甚至反复重输(重输还是 0.949)。2 位才是这份存储 在自身可分辨精度上的正确读数——文档的 3 位来自「把 Q10 当成十进制存储」这个误会 - 单位挂在值串尾(10.00A / 500ms)而不是塞进显示名:值列只有数字会让 60000 既是 ms 又是 A。 为此给 ui_w_setlist_row_t 加了 unit 字段(该结构此前无任何消费者,扩展零成本);只挂在 非编辑态——编辑中是逐位输入串,挂单位会让位光标算错列宽。A/V/ms/Hz 全 ASCII 不占字库 新增可重跑核验:tools/setview_check/check_setview_limits.py - 从外面把三件事钉住:四张表的 id 恰好覆盖 0..39 各一次;数值行 min/max 等于 meta 上下限的 换算;kind 与 meta.is_ctl 一致、dec 查表与行表小数位一致、单位只允许 ASCII。改 meta 不改 行表就红。当前输出:行表 40 行 / dec 40 项 / meta 40 项 -> OK - 写这个脚本时自己踩的两个坑也留在脚本注释里:① 正则用 \w+ 吃不下带引号的单位,会让整行 **静默跳过**(少检一行比检查错更难发现)→ 改 \S+ 并加「行数必须等于 40」硬闸;② 脚本要 打印中文,Windows 控制台按 CP936 写 stdout 会崩在半路(崩在半路 = 一条都没检查)→ 开头 把 stdout/stderr 锁到 UTF-8 两个只有画面才会暴露的错(都不是静态检查抓到的,都记进了注释) - ui_data_par() 的通道是 0=成功 / -1=尚无首帧,我按 bool 反义用了:真机与模拟器上都会「永远 读不到」,整页值列恒 --,而页面看起来完全正常、编译与钩子全绿。看像素图才发现满屏横杠 - 肉眼把 CT 一次额定读成 1000A,字形匹配器证明值列只有 4 个字符位(命中 x=101/107/113/119): 实际是 100A。断言因此按匹配器能证明的写。顺带一个自洽佐证:PT 10000/100 V 配 CT 100/5 A 正是 10 kV 馈线终端的变比;控制字页默认值天然两两一组(Ⅰ/Ⅱ 段跳闸、Ⅲ 与过负荷段告警), 行表若整体错位就不可能复现这个组合——这条已进像素断言,成为 id 对齐的机械证据 无数据时的显示口径 - 参数帧未就位:名字照打、值列打 --(不把 0 当定值显示,也不给整页空白让人以为菜单坏了) - 越界反显只在 bad_valid=1 时参与:把「没数据」画成「这行有毛病」是假告警,比不画更糟 - Q10 换算不用浮点也不用 64 位除:整数段 + 小数段拆分四舍五入,满位进位(MicroLIB 软浮点与 __aeabi_ldiv 一进镜像几百字节,模拟器(宿主 CRT)永远照不出这笔代价) 批 1 那个 KEEP_EXTRA 决策在这里第一次兑现 - 本批行表用到的「延时 / 电流 / 压力 / 总 / 配 / 重 / 间 / 未」等字,正是当时成组并入 KEEP_EXTRA 的 35 个「未知冗余」字里的一批;若当时按 extract 输出重写字符集,这些字形已被洗掉,本批 上屏就是白框,而 font-charset 只会报「字在清单里没有」——它报不出「字曾被删掉过」 - 本批 font-charset 直接 Passed,未扩字库、未重生成字体 验证 - @build 0 错误 0 警告;模拟器 0 警告;内容页 19 项 PASS(16 页像素 + 四页共 39 条文本断言), 其余 12 页逐字节不变;菜单 8 状态 PASS;ui-data 83/83、soe 32/32 - 四页出图逐张肉眼确认:值列右缘不越屏(10000V 最宽)、名值不重叠、第 1 页行槽与页脚不撞; 分页页码正确(定值 18 行 = 3 页、软压板 10 行 = 2 页) - check_setview_limits.py OK;ui_menu_tab.h 等生成物全部由 gen_menu.py 重生成(手改生成物 = 一定 会被覆盖,见批 2 那条),uvprojx/CMakeLists 注册由生成器写入,另手工登记 ui_w_setview.c 两处 - 全量钩子 --all-files:见下一行 gate;红项只有同族三条(台账与同码凭证回填归单写者) 体积(相对b746a1f台账 217204 / 21504 / 1256 / 132680) - 实测 Code 221856 / RO 23508 / RW 1300 / ZI 134660 → Code +4652、RO +2004、RW +44、ZI +1980 - Flash 合计 +6700 B、RAM +2024 B(三批累计)。本批 RO +2120 主要是 40 行行表与名称/单位串, ZI +388 是 par 帧缓存(184 B)+ 四个组件实例 + 注册表增量;这是把 40 项真定值上屏的代价, 不是可省开销 待台架实测:装载真定值后的四页读数(含真机 bad_mask 非 0 时的反显行)、翻页节拍、 PT/CT 变比与 104 上位的定值读回(C_RD)一致性。 gate: only mem-budget/mem-numbers/size-gate pending lead ledger backfill (同族三条,root cause 一条:台账与同码凭证回填归单写者) 像素基线单独提交(下一笔新增 4 个 shot_v*.bin):本笔单独 checkout 时 ui-golden 会报缺参考, 属预期拆分;上面的钩子结论取自「本笔 + 下一笔」的合并树。 自查:12 个新增/改动 UI 源文件 BOM 与 HEAD 对齐(本批 edit 又剥掉 4 次,逐次补回);CR = 0; U+FFFD = 0;C 字面量纯 ASCII(中文只在带 BOM 文件里)。这是今天第一份**硬件侧**证据(此前所有绿色都只到静态与主机侧为止)。身份按新口径钉: 以固件身份戳为准、SHA 只当坐标——**fw build: Oct 26 2026 22:52:09 suite=NONE dstr=off** (坐标 HEAD b24b2b5f;该像与在库固件源同一棵树已由两条机械判据证过)。 计数严格按两句并列,不合并:**烧录启动 1 次**(00:25:06,@test rc=0,不进仅复位序列)+ **仅复位 4 次**(01:46~01:52,serial gen 273→276 单调递增,逐圈自证窗口不重叠、零重烧),4/4 PASS: · [ERR] / CONSOLE dropped / 任一 Fault 令牌 / ERROR / assert 全部零命中; · 两条历史受害行逐字节完整:[PROT] 91 px、[NAND_FTL] 56 px(行首 '[' 齐全、loose_neighbours=0、 head_bad=0)——这正是「未注册期裸发丢行首」那一类缺陷的正面判据;comm init 57 px; prot set loaded ×4、CORRUPT ×0;boot→net init 733~786 ms; · **反证**:同一把尺指向修复前归档 → rc=1 FAIL 并打回缺行首的邻居 ⇒ 四个 PASS 不是空跑; · 锚定用的是仓内 verify_log.py --exact(b1280e3c 并进的那半,今晚第一次自用); · 堆与栈:heap free=15624 / min_ever=8464 与 task-30 逐字节相同;ui 峰值 4188 四轮内部零摆动、 与上轮 max 持平(按 task-32 未定稿口径只报数、不做单次比较); · -CmpFile 排在全部采集之后(它会导致目标复位)→ No difference found。 **执行类页面的硬闸在真机上证死了**:--to clear / --to reset 在下钻前就拒绝(非零退出、连轨迹 文件都不生成、理由文本点名「第二次 ENTER 即真执行」);--to clock 正常到达(9 步,ENTER 全落在 菜单上下文),时钟页渲染无缺字白框(年月日时分秒与页码行全显)。帧与判定已归档。 作者自报两次测量错误(都不是板子的问题,也都没有产出假绿,处理方式正是本仓要的): ① 01:34 头一圈采到 0 字节。根因在测量侧:**常驻 serial daemon 已经死了**,而 serial_tool.py 的 status 会自动拉起一个新 daemon,于是它一直「正常」地报 port.ready=false / rx_lines=0、日志只剩 58 B——**status 的自愈把端口是死的这件事遮住了**。已立口径:每圈复位前 open + 断言 ready + 观察 rx_lines 增长,不假设 @test 留下的端口状态能过夜。该圈按两条独立理由排除(前面有烧录、实际 零字节),不进任何计数。 ② 我自己加的 Forbidden 令牌 Fault 是**假红**:子串命中 de**fault**,打到 factory default password 与 [STACK] default peak_used。已换成显式故障令牌(四个 Fault 本来都以 [ERR] 开头,裸 Fault 只是 噪声)。这条口径移交 task-32:**Forbidden 令牌要短到不落在英文单词内部**。 一条观察移交(不下结论、不属台架职责):装置墙钟显示 2026-10-26 18:00:35,而主机记录是当日 01:5x +08:00,固件日志时标与 daemon 时标也恒差 8 小时——**RTC 与 UTC/本地时区的语义归谁**由 Lead 路由(已立案,见 task-44)。 归档物:boot_log_data 下 20261027_bench42_{reset_only_boots_r1_r4,verdicts}.txt 与 _ui_clock_page.png; README 台账行按新口径书写。编辑 README 前重读、编辑后复核:其他两条道的「证据可达性」规则与 §2.1 都还在,无 BOM / 无 U+FFFD / LF。先认一笔自己的错:上一笔我宣称「实测」得出未绑 21 叶,那是**探针错**——`ui_menu_tree.txt` 文件头自己写着「缩进 即层级」,我把 42 个**节点行**全当成叶子数了。按缩进重判(有更深缩进后继者即非叶子)后的真值是 **33 叶 / 节点 行 42 / 已绑 21 / 未绑 12**,而未绑 12 = 待做 7(通讯设置 1 + 记录 5 + 运行报告 1)+ §5 不做的 5 ⇒ 与分类表 7/3/2 逐项对得上。原文的 33 反而是对的,只有 16/17 陈旧;这条错口径与正确重算方法都写进了 §1,免得下一个人 再用错算法。 §2 与 §3 按已落地事实改写: · **通讯设置**状态从「未开始」改成「配置层已落地,页面未开始」——`[comm]` 段与端口真源已在 `App/protocol/iec104/iec104_comm.{c,h}`,服务端口不再抄 `IEC104_SERV_PORT` 字面量,这 一项原本的第①条判据 已完成;还缺的是页面、「改后重启即生效」的真机判据、配置段数与键数上限核对。§3 数据源表同步。 · **运行报告**把四个数的事实写进完成判据,不给人留含糊空间:复位原因解析有但 **RCC 标志读后即清** ⇒ 需开机快照 驻留;上电次数**无现成计数器**,正源建议 RTC 芯片的用户 SRAM(电池保持、零 NAND 磨损),**文案禁写「终身 次数」**、写「自 RTC 电池周期起」;运行时长只有**本次开机内有**,跨开机累计口径**待用户裁定**;104 现有的量 全是控制量 ⇒ 需新增只读计数与 getter。落点 `App/util/boot_stats.{c,h}` 双端注册同笔,104 计数就近留 `iec104_serv.c` 不并进。 · §4 生成器入口统一走 `.venv`(并注明 gen_menu 本身只用标准库、字库链才依赖 fontTools ⇒ 指 font-regen skill), 避免下一个人按裸 `python` 跑字库链踩解释器坑。 新增两条防越界的口径:§4 **时基口径未定,勿顺手统一**(面板与 8888 写本地钟面、104 时标写 UTC,后写者赢 ⇒ 恒差 8 小时;裁定前不要动 RTC/epoch 语义,也不要把时钟页的「回读 RTC」判据改成按时区补偿,那等于替产品做掉 一个未定的决定);§5 增列「RTC 时基统一」并写明重开条件 = 用户裁定后作为跨模块变更单独立项。 README 同步同一口径(§1 首段与顺序表第 1 行)。 自查:**净室逐文件注入**(仓外 detached worktree,一次只放一份在飞文件)证明本笔两只规划文档对不可判定绝对数 是 **+0**(HEAD 基线 1,156 → 注入后仍 1,156);`check_doc_links.py --all` exit 0(195 文档)、两篇 `--sections-strict` 通过、`check_ids --strict` exit 0;文件 noBOM / LF / U+FFFD= 0 / 结尾换行齐。本笔零代码。 另注:同一轮净室测出当前绝对数 1,156 → 1,161 的 +5 全部来自 `.dsh/skills/task-worktree/SKILL.m d` 的新增段落 (作者另治,不在本笔、也不由我代改 skill 写域);`mem-budget` skill 为 +0。task-33 诊断结论的落地(道G 执行,环境**一个字节没动**:不装包、不重建、不改 requirements 约束)。 **要防的是什么**:`.git/hooks/pre-commit` 把 `INSTALL_PYTHON` 钉在 `.venv`,而 43 条 `language: system` 用 裸 `python` ⇒ pre-commit 本体走 `.venv`(3.12.4)、钩子 entry 走 PATH 首位(3.14.7,零第 三方包)。今天 不炸是**巧合**(那 43 条模块级 import 全是标准库,唯一要 fontTools 的那条自己解析解释器)。真正的危 险形态是:某条钩子在**跑到它那一刻**才报 `No module named ...`——一次提交里前 14 条已经改写完工作区、 第 15 条才崩,现场最难看,也最容易被误当成代码问题。L0a 把这件事提前到门禁第一段。 **FAIL 判据刻意收窄成三事齐备**:模块级第三方 import + 该脚本不自解析解释器 + PATH 首位那个解释器 真缺包。惰性/条件导入(`make_font` 的 fontTools 在函数体内)只列为**候选**、不判决——否则就是假阳性 (见下)。退出码 2 = 环境错误,已写进文件头退出码段与用法。 **道G 自己推翻过自己两处,都留在文档里没删**: ① 上一版诊断说「42 条里唯一需要第三方包的那条自带解析」是**宽松正则**审出来的(把注释与字符串里的包 名也算命中),机械口径下按模块级 import 判是 **0 条**,条目数也因 nested-comment 落地从 42 变 43; ② 第一版 L0a 把 font-charset 判成 FAIL,他拿真解释器实跑反证(`C:\Python314\python.exe scripts/check_font_charset.py` = exit 0,因为 fontTools 的导入在函数体内、加载期不执行) ⇒ 判据改窄。 **红-绿自证**:临时假仓库三条钩子(要 fontTools 且不自解析 / 同样要但自解析 / 纯标准库)→ 坏的那条判 FAIL、好的判 OK、整体 rc=2;真实仓 → 43 条、forced 缺包 0、候选 fontTools、exit 0;同一预检在 .venv 与 PATH 首位两个解释器下都 exit 0。 文档 §5 重排成**依赖表(§5.2) → 动之前死顺序(§5.3) → 遗留账(§5.4)**(原文按追加顺序把失效顺序插成了 §5.4,读起来先结论后依据;重排时同步修掉块内那条会指错的「见下面 §5.3 第三条」,以及 ci_local 里指向 死顺序却写着 §5.2 的指针——这两处 doc-links 都**静默放过**,因为本文恰好有同号小节,见 §3.5 盲区)。 四种现场(删/换 `.venv`、只换包、换 PATH 首位、改 requirements 约束)逐行钉死「第一现场 / 症状 / 还能 动什么」,回滚按现场分条不混用:freeze 备份放仓外、只 `--force-reinstall --no-deps` 单包不 `-U`、 `pre-commit clean` 会重建别人的钩子环境所以必须先要窗口、最后手段才删 `.venv`(删前记住基底是 Anaconda, 换机复制不出来)。 ⚠ 一条与本笔无关的既有红灯(道G 报出,我核过成因):`check_size_gate.py --local` 现在 exit 1 —— 它按 **git 索引 blob** 生成代码指纹,而工作区正有 comm 道在飞的 `App/protocol/iec104/iec104_comm .{c,h}` 未入 索引,「同码」必然不成立;**该脚本不是 pre-commit 钩子**(只挂在 ci_local),所以不拦提交,但别人跑 ci_local 会看到它,别误判成 L0a 新加的东西。台账回填等这批在飞代码提交后统一做一次。 自查:py_compile 0、`--preflight-only` exit 0(两个解释器下都是)、`check_doc_links --a ll` exit 0(198 篇)、 本篇 `--sections-strict` 通过、`id-check --strict` exit 0 且不可判定绝对数 **978 未变** (写文档没吃余量)、 mojibake / bom(81) / nested-comment(409) 全 0、尾随空白 0。三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572 ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec k_mem_numbers.py --write`),凭证与实际 .map 同源。归属如下: · **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死 区页同骨架同纪 律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化, 新增六态含 WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位 轮询;`port_104` 由 宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改 [comm] 的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o n 9004` → 还原 2404,全程无 FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057 钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。 · **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户 SRAM(magic + 小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥 UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**; 文案口径「自 RTC 电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同 源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open /close 洗掉)。 运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入 **全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。 · **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()` / `adc_cal_note_persisted()` (水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在 `adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。 三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色): ① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份; ② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功; ③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。 另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c `(依赖 I2C 与 RCC 位掩码, MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1 04-034、 TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。 门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche ck / coding-standard / bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11 7/117、run_par 67/67;菜单 8 状态 池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保 证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。4cf2df47与 b5390bb4) 403e2e558dASCII 拓扑图把 C44 / R32 画成挂在 ADC7 下的一根竖线,读起来像 2:1 分压, 恰是文档要否掉的结论。改为两个节点分开画的 SVG,并加 STM32 侧对照面板。 - 新增 tools/arch_diagram/gen_adc_frontend_svg.py → docs/images/ADC前端电路.svg(纯标准库 + 文字溢出预检 + 幂等重跑) - docs/测试说明/ADC前端注入验证流程.md §2、docs/硬件平台/ ADC通道映射说明.md §7.1 的 ASCII 图换成该 SVG - ③ 段(AINn ↔ 转换器引脚)定死变比 1:1:ADCn 节点只有 {串阻.2, 滤波 C, 转换器引脚},无对地元件;"1k 到地"接在差分负端 - 305.18 / 1611.3 µV/码(读法 A)作废;数据可信.md §2.1 表改为 引脚侧系数 152.59 / 805.66,卡 0 判据从"判别两种读法"降为 "确认转换器 LSB 与逐通道映射" - 顺带修 ADC通道映射说明.md §8 步骤 1 的 §5.1 自引用误判 (文档名以 V0.0 开头被前缀规则误认,改写成全路径形式) 门禁:Keil 无关(纯文档 + 生成器);check_ids --strict、 check_doc_links --all --sections-strict、pre-commit 全过。这族是延迟测试的分段号(L1 SPI 字节命令 … L11 uiTask 节拍)。特殊之处:**号直接打进 串口日志字符串**,而 C 字符串必须纯 ASCII(AGENTS 铁律)——所以两份落地面用两套名: 文档(人读):SPI 字节命令 / SPI DMA / 发送分块握手 / 芯片→线上+PC / 队列往返 / 请求→首字节 / 整帧 3209B 往返 / 生产路径帧周期 / 连接/重监听 / 日志税 / uiTask 节拍 C 日志(ASCII):SPI_CMD / SPI_DMA / CHUNK_WAIT / TO_PC / QUEUE_RT / ECHO_RT / FRAME_RT / PROD_CYCLE / RELISTEN / LOGTAX / UI_LOOP 改动:`链路延迟测试说明.md` 84 行(分段定义表的「段」列删掉、名称列即段名;正文与 基线表同步);`test/board/net_latency_test.c` 61 行(日志串与注释,行数不变、逐行替换)。 标识符一律不碰:`LAT_P13_OP_L5_RECV` / `P13_ST_L5_SEND` 这类宏与枚举仍在(它们是代码符号, 不是给人读的段号)——7 处已核完整。 一次事故:第一版把该 .c 写坏了(整篇被同名表格行覆盖),已 `git checkout` 回滚,改成 「只改命中行 + 行数断言」重做,本次 61 行、1313→1313 行不变。 验证:文档与 C 两侧段号残留为 0;doc-links / mojibake / 新编号门禁 / git diff --check 全绿。 该文件属板级测试套件(产品构建不编译它),本次只动注释与字符串,不改任何逻辑。三处口径按仓库实况改写: - 看门狗必需位掩码现值是 0x1F7(App/util/task_watchdog.c 的 wd_required_mask:default/net/ftp/ui/timesync/adc/iec104/protect), 本项的口径是「一个位都不加」,不是引用某个掩码常量。 - RTOS 堆是「紧张」不是「动态任务必然创建失败」:抬栈后台架实测 free 16,400 / min_ever 8,464 B(口径 内存使用分析 §2.3);768 字的 退路任务只剩几 KB 余量,所以仍首选不新建。 - uiTask 那族阻塞里的「约 4 s」标成**推导上界**(netTask 内 ch395f_poll_cmd_status(4000U) 六处调用 + 调用方 portMAX_DELAY), 并补实测锚点:uis 峰 183 ms、uip 地板 61.3 ms、对时突发单轮 800 ms+。 补 DMA 收尾的现实约束:全工程没有任何 HAL_UART_TxCpltCallback / RxCpltCallback 实现,HAL_UART_ErrorCallback 只有一处且只分发它自己 那条通道的句柄 → 三个回调都得自写并按 huart 句柄分发(写进 §1.3、 批 2 的 2-4、§6 风险行)。另记 .ioc 在仓库根、DMA2_Stream7 三处自洽。 §1.1 补新建任务的正确落点:Src/freertos.c 的 USER CODE 区 osThreadNew 是仓内先例,app_main_init 跑在 osKernelStart 之前、禁调创建类 API。 名目表加第四名目「数秒量级的等待」;defaultTask 的否决理由改挂到这 一族;§6 补一行采纳方案继承的 uiTask 挤占面(250 ms 日志排空、对时 突发);§7 加第三条另测:7003 拥塞时串口侧照常应答。 同时按「文档只写现状」清掉决策三里三处自我纠错措辞。 只改方案文档一份;需求文档不含这三处口径,本轮未动。 gate: 逐文件 check_doc_links --sections-strict(29 处引用 / 27 可判定) 与 check_ids_new 均 RC 0;合并后全量 ci_local 待他人技能文件 提交后补跑(主树仍有他人未提交的在飞文件)。按「主文留头量、深规程外置」重写本仓技能,全部是逐字搬移: 搬移前断言原文每条非空行必须出现在新主文或 reference 文件里, 0 丢失才落盘;步骤编号一律不重排(正文有「第 12 步」交叉引用); 每个切点保留原小标题 + 索引(加粗引导句与步骤标题)+ 指针行。 主文变化(上限口径 = 插件写侧 65536 字节,见上一笔 skill-size 钩子): - id-family-semantic-migration 33392 -> 10968(17%),reference 3 篇 - stale-doc-count-sweep 19811 -> 6945(11%),reference 2 篇 - mem-budget 19963 -> 13042(19%),reference 3 篇 - keil-build 12917 -> 8879(14%),reference 2 篇 - task-worktree 72601 -> 28139(42%),reference 8 篇 task-worktree 本笔同时收录另一会话已写完的 C2/D/G 三节正文——那 三处手改与我的拆分落在同一份文件改动里,不可分离;其内容同样已 随 reference/ 外置,主文只剩标题、索引与指针。 外置语料合计 122211 字节 / 30 篇:围栏数逐篇为偶,无 BOM/CR/U+FFFD。 draw-diagram 与 serial-boot-test 脚本是另一会话的在飞改动,本笔未含。 skill-size 钩子在本笔首次真跑(前一笔无 .dsh/skills 文件可触发)。 gate: pre-commit 全绿(doc-links、id-check-new、check-yaml、 skill-size、check-added-large-files);30 篇 reference 手工字节 体检 clean;skill() 逐个复载确认加载的是拆分版。scripts/check_ids_new.py 的 KNOWN_BARE 加 WAVE(附两行注释说明来源: App/algo/wave_store.c 拼名、FTP 根下 /wave/、100 个号轮转)。 此前文档里写段文件名只能改成 WAVE<NN>.BIN 绕开裸形态,登记后按字面写。 classify_bare("WAVE00") 由 fail 变放行,--self-test rc=0(5 正例拦下 / 29 反例放行,FOO99 仍 fail)。 docs/门禁说明/id-check-new.md §7 表加一类"固件生成的产物文件名", 并提醒先分清产物名与编号族——加白名单是唯一放宽手段,没有整库豁免入口。21:30~21:44 挂机窗口末轮计数:rx=4258 ok=4258 crc=0 err=0 bits=0x00 rearm=0(连错误中断都没进过);末段 5 s 18 帧 = 277.8 ms/帧; tx=4249 done=4248 drop 窗口内零新增 kerr=0 abor=0;keys=53 kq=0 (按压期间零丢弃);fb=902 bad=0、us max=812 µs < 1 ms 改口判据; 窗口内无 OFFLINE/back online 行。验收清单第 1 条主机侧半边闭环。 §7 清单状态同步:第 2 项 ✅;第 1 项拆成"主机侧已测/目视半边待测"; 5.5 接线教训顺延为 5.6 并同步三处跨文档引用。- 规划批 3 表改两项(3-1 映射表 / 3-2 下发),3-3 LOGO 标 ❌ 出局 (用户裁定 2026-10-29:LOGO 功能无用、不实现,sb_proto 组帧函数列待删); §0 执行序、验收清单第 8 条、另测三项(LOGO 那条连同串进去的残句一并清) → 两项、风险表两行同步。风险表"const 数组"防返工手段按实际实现改口 (位宏 + 单点纯函数 + 单测钉定义集——5 路输入判据形态各异,表统一不了谓词)。 - 设计说明 §3 决定 7 改写(LED 掩码独立纯逻辑模块折算)+ 新增决定 9(LOGO 出局); 新增 §5.7 双通道并发回归实测(7003 侧 2200 拉帧零错零超时 / 屏板侧隔离窗口 每召必答零丢弃 / 画面一致性由同一份活显存机制保证 / len=7 观察项如实记); §7 清单第 8 项销项、第 10 项状态灯改为"已实现待目视"(含 ID2/ID5 在未标定 闭锁下恒灭的口径提示)、第 11 项 LOGO 划掉。 - 裸 §5.7 跨文档引用改全路径(--sections-strict 自引用判据)。根因:今日在飞会话给 description/whenToUse 追加了新内容(「pw: 前缀」等), 但两行值为未加引号的 YAML plain scalar——其中的 ASCII「冒号+空格」让 DSH parseFrontmatter() 所用的严格 yaml 解析器报 Nested mappings 错误 → keil-build 技能整份不加载(skill() 读不到、技能目录不收录)。 js-yaml 能吃下去,拿它自检会假绿。 修复:description/whenToUse 整值加双引号(pw: 字面量原样保留), 正文护栏段同步为「要么整值双引号,要么一个 ASCII 冒号都不写」并点名 解析器与假绿坑;另有两段纯重排(护栏段前移、衔接项目符号归位)。 验证(三重):DSH 自带 yaml 包严验全仓 17 个 SKILL.md 全 PASS; skill-size 门禁 17/17 无悬空指针;skill('keil-build') 全文可读, 技能目录已恢复收录。字节卫生:无 BOM、纯 LF、无 U+FFFD。 注:本笔仅含 SKILL.md。索引中另有一笔在飞会话暂存的 uvprojx, 属 UV4 XML 归一化抖动,按 AGENTS 口径不入库,留给该会话自行处置。2026-10-30 测试说明分层那次(28 个路径、209 文件)实测踩到两条,都是 「把不该改的东西改脏」,当时的补救全靠事后从 git 里捞原始字节: B1 整目录搬迁会改写目录内部文件的内容。「相对引用文件所在目录」这条 解析规则不检验目标是否存在,于是台架日志里不是路径的斜杠片段被当成 「旧目录下的子路径」并插上目录名:`1/1 PASSED` → `test_data/1/1 PASSED`、 `13639/32768`、日期占位 `[----/--/-- --:--:--]` 全中招,连有效的 `see ../a.md` 也被换成指向不存在的 `see td/a.md`。23 个证据文件是 用 `git show HEAD:<旧路径>` 逐字节还原才救回来的。 B2 目录内部文件指向外部的 `../` 出链不补层级。emit_dir 取的是 `new_abs.parent`,整目录搬迁时少算一层,`../images/x.svg` 搬深一层后 仍指旧层级;图片按文档目录相对(§3.2),check_doc_links 看不见 → 静默断链。 改法(尽量小面): - resolve_token:按引用文件目录解析时要求目标真存在;根相对写法保持原宽松口径。 - rewrite_text:moved 文件的 emit_dir 改成「本文件搬完实际所在的目录」; 单文件搬迁走原分支,一字未动。 - 整目录搬迁时目录内部互为相对的引用一律不改(两侧一起搬,改了必错); 「旧路径是不是目录」必须在搬迁前记进 old_is_dir_global——--apply 是先 git mv 再改写,那时旧路径已经不存在了。 - glob 形态改成看得见:搬目录时前缀整体跟着搬(本来就该改);搬单文件时 一条 glob 可能同时盖住搬走与没搬走的两批文件,不猜,只点名列出。 分层那次有 4 处这种引用是静默漏改、事后手改的。 回归:新增 `--selftest`(在一次性临时 git 仓里跑本脚本的副本,`.gitignore` 先排除 /scripts/ 免得脚本自己进扫描面)。案例 A 守整目录搬迁(证据文件 逐字节不变、出链补层、别人文件的入链照改、glob 前缀跟着搬),案例 B 守 单文件搬迁的层级换算与 glob 提示,18 项断言全绿。反向对照也做了:同一 夹具在改前版本上 B1/B2 都复现,所以断言不是凭空写的。 文档同步:docs/文档规范.md §3.6 第 4/5 条从「现存危害」改写成「已修 + 验收判据」,新增第 6 条 glob 提示,并加「改本体必须先跑 --selftest」; docs/测试说明/README.md 第 3/5 节口径同步;scripts/README.md 补 --selftest 与任务树 --no-verify 的用法行。顺带修掉分层那笔里我写错的日期: 2026-11-01 → 2026-10-30(文档规范 4 处、测试说明 README 2 处,真值就是今天)。 树内验证:AST OK;--selftest PASS;改动的 3 个 .md 单文件 check_doc_links --sections-strict RC=0(36 处章节引用、可判定 34、无法判定 0); 字节体检干净。未跑 @build(无 .c/.h 改动)。