Commit Graph

63 Commits

Author SHA1 Message Date
wandering1 2e608118ce feat(对时): 8888 对时补一条操作记录留痕——与 104 对时对称,被拒不落痕
task-49 盘点盘出的唯一供给缺口,task-50 实现(道H 交付)。为什么这算缺陷而不是「维护工装不值得留痕」:
对时改变的是**其后每一条记录**的时标,而 104 侧的对时**已经留痕** —— 同一类行为两种结果,这个不对称
本身就是缺陷。FTP 上传不留痕则维持原状(那是传输动作,与「装置时钟被改了」不同类),本笔
刻意不扩面到它。

实现:`time_sync_handle_conn` 里 `sys_clock_set_unix` 的返回值原先是**裸丢弃**,现改为接住判
0,==0 才
投 `SYSLOG_OPER`(走 quiet 口,netTask 侧纪律,栈成本已在注释写明);**被拒零投**(不把失败记成改过时
钟);无合并锁存(连续两次对时留两条,不折叠);**epoch 语义零触碰、零时区补偿** —— 时基口径仍在用
户手里,本笔不替产品决定。

新用例四组十二断言:成功恰一条 / 被拒不落痕 / 连续两次两条 / 魔术字不符皆零。假件把 rc 做成可脚本,
并专门钉「文案出现次数」以证不合并;`sys_log.h` 用真实头(纯 stdint,走 task_watchdog 先例),零桩漂移面。

**停发口径的第一批**:新断言**不配 TC 号**,改用语义化用例名 + 函数名追溯,注释里标了口径出处 —— 裁定
能不能落地看的就是第一批,这条留在这里给后来者当样例。

证据:time_sync 92/92(存量 82 零回归);@build **0 错 0 警**,Code=235268 RO=26308 RW
=1572 ZI=136924
(与上一笔的差值是 728d6067 合账所致,不是本笔);栈**以表为证**:timeSyncTask 链 1272→1272 B 零增
量,own frame 152 不变,handle_conn 200 / post_quiet 40 / post 264 逐函数帧全等 ——
按今天刚定稿的栈判
据,「零增量」这句话必须是表,不能是论证。id-check --strict 0(零新号零裸形)、乱码/嵌套/BOM 净。

台账:`ram_baseline.py --update` 已记当日行(IRAM1 61.9% / CCM 87.9%)。覆盖率基线**留给 L
ead 重建** ——
上一版基线是在 time_sync 套件编译不过的破树下打的,少算了该套件的覆盖。

台账同步:本笔代码改了体积(Code=235268),RAM 预算台账受管区与文档快照区按 12:46 那次权威构建回填(ram_baselin
e --write 加 check_mem_numbers --write)。代码与数字不同笔,就是下一次 mem 钩子假红的来源——上一笔 d
ocs 提交被 mem-numbers 与 mem-budget 双双拦下,正是这个原因。
2026-10-27 13:49:38 +08:00
wandering1 26a97e99f9 chore(门禁): 配置注释里的三个功能号改成功能名(批次二范围内、由 Lead 改的 config)
批次二(保护功能号转语义名)的范围按写域切给两条道,但 `.pre-commit-config.yaml` 里也挂着三处号,而
配置是 Lead 写域——注释归注释,不占任何钩子行为,改动不影响 YAML 解析的 51 条口径。

· `7h) ADC 采样有效性校验(L1-6)` → 去掉号,条目名本身已完整;
· `7i) 保护定值模型(P1 / L5-1 载体)` → 「定值校验和载体」(P1 同时是 gitlint 规则名的撞形,一并去掉);
· `规划 §3.3 L3-5` → 「规划 §3.3 过频 / 低频」(按 ci-artifacts/lprot_map.tsv 的唯一口径,未自造词)。

validate-config 退出 0;改后本文件内 `L<层>-<号>` 正则命中 0。其余 219 处代码/测试与文档侧由两条道并
行处理中,措辞同表;`tools/arch_diagram/` 生成器 5 处已转给文档道与三张 svg 同笔(动 docstring 不重跑
生成器会破 generated-fresh——这正是该钩子存在的意义)。
2026-10-27 08:02:22 +08:00
wandering1 31ca4a1faa docs(计数): 内容页黄金参考 40 → 47 页全仓同步,登记簿剩余项与顺序一并更新
上一笔 UI 落地时我把「文档计数与真值同源」这件事留在了别人身上(setpage 的移交项写得很明白:内容页 40→47
的全仓计数改写由 Lead 派)。这一笔把它做完,**只改当前态陈述,历史不动**:

· **改成 47 的判据**:simulator/tests/ref/ 实测 47 张 .bin(7 张新增、存量 40 张逐字节零漂移),共 1
6 处当前态
  陈述跨 11 只文件同步——含 AGENTS 的 ui-golden 条目、**钩子显示名**(.pre-commit-config.yaml)
、门禁索引与
  ui-golden 详解的标题与正文、UI 自动化流程的页数与 PASS 文案与快检张数、内容页架构回归段、覆盖率口径的
  「不做 UI 单测」理由、启动自动化流程回归段、导航器脚本 docstring。
· **不改的两处,理由写明**:5 只文件里的历史变更记录行「27 → 40 页」是当天事实,原样保留,只在各文件
  变更记录顶部加一行 2026-10-27 的 40 → 47;UI_界面制作流程 的「< 40KB」是绘制缓冲红线,与本数无关,
  属扫描误报,不动。
· **一处加限定语而不是改数**:UI_自动化测试流程 的「文本断言 310 条、覆盖 40 页中的…」带 2026-10-26 实跑
  日期戳,那是那天的测量口径 ⇒ 改成「覆盖**当时** 40 页」,310 条不臆改,等下一次实跑回填真值。

顺带同步 docs/任务规划/README.md(登记簿销项,用户授权):开篇「第一项是实现 param_request 写路径、然后做
整定 4 页」早已完成 ⇒ 改为剩余三项「通讯设置 → 记录 5 页 → 运行报告」;§2 顺序表「按 §1 的 1→5 推进」改为
「剩余 3 项按 1→3 推进(原第 1、2 两项已落地)」,与上一笔 UI补齐.md 的销项结果对齐。

三数同源自查:AGENTS 钩子数 51(YAML 解析口径)、内容页 47 页、门禁索引 27 篇均与配置一致;
check_doc_links.py --all exit 0、check_ids --strict exit 0 且不可判定绝对数 **1,156
 未变**(本笔没吃余量、
没新增编号引用);13 只文件全部 noBOM / LF / U+FFFD=0 / 结尾换行齐。本笔零固件源,不改构建产物。
2026-10-27 07:16:32 +08:00
wandering1 1d39571114 feat(门禁): 第 51 条 nested-comment——把 ARMCC #9-D 的按包含者刷屏拦在提交前
动机是今天一天复发三次的同一形态:在块注释里写作用域路径,那个 `/*` 被 ARMCC 当第二次注释起点(诊断 9-D),
而且**按包含者数量刷屏、只在重编译那个 TU 时才暴露**——本仓的诱因就是「注释里写 Drivers/BSP/UI/**」。
AGENTS 早就写了这条铁律(写全目录或用尖括号),但它一直是纯口头纪律,所以同一天还是复发三次。

新脚本 scripts/check_nested_comment.py:纯标准库、只读、五状态机(代码 / 行注释 / 块注释 / 字符串 / 字符
字面量),**只有块注释体内的 /* 才算问题**——字符串里的、行注释里的、未闭合判定都不误报。口径上刻意与钩子
的 types: [c] 对齐(403 个 .c/.h 同面),避免「手跑面 ≠ 提交面」这类我们今天就踩过的错位;零第三方依赖是
刻意的,为了让 generated-fresh 那天的解释器假红不长第二遍(实测 .venv 与机器级 C:Python314 两种解释器结论
一致)。

存量口径:全仓实扫**新发 0、存量 2 处**(test/host/common/ch395f_fake.h:7 与 fatfs_fake.h:9,同一形态
「注释里写作用域 test/host/**」)。两份假件头不参与 MCU 构建所以今天不报,一旦被 include 进板端 TU 就会
按包含者刷 9-D——属 test/ 写域,作者**没代改**,而是钉进 BASELINE 走只减不增,**其余零豁免**。

自证有一条必须写清的诚实边界:作者想「拿旧字节回放今天那三处真复发」,实测**做不到**——近 150 个提交里被
改过的 281 份 .c/.h 旧字节**零命中**,因为那三处同类从来没有一处在 HEAD 里留过字节(全靠人工编译后清掉)。
于是改测**形态**(这是能证的部分):10 项形态电池 10/10,含把已入库的尖括号写法退回星号形态 → 命中 1 处;
另用现存两份真字节做双跑:空基线 → 2 条 [NEW] + exit 1,加基线 → exit 0,证明**基线只决定算不算新的,
不掩盖有没有**。编译器侧原文(run_par.c(267) #9-D)冻结抄进新文档 §2.1——构建日志每次 @build 被覆盖且不入库,
所以文档里刻意没写「最近一次日志里仍有」这种会过期的话。

计数三处同批:AGENTS「共 **51** 个钩子」、门禁索引「51 个 hook → 27 篇 + 1 指针 + 1 外置」、配置声明;并记
文本 grep「 - id:」= 52 的成因(第 65 行被注释掉的 clang-format 候选),收紧行首口径 = 51。作者顺带自打了
一次:在配置注释里复述了那个 grep 字面量 → 文本计数当场虚高 1,已改写并把「配置注释里别复述字面量」写进
README §2 与 AGENTS。

同笔带上 task-28 的裁定句(AGENTS 一只文件里两卡改动天然混在一起,拆开会多两次改写与一次风险):不补第
23 根 ui-sink 专属钩子——22 根的唯一意义是秒级归因,而 ui_sink 失败面全落在 test-coverage 总闸必跑路径、
失败照样拦;读「这套跑没跑」去看总闸输出,别数钩子。

实测与本笔自查:validate-config = 0;nested-comment --selftest 4/4、--all = 403 / 新发 0 /
存量 2 → exit 0;
doc-links --sections-strict(4 篇)= 0;gen_task_table --check = 0;generated-fresh
PASS(5 处存量走 WARN);
check_ids --strict exit 0,**不可判定绝对数全程未被本闸吃掉**(作者开工 1,162、写完新闸与新文档仍 1,162;
现 1,156 是队友还清的);全部改动文件 noBOM / LF / U+FFFD=0。未跑 pre-commit --all-files(只跑本闸)。

**本笔使用 --no-verify 的凭证(唯一允许的两条之一,按 MEMORY 台账例外口径)**:把本批 6 只文件单独
staged 后跑 `pre-commit run --files <这 6 只>` → **13 条 Passed / 1 条 Failed**
唯一失败的是 `mem-budget`
(RAM 92% 阈值,**全树检查**)。本批**零固件源、零 .map 变更**,该红来自其他队友在飞的固件改动造成的 .map
漂移(当前树 Code=231,952 / RW=1,528 / ZI=136,264,在库台账 231,712 / 1,504 / 136,216
差 = run_par setter
与 vol_lock 计数两笔,均未提交);台账回填由我在树能编后统一 `--write`,不在分支上跑(避免多分支同改受管表)。
gate: only mem-budget pending lead ledger backfill。字节卫生本笔自查:6 只文件均 noBOM / L
F / U+FFFD=0,
新脚本纯 ASCII 输出路径无关,AGENTS 与索引两处计数与配置 YAML 解析口径 51 一致。

另收一条今天反复出现的元教训(Lead 与作者各错过一次、方向相反):**`pre-commit run` 与 git 钩子不是同一根
探针**——config 处于 modified-unstaged 时只有后者 [ERROR] 拒绝,前者与 validate-config 都 Passed
。它与「stash
只覆盖已跟踪未暂存」「未跟踪文件永不进 stash」同族:手跑的探针不能替真提交的探针作证。
2026-10-27 03:44:18 +08:00
wandering1 ac9ca33f0f fix(台架脚本): 导航器加两条硬闸——执行类页面默认不进、未知屏 ENTER 最多一次
bench-runner 在给 UI 窗口做准备时报出一条**三段叠加就会真执行破坏性动作**的路径,
而我就是其中一段的作者:496b4dc5 把 e 放进 UNKNOWN 屏的回退键序(当时的动机是真的
——本机待机态按 s/l 无效,冷启动落在待机态时闭环导航直接开不了工)。叠加面在这里:
· 导航器每进一个叶子就发一次 ENTER,且会替「查看密码」自动确认;
· 装置维护的 强制复归 / 清除记录 已进菜单树;
· 这两页是**两段式确认**——第一次 ENTER 只把该行标成「确认?」,第二次 ENTER 就真的
执行(复归全部保护动作位并写 SOE / 清 SOE·运行·操作记录)。
单个 ENTER 安全,但**同一屏上第二次 ENTER 不安全**;而退避键序是循环消费的,第一次
按 e、第四次又按 e。更要命的是:新字库缺字、白框、状态行变化恰恰最容易让页面被判成
UNKNOWN——那正是我们最想在 UI 窗口里抓的东西。**越是显示异常,越容易被脚本按执行**。

两条硬闸(不改任何固件、只动这一只台架脚本):
① to_root() 内 ENTER **最多按 BACKOFF_MAX_E 次**(默认 1);还需要第二次就当场报错
停机、并把 dump 的帧留在盘上,而不是把第二个 ENTER 投出去。报错文案直接点名执行类
风险与放行方式(改用无 ENTER 的键序 --backoff-keys sl)。
② 执行类页面 EXEC_PAGE_IDS = clear / reset **默认拒绝下钻**;确有真机需要才显式加
--allow-exec-pages,且运行时打印警告。要看这两页的显示正确性,正路是模拟器黄金参考——
同一份 UI 源码、零真机副作用。

通用口径一并写进文件头:**导航类自动化脚本默认零 ENTER**,ENTER 只允许出现在明确
指定的、非执行类的用例步骤里。「靠按 ENTER 从未知状态脱身」这个前提本身就与执行类
页面冲突,不该由脚本替你承担后果。

行为验证(假设备,绝不连真机、绝不注键):clear / reset 均被拒且理由含「真执行」;
clock 不受该闸影响(走到设备层才失败);持续 UNKNOWN 的循环实际按键序列是 e-s-l 后
在第 2 次 ENTER 前停机,实测 ENTER 计数 = 1 = BACKOFF_MAX_E,报错理由点名执行类风险。
5/5 通过;py_compile 通过;--help 可跑。临时脚本没留在仓库里,__pycache__ 已清。

另两处纯计数更正(与文档道 task-39 同批真值:黄金参考 40 页):本文件 selftest_refs
的 docstring 27 → 40;.pre-commit-config.yaml 里 ui-golden 的显示名 27 → 40(这是
「声明 / 文档 / 实跑」三数同源的最后一处不同码)。
2026-10-27 00:13:41 +08:00
wandering1 7d9db33a25 chore(门禁+文档): 生成器幂等钩子落地,文档真值与 UI 清单销项同步
新增 generated-fresh 钩子(scripts/check_generated_fresh.py,只读):把 gen_menu.py、
中文字库链、tools/arch_diagram/gen_*.py 拷进系统临时目录重跑,与工作区逐字节必须
一致;钩子本身不写仓库、不 stash 别人在飞的改动。实证起点是上一笔 0f7b5a87 改了
模板文字却没重跑生成物,接线前实测另有 5 张 docs/images 配图已漂(文档挪目录只改了
生成器里的路径)——那 5 张按既有范式钉成只减不增基线,业务生成物零豁免面。

计数三数同源核查:文本 grep 配置得到 51 是把第 63 行**注释掉的** clang-format 候选
算进去了,口径统一为「YAML 解析后的 hook 条目数」= 50(实跑 49 + gitlint = 50),
AGENTS 与 docs/门禁说明/README.md §2 用同一条自查命令。顺带更正「主机侧单测 23 个
(含 ui-sink)」:实为 22 个,test/host/ui_sink 有 run_unit_test.py 却没有 per-module
钩子,只有覆盖率总闸跑得到它(已另立待办,不当既成事实写进文档)。

文档真值一批(都按脚本/仓库实测取数,不抄旧文档):内容页黄金 11/18 页 → 27 页
(8 处,含 ui-golden 钩子显示名)、文本级断言 50 条 → 242 条、池容量 4784/4864 →
5368/5448(含菜单页面设计.md 与同文档 1.5 节自相矛盾的那处)、页面补齐规划的立项
快照加限定语(日期按该文档自身标题 2026-10-23,不臆造现值)。变更记录里的历史数字
(11 页、50 条、老基线时代的绝对峰值)一律不改;无法实测的绝对峰值改成增量口径并
标注「待按新基线重测」。

销项(用户裁定 2026-10-26):UI补齐.md 与 README §1 删除「param_request 写路径」
与「整定 4 页」两行并重排序号,叶数口径 12/21/16 改 16/17/12;定值项数按
App/algo/prot_set.h 的 PS_ID_COUNT 更正为 40(原写 31 是旧口径);「主站地址白名单」
移入当前不做并写明理由:CH395F 单连接 socket 上「拒绝即 close+重 listen」等于给扫描
风暴开门,且该值现在没有任何既有消费点。

台架证据入库(像 = 77779d49,仓外 detached worktree 只读出像):启动日志、仅复位不
重烧日志、104 定值写读腿、控制台交错一手字节证据与四张板照,boot_log_data 台账登记。
.gitignore 兜一条 %SystemDrive%/:子进程环境缺 SystemDrive/ProgramData 时 UV4 会按
字面量在仓库根建字体缓存目录,本会话实测复发两次,不兜住某次 git add -A 就进历史。

自证:改前后编号门禁 refs / 不可判定 / 比例 / 错误级逐项差值为 0(未把可判定引用改成
散文);check_doc_links --all 无法判定 0;文档路径检查通过 177 篇。

本笔不含三条在飞源码道与板端导航脚本修复,它们各自单独一笔。

为什么本笔走 --no-verify(按仓库既有约定,非偷懒):本笔内容是**纯文档 + 门禁脚本/配置 +
台架证据**,不含任何业务代码;而工作树里同时压着三条在飞源码道(UI 装置维护 3 页、
[comm] 配置段、控制台行级原子性)。pre-commit 跑钩子前会 stash 未 staged 改动,那
等于把别人未完成的代码算进本笔账——console_log 套件的属主自己就报告过「当前还没跑过
该套件」。逐条相关门禁已单独实跑:check_doc_links --all(无法判定 0)、check_ids
(余量差值为 0)、generated-fresh 用 .venv 解释器直跑 PASS(4 项 / 一致 3 / 存量基线
1 / 报错 0)、validate-config 0、BOM 漂移与 mojibake 干净、YAML 解析条目 50 与文档
及实跑三数同源。唯一会红的是 mem-budget:磁盘上的 .map 含上述在飞代码,与 HEAD 台账
不同码;台账与体积凭证由我在源码道全部落地后统一重建回填。证据文件:
ci-artifacts/commitA2.log(上一次尝试的完整钩子输出)。

另记一条本笔实测到的坑(已写进 AGENTS「Python 环境」与 docs/门禁说明/generated-fresh.md
§4):钩子 entry 的裸 python 会被 pre-commit 解析成机器级解释器(本机 = C:\\Python314,
无 fontTools)→ 字体链生成器报 No module named fontTools、generated-fresh 假红。修法
是脚本内自己按「显式 var > 当前解释器 > 仓库 .venv > PATH」解析并做探针校验(装了
pre-commit 不等于装了 fontTools),但 entry 不写死绝对路径,否则门禁被绑死在一台机器。
这与 scripts/ci_local.py 文件头记的 2026-10-11「L0 全灭」是同一族问题的第二次发作。

台架证据补两行:板端 UI 导航脚本修复的真机冷启动闭环 PASS(首帧待机页 UNKNOWN →
按 e → 9 步到 view_set,终帧墨量与黄金 shot_vset 一致),存证同目录;该脚本本身的
改动留给下一笔。
2026-10-26 18:26:47 +08:00
wandering1 77779d49d6 feat(ui+记录): 整定写路径与四类页落地,五类记录写入方补齐
UI 面:ui_data 增加深度 4 参数写队列(请求 168 B、队列 .bss 672 B)与状态机
OK/ERR_RANGE/ERR_LOCKED/ERR_BUSY,ui_data_feed 出队后按注入 ops 走「闭锁 → 整批
预检 → 逐项 prot_set_set_sealed」,只提交改动位图;par 帧尾追加 cal_block
(口径 = UA/UN/IA 三路 ADC_CAL_F_VALID,与 protect_task 判据同构,不用 rt.eng
反推,V0.1 下 eng 恒 0)、UI_DATA_VER 升到 2。参数/定值/控制字/软压板四叶绑定
内容页(UI_PAGE_MAP_COUNT 12→16,复用四个查看模块,零新增 .c),编辑态按
is_editable 分派,LOCK(写不进去)与「未标定」(仍可改值)两源两通道两词。

记录面:syslog_request_clear/state/apply 三段式删除 API(UI 只发请求,落地在
defaultTask 的 drain 尾部且必须后置,卷锁一律 try,幂等只认 FR_NO_FILE);
事故/告警/启动/操作/遥控五类各有一个真实写入点,记录类型 = 日志 ID 本身
(一类一个 /log 严格 8.3 文件;64B 定长体没有类型字段位,塞字段会砸掉既有读者)。
为守 protectTask 872 B 静态最坏链另拆 syslog_post_quiet()(无 DBG 的投递核,
丢弃留痕挪到 drain 侧差值补发),syslog_post 对外语义逐字不变。

为什么两道合成一笔而不是各一笔(实测,不是省事):simulator/main.c 同时消费
两道新增 API(整定 pump 与 logtest 清除用例),按道切文件会让中间提交编不过;
RAM 受管快照与体积同码凭证描述的是合并后的 .map,拆开必落在「台账与 .map 不同
码」这条红线上。pre-commit 跑钩子前会 stash 未 staged 部分,这类耦合没法绕。

实测:@build 0 错误 0 警告;Code 228,260 / RO 24,960 / RW 1,440 / ZI 135,976 B;
RW_IRAM1 79,992 B = 61.0%、RW_CCM 87.9%(阈值 92%);九个任务静态最坏链全 PASS,
protectTask 872 B / 57% 改前后逐字节不变(记录投递没垫栈)。主机单测 ui_data 82→
164、sys_log_mcu 106→137、prot_elm 47→60、iec104 141→154、iec104_serv 28→34,
既有断言零删改;48 个钩子 --all-files 全绿;编号门禁可判定 86.34%(基线 86.0%)、
不可判定 1,153 处低于上限 1,166;doc-links 无法判定 0;UI 黄金 18 页扩到 27 页
连跑两次逐字节一致;字库 diff 空。

顺手修掉三处既有缺陷(都是本轮第一个消费者才暴露):sim_syslog.c 的 s_paths 行宽
16 而 "sim_selftest.log" 正好 16 字符没有 NUL 位(stat/remove 读越过数组尾,此前
靠后面 .rodata 碰巧为 0);ui_w_setlist 二态行 ENTER 不回绕(控制字与软压板出厂值
全在最后一项,按了像没反应而叠层里已是越界值);生成物 ui_menu_tab.h 与 gen_menu.py
模板漂移(上一笔改了模板没重跑生成器),现随生成器输出对齐。

未收口(不算完成):保存后重启值保持与主站 C_RD 读回两条只能上板,台架窗口执行;
LOCK 屏显与越界反显的页面侧在模拟器造不出来(模拟器不跑定值自检、无 NAND),
已由生成侧单测与代码注释点名差距;同类页数与钩子数陈述残留另立待办。
2026-10-26 15:51:12 +08:00
wandering1 0f7b5a87fb chore(编号): 存量编号全量转语义短语,门禁识别面与语料同批重打
登记簿、文档与代码注释里的存量字母加数字编号(N-x / S-x / T-x / E-x / A-x / 卡号)
一律改写为语义短语,净删 2,028 处引用;删除基线与待确认、并行分道方案、排期详述、
采集保护收尾 4 篇(内容已并入 docs/任务规划/README 与登记簿)。

门禁机器与三张账同批重打:check_ids.py 新增变更记录行豁免与第二根护栏
undecidable_total_ceiling;check_test_traceability.py 只加 stdout/stderr UTF-8
reconfigure(TC_RE 未动);check_cubemx_survival.py 的 --write 固定 newline=LF;
id_registry / id_aliases(+19 条 retired-to-phrase)/ id_known_backlog 重打。

test/ 56 个文件同步 TC 标签语义化,其中 18 个 TU 改了 CHECK / printf 里的标签文字,
是本批唯一改变构建产物的文件集合。

为什么合成一笔、不拆四笔(实测撞闸,非偏好):pre-commit 跑钩子前会 stash 未 staged
部分,拆开后中间提交必然是「新规则 + 旧语料」——实跑可判定 85.15% < 基线 86.00%、
不可判定 1,453 > 上限 1,166,编号门禁直接拦下。编号识别面与它描述的语料只能同码移动。

实跑基线(工作区全量):754 文件 / 8,230 处编号字面量 / 可判定 7,079 = 86.01%
(基线 86.0%,容差 0.1pp)/ 不可判定绝对数 1,151(上限 1,166)/ 校验 1-6 错误级 0 /
告警 = 悬空 2(App/algo/freq.c 的 S2,属 §4.4 同号不同义)+ 孤儿 50。

基线从 84.0 抬到 86.0 的理由见 docs/门禁说明/id-check.md §7.6:本批真删了 2,028 处
引用,可判定面随真实内容上升,不是把分母做小。同批新增排除 docs/硬件平台/ 语料,使
识别面少 264 处——芯片手册位号与网表料号不属编号空间(registry 有 excluded_paths_note)。

⚠ 余量只剩 0.11pp(约 10 处)与绝对数 15 处:把「可判定的引用」改成散文会立刻撞第二
根护栏。另遗留一项:id_aliases 里 retired 的 N / S / T / A 别名没有 scope(U1~U5 有),
今后有人新写一个 A-4,校验 3 不会响,只能靠人,建议下批补 scope。

固件不变证据:115 个 .c/.h 剥注释并归一字符串后与 HEAD 逐字节全等,残留代码差异 0;
App / Src / Drivers 的字符串字面量零改动。@build 0 错误 0 警告;40/40 主机套件、2,889
断言全通过;覆盖率 line 89.2% / func 92.6%;pre-commit 48 钩子 --all-files 全绿;
check_doc_links --all 通过(176 文档,章节引用无法判定 0);逐字节卫生核过:14 个带
BOM 文件与 HEAD 一致、0 个 CR、0 个 U+FFFD,字符串字面量纯 ASCII。

.cubemx_survival_baseline.json 的 watchdog-module 哈希 56bf27 变 2bb051 属合法:
Src/freertos.c 的 USER CODE 区内只有注释文字随编号改写,代码未变。

同批带上体积同码凭证与 RAM 台账行(@build 之后盖戳):RW_IRAM1 79,008 B / 60.3%,
RW_CCM 57,376 B / 87.9%,阈值 92%。
2026-10-26 12:28:14 +08:00
wandering1 6a904c1bee feat(判据脚本): 板端 UI 有状态导航器入库 scripts/board_ui_nav.py
票 = task-25(Lead 定位置与钩子档位)。ci-artifacts/uinav/board_nav.py 收编入仓:判据依赖一
个不受版本控制的东西,本身就是隐患(今天刚写过这条道理:只有记得做的人才做得到)。

为什么要有这个脚本(大窗实测,不是预防性工程)
- RemoDispBus 的 KEY 应答是**空帧、不携带状态**:发了 5 个键不等于到了第 5 层。
- 靠预置键序盲走到深页,9 个「应当到达」的态里只有 3 个真是那个态:03 停在菜单级联、
  04/05/06 停在**密码输入页**(墨量 512/515/512,与黄金参考的两张密码态逐字对得上——此前
  它被记成「512 bits 神秘空白态」)、07 停在网口设置、08 停在装置信息。
- 所以导航必须闭环:每按一键重新认屏,再决定下一个键;「到达」只由页面身份裁定。

认屏三条判据(全部精确字形匹配,复用同一份 ui_font_cn_12 与黄金回归脚本,不复制第二套规则)
- 内容页:标题带命中 + 该页黄金行文本至少一条 + 读页码行「第 N 页,共 M 页」;只认标题会把
  两屏认成一屏(「参数」既是菜单项也是页名),页码让「第 2 页 / 共 5 页」成为可比对的事实。
- 菜单:在白底横条(反白选中行)覆盖的行范围内、**整幅宽度**找反白标签,长标签优先、命中框
  被包住者丢。三条实测教训都在注释里:① 选中行是黑字白底,只按正常极性永远读不出选中项;
  ② 一条连续白 run 里可能**贴着两列**(子列左边框就是根列右边框),按 run 限宽找标签会把
  子列整个吞掉;③「参数定值」那行必然同时落进子串「参数」「定值」,不去重就凭空多出两列。
- 密码页:「用户密码」四个字中三个即判 PWD(整串匹配实测差约 1 px 打不中;数字格随输入变,
  不参与判定)。
- 另两条纪律:收级联列按 l 不按 s(菜单态按 s 不收列,用 ssssssss 回根正是错位来源);菜单树
  的 #P 注记可能写在 #page= **之前**,解析顺序错了就剥不掉、那一叶的 id 永远认不出来。

结构与安全边界
- SimDevice(无头模拟器当设备,不需要台架)/ BoardDevice(只连 7003 取图注键)双后端同一套
  导航逻辑;脚本不含串口、烧录、复位,跑真机必须自己持 bench 锁。
- 「过密码」默认只走**查看密码**(出厂 000 直接确认);修改密码要显式 modify=True,不因
  「能进去」就自动升格成可写。
- 末列弹框被屏幕裁掉时读不到选带 → 「枚举 + 验终点」兜底(末列盲按 N 次再进,成败仍只由
  页面身份裁定),并可用 --dump-fail 把认错那一屏存下来;轨迹只在 --trace 给了路径时落盘,
  默认不往工作树里写文件。
- 缺模拟器可执行文件时报 FAIL + 可执行提示,不「跳过当通过」(与同一天 mem-numbers 缺 .map
  同一课);--selftest-quick 是那条限制下仍可用的快检档。

门禁
- 新钩子 uinav-selftest 跑 --selftest-quick(files 只覆盖脚本自身与 simulator/tests/ref 帧,
  档位选择理由写在配置注释里)。本笔**未用 --no-verify**:本树先 @build 一次让 .map 事实源
  就位,全量钩子通过。
- 实测(模拟器后端):黄金参考 18 帧分类 0 错、真机留档帧 9 帧 0 错、闭环 12 个绑定页到达
  10 页(差的 2 页正是上面第 1 条限度,报错大声、现场帧已取证)。
2026-10-25 18:00:56 +08:00
wandering1 3965eb3f28 feat(门禁): bom-guard 机械化 BOM 漂移,附 setview-check 行表一致性
把 AGENTS 铁律第 2 条从"人工逐文件比 HEAD"变成提交硬闸:HEAD 有 EF BB BF 而工作区没有 → FAIL。
2026-10-25 一天之内道E 在 task-15 里连踩 4 次、道B 批 1 踩过 1 次,此前全靠人记得做审计兜底——
人工审计只防得住记得做的人,这正是它该变成门禁的理由(票 = task-20)。

scripts/check_bom_drift.py(纯标准库)
- 判定只有一条:HEAD 前 3 字节是 BOM 而工作区不是 → 列出文件与补救动作,不含二义措辞。
- 两条刻意不报(票面要求的边界,--selftest 逐条自证):① HEAD 里根本没有的新文件无 BOM 不误报;
  ② HEAD 无 BOM 而工作区加了 BOM 也不报——那是另一个方向的改动,可能是有意的(把文件纳入名单),
  报红只会逼人 --no-verify,而"养成绕门禁的习惯"比漏检一次贵得多。
- 一条刻意不做:不自动补 BOM。正确修复是在文件最前面加 3 个字节;脚本整文件重写会按系统默认编码
  重打中文,把可修的丢 BOM 变成不可修的 U+FFFD(AGENTS 铁律第 1 条那条更大的坑)。补救文案里把
  这句完整写出来了——报错信息的作用是让踩的人当场做对动作,不是让他知道有人不高兴。
- 名单三处互指:AGENTS 铁律名单 / 本钩子 files 正则 / 脚本内 BOM_LIST,每次运行核一次;单独改任何
  一处都会在别人的提交里被拦下。名单散在三处而无人同步,等于没有名单。
- --selftest 在临时 git 仓库里跑 4 条判定(含上述两条边界 + 主用例 + 名单外文件不参与),全过。

setview-check(Lead 指令:并入 task-20 同窗机械化)
- 核 Drivers/BSP/UI/ui_w_setview.c 的四张视图表与 App/algo/prot_set 枚举序、
  g_prot_set_meta 上下限是否同一份口径:id 恰好覆盖 0..39 各一次 / 数值行 min-max
  等于 meta 换算 / kind 与 is_ctl 一致 /
  单位纯 ASCII / 小数位查表与行表一致。机械化的理由不是"多一道保险":BSP 侧看不到 App 头,
  行表里的 id 是裸数字 + 行尾注名,这条对齐在编译期**物理上无法自证**;而页面上只有当前可见
  的 8 行会被像素断言看到,错位要翻到第 2、3 页才可能被人发现——那时它已被黄金参考钉死了。
- 跨分支先后写成注释:脚本文件随 task-15 分支合入主干;在此之前 files 永不命中 → 钩子自然 skip
  (skip 不是失败),合并后自动生效,不需要任何人再动这条配置。

AGENTS.md(票面授权:只加一句机械化标注;下面实际动了两处计数,一并申报)
- 铁律第 2 条末尾加"已机械化:bom-guard 钩子"+ 三处互指一句。
- 钩子计数 46 → 48 两处同步(不同步就是让 AGENTS 说谎);并在文档索引行标出"bom-guard /
  setview-check 两篇待补",把欠的详解篇目挂在明面上,不留沉默债。

验证
- 本笔**未用 --no-verify**:全量 pre-commit --all-files 46 项 Passed、0 Failed(含两条新钩子)。
- 端到端实测(不止 --selftest 自证):真把 Drivers/BSP/UI/ui_w_records.c 的 BOM 剥掉并 stage →
  bom-guard rc=1 并打印补救动作;还原后该文件与 HEAD 逐字节相同,工作树干净。
- 顺带发现并行开发方案的一条空白(本笔未修,写域之外):全新工作树没有 .map,mem-numbers 会因
  "找不到 .map 真实源" FAIL,跑一次 @build 才绿。门禁隐含的"本树必须先构建过"前提在
  docs/环境与部署/并行任务工作树方案.md 里没写;已回报 Lead 定夺归属。
- 自报一条:本笔第一次提交被 gitlint 拦下——正文有一行 92 > 80,**我量出来了却仍然按了提交**。
  记在这里是给下一篇门禁详解留素材:这条门禁拦下过的人里也包括写门禁的人自己,而「拦下过
  什么真事」那一栏不该只挑别人的事写。
2026-10-25 14:36:50 +08:00
wandering1 8c72698666 feat(运行参数): 卡10 run_par真源收编+104段0x8020~0x8058读写映射(道D M6-①)
新增 App/algo/run_par.c/h(「数据可信」卡 10,lead 四裁落地):
- [run_par] cfg 段:遥测死区 9 键 int32 毫单位(db_ua…db_freq),工厂默认 =
  卡 6 额定 0.5% 档(500mV×4 / 5mA×4 / 500mHz);注册走 adc_cal 同款内核前
  契约(app_main_init),不挂回调 = 引擎副本即生效值;键数 9≤16 不拆段
- 真源收编(裁③):pts 层 s_yc_db 运行时副本删除,COV 每拍实时读 run_par
  (协议→算法向下依赖,同 meas 先例;无投递无缓存 = 顺序/幂等平凡成立);
  iec104_pts_yc_set_deadband()/deadband() 保留为转发壳,壳内保留"成功清
  半截确认计数"语义并由 TC-IEC104-027 追加断言钉住(裁⑤)
- 104 地址映射(裁①):0x8030~0x8038 死区 9 点 / 0x8040~0x8047 k 覆盖 /
  0x8048~0x804F b 覆盖(逐项地址本仓自定——复现规划 §5.4 权威表不在仓,
  送检点表到位后替换 run_par.c 顶部常量段一处;已在 IEC104 §3.4 表头挂账);
  路由顺序 = run_par 表先、未命中落 prot_set——0x8027~0x802A 属 prot_set
  告警段永不覆盖,TC-IEC104-029 双向往返钉死;空隙地址自然 COT14/PN
- 通道系数覆盖 = adc_cal_set 代理(裁②a):合法带/越带 [ERR] 留痕全复用,
  run_par 不存第二份系数(防双真源);单侧写 RMW 用另一侧当前生效值;
  回显 = 生效值视图(文件坏值被哨兵回退 ROM 时主站看到的就是下游吃到的);
  adc_cal 配套:+adc_cal_b_codes() 读取口(回显/RMW 用)+ set() 授权写者
  注记扩至 iec104Task 直通路径(单字原子契约同「数据可信」§4.4)
- 持久化(裁②b):新事件位 SYS_EVT_RUN_PAR_SAVE(1<<8);iec104Task 主循环
  peek 投递 → defaultTask run_par_store_save() 一条事件一趟 cfg_file_save
  整档外科(覆盖 [run_par] 9 键 + 联动 [adc_cal] 两段);失败脏保留 = 天然
  重试(B7 prot_set 同款)。C_SE 现行无"暂停/测试态"门控——文档现行如此
  不造新语义,送检若要求接线点已写进 §3.4 注(裁④)

测试:新建 test/host/run_par(TC-RUNPAR-001~008,67 断言,真实 cfg 引擎 +
真实 adc_cal + 内存虚拟盘同 adc_cal 套件先例;[run_par] 注册契约/落盘往返/
外改文件即副本生效/两段式不提交不置脏/合法带拒+RMW+生效值回显/init 幂等
重注册留痕);iec104 套扩真链 TU(cfg_file+adc_cal+run_par 非桩,141 断言:
+TC-IEC104-029 路由 + 壳清确认计数 3 断言);meas 套静态更名 s_snap*→
s_ms_snap*(与 cfg_file.c s_snap 同 TU 相撞,D1 同款改名法,226 断言不变);
iec104_serv 套补 run_par_dirty_peek 假件。id_registry 建族 TC-HOST-RUNPAR
(游标 008)+ TC-HOST-IEC104 游标 028→029;新钩子 run-par-unit-test +
iec104 钩子触发面随真链扩;uvprojx +run_par.c(合法入库)+ SIM_EXEMPT 登记
(模拟器不编 104 协议,已核实 CMake 面)。

文档:IEC104 §3.4 运行参数段表(含送检替换账/门控未来条件/代理声明)、§7、
§8 第 4 条、变更记录;网络配置说明新增 §3.5 [run_par] 键表;复现规划 §5.4
补运行参数段落实进度。**遗留(交 lead)**:数据可信 §4.4 "引擎 RAM 副本"
一节当时的措辞对象含 pts 侧副本,s_yc_db 删除后该句过时(裁③b 点名,§4.4
在写域外未动)。

gate:@build 0/0(Code=219620 RO=22036 RW=1280 ZI=132864);vs 0cbae7d:
Flash +1912B、RAM(RW+ZI) +8B(37B 新静态被链接器并洞吸收);iec104Task 栈
1456B/margin 43%、defaultTask 3032B 不变;id-check/doc-links/cppcheck/
coding-standard 实跑过;--all-files 全量 45 钩子实跑(43 绿 + 仅 mem-budget /
mem-numbers 两红 = 台账受管区 vs .map 漂移,回填属 lead 单写者区,批 1/道D-1
/D2 同口径):gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 逐文件自查通过。
2026-10-25 13:15:41 +08:00
wandering1 62987b4596 Merge branch 'v2-on-ui' into task/meas-snap
# Conflicts:
#	scripts/id_registry.json
2026-10-25 10:29:18 +08:00
wandering1 48bc50e794 feat(遥测快照): 卡8 meas 双区快照模块 + adc_task 200ms 唯一发布点(道D M5)
卡 8(App/algo/meas.*,新增;布局权威 docs/任务规划/数据可信.md §4.5):
- meas_snap_t = §4.5 布局(sizeof 72 B;字段偏移由 TC-MEAS-002 钉死——卡 9/
  UI 按整体拷贝消费,破布局=破协议;谐波 k-bin 扩展(docs/算法/继电保护功能
  复现规划.md L2-8)只许尾部追加,留口注释已写进 meas.h)
- 并发 = phasor 双区 + 单字节索引翻转(L2-7 同型,无锁);seq 只增供判"换帧";
  meas_get 失败路径清零填充(-2 未就绪时调用方可安全读)
- 值口径 = A-lite 裁决(lead 2026-10-25 快裁,推翻票面全链):基波有效值瞬时
  透传,复用 phasor_calc 纯核 → 与 phasor 使用区同拍同值(TC-MEAS-005 逐位
  相等钉死);不做 4 点平均/方和根(§4.5"200 ms 是上送节拍不是平均窗")
- 品质注入:CLIPPED 位图唯一源 = adc_cal_flags()(「数据可信」§4.2 三态表,
  编排方注入、本层不链 adc_cal);取窗不足路 = IV|NT 且整帧 valid=0 但照常
  发布(品质节拍性刷新);TEMP = 最新一点透传 + NT(「数据可信」决策 5);
  MEAS_QDS_* 低字节 = 104 QDS 线上值(本层不 include 协议层头——分层方向,
  卡 9 直接截低字节映射);OV 留位不填(判据待运行参数化,卡 10 域)
- adc_task.c 编排:新帧拍 freq_run() 后按 **tick 经过**分频(非拍计数,idle/
  skip 拍会拉长名义节拍)调 meas_tick()——全仓调用点数 = 1(「数据可信」§8
  风险表"快照双写"行评审判据);meas_init 挂 adc_task_init(不动 app_main.c);
  心跳新增 meas: 行(seq/v/ua·un·ia_mv/f/fv)作 M5 台架观测面
- 死区 2 点确认状态机本卡不建(卡 9 迁移 104 语义时随建,lead 裁③);104 层
  现有 deadband 常量未动;频率点绑定归卡 11
- 实测采样类判据(读数 vs 注入源)= 待台架实测,本卡零台架动作

测试与注册:新套件 test/host/meas(226/226,链真源 meas+phasor+sample_ring
免桩;覆盖 seq/双区无撕裂/布局/品质汇总/回绕/守卫);id_registry 建族
TC-HOST-MEAS(游标 011);meas-unit-test 钩子 + SIM_EXEMPT 一条 + uvprojx
仅增 meas.c 单条挂载(diff 已核零 UV4 抖动);App/algo/README.md §1/§2/
§3.9/§4 同步(设计说明大节与 AGENTS 口径留 lead 收口,同批 1 先例)

gate:@build 0 错 0 警(Code=216352 RO=21476 RW=1232 ZI=132128);RAM 增量
RW+ZI = +152 B(快照双区恰 +144 B = 卡面 DoD;余 s_meas_ms 等编排状态),
IRAM1 57.8%→约 57.9%;Flash(Code+RO+RW)+696 B;adcTask 静态最坏链 1408 B
/ margin 1152 B(45%)PASS(stack_report 实测,无抬栈);BOM/CR/U+FFFD/
字面量 ASCII 逐文件自查通过(未触碰带 BOM
文件)。pre-commit run --all-files 实跑:其余钩子全绿(含 doc-links/id-check/
覆盖率总闸/双端注册/ui-golden/全部主机单测套),仅 mem-budget + mem-numbers
两条红 = 台账受管区 vs .map 漂移(Flash/RAM 台账回填属 lead 单写者区,批 1
同口径):
gate: only mem-budget/mem-numbers pending lead ledger backfill
2026-10-25 10:02:10 +08:00
wandering1 98664a11ed merge: task/ui-data-feed 合入 v2-on-ui
任务 ui-data(2 个提交)经 task-worktree 回收:
- feat(ui-data): ui_data 三通道快照 + ui_data_feed 装配 + SOE 显示名映射
- feat(ui-font): 字库补 SOE 显示名用字 采/样/恢/效(179→183 字 / 274→278 字形)
2026-10-25 09:59:35 +08:00
wandering1 201d713226 feat(ui-data): ui_data 三通道快照 + ui_data_feed 装配 + SOE 显示名映射
按 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(中文只进注释)。
2026-10-25 09:56:22 +08:00
wandering1 484d068250 feat(存储): 写回缓存空闲 flush 兜底——卷空闲周期 CTRL_SYNC(登记簿 N-22 收口)
道F 批 2 / task-16。FTL 单页写回缓存(nand_ftl.c s_cache_dirty)与 dhara
journal 只在换页/整页写/f_close 时刷出,漏网脏页的掉电驻留窗无上限。本
提交以最小面收口:不碰 GD5F/dhara 驱动,全部走既有 FatFs CTRL_SYNC 路径。

实现 App/fs/vol_idle_sync.[ch](新增,仅 MCU 端,uvprojx App 组注册):
- 四级门:STORAGE_READY 就绪门 → 5 s 节流(无符号差回绕安全判据)→
  vol_lock_wait(0) try(锁被持有=文件序列在做,跳过,不改 STOR 持有期
  语义)→ disk_ioctl(0, CTRL_SYNC);
- clean 态脏标志+journal 双短路、零 NAND 触点;脏态最坏 = 一次 f_close
  的同步段同型成本,不引入新磨损来源;
- 失败按边沿 latch 一条 DBG_ERROR、成功重新武装;失败路径也成对放锁;
- TEST_SUITE_STORAGE 构建整体 no-op(卷由 testTask 独占)。
接线:defaultTask 主循环 syslog_drain 之后(default_task.c v1.3)。

文档:docs/架构设计/STM32F4-Base存储架构说明.md §5.5 补空闲兜底与边界;
本板无掉电检测硬件、不做掉电前强制 flush、勿当掉电保护——口径同步落
注释与文档(sys_log_mcu.c 两处旧「暂不做」注释随实现改口)。

测试:新主机单测 test/host/vol_idle_sync/(TC-VOLSYNC-001..012,19 断言
全过);scripts/id_registry.json 建 TC-HOST-VOLSYNC 族(仿 ADCCAL 先例);
.pre-commit-config.yaml 加 vol-idle-sync-unit-test hook(只加自己条目)。

越界申报(①②③ 均 lead 批准,④ 为门禁连带必要):
① App/task/default_task.c 单点接线(include + 1 调用 + 头修改记录);
② docs/架构设计/STM32F4-Base存储架构说明.md §5.5;
③ .pre-commit-config.yaml 单 hook + scripts/id_registry.json 单族;
④ scripts/check_registration.py SIM_EXEMPT 加一行(vol_idle_sync.c 模拟器
   侧豁免,registration-check GAP 所需;与批准③同类别但未列名,特此申报)。

体积增量(vs scripts/ram_baseline.tsv 2026-10-25 行):
Flash(Code+RO) 237140→237328(+188 B)、RW 1224→1232(+8 B)、ZI 0;
增量 = 3 个文件级 static + 一处 DBG_ERROR 调用点;栈/堆无变化。

gate: only mem-budget/mem-numbers pending lead ledger backfill
全量 pre-commit --all-files:40 项 Passed,仅 mem-numbers(D-7 体积档)与
mem-budget 两条红 = 台账数字待回填(增量如上,属 lead 单写者禁区);
其余含 doc-links/id-check/registration/test-coverage/全部主机单测皆绿。
@build 0 错误 0 警告(Code=215932 RO=21396 RW=1232 ZI=131984)。

台架:空闲 sync 实机节奏(clean 零触点、脏态时长、无心跳扰动)属实际
测量采样类——本批跳过,留「待台架实测」。
2026-10-25 09:23:20 +08:00
wandering1 ffd462ef3c feat(保护标定): 卡5 未标定闭锁 + 卡6 下游三常数重定 + 卡7 量纲回归单测
卡 5(protect_task.c 第⑤闭锁源,「数据可信」§4.3 决策 1=闭锁):
- UA/UN/IA 的 adc_cal VALID 未全置位 → prot_elm_init + 本拍不 eval;每拍现查
  flags、无自有状态(卡 12 标定写入后自动解锁);判据链优先级 = 采样可信在前、
  标定可用在后(数据都是旧的谈不上标定)
- 边沿日志用 DBG_INFO 而非 [ERR]:未标定是出厂常态、非故障,且 @test 禁用
  [ERR](「默认构建保护不动作 = 期望」的台架判据同步项已在 MR 报 lead);
  SOE 分型 SOE_T_CAL_BLOCK/RESUME(soe_rec.h 枚举尾追加,值 6/7——reader 侧
  显示标签映射归道B reader,MR 已报)
- 台架旁路 = 编译期宏 DTU_BENCH(§4.3:不进 cfg 不进 104;本仓所有提交目标
  都不定义它 = 产品恒强制闭锁)

卡 6(下游三常数按工程量重定,依据同源 docs/任务规划/数据可信.md §2.3):
- FREQ_AMP_MIN(freq.h):语义从拟定码值域重定为二次值 V——50 V = 0.5×Un
  (标称 100 V),断保险/PT 空载/浮空必低于之;数值沿用 50.0f 属巧合并在
  注释明写;eng=0 过渡期本门恒失效 → OF/UF 按必不启动填充,与卡 5 同向安全
- k_yc[].deadband(iec104_pts.c):9 点按「额定 0.5 %」规则重定:电压类
  0.5 %×100 V=0.5、电流类 0.5 %×1 A=0.005、频率维持 0.5 Hz 绝对值(非额定
  比,卡 10 运行参数化);标称基准与 prot_set 头部同源,防两处真相
- g_prot_set_meta[](prot_set.c):数值/范围复核结论=不动(0~4×标称自洽),
  文件头补量纲口径块:A/V = 二次值、标称 100 V/1 A、OV120=1.2Un/UV20=.2Un/
  3U0 15=.15Un、OC1/2/3=10/5/2×Ir、OL=1.0×Ir;PS_ID_COUNT/blob VER 未动
- iec104 套件 TC-008 死区用例同步重定标(100.4 带内 / 101.25 越带),97/97

卡 7(test/host/prot_dim 新套件,真实链 adc_cal→ring→phasor→prot_elm):
- 用例从 ADC 码值出发、显式写出 code↔二次值互逆换算(dim_code vs apply),
  折算点被旁路或量纲再错位断言立刻翻转(M4 判据⑥「不该动作时不动作」)
- 合成 ROM 覆写(#undef K_Q16 宏)模拟卡 12 标定后板级口径,走 adc_cal_set
  正规路径置 VALID;b=-30 码全链对消进 rms 断言
- 38/38:基线静默(含 OL 低定值陷阱)、反错配对照(raw 码值灌环复现历史事故
  必假动作 vs 经折算必静默)、OC1/2/3+OL+OV+UV+3U0 七类逐一:越限定值→
  受控启动拍→dly-2 不动作/+5 动作→返回系数带内保持→跌破释放、双件聚合位图
  独立性(撤 IA 只回 OC1)

测试与注册:prot-dim-unit-test 钩子 + TC-HOST-PROTDIM 族(游标 010,B 层口径
同 ADCHEALTH/ADCCAL 先例);无新 MCU 源文件(uvprojx/SIM_EXEMPT 零改动)

gate:@build 0 错 0 警(Code=215640 RO=21388 RW=1220 ZI=131980 vs 基线
215440/19780/1220/131980 → Flash +1808 B、RAM 零增量);protectTask 静态最坏
链 872 B/margin 1176 B(57%)PASS,adcTask 不变;staged 门禁除 mem-numbers/
mem-budget(台账漂移,回填归 lead)外全绿:
gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 已逐文件自查(本轮未触碰带 BOM 文件)
2026-10-25 05:22:17 +08:00
wandering1 3528644e9d feat(采集标定): 卡2 adc_cal 折算模块 + 卡4 系数落盘 + L5-2 码值贴轨检出
卡 2(App/algo/adc_cal.*,新增;adc_task.c:335 折算点收口):
- 模型:工程量 =(code+b)×k(「数据可信」§4.1);k 以 int32 Q16(µV/码×65536)
  落盘、b 以 int32 码值落盘;ROM 默认 = 卡 1 定死的引脚侧 LSB,Q16 下整除无
  舍入(TPAFE 10,000,000=152.587890625 µV/码;STM32 52,800,000=805.6640625)
- adc_task 每拍 frame[s] 改 adc_cal_apply()(现单位 = ADC 引脚电压 V,eng=0,
  二次值语义等 VALID);TEMP 透传码值(决策 5);不缓存 k_f 副本——逐点由
  RAM 现值派生(无回调设计下杜绝陈旧系数;18k flops/s 可忽略)
- 三态 flags(§4.2):VALID = 文件位图 ∧ k 在合法带;CLIPPED 按来源运行期
  派生(V0.2/N-18 后自动恒 0);消费者判可用性只查 adc_cal_flags()
- 合法带哨兵:k∉ROM×[0.5,2.0] 或 |b|>8192 → 折算回退 ROM + 每通道每开机
  一条 [ERR] + VALID 压制(位图说谎也兜住)——同时挡坏文件与坏标定
- 两点标定公式 adc_cal_two_point() 算术核齐备(int64、±舍入界、斜率/零点
  守卫 -1/-2 分档);台架两点标定(卡 12)按用户指示未跑,留台架注入口径:
  心跳新增 cal: 行(ua/un/ia_mv 定点 + vl/cl/rail 位图,未标定 vl 应恒 0x00、
  cl 应恒 0xE6,注入饱和电平 rail 位应起)

卡 4([adc_cal] k_ua..k_in+valid / [adc_cal_ofs] b_ua..b_in,§4.4 布局):
- 两段注册进 cfg 引擎(data_size 33/32,不挂回调——RAM 副本即生效值);
  adc_cal_init() 落 app_main_init 内核前(注册必须先于 defaultTask 的
  cfg_file_load;跨任务读走 4 字节对齐字,µs 级混窗为「数据可信」§4.4 明载
  设计);文件缺失/损坏 = 引擎回退工厂默认 = ROM 表本体(默认即理论系数)
- adc_cal_set():整定写入口(属主上下文),越带拒绝 RAM 不动
- 单测走真实 cfg_file 引擎集成:文件值生效 / 越界 k 哨兵兜住且不报 VALID /
  超长损坏文件回退默认且 flags 无 VALID(卡 4 DoD 原句)/ 段缺失默认

L5-2(adc_health v1.2,越界门限按 FS 百分比、码值域与标定正交):
- adc_health_rail_watch():连续 20 拍(=一周波)贴同侧轨带(TPAFE±98%半量程、
  STM32 顶 98%/底 2%,门限由 adc_cal_rail_ths 供)且此前见过中带释放沿才
  锁存——空载恒 0 / 开机恒 0 合法工况不误报(短板评测 §6.2 判据①);
  adc_task 编排:边沿 DBG_ERROR + SYSLOG 每开机一次 + 心跳 rail/edge 位图
- 健康用例 60/60(+13):释放沿/清零/交替/双侧/init 复位/越界通道守卫

测试与注册:新套件 test/host/adc_cal(89/89,8 通道×≥3 码点期望值由 LSB
常数独立推导、Q16 无舍入、set 边界含端点、两点还原合成 k/b 往返);
编号形态返工一轮:TC-ADCCAL-00A~00D 改 014~017、注释 FS-1 字样改写、
id_registry 族 pattern 转义修正、族游标登至 017;coding-standard 返工:
公开实现函数补齐函数头注释块(CODE-RULE-09)
uvprojx 仅增 adc_cal.c 一条挂载(diff 已核零抖动);SIM_EXEMPT/id_registry
(TC-HOST-ADCCAL 族)/adc-cal-unit-test 钩子行按 new-source-file 与 B 层口径
补齐;覆盖率 34/34 套件 89.6%,adc_cal.c 99.2%(baseline --update 属共享
台账文件,留 lead done 后统一回填)

gate:@build 0 错 0 警(Code=215440 RO=19780 RW=1220 ZI=131980);
RAM 增量 +144 B(RW+16/ZI+128:贴轨 9×6B + 两段 RAM 副本,IRAM1 57.7%→
约 57.8%);Flash +1336B/+920B RO。adcTask 栈 worst=1344B margin 1216B
(47%) PASS(stack_report 实测)。staged 门禁除 mem-numbers/mem-budget 外
全绿(该两条 = 台账漂移,回填 --write 落 docs/内存管理/** 单写者区,按
Lead 既定例外归 lead done 后处理):
gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 已逐文件自查(本次未触碰任何带 BOM 文件)
2026-10-25 04:21:12 +08:00
wandering1 4ac869058d ci(gates): 自触发已生效;纠正我自己误读提交输出得到的两条假结论
先说结论:**触发面含门禁自身是生效的**。只改 .pre-commit-config.yaml 的两个提交
(e327636、c4ca292)里 test-coverage 都实跑并 Passed(约 45 s、28 套),bsp-err 同样
实跑。上一提交写下的"改 files: 那次由旧配置管辖"是**我误读输出得到的假结论**,
已按事实改写;那条猜测当时被当成"实测语义"写进配置注释,正是本仓反复防的问题,
所以这一批先降级为"待证"、再判定、再落事实。

误读的机制(值得留档,因为它会让人系统性看错门禁状态):钩子没声明 stages: 就
默认属于**所有阶段**,于是同一次提交的输出里每根钩子出现两次——pre-commit 阶段
那一遍才可能实跑;commit-msg 阶段那一遍拿到的"文件"是提交信息文件,跟这些钩子的
files: 都不匹配,几乎必然 (no files to check)Skipped。我前几次都只 tail 了输出结尾,
看到的就是第二遍,于是连着误判了两次(第一次判成"自触发没生效",第二次为了自洽
又编出"旧配置管辖"这条规则)。同一批里 bsp-err 的表现也是同一成因。

判据写成一句话:**看某根钩子跑没跑,认它第一次出现的那行**,别读尾巴。已同时补到
pre-commit使用说明 §2.2 的通用机制表里(新增一行),配置注释里也留了实跑证据的提交号。

自查:本提交只改配置与一份机制文档(无代码改动,未跑 @build);提交自身会触发
test-coverage 实跑一遍作为回归;doc-links 与 id-check 见下条。未 push。
2026-10-24 08:18:45 +08:00
wandering1 c4ca292102 ci(gates): 把"改触发面由旧配置管辖"降级为待证,并用这次只改配置的提交判定
上一条提交我把一个**猜测写成了实测语义**——这正是本仓一直在防的事(门禁文档骗人
比没有文档更糟),所以先降级为"待证",再用这次提交判定。判定依据是一根现成的对照
钩子:bsp-err 的 files: 从很早就含 ^\.pre-commit-config\.yaml$,它跑不跑直接反映
"本次提交的文件列表里有没有这个配置文件"。

预期读法(结果回来后写进上一条注释,或删掉待证标记):
  - bsp-err 与 test-coverage 都实跑 → 配置文件确实进了列表,那么 2d7c4fd 里两个都
    Skipped 只能用"新加的模式当次不生效"解释,猜测方向成立;
  - bsp-err 跑、test-coverage 不跑 → 说明 test-coverage 的自触发没生效,得查正则;
  - 两个都 Skipped → 说明"改配置那次不看配置文件本身",猜测成立但要改写措辞。

另外两次我误当证据的运行其实是 nothing to commit(暂存集为空 → 所有钩子 Skipped),
不能作为任何结论的依据——这也是我这轮反复的地方,记在这里免得下次重踩。
2026-10-24 08:05:23 +08:00
wandering1 e3276368b2 ci(gates): 记下 test-coverage 自触发的实测语义(改触发面由旧配置管辖)
接线时遇到一个说不通的现象:把自触发写进 files: 之后,那次提交里 test-coverage 仍然
Skipped;而我用 --files 与 staged 两种方式手工跑都会实跑。今天补做对照才定位:
同一批输出里 bsp-err 也 Skipped,而它的 files: 从很早就含 ^\.pre-commit-config\.yaml$。
两次现象一致指向同一条规则——**修改钩子配置的那次提交,按"修改前"的配置求值**。

这条对门禁的自洽性是好事:
- 想**拆掉**自触发(把 files: 里的本文件删掉):这次提交仍受旧模式管辖,钩子照跑,拆不掉;
- 想**加**触发面:要等下一次提交才生效,也就是加固会迟到一步、拆除不会漏网。

顺带说明:上一轮我"还原诊断探针"时命令顺序写错(先 git reset 再 git checkout --,
后者又从暂存区把那版恢复回来),弄出一条 # probe 残留在工作区;把它删掉后文件与
HEAD 完全一致,所以刚才那次 git commit 其实是 nothing to commit(不是被钩子拦的)。
现工作区已核实与 HEAD 一致、无残留。
2026-10-24 08:03:03 +08:00
wandering1 2d7c4fd40b ci(gates): test-coverage 的触发面补上门禁自身,否则改坏 files: 就没人跑这根闸
上一提交落地后立刻暴露这个口子:那次提交里新钩子显示 Skipped (no files to check),
因为它自己的 files: 不含 .pre-commit-config.yaml —— 也就是说,任何人改坏这一行
正则(哪怕只是打错一个字符),这根闸就永久静默停用,而提交照样全绿。这正是
本仓反对的"会静默失效的门禁",且已有先例处置:lv-conf-sync 与 bsp-err 都把
"门禁自身"写进触发面。

验证方式(不是只测绿的一半):
- 改前:pre-commit run test-coverage --files .pre-commit-config.yaml → Skipped
- 改后:同一命令 → 实跑并 Passed(约 45 s,28 套)
代价:以后改门禁配置要多等 45 秒——这种提交本来就是低频操作,值。

顺带修两处上一批留下的表述:
- 口径 §8 的 YAML 块补同一个 files: 模式(保持与落库那份一致)。
- 该节抬头那句我上次写坏了:JS 里把续行写成独立表达式,导致"…README.md 里只挂
  一行索引指向本文"这半句被丢掉,句子断在文档名上。已补全。
- 门禁说明 host-unit-tests.md 的触发面描述同步为"含门禁自身",并写明理由。

自查:doc-links --all --sections-strict 通过;改动文件 U+FFFD = 0、无 CRLF;
零代码改动故未跑 @build。未 push。
2026-10-24 07:53:40 +08:00
wandering1 744a1cb086 ci(gates): 接线 test-coverage 覆盖率总闸(FAIL 语义),并修三处已成假话的门禁文档
用户裁定 2026-10-24:缺 mscoverage 组件时**失败**,不做 skip。于是 P1 之后那个
"钩子只挂 16/28 套"的缺口关闭了。

为什么挂这一根而不是补 12 根 per-module 钩子:本闸跑**全部 28 套**,并且守
"已覆盖行只增不减"的基线台账。这是 per-module 钩子结构性做不到的——删掉一套
测试,它那根钩子只会因为"没有文件要检查"而静默跳过(Skipped 不等于失败)。
实测接线代价:45 s(28 套,含 28 次 MSVC 编译与插桩运行)。

FAIL 语义已按本仓"合成自证"的纪律验证(不是只测绿的那一半):把
DTU_MS_COVERAGE 指到一个不存在的文件 → 钩子 Failed、脚本退出码 2、并打印可
执行的 [ENV] 提示(说明该 exe 装在哪、怎么覆盖)。脚本对显式环境变量刻意
"不做自动探测回退",所以指错会被点名,而不是悄悄换一份工具跑出另一本账。

顺手修掉三条**现在已经不成立**的文档说法(门禁文档最贵的就是骗人):
- 口径 §7 第 8 条:原写"只有 BuildTools 且没勾代码覆盖率工具的机器会退出码 2"。
  实测本机 Community 18.10 与 BuildTools 18.9 **两份安装都自带**该 exe(mtime
  2026-09-15 02:30 / 2026-09-05 16:17,都是各自安装当日,不是后来补勾)→ VS 18
  的 BuildTools 默认就带它。真实风险面只剩:裸 MSVC 无 VS 的机器、环境变量指错、
  以及靠 PATH 兜底的环境。
- 门禁说明 host-unit-tests.md 盲区第 4 条:原写"没有对应钩子的模块 = 零回归"——
  如今 12 套新模块由总闸兜住,这句话已经不成立,改成两种口径并写清触发面。
- 同篇第 6 条:原写"无覆盖率统计"。行/函数覆盖**已有**度量(本闸出 Cobertura 并
  守基线),仍无的只是分支覆盖——那是用户裁定 2026-10-23 记下的**已接受缺口**,
  不是待办,改口时把这个区分写清楚。

钩子计数同步(新增 1 根 → 声明 40 根 / pre-commit 阶段 39 根 + commit-msg 的
gitlint,已用脚本核对,不是手数):AGENTS.md(总数、枚举、索引表)、
pre-commit使用说明.md(66 / 91 / 234 三处,含清点日期)、门禁说明 README
(正文与索引标题,另加一行索引指向口径文档——23 篇不变,同一事实不在两处维护)、
门禁说明 gitlint.md(--no-verify 跳过的"其它 38 个"→39)、并行任务工作树方案。

自查:end-of-file-fixer 与 trailing-whitespace 复跑 Passed(首轮 --all-files 只
因末行空行红过一次,已按钩子自身规范化);doc-links --all --sections-strict 通过
(178 个文档);id-check --strict 零台账外新账;8 个改动文件 U+FFFD = 0、无 CRLF。
零代码改动,故未跑 @build。未 push。
2026-10-24 07:50:53 +08:00
wandering1 1e4f74ba14 merge: task/ui-golden-ui-golden-ref 合入 v2-on-ui
任务 ui-golden(1 个提交)经 task-worktree 回收:
- chore(ui-golden): 像素黄金参考 ref/*.bin 入库(把门禁改成可移植)
2026-10-23 12:22:02 +08:00
wandering1 6822726b2c docs(门禁说明): 手册 §2 收敛为通用机制与查号表,逐项详解归一到 23 篇
用户一句"现在门禁都在干什么,我一概不知"引出的收尾。原手册 §2 有 27 个逐钩子小节,只回答
"拦了什么、怎么配",回答不了"什么时候会骗我";docs/门禁说明/ 已把逐项详解(含"限制与盲区"
一节)建好,本笔做收敛:同一事实只留一处(docs/文档规范.md 的要求)。

**先搬后删**(否则收敛等于丢事实):旧 §2 里独有条目逐篇折回,共 18 处插入、15 篇受益。折回的
东西包括:六套主机单测的来历与用例规模(13/36/82/46/32/149 断言,含 nand-port 的"与旧算法
对照")与"改动公共骨架 unit_test_harness.py 会同时触发 16 个钩子";UI 页面**两层注册**(构建
注册 vs 框架注册:枚举 + s_defs[] 描述符表)与"豁免不整目录一刀切";lv-conf-sync 首次落地清掉
26 个 LVGL 9.3 不存在的选项、3 处重复定义、10 项隐式开关显式化,以及 ALLOW_DIFF 端专属白名单
与必须穿过 lv_conf_internal.h 里那层 KCONFIG 包裹;cubemx-survival 的 16 项清单、断言前先剥
C 注释(本仓注释刻意引用历史代码,不剥就恒定误报)、旧手册"自造标记区"建议与看门狗文档实测
教训之间的优先级;id-check 顺带收编 check_test_traceability.py、裸形同字母多义属**显式承认的
盲区**、--update-baseline 只许涨;layer-deps 的注入自证与报错时三条修法(注入回调 / 上层递数据
/ 才谈 ALLOW);coding-standard **nand_ftl.* 也在排除清单**(我先前那篇漏了);test-suite-switch
的剥注释判定与派生宏白名单;mem-budget 两类软提醒不拦、退出码 2 是解析失败不是通过;doc-links
的排除目录与状态词 ±40 字符距离;task-table 改栈三步;gitlint 报错样例;no-mojibake 的修复路径。

**顺带抓到旧文档两处错**(记进文档,不悄悄改):一是 check-added-large-files 的阈值旧手册写
"单个文件 > 1MB",按本机安装的钩子源码实测是 --maxkb 默认 **500**(500 KB)——已更正,并把
"本目录任何默认值都以实测或安装的钩子源码为准"写进 end-of-file-fixer 当反面教材;二是
**ui-golden 在旧手册里连小节都没有**,只活在配置注释里:除了"有基线不等于有门禁",这是另一种
失踪——有钩子、没文档。

手册 §2 改写为三节:迁移说明 / **旧编号 → 新文档 查号表**(历史引用靠它读,旧号按"冻结历史不
回改"保留)/ **跨钩子通用机制**:两阶段与换机必须重装 hooks 才带 commit-msg、stash 陷阱、
always: true 已废、改写型与只读型豁免不对称、增量拦截三段式且刻意没有 --write、没有 .map 是
"跳过"不是"通过"、改配置的验收是看目标钩子出现在输出里且不是 Skipped、新增门禁三步必须做
注入自证且"限制与盲区"不许留空,另有快速自查入口。手册 707 行 → 273 行。

引用**按内容**重指(只核编号存在与否不够,同号小节会被当本文自引用静默放过):编号规范、
scripts/README(顺带更正那里过时的 warn-only 说法,早已关闸为增量拦截)、RAM 使用分析与优化
方案、测试开发标准流程、lvgl-simulator skill、AGENTS 文档表(拆成"每门禁一篇详解"与"通用
机制"两行)、.pre-commit-config.yaml 头注释、docs/README.md 描述、手册自身 §5.5 与 §6 的两处
内部引用。另:表格里的裸节号会被 doc-links 判成本文自引用,查号表统一写成不带 § 的形态。

写说明过程又被自家门禁拦一次:gitlint 篇里引用报错样例时写了两个规则号字面量,被判形态不合规
→ 改成中文描述,没有往台账里塞 reason 解决。

验证:纯文档与注释改动,不动代码,Program Size 不变,故不跑 @build/@test;
check_doc_links --all --sections-strict **171 篇 0 问题**;id-check 增量拦截通过;
pre-commit run **RC=0 / 13 项 Passed**;28 个改动文件字节卫生 **0 异常**(无 BOM、无 CR、
无 U+FFFD、均 LF、无行尾空白)。
2026-10-23 11:44:21 +08:00
wandering1 3488617bc9 docs(门禁说明): 新建逐项门禁文档目录,先落索引 README 与 bsp-err 首篇
用户的原话是:"现在门禁都在干什么,我一概不知。" 现有文档只回答"有哪些钩子、怎么配"
(docs/代码规范/pre-commit使用说明.md),回答不了"这一条到底在拦什么、什么时候会骗我"。
故新开一个目录,**每种门禁一篇**,八段固定结构:查什么 / 为什么要它(拦下过什么真事)/
触发面 / 报错怎么读 / 好处 / **限制与盲区** / 豁免与基线口径 / 改门禁本身要注意什么。

**最后一节是这批文档存在的理由**:只看"拦了什么"的门禁文档会让人以为绿就是对的。而本仓
三个增量拦截门禁(layer-deps 6 条、bsp-err 38 条、id-check 台账 640 处)的绿灯含义只是
"没有比上次更差"。

索引按 39 个 hook → 23 篇规划(16 个主机单测是同机制 16 实例,合一篇;cppcheck 与
test-cppcheck 是同工具两套范围,合一篇),文件直接用 hook id 命名,与提交输出里的名字
一致、可 grep。README 另有一节"三种拦截姿态"——改写型 / 硬拦截 / 增量拦截——分清这三类,
报错时才知道该改代码、还是该还基线。钩子清单、files 范围、pass_filenames 全部从
.pre-commit-config.yaml 实测解析(39 个 id:官方 5 + gitlint + local 33),不是手抄。

本次交付:索引 README + 首篇 bsp-err.md。首篇里把今天踩过的坑全写进"限制与盲区",共 10 条,
例如:只扫 Drivers/BSP(App 层同类问题仍无人管);consume-result 只认单行整句的裸调用,
跨行写的调用按正则形状测不到;must-use 名单靠"返回类型 + 注释关键词"启发式,注释不写
"错误码/失败"就漏、改名就能绕开规则;HAL 族是固定清单,清单外的返回 HAL 状态的函数不在
名单里;return-doc 的判据是"注释块里出现『返回值』字样",**散文里出现这三个字也会放行**
(本仓真发生过两条靠函数说明里的关键词误判通过);基线键带出现序号,--prune 只报"消失"、
不会报告某处只是搬了家;改标签名等于改基线键,必须原子完成。在册数量与构成按当前基线
逐条数出来写(38 = consume-result 37:gd5f2gq5ue.c 28 含 14 处 HAL 丢弃 + 14 处自家原语、
ui_net_sink.c 6 全为 net_close、rs485.c 2、nand_ftl.c 1;err-type 1 为 bool
ui_content_page_open())。

同步指向:AGENTS.md 提交门禁一节改为"每种门禁一篇详解 → docs/门禁说明/README.md",通用
机制仍指 pre-commit使用说明.md;docs/README.md §7.1 增加索引行;.pre-commit-config.yaml 的
bsp-err 注释块顺手更正(原文还写着"三条规则""现 1 条""只能靠书写约定兜着",均已过时)。
迁移状态用索引表的"状态"列跟踪;全部写完后 pre-commit使用说明.md §2 收敛为通用机制 +
总览,同一事实只留一处(docs/文档规范.md 的要求)。

验证:纯文档与注释改动,未触碰构建产物;check_doc_links --all --sections-strict 149 篇
0 问题(新目录的相对链接、跨文档章节引用都能判定);bsp-err 仍 38/38/0;本次受影响文件的
pre-commit 全绿 RC=0;两份新文档字节卫生 ok(无 BOM、无 CR、无 U+FFFD、有尾换行)。
2026-10-23 09:48:26 +08:00
wandering1 cc2966319a docs(S-5): 补齐 16 条返回值约定,bsp-err 基线 19 → 1,并修门禁自身误判
A(还债)先行,B(GD5F 的 SPI 失败被吞成成功)另波处理。本笔不改行为:@build 的
Program Size 与上一笔逐字段相同(Code 213,648、RO 18,860、RW 1,188、ZI 131,500)。

**补齐 16 条「返回值:」约定,bsp-err 存量基线 19 → 1**:
· gd5f2gq5ue.h 九个 SPI 原语(wait_busy / write_enable / read_status / page_read /
  read_from_cache / program_load / program_exec / block_erase / check_ecc)
  写全规范五要素,
  并把**每个函数能返回哪些码**写死:哪一个是"可重试"的 GD5F_WAIT_EXPIRED、哪一个才是
  器件自认坏块的 P_FAIL/E_FAIL、失败时出参为什么不可信(E-42 同族)。
· nand_ftl.h 从零散三行补成带模块头,并明写 **nand_ftl_init() 的 −1 抹掉了失败类**:
  分类目前只在失败点进日志,要按类分支需要 C4 快照(现无消费者故未做);同时警告"不得
  把 −1 解读成卷坏了可以重格式化"(E-31 的教训正在这条上)。
· ui_menu_tab.h 五个表查询 API 由行尾短注释升级为完整块(含"越界返回 false 是保守取
  只读"、"宁可按一张错表导航失败也不要按错表导航成功"这类判断依据)。

**修掉门禁自己的一条误判口径**(本笔最该记的):R3 原本要求紧邻注释块里出现字面"返回值",
而规范 V1.0 允许列对齐写法「返 回 值:」。后果有两层:① 合规写法被判违规;② 反向更糟——
gd5f_write_enable / gd5f_read_status 两条**早就"通过"了**,原因竟是它们的**函数说明**里顺口
写了"返回值"三个字,而不是真有一行返回值说明。**一条会因为散文里出现关键词而放行的规则,
等于在奖励运气**。修法是在块内忽略空格后再匹配关键词(排版选择不是违规),并在注释里写明
为什么不许把"必须写成不空格的形式"写进规则。

剩余 1 条刻意保留:bool ui_content_page_open()。它与"补注释"不是一档事——要改签名、要处理
3 个调用点(ui_menu.c:138/192/196 **今天三处全部丢弃返回值**,即"按 ENTER 开页失败是静默的"
这件事现在没人知道)、且 UI 源码双端编译(模拟器同一份),验证面是"重建模拟器 + 像素回归 +
真机逐页进菜单"。这不是还债,是独立一波改动,留档待决策。UI 三个文件带 BOM,Edit 会静默
剥掉 → 已按原样补回 3 字节(AGENTS 铁律:丢 BOM 后 ARMCC 按 CP936 解码,中文报 #870-D)。

**更正我自己写进文档的一条错话**:S-5 各条里我把 GD5F_SPI_TIMEOUT 100 记成"自旋圈数(改
分频时长会变)"。这次逐行核它 12 处用法,**全部是 HAL_* 超时参数或 HAL_GetTick() 差值 →
它也是毫秒**。真问题降级为"单位没写进名字(缺 _MS 后缀),读代码必须回实现里查单位"。
osWaitForever 与 HAL_MAX_DELAY 同为 0xFFFFFFFFU、以及 *_BUSY_TIMEOUT = -2 其实是返回码这两条
仍然成立(后者已改)。现状类条目(报告 §0.4、bsp_err.h、pre-commit使用说明.md §2.27、脚本
文件头)已全部改准;**已提交的变更记录行仍写着旧说法——按 §8.1 冻结历史不回改,以本笔新增
的更正行为准**。

验证:@build 0 错 0 警告(尺寸同上,故**不跑 @test**——无指令变化,跑它没有新信息);受影响
文件的 17 个钩子全 Passed(含 ui-golden 像素回归、cppcheck、编码规范、bsp-err 1/1/0 且
--prune 干净);check_doc_links --all --sections-strict 147 篇 0 问题;id-check
  --strict 通过;
9 个文件字节卫生 0 异常(UI 头 BOM 仍在、无 CR、无 U+FFFD、无行尾空格、均有尾换行)。
口径同步:AGENTS.md、.pre-commit-config.yaml、pre-commit使用说明.md §2.27 的基线计数 19 → 1;
报告 §0.4 / §9 / 附录 A 与变更记录两条(还债一条、更正一条)。
2026-10-23 08:25:58 +08:00
wandering1 78248a1cdf feat(S-5): 收编 SD2506/TPAFE 并给 dhara 失败分类,门禁基线 19→17
前一笔立契约与门禁(`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 状态改为 🟡 主体已落地。
2026-10-23 07:42:02 +08:00
wandering1 6ede1443e9 feat(门禁): 新增 layer-deps——BSP 反向 include App 的增量拦截(37→38)
推荐清单第 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`。未推送。
2026-10-22 19:30:02 +08:00
wandering1 a38f862fb9 chore(ui-golden): 像素黄金参考 ref/*.bin 入库(把门禁改成可移植)
问题:ui-golden 是逐字节比对(check_content_pages.py 里 data == refdata),
而基准 simulator/tests/ref/ 整目录被 .gitignore 忽略、从未入库。后果不是
"少几个文件",而是这条门禁在任何新克隆 / 新并行工作树里必然报「缺黄金
参考」——只在有老副本的机器上它才是真的回归门禁。本会话已两次实测踩到
(并行工作树里只能现场 --update 造基准,而现场造出来的基准恰好失去意义)。

入库前先证明基线可复现,否则等于把一条天天误报的门禁写进仓库:
- 主树 ref/ 生成于 2026-10-11,其间合入 B6~B9 与 UI 令牌化等多次改动;
  今天用刚重建的 lvgl_sim 重跑 → 11 页 + 开页-关页往返池水位逐字节全过
- 主树与新工作树两份**独立构建**的 lvgl_sim(不同构建目录、不同绝对路径)
  各自生成基准 → 11 个 .bin 的 SHA-256 全部一致
即渲染是 CPU 侧确定性输出,可以当仓库内容。

改动:
- .gitignore:整目录忽略 → 只忽略派生的目检 PNG。另加一条
  !simulator/tests/ref/*.bin 反选——第 115 行的全仓 *.bin 兜底(挡
  sim_netcfg.bin 之类运行产物)会把基线一起挡掉,且反选必须排在它之后
  才生效
- 不引入 LFS:11 x 3200 B = 34.4 KB(160x160 单色打包)。走 LFS 反而新增
  失效面——没拉 LFS 的克隆会拿 pointer 文本去比像素,把"缺基准"换成
  "比不中"。.gitattributes 已有 *.bin binary,check-attr 确认 text unset,
  不会被行尾转换改写
- 基线不会被改写型钩子毁掉:trailing-whitespace / end-of-file-fixer 声明
  types: [text],位图含 NUL 被判二进制 → 实测 Skipped;索引 blob、磁盘、
  主树参照三方哈希 11/11 一致
- 新增 11 个基线;同步 5 份文档与 3 处脚本口径:缺基准的提示从"首次/换机
  请加 --update"改为"检出不完整 → git checkout -- simulator/tests/ref/ 取回,
  不要用 --update 就地造基准"(旧提示正是自我实现型基准的来源)
- 顺带修掉 simulator/tests/README.md 两处与实现不符的描述:基准写成 PNG
  (实为 .bin)、regen_ref_png.py 写成"重建黄金参考"(它只出目检图,基准
  由 check_content_pages.py --update 刷)
- .pre-commit-config.yaml 只改注释(原文明确断言 ref/ "永远不会进 git
  索引",已被本改动推翻);ui-golden 的 files 面覆盖 simulator/tests/,
  基线被改即触发回归,刷参考从此在 git diff 里显形

历史口径按纪律冻结:UI_自动化测试流程 2026-09-10 的"整目录不入库"等 6 处
变更记录行不改写,以新行登记反转。

实测:@build 0 错 0 警(Code=213520,与内存台账一致);ui-golden Passed;
pre-commit run --all-files = 36 Passed / 0 Failed(RC=0)。
2026-10-22 06:17:00 +08:00
wandering1 858434492c feat(门禁): id-check 关闸——按债务台账增量拦截,warn-only 时代结束
机制(--strict 不再「有错就失败」,而是只拦台账外的新账)
- scripts/id_known_backlog.json:340 条指纹覆盖 710 处存量(②167 类 / ⑥135 / ③38)
- 指纹 = (文件, 校验号, 字面量) → 计数,故意不含行号:行号每改必漂,
  用它当指纹会让台账天天假新增,三天后没人敢看 strict,台账失效比没台账更糟
- 带计数而非集合:同一文件里同一个号多出现一次也算新账(复制粘贴式扩散
  正是编号烂掉的主路径,只按「出现过没有」判会放过它)
- 新增 --write-backlog 与 --prune-backlog(只减不增;台账内条目变多当场点名);
  --strict 缺台账返回 2,绝不退化成一次性拦死存量;--only 与 --strict 互斥
- 钩子 entry 加 --strict;--only 与 --strict 互斥
- 顺带修 _scan_malformed 的反向 bug:原条件 (skip and name != "空格形 Trap")
  语义正好写反,带 [示例] 的行反而专被 Trap 规则咬;改为示例行全部不判
- 第一次提交就被自己的台账咬住(id_known_backlog.json 里当然写着 P1-3 等号,
  被当成新引用)→ 台账加入 excluded_paths:登记号的表不是引用号的文档,
  与 id_aliases.json / id_registry.json 同一条道理

为什么不照「清零再升 strict」的原节奏(§8.4 详述)
- 存量 710 处清完要数周,这期间新债一条都不拦,而新增才是烂掉主因
- 「等清零」让人习惯「报红但照样提交」,真关闸那天没人再信门禁;
  本仓已有 mem-budget / mem-numbers 走 --no-verify 的先例,不再开第二个口子

关闸验收三例(临时件当场还原、git diff 干净)
- A 全新前缀 Z-99 → exit 1 点名 ✅ 咬
- B 同号计数 1→2 → exit 1 点名 ✅ 咬
- C 干净态 710 处 → exit 0 放行 ✅ 放(放不住存量就会逼出 --no-verify)

验证:--strict 干净态 exit 0;可判定比例 82.74%(基线 82.6%);
doc-links 失效/待确认 0;未改 C/H 不需 @build。
文档:编号规范 §8 上线节奏改写 + 新增 §8.4 机制表;pre-commit使用说明 §2.25;
AGENTS.md 门禁行由 warn-only 改为已关闸增量拦截。
2026-10-21 11:35:30 +08:00
wandering1 301c3e5183 feat(门禁): 编号门禁 id-check 落地(36→37 钩子)+ 批 1 编号单词化改名
新增门禁本体
- scripts/check_ids.py:实现 docs/编号规范.md §8 八项校验;默认 warn-only,
  唯一硬拦项是「可判定比例跌破基线」(防回退的本体)
- scripts/id_registry.json:66 个前缀族,逐项来自 §4 总表 + 全库实测游标,
  偏差一律写进 cursor_note,不凭记忆编造
- scripts/id_aliases.json:旧号 → 新号 / 作废标记(照搬 section_aliases.json 范式)
- 顺带收编 check_test_traceability.py:正则补齐大写后缀与无模块码形态,
  比对范围扩到 test/host 与 7 篇「单元测试说明.md」,另加 --board-only 回退旧口径

批 1 改名(用户授权解禁「不改写存量号」)
- 代码规范条款 S1~S19 → CODE-RULE-nn:78 处 / 2 文件
- UI 像素回归状态 S0~S7 → MENU-STATE-nn:61 处 / 3 文件(冻结在 07)
- 两族属主登记处 = 脚本自身的规则表 / STATES 表,文档只作引用方
- 新增 scripts/rename_ids.py(§7.3 规划脚本):映射表驱动、显式 scope、默认干跑、
  自动写 scoped 别名,跳过 [示例]/[计划] 行与变更记录表,按原字节读写保 BOM 行尾
- 已应用映射表存档 scripts/rename_maps/batch1.json 作审计轨迹

有牙实测(证明门禁不是空转)
- 注入 Z-99 / 裸形 / 悬空 / 越游标 / 未紧邻 / 跨文档重复定义 六例,
  分别被校验 1/2/3/5/6/4 点名,--strict 退出码 1;临时档删除后工作树干净

验证与同步
- 可判定比例基线 82.3% → 82.7%(已 --update-baseline 入档);错误级 1312 → 1291
- ci_local.py 全绿(L0 + L0.5 共 1104 断言);ui-golden Passed;
  check_doc_links.py --all 无法判定 0
- 未改任何 C/H 源文件,故本次不需 @build
- 文档:AGENTS.md 与 pre-commit使用说明.md 计数 36→37 并新增 §2.25 详解;
  编号规范 §4.1 补两行、§8.1 落地记录、§8.2 规范与仓库偏差、§8.3 迁移批次记录
2026-10-21 09:33:54 +08:00
wandering1 44dec5c49b feat(UI): golden 像素回归入提交门禁(W6 U4 第三件,35→36 钩子)
补上我上一轮自己标记为 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 页"是目标数而非现状数)。
2026-10-20 22:14:42 +08:00
wandering1 dafc18f5ed feat(UI): W6 第一波——设计令牌层、页面注册表生成器与 UI 门禁补强
启动 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 改同一批文件、文件内
交叠,硬切会产生不可编译的中间态,故整波一笔。
2026-10-20 21:14:14 +08:00
wandering1 720f841494 fix(storage): dhara 移植层 is_free 整页精度 + HAL 越界守卫;日志读写批量化
登记簿 N-21(报告 N-12/N-13)与 N-24(报告 N-16)两项未闭环问题修复并销项。

N-12:dhara_nand_is_free() 旧实现只读页首 64 B 判全 0xFF,会把"页首
0xFF、后段有数据"的已编程页误判空闲,dhara 遂往已编程页再编程 = 位碰撞、
journal/map 指向垃圾且不报任何错。改整页扫描(栈上 64 B 分块、页首命中即
早退,故"已编程且页首有数据"的绝大多数页成本与旧实现同价)。

N-13:报告原建议经核对不成立——本仓裁剪版 dhara 既无 DHARA_NO_PAGE(哨兵
实为 DHARA_PAGE_NONE,journal.h:46)也无 DHARA_E_UNSUPPORTED(error.h 枚举
里没有,照抄直接编译失败),且 map.c 三处 dhara_journal_copy 调用点各自已
挡掉哨兵。真风险是移植层零上界校验:越界页号被芯片地址位截断成某个真实页
→ 静默读写错页。改为六个 HAL 入口统一几何上界守卫 + DHARA_E_CORRUPT_MAP。
错误码选择是刻意的:BAD_BLOCK 会让 journal.c:904 标坏无辜好块,RECOVER 会
让 map.c:275 启动回收;无错误通道的 is_free/is_bad/mark_bad 一律取保守值。

N-16 写侧:一次排空合并成"每文件一次 open/write/close"(≤8 条 = 512 B
静态缓冲)。掉电语义不变——批次仍在同一次 drain 调用内 close,而 close
回写目录项这个不同 LPN 会把 FTL 单页写回缓存逼出到 NAND;失败尾部回投
队列,条目不丢(旧实现 -2 卷忙会直接丢掉那一条)。容量轮转仍只在按条慢
路径做,批量路径发现装不下一批就返回 -3 退回。
N-16 读侧:s_read_raw() 加 8 条滑动读窗口 + 写者代数失效,API 与 UI 零改动。

验证:@build 0 错 0 警(Code 213,540 / ER_IROM1 232,280 B = 22.2%,
RW_IRAM1 75,232 B = 57.4%,ZI +1,640 B 全为静态缓冲、不吃任务栈);
@test PASS,且台架 journal 恢复点与修复前同轨——连续四次启动 sectors used
恒 14,094、Map head 每轮 +128 单调推进、epoch 稳定 3 → 整页 is_free 未
改变恢复语义、无双编程、无回退;stack_report 9 个任务全 PASS(defaultTask
静态最坏链 3,024 B / 余 40%,实测峰值 2,164 B / 余 57%)。

新增主机侧移植层单测 test/host/nand_port/(第 16 个 -unit-test 钩子):
RAM 假芯片 + 真实 dhara map/journal 源,149 断言,含"旧算法只看页首 64 B"
对照用例(将来有人把 is_free 改回去会立刻红)与 dhara 级 resume 双编程计数
为 0 的场景。L0.5 由 15 runner / 955 断言 → 16 runner / 1,104 断言。

文档:存储架构说明 §5.3 加移植层两条硬约束;运行与自检记录设计说明
§4.4/§4.5 写批量化与读窗口;GD5F Trap Records 新增 Trap 07(含三条遗留
代价与一条待办编译期断言);登记簿 N-21/N-24 删行销项并同步 README
§1/§2.0/§2.1、排期详述 C3、报告 §0.1/§0.2/§0.4/§4.x/§7 与去处表;
钩子计数 33→34、主机侧单测 15→16 全库同步。
2026-10-15 12:34:39 +08:00
wandering1 09d6195906 chore: 收口已结束会话遗留的本地改动(RAM 台账/AGENTS/SOE 日志)
CI / L0 静态门禁 (pre-commit) (push) Failing after 1m12s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 17s
提交此前已结束会话在主工作区遗留、未提交的改动:
- docs/任务规划/ 与 RAM 占用台账、快照的同步更新
- AGENTS.md 索引与 scripts/ram_baseline.tsv 更新
- docs/测试说明/boot_log_data/ 新增 B9 IEC104/SOE 台架日志与解码产物
- .pre-commit-config.yaml 微调

来源为该会话结束时的未提交快照,整体收口便于并入 master。
2026-10-12 20:10:12 +08:00
wandering1 0d39cf63b1 docs(b8): B8 销项回填——freq 钩子登记 + 规划 L3-5/P2 置位 + RAM 快照再生
- RAM 快照再生(合并后 .map):RW_IRAM1 96,776 B(73.8%)/ CCM 不变 /
  ram_baseline.tsv 追加 B8 行(B8 分支 --no-verify 时欠的两条钩子在此补平)
- .pre-commit-config.yaml:新增 freq-unit-test(31→32);prot-elm /
  iec104 两钩子 files: 追加 App/algo/freq
- AGENTS.md:钩子计数 32 / 单测 14;算法层行补 freq = L2.5 测频(B8)
- pre-commit 使用说明:§2.21 freq-unit-test 详解 + 计数同步
- 保护规划:§3.3 L3-5 置 ✅(软件侧)、§7 P2 更新、变更记录 B8 行
  (自适应窗三代弯路 0.317→0.197→0.0101 Hz、J1~J6 判据、blob VER 1→2
  换代/损坏分流、prot_elm 7→9、OF/UF 暂只经 YX15 可见归 B10 分点)
- W1 卡:B8 销项删行;M2 注 B8 ✅;mermaid 代码道只剩 B9;
  写范围注更新(B9 sync 时 iec104_test.c 两边叠加)
- 任务规划 README:代码道 B7✅→B8✅→B9
- 合并侧已完成:merge 37f74f0(@build 0/0,L0 全绿)
- 验证:doc-links 全绿(142 文档)
2026-10-12 19:29:34 +08:00
wandering1 d3107f374f docs: 待处理内容.md 解耦为 docs/任务规划/ 文件夹(README 排序权威 + 6 个职责单一文件)
- rename_ref.py 搬迁:git mv + 全库引用改写 33 文件 82 处
- README.md 保留 §0 编号空间 / §1 未闭环一览 / §2 推进顺序权威 / §3 维护约定
- 拆出 6 文件:W1-采集保护收尾(任务卡五字段)、排期详述、暂缓-RS485、
  登记簿、测试与架构专项、基线与待确认
- 外部引用重定位 ~50 处:裸「登记簿 §X」、§5.1→基线与待确认 §2.1、
  §2.4→暂缓-RS485、§6→基线与待确认 §3、脚本注释 T-x/N-x→登记簿
- 新增并行写纪律:任务分支不改本文件夹,状态列由 Lead 主工作树更新
- 验证:doc-links 141 文档全绿;mem-numbers 一致;refgraph 重生成;
  @build 0 错误 0 警告;新文件无 BOM / LF / 零乱码
2026-10-12 13:42:47 +08:00
wandering1 90db528032 W1 功能主线完成:内存台账三件套(W1-0) + IEC104 上行(B4) + protectTask 板端接线(B5)
W1-0 台账三件套:scripts/ram_baseline.py(四模式)+ ram_baseline.tsv 趋势 +
docs/内存管理/RAM预算台账.md + pre-commit mem-budget 钩子(RW_IRAM1/RW_CCM >92%
即 FAIL + Top-10 附表);审查报告 §5.5 三条闭环。

B4 IEC 60870-5-104 上行(被控站,TCP 2404):App/protocol/iec104/ 三模块
(asdu 编解码 / pts 点表+数据源 vtable / link APCI+FSM,纯逻辑零 I/O)+
App/task/iec104_serv.c(iec104Task,NET_READY 屏障 100ms 分片喂位)。
82 断言 L0.5 全绿 + iec104-unit-test 钩子(第 31 钩子);
设计说明 docs/子系统设计/IEC104上行设计说明.md 全量回填。

B5 protectTask(L3 板端接线):App/task/protect_task.c——20ms 判据节拍
prot_elm_run + L5-4 采样故障闭锁(adc_health 电平位→元件复位)+ L5-1 定值
校验和巡检;新增 prot_elm_request_reset() 收口跨上下文复归(104 YK11 落请求、
eval 头部消费);定值/元件初始化移入 app_main_init(内核前)。TC-PE-010
(prot_elm 28 用例)。

门禁与内存:TASK_WD_PROTECT=8 入 required_mask(产品 0x77→0x1F7、HW 层
0x1F5,看门狗单测 39 断言同步);FreeRTOS 堆 48→56KB(.ioc L1 参数 +
FreeRTOSConfig.h 同步,RAM预算台账 §6 登记配额 48→56KB)。
ZI 144,008→152,204(+8,196 B = 堆 +8KB,B5 扩容);Code=204,348。
stack_report:protectTask 512 words 静态链 872 B/余 57%、iec104Task
640 words 1456 B/余 43% 均 PASS;RW_IRAM1 73.1% / RW_CCM 87.9%(<92%)。

验证:@build 0 错 0 警;ci_local 全绿(31 钩子 + 13 主机侧单测);
doc-links 135 文档 PASS。登记簿 B4/B5 销项、W1 波次完成;保护规划/看门狗
说明/AGENTS(9 任务)/RAM 三文档全部同步。

遗留(验收窗口,非代码空洞):台架第三方 104 主站往返(送检窗口)、
标准源注入动作判据、@test PROT 锚点 + protect 栈水位 + 新堆 min_ever 复测。
2026-10-12 12:36:23 +08:00
wandering1 d1e6bb4ba7 W5波+N-19收口:FTP鉴权落地、Trap25三连定案修复(N-30)、N-20/N-23/N-26
N-19 FTP 鉴权:
- lftpd_auth(USER/PASS 状态机 + 连续5次失败锁60s)+ SHA-256
  (user||pwd) 口令哈希;config.ini [ftp] 段 user/pwd_hash/write,
  出厂 admin/admin 在用有启动告警;lftpd_log.c 删除并入 lftpd_auth
- ftp_cfg 配置读写;台架 ftp_auth_bench.py 13/13

N-30 / Trap 25(CH395F_Trap_Records.md 四次定案全记录):
- 修复1 closed-with-data accept:客户端发完正常 FIN 后芯片按
  FUN_PARA(0x09) §5.52② 保留 Socket+RX 缓冲,旧代码把 CLOSED 一律
  relisten 自毁缓冲 → STOR 必挂 425;改为接受"已关但可读"连接排空
- 修复2 丢 CONNECT 中断探针 net_lost_connect_probe + last_peer_port
  幽灵甄别(拆链窗口握手完成但中断丢失,双信源去抖补 accept)
- 修复3 send_list/send_nlst 批量单发:背靠背 ~60B 小段毒化芯片 TX
  (芯片报 SEND_OK 但对端全丢),整份目录一次性写入
- 证伪存档:固定沉降/Socket 轮换冷却/listen 门控(台架 v7 矩阵)
- 台架工具 test/board/ch395f_reuse_bench.{c,h} + host 脚本存档
  (不注册编译;实验分支 n30-chip-bench 保留)

N-20:Error_Handler 死循环降级(记录+复位路径)
N-23:scripts/check_registration.py 双端注册反向校验(pre-commit)
N-26:GD5F NAND 直 DMA 调用方缓冲窗口断言

验证:BUILD_OK 0/0;ftp_regression 连跑 4 轮 11/11(修复前
LIST#4/#7 100% 挂、STOR#2 曾 100% 425);ftp_auth_bench 13/13;
@test 启动标记 PASS;ci_local L0+L0.5 全过;doc-links 通过。
登记簿 N-30 已销项。
2026-10-11 18:50:24 +08:00
wandering1 c936441c83 门禁扩容:新增 lv-conf-sync、simulator/ 纳入全部钩子;含 L3 定值/元件框架落地
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 LVGL 特性裁剪与
L3 保护定值/元件框架。

门禁(.pre-commit-config.yaml,26 个钩子)
- 新增 scripts/check_lv_conf_sync.py + 钩子 lv-conf-sync:比对两份
  lv_conf.h 的**生效值**(未定义则取 lv_conf_internal.h 兜底默认),
  抓 MISMATCH / LVGL 不认的宏名 / 同文件重复定义
- 顶层 exclude 删掉 simulator/:其下 21 个 tracked 文件全是本项目自研
  代码,生成物(build/、SDL2/、tests/ref/、shot*)已在 .gitignore;
  doc-links 的 exclude 同步删除
- lv-conf-sync 的 files: 覆盖两端配置 → 只改 simulator/lv_conf.h 也触发
  (pre-commit 4.x 已移除 always: true,实测只告警并跳过,无兜底开关)
- 新增 prot-set-unit-test / prot-elm-unit-test 两个主机侧单测钩子
- S11(2.3.5) 判定细化:禁的是"用路径找文件",尖括号系统头带子目录
  (如 <sys/stat.h>)不再误报;全仓库复核仅 2 处项目代码判定变化

模拟器代码整改(纳入钩子后暴露,全部修代码而非豁免)
- cppcheck 4 处:sim_syslog.c 判空分支内解引用 p_epoch(真实 bug)、
  2 处 struct tm 未初始化、1 处指针可加 const
- coding-standard 61 处:44 处函数左大括号并到右括号同行、8 处补函数
  注释、3 处补文件头注释块、2 处 __ 前缀保护宏改 XXX_H、2 处 sprintf
  改 snprintf(写入字节数与原来完全一致)

LVGL 特性裁剪(F-1/F-2/F-3)
- 两端 lv_conf.h 对齐同一特性子集:删 26 个 LVGL 9.3 不存在的选项名、
  3 处重复定义,10 项靠默认值隐式生效的改为显式固化
- 实测对镜像体积中性;价值在防漂移,且模拟器 golden 回归跑的正是固件
  实际编译的配置

L3 软件侧
- App/algo/prot_set.* Q10 定值模型(范围校验 / 整表 all-or-nothing /
  校验和封存-自检-闭锁)+ prot_elm.* 通用"滞回+定时限"元件框架 7 实例
- test/host/prot_set、test/host/prot_elm 主机侧单测;uvprojx 挂载两处

文档同步
- pre-commit使用说明:§2.8 S11 口径、§2.15 lv-conf-sync 详解、新增 §5.5
  复盘(教训:exclude 只写真正不该进钩子的路径)
- AGENTS.md、UI_设计架构 §1.4 第 10 条、RAM 分析、审查报告刷新与销项

验证
- @build BUILD_OK 0/0(Code=194276 RO=15872 RW=876 ZI=139892)
- 模拟器重建:菜单 8 状态像素逐字节一致(池 5368 B)、11 内容页黄金参考
  全过(往返 5448 B)、remo sink 0/25600 位差异、--logtest/--cfgtest PASS
- 全仓库 cppcheck / coding-standard / no-mojibake --all-files 全过
- check_doc_links --all 129/0;check_mem_numbers --check 内存数字一致
- 端到端:往 simulator/lv_conf.h 注入一行漂移 → 钩子 FAIL(exit 1)
2026-10-11 08:48:49 +08:00
wandering1 13a4e61c27 L1-6 采样有效性校验:adc_health 判定层 + 告警编排 + 31 用例单测 + 台架闭环
新增 App/algo/adc_health.c/h(纯逻辑判定层,零平台依赖免桩单测):
- 静默:两侧帧序号连续 >=100ms 无前进(电平位可自愈,flags() 为 L5-4 闭锁预留)
- 丢帧:TPAFE 序号一次前进 >1 计 tgap(与 STM32 侧 adc1_gap 对称,边沿事件)
- 单调性:序号倒退锁存位;tick/计数差值法 (int32_t)(a-b) 回绕安全
- 错误增量:FRSTDATA / HAL 错误 / STM32 停摆计数增量,事件位与锁存位编号对齐,
  消费者按位图做"每类每次开机留痕一次"编排

adc_task.c v2.9 告警编排(判定与编排分离):严重事件/电平上升沿 DBG_ERROR +
SYSLOG_SELFTEST(每类每次开机一次防刷屏),下降沿 DBG_INFO 恢复;gap 不告警
(消费者超拍是已知现象,告警会淹没真故障);心跳行增 tgap 观测字段。

主机单测 test/host/adc_health/ 31 用例全绿(100ms 门限两侧 / 边沿语义 /
倒退锁存 / tick 回绕 / NULL 守卫 / init 幂等),登记 adc-health-unit-test 钩子。

台架 @test PASS:零 [ERR](健康不误报)、tgap=1->2 与 adc1_gap 同步可见、
frames 1000.0 Hz;证据 boot_log_data/20261011_prod_adc_health_boot_log.txt。

抬栈:adc_health_report->syslog_post 控制台回显尾使 adcTask 静态最坏链
832->1080 B(512 words 余 968 B < 1024 B ISR 底线)-> 512->640 words,
复测余 1480 B / 57%,堆 min_ever 7152->6640 B(-512 B 符合预算)。
AGENTS/README 任务表、RAM 方案 §2.3、规划文档 §3.1 L1-6 已同步置完成。
2026-10-11 01:28:20 +08:00
wandering1 a2f5d1397a 主机单测:新增卷锁与时间同步两套用例(33+80 项)并登记钩子
- vol_lock(App/fs/vol_lock.c):33 用例。核心不变量是 fail-open——
  互斥量创建失败时 vol_lock_wait() 必须返回 1 且绝不阻塞,因为调用者
  defaultTask 是看门狗喂狗点;板端该路径只在内存耗尽时才可能走到。
- time_sync(App/sync/time_sync.c):80 用例。本模块是 N-17 看门狗风险的
  全部逻辑贡献者,用例钉死分片喂狗节奏、监听退避、报文重组(半包/粘连/
  EOF/真错/预算耗尽/tick 回绕)、重听门控;用 longjmp 跳出 for(;;) 任务循环。
- 两套均为纯逻辑主机侧测试,MSVC 离线编译真实源文件(#include 纳入)。
- 新增钩子 vol-lock-unit-test、time-sync-unit-test(7f/7g)。
- 全量主机单测:watchdog 39 + sys_clock 100 + cfg_file 149 + phasor 57
  + adc_input 70 + vol_lock 33 + time_sync 80 = 528 项,全绿。
2026-10-10 18:03:31 +08:00
wandering1 696a1a49a9 主机单测:新增 ADC 输入层(L1)用例 70 项并登记 L1/L2 单测钩子
新增 App/algo/adc_input.c 的主机侧单测(L1),补齐 L1/L2 算法层
"每个函数都可离线验证"的最后一处空白。

- test/host/adc_input/{adc_input_test.c,run_unit_test.py,stub/}:
  70 个用例,覆盖 6 个公开函数 + map_check + stm32_scan。
  关键判据 = "通道号 <-> Rank 顺序 <-> 语义"三者映射:给每个 Rank
  注入互不相同的码值,逐通道钉死映射(板端无信号源,无法验此项)。
  另钉死 HAL 启动失败 / 轮询超时 / 参数错 / 无有效数据等分支。
- .pre-commit-config.yaml:登记 phasor-unit-test(7d)与
  adc-input-unit-test(7e)两个主机侧单测钩子。

主机侧全套:adc_input 70/70、phasor 52/52,均 0 失败、无 /W4 告警。
2026-10-10 17:46:45 +08:00
wandering1 5eed2151de CubeMX 生成 ADC1(9 通道)+ 复原重生成定制,FreeRTOS 加固迁 L1、钩子落 USER CODE 4/5,门禁豁免生成文件空白改写 2026-10-10 00:53:20 +08:00
wandering1 029437be89 台架执行《实机测试实施指南》步骤 3~7 并闭环
步骤 3/4(IWDG V2/V4/V5 + 堆低水位 S11):
- V2 任务饿死 stall 后 ≤2.5s IWDG 复位;V4 关中断死循环 ~4.9s 稳定复位;
  V5 30min 120×FTP 全 PASS、零 IWDG 复位。堆告警全程未触发(min_ever=4728B)。
- task_watchdog.c:stall 证据写盘会经 nand_ftl_wd_feed 喂狗 → 增 s_stall_evidenced
  每次 stall 仅取一次证据,避免"取证据"本身阻止复位。
- default_task.c:堆低水位告警(HEAP_LOW_WATER_B=3072)。

步骤 5(GD5F 错误通道 S9):gd5f2gq5ue.c/.h + nand_ftl.c 错误码统一为 int32_t 并
全量检查返回值;套件回归 16/16 PASSED。
- test/run_all_suites.py:--forbidden 改为按套件覆盖(GD5F 用 "HardFault;assert")——
  TC-GD5F-704 故意触发 [ERR] 回落轮询,统一默认值会误判 FAIL。

步骤 6(网络稳定性):7.1 正常路径 ✅(V5 证据);7.2 N-2 丢弃可观测性代码已具备、
49s 静默在 V5 未复现(降级低风险观察项);7.3 N-3 待 PC peer。

步骤 7(工程卫生):
- S5:新增 test-cppcheck 钩子(仅 warning,排除模拟器桩)。
- S4:新增 scripts/check_test_traceability.py(默认仅告警)。
- 待核实项 TEST_SUITE_SELECTED 实机核实 = 死代码(无使用点);RTC 套件实际正常执行,
  真缺口 = 缺套件级汇总行 → sd2506_test_task.c 补 === RTC Tests: N/M PASSED === 并
  修正 Phase 级汇总累计问题;test_config.h 补齐 RTC 并注释"当前无引用"。

验证:产品 @build 0/0;pre-commit --all-files 17/17 Passed;
run_all_suites.py --suites RTC → PASS。
文档:实机测试实施指南 / SD2506_Test_Guide / 待处理内容 / 测试与架构改进建议 同步。
2026-10-04 00:35:06 +08:00
wandering1 fe963e7d91 测试:新增 cfg_file 主机侧单测并修复外科手术保存死码缺陷(S1-b+S2)
CI / L0 静态门禁 (pre-commit) (push) Successful in 49s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 17s
2026-10-02 13:00:54 +08:00
wandering1 fac9d1c409 测试:新增 sys_clock 主机侧单测并修复 Zeller 星期缺陷 2026-10-02 11:40:35 +08:00
wandering1 2055de0303 测试:落地测试架构改进 T-1~T-5(套件开关门禁/汇总行校验/通用 harness/公共断言宏/多套件编排)
背景:L0.5 主机侧单测落地后复审测试架构,给出 6 条改进意见(P0~P3),
登记进 docs/待处理内容.md「测试与验证基础设施」后逐条实施。

- T-1(P0) 测试套件开关门禁:新增 scripts/check_test_suite.py + pre-commit 钩子
  test-suite-switch,默认要求产品构建(全部 TEST_SUITE_* 注释),跑套件回归用
  --allow-test-build / ALLOW_TEST_BUILD=1 显式放行。顺带把 test_config.h 中长期
  开启的 TEST_SUITE_CH395F 改回注释(仓库此前躺着的是测试固件)。
- T-2(P1) verify_log.py 增加 --require-suite-summary:要求
  "=== <名称>: N/M PASSED (failed=0) ===" 且 N>0,堵住"套件没跑到汇总行/
  断言数为 0 仍判 PASS"的盲区;run_boot_test.py 同名参数透传。不传参向后兼容。
- T-3(P1) 抽取通用 harness test/host/common/unit_test_harness.py(工具链探测/
  INCLUDE-LIB 拼接/wd4819/GBK 解码/退出码契约);watchdog 运行器 170→55 行,只声明 TestBuild。
- T-4(P2) 新增 test/board/test_common.h 集中断言内核(TEST_CHECK_IMPL/TEST_REPORT_IMPL),
  4 套件各用一行宏绑定自身统计变量(保持 g_xxx_stats 独立、防跨套件串数)。
  刻意不 include dbg_log.h——否则冻结 DBG_TAG 默认值触发 #47-D 重定义警告。
- T-5(P2) 新增 test/run_all_suites.py 多套件回归编排,try/finally 无条件还原套件开关。

验证:@build 产品构建 0 错 0 警;4 套件逐一构建均 0/0;看门狗单测 39/39 PASS;
pre-commit 14 钩子全通过;编排脚本 dry-run/开关切换/异常还原均实测通过。
2026-10-01 07:28:57 +08:00
wandering1 73941da6b1 看门狗判定状态机增加主机侧单元测试(L0.5 层)
为 task_watchdog.c 的判定逻辑(位图取走清零 / 必需掩码 0x37 判定 /
miss_streak 连续 8 次停喂 / 100ms 抽样节流 / tick 回绕安全)补一层
主机侧纯逻辑单元测试,弥补此前只能靠真机或模拟器才能发现回归的空白。

架构(沿用项目既有"共享核心 + 平台后端"做法,参照 cfg_file_store):
- task_watchdog.h 末段新增"平台后端(内部)"声明 3 个钩子
  (wd_platform_feed / _start / _tick_ms),MCU 端以 __weak 提供默认实现,
  行为与重构前逐字一致(@build 0 错误 0 警告,Flash +20B)。
- 主机侧单测以同名强符号覆盖三钩子,即可离线注入/观测,无需碰 MCU 代码。
- 测试文件 #include 生产源文件本身,保证测的是真代码而非副本。
- 测试专用复位 task_wd_test_reset() 仅 TASK_WATCHDOG_UNIT_TEST 下编入,
  MCU 构建零代码零符号。

测试(test/host/watchdog/,MSVC 离线编译,纯标准库驱动):
- 39 项断言全过(退出码 0);注入故障可复现退出码 1。
- run_unit_test.py 直接拼 INCLUDE/LIB 调 cl.exe,绕开本机被禁的 reg.exe。
- stub/ 三桩头覆盖 main.h / iwdg.h / dbg_log.h。

门禁:pre-commit 新增 watchdog-unit-test 本地钩子(L0.5 层),仅被测文件
或其测试变动时触发;测试开发标准流程.md 新增 L0.5 层说明。

顺带改善:task_watchdog.c 的节流基线 s_last_tick 由函数内 static 提为文件
static(可测性 + 模块状态归一)。
2026-10-01 06:57:01 +08:00