794906cdc8a1a46579127ee53fe956db1164873a
390 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
794906cdc8 |
test(host): P2 覆盖率批次,5 套主机单测与共享假 FatFs
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,台账回填留待主树合并后。
|
||
|
|
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。 |
||
|
|
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。 |
||
|
|
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。 |
||
|
|
2c3939494d |
docs(测试): 更正覆盖率口径 §8 的接线理由——现在缺口换了方向
P1 合入后本仓主机单测从 16 套涨到 28 套,而 pre-commit 只挂 16 个 -unit-test 钩子。§8 理由 1 写的是"再加一份全量插桩等于同一件事做两遍"——这句话在只有 16 套时成立,现在**反了**:P1 新增的 12 套(net_cfg / ftp_cfg / prot_set_store / task_prof / lftpd_io / net_select / net_listener / net_pending / net_events / lftpd_inet / lftpd / iec104_serv)目前只被 ci_local 的 L0.5(自动发现)与 check_test_coverage.py 跑到,**不在提交钩子里**。 仍然不擅自接线,理由只剩 §8 第 2 条那一个,而且它是要用户裁定的:挂之前必须 先定"缺 VS 可选组件(mscoverage)时 skip 还是 FAIL"——选 skip 就是造一根会 静默失效的门禁(与 AGENTS 里"pre-commit 4.x 移除了 always: true、必须靠 files: 覆盖"是同一类坑);选 FAIL 则任何没装该组件的机器都提交不了。与其悄悄挂一根 可能假绿的钩子,不如把缺口写在文档里。 同时更新 §8 下一步第 1 条的过期数字:P1 已吃完(files_tested 19 → 32), 下一步是 P2 的 5 个文件 / 2,226 行;"把已有 19 个文件从 88% 推到 95%"改成按 当前实测口径表述。核对:该文档 U+FFFD=0、无 CRLF,doc-links 钩子 Passed。 |
||
|
|
ec2dbc9e01 |
chore(merge): 回填内存台账与钩子规范化,收口 task/cov-host-line-coverage
done cov 已把 15 个提交合入主干(合并提交 c8de948),但它的门禁按已知顺序跑在 @build 之前(skill 实测陷阱那条),比对用的是合并前的旧 .map,台账必然失配。 这里按 skill 的官方处置在主工作树回填,使主干自身全绿。 内存台账(@build 已在 done 里跑过:0 错 0 警,Code=213652 RO=18860 RW=1192 ZI=131504;主工作树的 .map 是权威值): - scripts/ram_baseline.py --update(趋势行 2026-10-24:RW_IRAM1 75,272 B 57.4% / RW_CCM 57,376 B 87.9%)+ --write(受管区);check_mem_numbers.py --write。 - RAM使用分析与优化方案.md 两处正文数字:「当前状态」那句刷成合并后的真值 (75,264 → 75,272,日期改 2026-10-24);2026-10-23 那条变更行里的裸 B 值 232,536 改为 KB + 快照文件指针——**历史结论一字未改**,只是不再在正文里留 会过期的裸字节值(--scan 的判据正是"曾是区域值、现已不是",无内联豁免)。 - 补一行本次合并的变更:RAM +8 B(net_cfg.c 为 WAIT 契约修复新增 s_save_busy)、 Flash -24 B(lftpd_string 改名删死循环 + net_select 去冗余守卫)、CCM 零增量。 覆盖率口径文档:修我自己留下的占位符泄漏——§9 有一段写的时候用 @@ 当反引号哨兵 (run_code 里反引号与模板串冲突),漏做还原,共 52 处。后果不只是难看: - `docs/网络/网络自愈架构设计.md` §4.10 因失去反引号而不再被识别为"紧邻写法", 被 doc-links(钩子带 --sections-strict)按**自引用**判成失效章节——这条真红了。 - "test/host/common/ch395f_fake.h + .c" 被整体当路径引用检查,报文件不存在。 - 另有一处未配对的 @@ 与一个 ``` 残留,以及过期计数("套件 21+"、"族数 16"), 改为落地实测值:TC-HOST 族 16 → 22、套件 16 → 28,并指向下方落地进度表。 核对:该文档 @@ 残留 0、U+FFFD 0、无 CRLF。 12 个 run_unit_test.py:end-of-file-fixer 去掉末尾多余空行。分支期我用 --no-verify 提交(只为跳过 .map 台账那两条已知误报),把这两个改写型钩子的规范化机会一起 跳掉了——本批由钩子自己改正,**内容零改动**,也正是"提交门禁不该长期绕过"的账单。 自查:pre-commit run --all-files 在本工作树 **38 Passed / 0 Failed**(含 16 个主机 单测钩子实跑);doc-links --all --sections-strict 0 问题;mem-numbers --scan 绿。 未 push(推送需用户明确指示)。 |
||
|
|
957ec64645 |
test(APP/104): iec104_serv 模块(28 条,84.2%)——P1 十三块全部进分母
P1 收官批次。新增 test/host/iec104_serv/,iec104_serv.c 覆盖 117/139 = 84.2%。 做法:把生产源整个 #include 进 TU,直接驱动它的 7 个 static 辅助函数——**不跑 iec104_serv_task 的无限主循环**(那是 never-returning 的任务体,不适合单测;事件组 相关支路用 sys_evtHandle=NULL 走"不可用"分支)。链路层与 pts 层给假件:链路协议本身 有 test/host/iec104 的 97 条专测,本模块只管服务怎么活、怎么死、怎么重生。 钉住的契约: - 建监听:2404 端口过 htons、绑 NET_INADDR_ANY;socket/bind/listen 任一失败都要把 已经拿到的号关掉(漏关就是每次重试漏一个 socket 号,CH395F 只有 8 个)。 - **网络代次一变就拆掉旧连接与旧监听并重建**:现场改 IP 后端口必须在新地址上继续 服务,这条不重建就是"设备换了 IP 之后 104 主站再也连不上"。 - accept 门控:监听未就绪不 accept;表项已失效时把监听号丢掉、下一轮重建。 - 单连接 socket 的断线语义:对端 FIN 时只关数据口,**监听必须留着**;数据号与监听 号同一个时绝不关(关了就端口失联)。recv 返 -1 且 errno 是 WOULDBLOCK 是正常空转, 不能当成错误丢连接;真错误则丢弃并**当场重建监听**,不留空窗。 - 判死用**连续**短写计数:一次成功即复位。写成累计就会把偶发拥塞误判成断线,主站 看到的就是莫名掉线(连到 3 次才判死,I104_SEND_FAIL_MAX)。 - 32 分频探活:无可读数据时不是每轮都去收(省 CPU 预算),到点才收一次;对端 FIN 后 复位计数器。协议状态掉回 IDLE 也要丢并重生(否则服务挂着但永不服务)。 - 时钟桥接两个溢出边界:epoch 秒超过 32 位一律拒绝且不碰时钟(截断写进 RTC 就是乱值); 未对时(sys_clock_get 返回 UINT32_MAX)回 UINT64_MAX 哨兵而不是 0(否则对端会以为 设备时间是 1970-01-01)。 主机侧兼容处理(写进桩注释):真实 App/util/sys_clock.h 会 include sd2506.h(RTC 芯片 驱动)一路拉到 HAL,PC 上编不动,故桩掉 sys_clock.h 但**两个签名与真实头逐字一致**; 104 的头散在 App 与 Drivers/BSP 的多个子目录(iec104_serv.h/iec104_link.h/iec104_pts.h/ task_watchdog.h/sys_events.h/prot_set.h/sd2506.h…),逐个手写 include 目录必漏且报错会 落在被测源上、极易误读成生产代码坏了——运行器改为**按目录实际是否含 .h 自动收集**。 自查:初跑红 2 条,都是我的场景问题(分频到点那轮 recv 返回 0 会走对端 FIN 支路, 应给 -1 + WOULDBLOCK;"重建失败丢监听号"必须同时让表项失效),改正后 28/28。 P1 收官合计:12 个新模块、**308 条断言**;套件 16 → **28** 全绿;files_tested 19 → **32**; 未触及 .c 47 → **34**;恒等式 34 = 0(P1 剩) + 5(P2) + 29(P3);分母 2,893 → **4,684** 行; line=4020/4684(85.8%)、func=300/333(90.1%)。百分比"降"是摊薄不是回退(lftpd.c 64.3%、 lftpd_io.c 53.5% 这类只测主体的文件也进了分母),按 §5 口径该盯 files_tested/files_untested。 亮点:lftpd_inet.c **127/127 = 100%**、net_listener.c 99.2%、net_events.c 97.8%。 编号补建 TC-HOST-I104SRV(游标 041),TC-HOST 族数 20 → 22。§9 的落地进度表补齐并 统一为 NET 拆出的四块 ①②③④。 自查门禁:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量); 10 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。 --all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填) 与 ui-golden(ref/*.bin 不入库)三条失败,与本分支改动无关。 |
||
|
|
a0c261b5ee |
test(NET/lftpd): lftpd 控制通道模块(35 条),FTP 一套四块全部进分母
新增 test/host/lftpd/,lftpd.c 覆盖 304/473 = 64.3%。做法是把生产源整个 #include 进 TU,于是命令表 commands[]、17 个 static cmd_* 与 handle_control_channel 都能直接驱动: 假 inet 层给"脚本化命令行队列 + 响应记录",假 io 层给"极简内存视图 + 可注入失败", 认证与配置当可注入黑盒(认证本身有 test/host/ftp_auth 的 10 条专测,不重复验)。 钉住的契约(都是真机上贵、却难按需复现的那几类): - STOR 传到一半写失败必须 **unlink 半成品**:否则 Flash 上永久留个坏文件,现场表现 是"升级包传坏了再也传不上去"。真机要卡准"传一半掐线"的时机,这里注一个短写就行。 - 数据通道是单连接 socket:LIST/RETR/STOR 用完必须关监听并把 data_socket 复位成 -1; accept 失败要报 425 不留残口,会话退出(QUIT 或对端断线)时 cleanup 也要关掉还没 用的被动监听。漏一次就是下一条命令拿着上一个号继续 accept,偶发连不上。 - 命令名缓冲只有 command_tmp[4+1],前置判断 if (index >= 5) 就是防 memcpy 越界; 恰好 5 个字符必须被拒(改一行这条就响)。 - 认证前置白名单:未登录只放行 USER/PASS/QUIT/FEAT/SYST/NOOP,其余一律 530;口令错 必须清 auth_user_ok 强制重走 USER;被锁与口令错都只回 530,不给攻击者区分提示; 配置读不到时 USER 无法匹配、PASS 直接 530(fail-closed)。 - 写开关([ftp] write_en=0)必须在**碰文件之前**挡住 STOR/DELE,且不去 accept 数据 通道;DELE 不许删目录。 - 227/229 编码:IP 经 net_htonl 后按高字节到低字节排(就是点分序),端口高/低两字节; 拿不到本机 IP 时要把刚建的监听关掉。FEAT 多行:首行 211-、四条能力无状态码、末行 211 结束(共 6 条响应)。 - 220 欢迎没送达(对端已走)就直接收工,不空转读命令;该留痕按真实分级算 INFO 而不是 ERROR(真实 private/lftpd_log.h 把 lftpd_log_peer 归到 INFO:任何人连 21 端口就走 就能无限制造固件级 ERROR,还会撞掉 @test 的 Forbidden 标记 [ERR])。 - 传输循环每轮喂狗(lftpd_task_wd_beat 计数可断言)。 主机侧两个必要的兼容处理(都写在桩注释里,不是随手改): 1. lftpd.c 会 include ff.h,而这份 FatFs 在 Windows 支路上把 winsock.h 拉进来,它带 "#define s_addr S_un.S_addr" 的 BSD 兼容宏,会把真实 net_types.h 里的字段声明 uint32_t s_addr; 展开成 uint32_t S_un.S_addr; 直接炸(实测 C2143 在 "." 前缺 ";")。 桩里只 #undef 这个污染宏,让固件那份结构体定义原样参与编译,被测代码读的还是真字段。 2. 引号 include 先从 lftpd.c 自己目录找 private/lftpd_log.h,桩目录赢不过它——于是 顺着用真实那份(它只是把 lftpd_log_* 映射成 DBG_*),只桩最底下的 dbg_log.h。这样 真实日志分级也一起被编译验证,比我自己重写一套更对。 自查(35 条里前两轮红了 4 条,全是**我的期望/前置态写错**,实现均正确):未登录时 FOO 被白名单以 530 拦掉测不到 502 分派;227 的 IP 顺序我把 htonl 前后搞反;FEAT 是 6 条响应我按 5 条索引;PASV 之后走完整会话会被 cleanup 关掉监听(那正是 006 要验的 行为),观察 PASV 留下的状态必须直调 cmd_pasv。 覆盖率与台账:套件 26 → 27 全绿;files_tested 30 → 31;未触及 .c 36 → 35;分母 4,072 → 4,545 行。**总体 line_rate 88.4% → 85.9% 是摊薄不是回退**(64.3% 的文件进 了分母),按 §5 口径要盯 files_tested/files_untested——它从 19 一路涨到 31。 恒等式 35 = 1(P1 剩 iec104_serv.c) + 5(P2) + 29(P3)。编号补建 TC-HOST-LFTPD (游标 034),族数 20 → 21。 自查门禁:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量); 9 个文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填)与 ui-golden (ref/*.bin 不入库)三条失败,与本分支改动无关。 |
||
|
|
8aaf9bbb0e |
test(NET/lftpd): lftpd_inet 模块(28 条,127/127 = 100%)
FTP 的 socket 适配层吃满——全仓第一个 **100% 行覆盖**的文件(127/127)。 新增 test/host/lftpd_inet/,28 条断言全部 MSVC 编译通过并 PASS。 钉住的契约: - 端口字节序:bind 前必须过 net_htons(sin_port 的注释就写着"网络字节序"),问名字 回来要 ntohs 还原;问不到名字报 -1,不拿脏端口当结果。 - 失败路径一律关 socket:建口失败不 bind;bind 失败关口不 listen;listen 失败关口 并各留一条 ERROR(漏关就是白占一个号,CH395F 只有 8 个)。 - accept 每轮失败都让出 CPU(同一段里也喂狗),直到成功;**网络代次一变立刻撤掉 accept**——否则改了 IP 之后监听还挂在旧代次上,端口在新地址上根本不通。这是现场 最常见的自愈路径,台架上要凑"对面正连着我顺手改 IP"极难。 - 带超时的 accept:监听门撤防时**立即放弃**,不对着死监听耗满窗口;对端一直不开 数据通道就按 100ms 步进等满再 INFO 收场。 - read_line:CRLF 就地截成结束符(FTP 命令行的边界);跨包分片要能拼出整行;读到 0 (对端关闭)立刻失败;-2 表示这一片超时继续等;其它负值**原样返回**(错误码不许被 吞成 -1);等待按 200ms 分片、30 s 总预算到点退出(分片写错就饿死看门狗);缓冲 填满仍无行尾要失败且**不越界写**(最后一格留给结束符)。 - 短写续传:net_send 可以只写一部分,续传必须从正确偏移接着写(少半条响应就是协议 错乱);写 0 字节按失败处理;二进制写成功时回请求长度。 桩的口径:private/lftpd_inet.h 与 net_types.h 用真实头;net_socket.h 是桩但函数原型 与真实头逐字一致,并**照做真实头对 FreeRTOS.h / task.h 的 include**(lftpd_inet.c 的 TickType_t / vTaskDelay / pdMS_TO_TICKS 就是从那条链上拿的,桩少这一行就是 C2065)。 lftpd_task_wd_beat 是 __weak 空实现、主机侧替换不掉,因此用同段代码里的"睡 100ms" 次数当喂狗轮数的代理(写进桩注释,不假装能直接数到喂狗)。accept 循环带 100 轮跑飞 保险(net_select 批次被挂死教训换来的)。 覆盖率与台账:套件 25 → 26 全绿;files_tested 29 → 30;未触及 .c 37 → 36;分母 3,945 → 4,072 行;line=3599/4072(**88.4%**,连涨三批)、func=272/299(91.0%)。 恒等式 36 = 2(P1 剩:lftpd.c 与 iec104_serv.c) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-LFTINET(游标 070),族数 19 → 20。 自查:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量); 9 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。 --all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填) 与 ui-golden(ref/*.bin 不入库,新 worktree 天生没有黄金参考)三条失败,与本分支无关。 |
||
|
|
59e6d77224 |
test(NET): net_events 模块(34 条,覆盖 97.8%),NET 三块收官
假 CH395F 的第三批、也是 NET 域最后一块。net_events.c 覆盖 175/179 = 97.8%。共享 假芯片本批补齐**对端六元组**(get_remot_ipp 的 6 字节)与**链路变更计数**( link_change_count / link_up_cached 改为可预置,原来硬编码 0/1)。 钉住的契约(都是真机上会发生、却不能按需复现的那类): - 对端六元组解析:remote_ip 是**低字节在前**拼的(192.168.1.50 -> 0x3201A8C0,与 网络序相反)。钉住它是防止有人"顺手改成大端",让所有按 IP 比对/白名单静默错位。 - 陈旧中断防误判:芯片说 TCP 还 ESTABLISHED 时,迟到的 DISCONNECT 一律忽略并报 ERROR(拆活会话的代价是 104 主站看到莫名掉线);一条数据都没收就报断且芯片接收区 还有字节,判为疑似误报、只置 lost_conn_seen 延后交给对账器。 - 正常断开:优雅级沉降 + session_dead + last_peer_port 记旧端口 + 转 CLOSED + 发事件。 - 超时:ESTABLISHED 走隔离级沉降并置 released_abnormal —— 这正是 net_pending 收尾时 把挂起 recv 报成 CONNRESET 而不是 0 的那条输入(TC-NETPEND-014),**跨文件因果** 只有两边都能离线跑才可测。 - 丢中断自愈:芯片已连上而软件还写着 LISTENING 时补一次 accept;若软件已 CLOSED 则 只留痕不凭空造会话(TC-NETEV-042b 记现状)。 - 对账器:100ms 周期闸门(同一时刻不重复查芯片);链路计数**基线那趟不打日志** (0xFFFF 哨兵,否则每次上电都冒一条假"link up")、变化即留痕;五种不一致形态的 稳定计数分三档(会话死亡要连观 4 次、芯片偷重开监听 2 次、丢 CONNECT 自接 1 次); 动作后 1000ms 冷却内不重复折腾;U11 重同步(软件 CLOSED 而芯片在 LISTEN -> 直接跟 芯片,不做多余重建);不在用的槽位直接跳过。 三条初判失败的复盘(都是**我的期望值/场景写错**,实现是对的):① remote_ip 字节序 我按网络序写了期望;② 自愈场景我用了 CLOSED 态,而 accept 分支只认 LISTENING/ CONNECTING,改场景并补一条现状断言;③ 链路"基线不打日志"我测在非基线场景(s_ link_last 是函数内 static,跨场景本就延续)。对账器的 s_last/s_bad[]/s_cool[] 都是 函数内 static(外部够不着),因此每个形态**各占一个 fd**、时基单调前进,互不串扰。 覆盖率与台账:套件 24 → 25 全绿;files_tested 28 → 29;未触及 .c 38 → 37;分母 3,766 → 3,945 行;line=3472/3945(**88.0%**,连涨两批)、func=263/290(90.7%)。 恒等式 37 = 3(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETEV(游标 058), 族数 18 → 19。 自查:test-cppcheck Passed(含补齐后的假芯片);check_ids --strict 通过;doc-links 35 处(= 存量);16 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。--all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后 统一 --write 回填)与 ui-golden(simulator/tests/ref/*.bin 不入库,新 worktree 没有 黄金参考)三条失败,与本分支改动无关;提交按并行分支约定走 --no-verify。 |
||
|
|
b4441f0e1e |
test(NET): net_pending 模块(43 条,覆盖 87.1%),钉住通知值回传契约
假 CH395F 的第二批消费者。新增 test/host/net_pending/,net_pending.c 覆盖 169/194 = 87.1%。共享假芯片补了**缓冲尺寸宏链的同构转抄**(RAM_BLOCK 512 / SEND_BLOCKS 2 → SEND_BUF 1024;RECV_BLOCKS 4 → RECV_BUF 2048 → SPI_DMA_BUF 2052 → SPI_DMA_MAX_PAYLOAD 2048),于是 net_pending.h 里的 NET_SEND_CHUNK_MAX 是**真值 1024**,分块断言测的是芯片上界而不是假件自己发明的数字。 本模块的核心契约是跨上下文回传:AGENTS 模块约定写着 net_* 的错误码经**任务通知值** 回传(net_msg_t 按值拷入队列,netTask 改的是自己那份副本,调用方永远看不到;也勿给 net_msg_t 加回传字段,否则调用方读到栈垃圾)。台架读不到通知值,所以把 xTaskNotify 做成可观测假件(记目标句柄与通知值)之后,这条契约才第一次被真正验证。 钉住的契约: - 注册闸门:越界 fd 报 BADF;同 fd 已有挂起报 BUSY;caller 为空报 BUSY;**属主任务 自己发起被拒**(自己等自己等于死锁 netTask);to_ms=0 记为无限等(不置 deadline)。 - 到达与截断:读量钳在调用方缓冲内(绝不越界写别人的 buf);readable 但芯片没数据时 只清标志继续等,不误交 0 字节。 - 收尾四条分支:还有残留先把最后一包读走再收尾(**不丢尾包**);干净结束报 0 并按 auto_relisten 请求重开监听(不自动重开的不被偷偷要求重建);released_abnormal 报 CONNRESET 而不是 0(调用方要能分辨好坏收尾);session_dead 与 CLOSED 同路。 - settle_close:没挂起时直接返回不打扰芯片;有残留交还字节数;无残留报 CONNRESET。 - 挂起发送:参数校验(buf 空 / len<=0 / sent<0 / sent>=len 报 INVAL);单次写入钳在 NET_SEND_CHUNK_MAX;**deadline 每写一轮就续期**(还在推进就不该被 1000ms 掐死, FTP 上传大文件最容易撞这条);停滞不前进才报 TIMEDOUT;槽位消失或会话塌掉都**交还 已发字节数**(不是错误码,调用方据此算进度);cancel 留痕且幂等;**clear 不发通知** (与 recv 侧同一条纪律,不给调用方假答复)。 自查:42/43 首跑,唯一红的是**我的测试构造缺陷**——无限等那条没先把槽位置成在用, 被 in_use==0 兜底分支提前收口,测不到时间轴;补 mk_sock 后 43/43。reset_all 从上一 批的教训直接带过来(连被测模块的 static 挂起表与属主句柄一起复位),这次没再红。 覆盖率与台账:套件 23 → 24 全绿;files_tested 27 → 28;未触及 .c 39 → 38;分母 3,572 → 3,766 行;line=3297/3766(87.5%)、func=255/282(90.4%)。恒等式 38 = 4(P1 剩) + 5(P2) + 29(P3) 仍咬合。编号补建 TC-HOST-NETPEND(游标 040), 族数 17 → 18;注册表仍只做增量插入。 自查门禁:test-cppcheck Passed;check_ids --strict 通过;doc-links 35 处(= 存量); 12 个新增/改动文件 BOM/CR/U+FFFD/行尾逐项净。零生产代码改动,未跑 @build。 --all-files 仍只有 mem-numbers / mem-budget(.map 台账漂移,等 Lead 合并后统一回填) 与 ui-golden(ref/*.bin 不入库)三条失败,与本分支改动无关。 |
||
|
|
0a3b932395 |
test(NET): 假 CH395F 芯片落地 + net_listener 模块(34 条,覆盖 99.2%)
按 §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 天生没有黄金参考)三条失败,与本分支无关。
|
||
|
|
fa4cde36f6 |
docs(覆盖率): §9 新增"假 CH395F 芯片"实施规格,并更正一项被我高估的前置成本
立项假 CH395F:NET 域剩下三块(net_pending 287 / net_events 263 / net_listener 191 = 741 行)都把芯片当真理读,这些语义真机上只在总线上一闪而过、台架不可复现;做完它 连 P2 的 net_socket.c(1,251 行,全仓最大逻辑块)一起解锁。 **一处更正**:我上一轮(提交 8bf4273 的正文与对话汇报)说这三块"还调 net_socket.c 的 内部函数(net_drain_chip_rx / net_relisten_done / net_relisten_locked),要一并给假件"。 实测这三个名字全是 **net_listener.c 文件内的 static**(net_listener.c:86 / :114 / :273), 不存在跨 TU 引用 → 该项前置成本**不成立**,工作量比我说的小。三块之间只走公开 API (net_listener_request/settle、net_pending_*、net_get_sock/net_sock_raw 等)。 规格内容(全部从真实头逐条核出,不凭记忆): - 15 个芯片函数的真实原型,假件必须一字不改照抄;特别标出三种形态: ch395f_get_socket_status / ch395f_get_remot_ipp 是**出参**、 ch395f_sock_state_cached 返回 **ch395f_sock_state_t 枚举**(不是 uint8_t,虽然底层 整型兼容,照抄才会在原型漂移时编译期报错)。 - 假芯片必须复刻的两条芯片怪癖:① get_sock_int_status **整体读即清**、芯片无按位 清除手段 —— 假件要按"返回该 socket 挂起位图并同步清空、可逐 socket 预置"实现, 否则照不出 AGENTS 模块约定里 B3 的实证坑("顺手全量清一遍"把别的在用 Socket 的 门焊死);② sock_state_cached / link_up_cached 是**缓存值**而非现场值,要把"缓存 在第几次调用后更新"做成可脚本化,才能测对账循环读到陈旧状态的路径。 - 脚本化模型沿用本仓既有假件风格(nand_port 的真头+假 diskio、lftpd_io 的可注入假 堆):读函数一条按调用序号取的返回队列、写函数一条调用记录、外加每函数调用计数 以断言"这一轮不该碰芯片"。 - 桩放置点需要拍板,已给推荐:test/host/common/ch395f_fake.h/.c 三模块共享,理由是 三份各自漂移会让"假件与被测代码同时错"这类问题再也报不出来;代价是不进 MCU 构建、 要靠 test-cppcheck 覆盖。若沿用每模块自带桩的惯例,则三份必须 include 同一份头锁签名。 - 实施顺序 net_listener → net_pending → net_events,逐个写明各自要桩什么、判据抄哪份 文档(网络自愈架构设计 §4.10 的读清点与三条准入判据),以及每步的验收动作 (@build 0/0、套件全绿、--update-baseline、先补 TC-HOST-* 族再往文档里引号)。 本轮**只动这一篇文档**(零代码改动,故不跑 @build);check_doc_links --all = 35 处 (= 存量基线,本分支贡献 0);文件 BOM/CR/行尾与 HEAD 一致。 另:本会话上下文见底,故把这块以规格形式交付而不是留下编译不过的半成品 —— 规格里的 原型、static 归属、怪癖与顺序都已核实,下一会话可直接从"写 test/host/common/ ch395f_fake.h"开始。 |
||
|
|
e307213745 |
docs(任务规划): 删除查无正文的 E-25 交叉引用,net_select 处置改用平实语言留档
起因:上一提交修掉 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
天生没有黄金参考)三条失败,与本分支改动无关。
|
||
|
|
5224f21622 |
fix(NET,lftpd): 处置覆盖率照出的三处死路径(改名 + 修 timeout=0 + 激活异常集)
三处都由本轮 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 天生没有黄金参考)。
|
||
|
|
658bc40ccc |
test(NET): net_select 模块(21 条)与 NET 域首份 ch395f 桩,登记两处死路径
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 天生没有黄金参考)三条失败,与本分支无关。
|
||
|
|
6bdf063f50 |
test(NET/lftpd): P1 增补 lftpd_io 模块(33 条),登记 FTP 域一处潜伏缺陷
新增 test/host/lftpd_io/:一个 TU 同时纳入生产源 lftpd_io.c 与 lftpd_string.c (两者静态符号不冲突,省一套桩),33 条断言全部 MSVC 编译通过并 PASS。 钉住的契约(都是真机上偶发、日志看不出根因的那类): - 路径规范化:**".." 上跳过头仍被钳在根内**("/a" + "../../x" = "/x")——FTP 客户端 可发任意路径,这是安全属性;绝对名忽略 base、"." 丢弃、段间多余斜杠折叠、 NULL 入参容忍、全空结果归 "/"。 - 分配失败:这个函数返回 pvPortMalloc 出来的指针(历史上正是它落在 C 库 malloc 上, RAM 方案 §4.2.1)。用**可注入失败的假堆**跑两条分支:首块失败 → NULL;结果缓冲 失败 → NULL **且第一块已 vPortFree**(不泄漏堆)。模拟器宿主 CRT 永不失败,这条 只有假堆能构造。 - 卷锁时机:open 成功**故意不释放**(整个传输期独占卷,由 close 释放),失败必须 释放并留 ERROR;单文件槽守卫 s_file_open 命中时**不碰卷锁也不碰 FatFs**。 - 等卷锁循环每轮喂看门狗(lftpd_task_wd_beat):defaultTask 与 FTP 争卷时这里可能 空转上百毫秒,漏喂就是整机复位。脚本化 vol_lock_wait 的返回序列直接构造 2 轮空等。 - lftpd_string_trim:行首空白由**返回指针**跳过、原缓冲不搬移(调用点 lftpd.c 用的 正是返回值);全空白串返回空串。 **本批照出一处,未改语义、只登记**:lftpd_string_trim 的尾部循环写成 for (i = strlen(p); i >= 0 && isspace(p[i]); i--),i 起步那格是 NUL、isspace(0) 为假 → 循环一次都不执行,**尾部空白与 CRLF 从未被裁掉**。今天不发作是因为 CRLF 在 lftpd_inet_read_line 里已用 strstr 截断、且唯一调用点只关心行首;风险在于"以后有人 拿它去尾部空白"不会生效。修法只要一行(i 从 strlen(p)-1 起),但那会改变 FTP 参数 (含空格的文件名/口令)的可见行为,需要 FTP 域一起过,**留给用户裁定**;当前用 TC-LFTSTR-005 把现状钉成"改了会响"(明确标注是现状桩、不是期望行为)。 未覆盖也写在文件头:lftpd_io_read/write/close/stat/unlink/opendir/readdir/closedir 要先把假 FIL/FILINFO 的字段语义(fptr、obj.objsize、fname 等)做实,否则断言会落在 假件自己写的规则上;这 8 个 f_* 只给了可链接的 FR_INT_ERR 实现,走进即失败不静默。 覆盖率:套件 20 → 21 全绿;files_tested 23 → 25;未触及 .c 43 → 41;分母 3,163 → 3,332 行;lftpd_io.c 85/159(53.5%)、lftpd_string.c 8/10。总体 line_rate 88.9% → 87.2% —— **这是摊薄不是回退**(新进来的文件只测了一半,分母涨得比分子快),已按 §5 口径写进口径文档 §9 的落地进度表;恒等式更新为 41 = 7(P1 剩) + 5(P2) + 29(P3)。 编号:按 B 层口径补建 2 族 TC-HOST-LFTPIO(游标 037)与 TC-HOST-LFTSTR(005), TC-HOST 族现共 **15** 族;id_registry.json 仍只做增量插入(无重排)。 自查:test-cppcheck Passed;coding-standard Passed;check_ids --strict 通过(无台账外 新账);check_doc_links --all = 35 处(= 存量,本分支贡献 0);新增/改动文件 BOM/CR/ U+FFFD/行尾卫生逐项净。**本增量零生产代码改动**(只加测试、注册表与文档),故未跑 @build。pre-commit --all-files 仍只有三条失败且与本分支无关:mem-numbers / mem-budget (本 worktree 上次 @build 生成的 .map 与受管快照漂移,增量 ER_IROM1 +12B、RW_IRAM1 +8B、CCM 0,按并行分支约定不在分支里 --write,等 Lead 合并后统一回填)与 ui-golden (simulator/tests/ref/*.bin 在 .gitignore 内,新 worktree 天生没有黄金参考)。 |
||
|
|
b1291f1308 |
test(NETCFG,CFG): P1①四个主机单测模块,并修 net_cfg 的 WAIT 契约与定长错记
按 docs/测试说明/主机单测覆盖率口径.md §9 的清单吃 P1 第①批「配置与工具」四块, 新增 4 个主机单测模块共 104 条断言,全部 MSVC 编译通过并 PASS: - test/host/net_cfg/(45 条):出厂默认可用、注册幂等与内容、load 报 -1 仍返回 0 (RAM 恒有效契约)、跨任务保存交接状态机 IDLE/WAIT/OK/FAIL、MAC 三态优先级、 IPv4 定长格式化。 - test/host/ftp_cfg/(16 条):[ftp] 段注册(**p_cb 必须为 NULL**,口令下次登录才 生效)、默认口令判定大小写不敏感、NULL 一律判不等(fail-closed)。 - test/host/prot_set_store/(16 条):blob 返回码分流 1 无档 / 0 成功 / **2 换代 (只打 INFO)** / -1 损坏;写盘失败必须回置脏标志(否则定值静默丢失)。 - test/host/task_prof/(27 条):无效样本闸门(防 DWT 复位把 max 抬到 2^32)、 未 enter 就 exit 不记账、首次 5s + 每 2s 节流、环形缓冲与裸发两通道、 周期→微秒换算随 SystemCoreClock 变。 覆盖率收益(已 --update-baseline 登记):套件 16 → 20 全绿;files_tested 19 → 23; 未触及 125 → 121 个文件(.c 47 → 43、9,657 → 9,228 行);分母 2,893 → 3,163 插桩 行;line=2811/3163(88.9%)、func=225/240(93.8%)。§9 的恒等式同步更新且仍咬合: 43 = 9(P1 剩) + 5(P2) + 29(P3)、9,228 = 2,188 + 2,226 + 4,814。 新单测照出并修掉两处真问题(都在 net_cfg,不是测试写错): 1. 实现违反自己的文档契约:net_cfg.h 明写"属主任务写盘中显示 WAIT",但 net_cfg_save_run 为了不丢新请求会先清 pending 再写盘 → 写盘那几百毫秒里 UI 保存行退回上一轮的 OK/IDLE(第一次保存按 ENTER 后屏幕上还是 ENTER),用户只 会再按一次。补 s_save_busy 标志,TC-NETCFG-028/026/030 钉死(含失败路径必须 清 busy,否则 UI 永久卡 WAIT)。 2. "定长 11 字符"是错的:%03u.%03u.%03u.%03u = 4 段×3 位 + 3 个点 = **15**。这个数 错了 4 处(net_cfg.h、net_cfg.c、网络配置说明.md 两处)——是我照抄它才撞上 FAIL,错在文档不是代码。已全部更正,并确认 len ≥ 16 的要求与 UI_BUF_STRLEN=32 本来就没问题(无截断风险)。 桩的口径(沿用既有模块,不另起一套):真实头优先——cfg_file.h / cfg_file_store.h / console_log.h / prot_set.h 全用真头(接口一漂移就编译期报错),只有 main.h 必须桩 (真实 main.h 拉整棵 HAL 头树,PC 上编不动;桩里字段名与位掩码宏名与 CMSIS 一致)。 prot_set_store 连真实 prot_set.c 一起编:blob 的编解码与脏标志在它里面,只有拿真 serialize 产出的 blob 才能造出"换代"与"损坏"两种真实形态,手搓等于测自己的桩。 配置引擎本身(解析/写盘/沉降)归 test/host/cfg_file 的 103 条,本批不重复测。 编号门禁:按 2026-10-21 批 3 的 B 层口径补建 4 族 TC-HOST-*(NETCFG 游标 056 / FTPCFG 014 / PROTSTOR 016 / TASKPROF 020,代码即属主登记处)。这一步不是走形式: 建族之前文档里引用 TC-NETCFG-028 被判「台账之外新账」直接卡提交,建族后才通过; 编号规范 §8.4 变更记录加一行说明族数 9 → 13。id_registry.json 只做增量插入(+88 行, 无重排)。 自查(提交时 pre-commit run --all-files,**仅 3 条失败且都与本分支改动无关**): - mem-numbers / mem-budget:本 worktree 因需验证代码改动而 @build 过,新生成的 .map 与 docs/内存管理 受管快照漂移。实测增量 ER_IROM1 +12 B、RW_IRAM1 +8 B、Flash +16 B、**CCM 0**,水位百分比全不变(改动 = 1 个静态字节 + 几条指令)。按并行分支 约定**不在分支里 --write**,台账由 Lead 合并后统一回填。 - ui-golden:simulator/tests/ref/*.bin 写在 .gitignore 里(黄金参考是本地生成物), 新建的 worktree 天生没有,报的是"缺黄金参考"而非像素差异;本分支零 UI 文件改动。 - 其余全部 Passed,含 @build 0 错误 0 警告、test-cppcheck(它抓到过我一条 p!=NULL 后又解引用的写法,已改为断言+显式守卫)、coding-standard、doc-links(35 处=存量)、 id-check --strict、mojibake 与 BOM/CR/U+FFFD/行尾逐项卫生。 - 自审修掉自己写的两条恒真断言(形如 x==0?1:1 的假绿)与一个重复编号标签。 - 4 个新模块**尚未挂 pre-commit 钩子**:.pre-commit-config.yaml 是单写文件,任务工作 树内不改;覆盖率脚本按 test/host/**/run_unit_test.py 自动发现,已实跑 20/20。 - 未跑 @test:改动仅 1 个静态字节 + UI 保存行的显示时序,无硬件语义变化。 |
||
|
|
e70b861215 |
docs(覆盖率): 新增 §9 未触及面建桩优先级清单(P1/P2/P3 逐个画像)
回答"那 125 个到底怎么吃":对 47 个未触及 .c(9,657 有效代码行)逐个量化后再排序, 不靠拍脑袋。排序口径写进文档、可复核可反驳:价值 = 有效代码行 × 逻辑度 × 缺陷历史 (该文件的 fix/refactor 提交数),成本 = 要桩的外部头数 − 能复用的现有桩;逻辑度 = 1 − 硬件符号密度(HAL_*() / FatFs f_*() / CH395F_* / SPI-I2C / OSAL 调用数 ÷ 行数)。 三档(数字互相咬合,覆盖率"不该 100%"在这里是算出来的): - P1 先吃 13 个 .c = 2,617 行:配置与工具 429(真实头即可编译,建议第一个落地,先用它 跑通"新增文件进台账 + NOTE 不误报回退"这条链)→ FTP 一套 1,083(20 次提交里 6 次 fix,最近两条都是静默型)→ NET 拆出的四块 903(须新建 stub/ch395f.h)→ 104 服务层 202。 - P2 值得但工程量大 5 个 = 2,226 行:net_socket.c 1,251(51 函数,**必须分片**,先测状态 迁移与语义判据);假 FatFs 一次吃 sys_log_mcu/cfg_file_store_mcu/soe_store 共 710; console_log 265(单写者契约 K1/K2 已写成表)。 - P3 明确不做 29 个 = 4,814 行,并把理由写死免得下次又列回待办:芯片驱动 1,990(假到底 ≈ 重写驱动,测的是自己的桩)、UI 侧 1,942(与模拟器同一份源码,已由 ui-golden 覆盖)、 任务编排 747(4~5 个函数的装配层,写成单测就是"断言我调了几次")、app_main 135 (被测对象本身就是内核启动前的时序约束)。 - 恒等式:47 = 13 + 5 + 29、9,657 = 2,617 + 2,226 + 4,814;做完 P1+P2 约 4.8k 行进分母 (现分母 2,893),files_tested 19 → 37。 配套口径修正: - "125 个未触及"易被读成 125 个待办,三处写精确 = 47 个 .c(9,657 行)+ 78 个 .h (2,182 行,多为声明与宏);待办池是那 47 个 .c。 - §8 下一步①改为指向 §9;终点判据从百分比改成"untested_sources 里剩下的每一条都能在 P3 表里找到归属",并提醒盯 files_tested/files_untested 而不是 88.2%(新增大块未测代码 会把它摊薄,那不是回退)。 自查:check_doc_links.py --all = 35 处(与主干同数,本分支贡献 0;期间修掉一处 §4.5 被 误算成本文自引用);check_ids.py --strict 通过;check_mojibake + BOM/CR/行尾卫生净。 本轮纯文档,零 .c/.h 改动,未跑 @build、未占 build/bench 锁。 |
||
|
|
6505cef9df |
feat(门禁): coding-standard 加 CODE-RULE-20 禁 C 库堆分配(销 §4.8 空白 ②)
规则:在**掩码后**的文本上匹配 malloc/calloc/realloc/strdup/strndup/ free/aligned_alloc 调用。三条判据都是踩过坑之后定的—— - 大小写敏感:pvPortMalloc / vPortFree(唯一允许的配对)不会误报; - 词边界 \b:dhara_nand_is_free(、buf_alloc( 这类同形后缀不误报; - 用掩码:注释与字符串字面量里写 malloc 不报(规范示例就踩过这种)。 覆盖名比 AGENTS 列的五枚多带上 strndup / aligned_alloc——同族同类, 漏一个就等于留后门。 出处不在规范 V1.0,在平台约束:MicroLIB 的 C 堆由 startup_stm32f407xx.s 的 Heap_Size EQU 0x200 只有 512 B,KB 级分配必返 NULL,而模拟器(宿主 CRT 堆)完全正常、**照不出来**——配置引擎 2026-09-18 那次堆分配纠偏与 lftpd 的 malloc 隐患都是**先上线才发现**的那类,靠评审等于没管。 落地前按 门禁说明/coding-standard.md §8 的纪律先全库普查:钩子范围内 C/H 共 151 个文件,命中**只有** simulator/sim/cfg_file_store_sim.c 两行, 即 cfg_store_alloc/cfg_store_free = **平台分配后端的宿主侧实现**(设计依据 写在 docs/子系统设计/配置文件设计说明.md 的"MCU 端 = pvPortMalloc、 模拟器端 = 宿主 CRT")。所以零基线、直接硬拦截;豁免用脚本里的 HEAP_ALLOC_ALLOWLIST(**文件路径 + 函数名**双条件),刻意不做目录级豁免: 下次谁要碰 C 堆就得往这张表加一行,评审看得见。 自证:临时样例文件里 malloc/calloc/free 各报一处,pvPortMalloc/vPortFree/ buf_alloc(、注释、字符串五种陷阱形态全部不误报; pre-commit run coding-standard --all-files → **Passed**(= 全库零存量)。 顺带修三处口径不一致: - 规范 V1.0 §6.1 的示例自己写着 malloc/free(通用 C 模板遗留)→ 已在示例 下加"本仓例外,别照抄"批注,事实源指向 AGENTS.md 平台约束; - docs/门禁说明/mem-budget.md 里"这条纪律只靠 AGENTS 铁律与评审"已过期 → 更正为由 CODE-RULE-20 机器拦截; - AGENTS.md 平台约束条加"已机器拦截 + 唯一豁免"一句;scripts/README.md 补登 check_test_coverage.py(上一个提交漏登记)。 另:本次一并修好上一处编辑事故——报告 §4.8 曾被我的脚本调用截成半句 (只落第一行、丢掉 malloc 那半句),已重写为"两处空白均已闭合"。 自查:pre-commit run --files(9 个改动文件)适用项全 Passed; check_doc_links.py --all = 35 处,与主干同数(本分支贡献 0); check_ids.py --strict 通过(裸编号 P0-1 改平实说法,避免记成新账); 9 个文件 BOM/CR/U+FFFD/行尾与 HEAD 逐项一致。本轮零 .c/.h 改动,未跑 @build、未占 build/bench 锁;分支已 rebase 到 v2-on-ui,覆盖率脚本全量 回归仍 16/16、line=2552/2893 不变。 |
||
|
|
2bfa09ed15 |
feat(门禁): 主机单测覆盖率脚本与口径落地(销报告 §4.8 空白 ①)
落地物: - scripts/check_test_coverage.py:发现规则与 L0.5 同一条(test/host/**/ run_unit_test.py),直接 import 各运行器复用其 TestBuild 与 unit_test_harness._BASE_FLAGS —— 配置零复制,不会漂出第二套口径; 只算自研生产源码(App/**、Drivers/BSP/**)的行/函数、多套取并集、 生成物(文件头写"自动生成/请勿手改")不进分母、分母一律来自插桩 报告不猜行数;中间物全在 %TEMP%,不新增 .gitignore 规则。 - scripts/test_coverage_baseline.json:增量拦截台账(只拦回退不拦进步, 与 layer-deps / bsp-err 同姿态);ci_local.py 加 opt-in --coverage。 - docs/测试说明/主机单测覆盖率口径.md:口径 + 工具链实测 + 九条盲区。 - 报告 §0.1 / §0.4 / §4.8 / §5.5 / §7 改写 + 变更记录 + 去处表。 工具链是实测出来的(换机必读):Windows 原生侧无任何 gcc/gcov(全盘扫 Program Files*、msys64、scoop、choco、.platformio、LocalAppData = 0 处); MSVC 14.51 已砍 /coverage,且只报 D9002 后照常编译成功 = 静默假数据; CodeCoverage.exe 17.0 三个子命令只打印用法。最终用 mscoverage 18.x (Microsoft.CodeCoverage.Console.exe)collect -f cobertura 做加载期动态 插桩,并实测它透传子进程退出码 → "插桩运行下仍必须 PASS"可当硬条件。 首登数字:16/16 套插桩下全绿,并集 line=2552/2893(88.2%)、 func=193/207(93.2%)、19 个文件在测、**未触及 125 个生产源文件 / 约 11,839 行**——最后一栏才是这套口径真正照出来的东西。 门禁自证:人为抬高基线后 行/函数/总计 三条回退路径都报 REGRESS 且 退出码 1;--module 子集跑不再假报总计回退(首版会,已修:并集口径下 子集必然变小,全部回退判定只在全量跑时生效)。 GCC 追问的复测结论(口径文档 §2.1 决策记录):本机 WSL2 Ubuntu 确实有 gcc/gcov 15.2.0,16 套单测在 gcc 下全部编过、用例数与 MSVC 逐个一致, gcov -b -c 也确实给出分支覆盖(nand_ftl.c 分支至少走过一次 41.40%); 但换编译器=换分母(sha256.c gcc 105 行 vs MSVC 114 行;adc_input.c 101 vs 120)。**用户裁定:本仓只留 MSVC 一条覆盖率通道**,不建第二条—— 多一套工具链就多一套分母、多一本台账、多一个"这台机器到底有没有 WSL/ gcov"的未决项,换来的只有分支这一维。分支覆盖因此**记为已接受缺口** (不是待办):绿灯只证明行与函数被测到,补偿判据 = TC 双向追溯 + 用例显式列边界值;§8 写明重开该裁定的前提是先答"两份分母怎么对齐"。 刻意未挂 pre-commit:16 个单测钩子已经跑过同一批测试;依赖 VS 可选 组件(缺组件时 skip 会静默失效、FAIL 会拦换机);待一个真实回退事件。 YAML 片段与理由写在口径文档 §8。 自查:pre-commit run --files(6 个改动文件)全 Passed;check_doc_links --all 回到存量 35 处(本分支贡献 0);check_ids.py --strict PASS; check_mojibake + BOM/CR/U+FFFD/行尾卫生逐项自查全净。本轮零 .c/.h 改动 (纯 .py/.json/.md),故未跑 @build、未占 build/bench 锁。 |
||
|
|
49a257b34c |
docs(任务规划): 立项 W8 数据可信,补 ADC 注入验证两卡,新增并行分道方案
- W8 数据可信:通道标定 → 遥测链 → 运行参数,14 张任务卡。 - 补卡:核查确认 ADC 从未采过已知电压(只验过通路活),新增 "已知电压注入验证"(只需万用表)与"端子变比确认"两卡;M4 的承诺 边界改为"ADC 引脚电压",二次值降为依赖变比的条件项。 - 撤销"1k+1k 即 2:1 分压"的换算用法:该网络被定性为源阻抗匹配, 且端子→引脚变比仓库内无文档。 - 并行分道方案:全部待办归入 8 条道 / 8 个写域,给批次与合并次序。 |
||
|
|
c0ccd777ae |
docs(UI): 页面补齐规划、逐页扩充方案与实时数据供给设计
- 页面补齐规划:重算菜单树(42 节点 / 33 叶子 / 6 已绑页 / 27 空叶子), 27 叶归并为 7 模块 + 2 项前置能力 + 1 数据层,分 4 批。 - 页面扩充详细方案:逐页规格(行表、数据来源、交互、字号),并纠正 已建 6 页里 3 页实为 Flash 字面量。 - UI 数据供给设计说明:选定"快照推送"(App 推 / BSP 存 / 页面只读), rt 200ms + par 变化触发双通道;单写者同上下文故无需锁;记录页改为 注入 reader,可回收 3 条分层门禁基线(6→3)。 |
||
|
|
70dd6f8192 |
docs(评测): 新增采集与数据处理短板评测(对照 CLS100 参考项目)
按"采集→数据处理"全链逐层对照参考项目,结论:已建成的是保护链, 还不是能上送的测控装置。四类阻断短板:工程量折算缺失、遥测链整条 缺失、运行参数段 0x8020~0x8058 未实现、录波与 104 文件传输缺失。 另登记两处此前未记录的发现:固件无任何开入/开出代码;交流量与 直流量页为硬编码占位值,运行时不变。 建议分三阶段:数据可信 → 精度与安全网 → 录波与文件。 |
||
|
|
1e4f74ba14 |
merge: task/ui-golden-ui-golden-ref 合入 v2-on-ui
任务 ui-golden(1 个提交)经 task-worktree 回收: - chore(ui-golden): 像素黄金参考 ref/*.bin 入库(把门禁改成可移植) |
||
|
|
b22779d244 |
docs(存储): 补上 N-15 页缓冲可合并性论证,判定三块不可合并
报告 N-15 一直写"与 R-3 同批评估",但评估从来没写过——nand_ftl.c:300 的注释直接引用了这份不存在的"报告 R-3"。本次补齐: - 6,216 B = RW 16 + ZI 6,200,三块 2 KB 页缓冲 6,144 B 占 98.8% - 不可合并:dhara_map_write 先 auto_gc(map.c:327)再落数据,GC 搬页 与"待写的 s_cache_buf""攒了 16 页的 checkpoint 元数据 s_page_buf" 三方同时存活,任两块合并都是静默错数据而非报错 - s_page_buf 也不能缩:元数据跨 0..1,999 偏移,编程的是整页 - 唯一可动的是 s_copy_buf:分块 512 B(-1,536 B,4 次 CPU 轮询)或 内部搬页 copy-back(-2,048 B,须实证"奇偶属性相同"限制) - 收益落点 RW_IRAM1(余 55,808 B),红线在 CCM(余 7,904 B)→ 判定不做 顺带订正 nand_ftl.c 两处注释:悬空引用改指本节;s_page_buf 原文写 "仅装 132 B 走轮询"不成立,实际按整页 2,048 B 编程、同样走 DMA。 @build 0 错 0 警告,镜像逐项不变(232,536 / 132,688 / 232,824)。 |
||
|
|
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、无行尾空白)。 |
||
|
|
871ace422b | docs(门禁说明): 统一第八节标题措辞,新增模块单测的入口写在标题上 | ||
|
|
dc44c1e54e |
docs(门禁说明): 补齐 23 篇逐项门禁文档,每种门禁一篇含限制与盲区
用户诉求原话:"现在门禁都在干什么,我一概不知。" 原有文档回答"有哪些钩子、怎么配",回答不了 "这一条到底在拦什么、什么时候会骗我"。本目录按**每种门禁一篇**补齐 23 篇(39 个 hook id 全覆盖: 16 个主机单测是同机制 16 实例合一篇;cppcheck 与 test-cppcheck 是同工具两套范围合一篇), 文件名用 hook id,与提交输出里的名字一致、可 grep。 八段固定结构:查什么 / 为什么要它(拦下过什么真事)/ 触发面 / 报错怎么读 / 好处 / **限制与盲区** / 豁免与基线口径 / 改门禁本身要注意什么。最后一节是这批文档存在的理由—— 只看"拦了什么"的文档会让人以为绿就是对的;本仓三个增量拦截门禁(layer-deps 6 条、bsp-err 38 条、 id-check 台账 640 处)的绿灯含义只是"没比上次更差"。README 另立"三种拦截姿态"一节:改写型 / 硬拦截 / 增量拦截,分清才知道报错时该改代码还是该还基线。 有实证的地方全部带数字,没实证的地方直接写"预防性、未记录到实际拦截案例"(不编故事): · cppcheck:把 simulator/ 纳入受检后立刻揪出 sim_syslog.c 真实空指针;test/ 曾有 s_rx_buf 使用先于声明;**并记入两条实测负结果**——它对 14 处丢弃 HAL_SPI_* 返回值与"未初始化局部量 进 P_FAIL 判定"一条不报(这正是 consume-result 的由来)。 · lv-conf-sync:漂移导致 64,576 B 死代码入库(旧镜像的 23.5%),且 golden 跑的是模拟器那份配置。 · cubemx-survival:2026-09-29 一次重生成覆盖 4 处定制(P0×1+P1×2+P2×1),且不报编译错。 · registration-check:rs485_task.c、lftpd_log.c 两处都没注册 = 从不参与编译的死代码;第四类 "页面描述了但没接进 s_defs[] 表"编译链接全过,前三类完全照不出。 · test-suite-switch:实测已提交的 HEAD 里 TEST_SUITE_CH395F 就是开着的,.map 含 34 处 test 代码。 · task-table:2026-09 两张表都比源码落后一大截(写 2048/4096/…/16384,实际 1024…512)。 · mem-numbers:2026-09-26 复核,文档写 82,048 B / 62.6%,实际 87,040 B / 66.4%;Flash 写 249,432 / 23.8%,实际 261,932 / 25.0%。 · ui-golden:注入 .top_y +3 像素 → golden FAIL 点名"1366 px 差异"、退出码 1;并把"有基线 ≠ 有门禁"与"逼人 --no-verify 的门禁等于没有门禁"两条选型依据写进去。 · check-yaml:本仓已移除的 always 键只告警 Unexpected key(s) 然后忽略——配置"看起来生效其实没 生效"而本门全绿,所以验收方式是实跑看目标钩子是 Passed 还是 Skipped。 · no-mojibake:types: [c] 只管 C/H,**文档侧乱码无人管**——覆盖面与需求错配定性写进限制一节。 盲区写法示例(每篇都要求写真实限度,不留空):layer-deps 只查直接 include、按 basename 匹配、 不查是否真的用到;font-charset 管不到运行期拼出来的字符串与配置文件里的字;host-unit-tests 用宿主 编译器、桩是第二份真相、include_dirs 手写、没有对应钩子的模块等于零回归;mem-budget 的 92% 是管理 阈值不是安全余量、.map 缺失即"通过式跳过";registration-check 不看条件编译与注册指向是否正确; coding-standard 的 CODE-RULE-17 在无配对头时跳过、规则编号 14 作废不回收。 写文档过程自己被自家门禁拦了两轮,都按规矩改而不是绕过: ① doc-links 拦下我自己——我在解释"写 §数字 而没紧邻文档名会被判成本文自引用"这段话里,正好 就犯了这个写法(两处),另有一处示例输出未标记;改成正例写法 + [示例] 标记。 ② id-check 拦下 7 类新账:示例报错行里的号、档号引用未紧邻文档名、说明"裸形不合规"时自己写了 裸形。全部改成平实描述或补文档名、给样例加 [示例],没有一条是往台账里塞 reason 解决的。 这两次本身已写进相关篇目的"限制/变更记录",是这套文档最实在的注脚。 同步:手册 pre-commit使用说明.md 的 §2 顶部加指针(逐项详解已迁往本目录;两处收敛为"通用机制 + 总览 + 指针"排在 docs/门禁说明/README.md §4 的下一步,并注明收敛要按内容重指既有引用——只核编号 存在与否不够);README 索引 23 行状态全部置为已写、文档列改为可达链接。 验证:纯文档,不动代码与构建产物;check_doc_links --all --sections-strict **171 篇 0 问题** (新目录 24 个文件的内部链接与跨文档引用都纳入判定);id-check --strict 增量拦截通过;本次受影响 文件 pre-commit run **RC=0**;24 个文件字节卫生 0 异常(无 BOM、无 CR、无 U+FFFD、均有尾换行)。 |
||
|
|
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、有尾换行)。 |
||
|
|
2575177e81 |
refactor(gate): bsp-err 四项检查改用自描述标签,废掉 R1~R4 这组撞车代号
纯改名,检查行为一个字没动:规则逻辑、阈值、豁免形状全部原样。 改名原因(用户一句「R4 到底是什么」问出来的):R1~R4 是我在 check_bsp_err.py 注释里 自造的序号,而这个裸形在本仓另有含义——R1~R8 已在 scripts/id_registry.json 登记为 "专项风险"的文档内编号(保护规划与看门狗的风险表都在用),带连字符的那一族又是审查 报告的可靠性 / 内存优化项编号(编号规范 §1 的"连字符承载语义"一行正是这么写的)。 **同一个裸形四种意思**。最刺人的一点:编号门禁**不会报**这种撞法,因为编号规范 §4.3 已把这些裸形声明为"同号不同义"、走 collides_with 白名单,校验 4 不计事故——同一天里 我起的另一组自造号被当场拦下、这组代号却一路绿灯,差别不在我做对了什么,而在白名单 挡的是"事故"、不是"可读性"。 新名同时是基线键的第二字段(格式 文件|标签|名字): · err-type 错误通道不外泄(对外接口不拿 HAL_StatusTypeDef / bool 当成败) · timeout-name 超时宏不取负值 · return-doc 公开函数必须写「返回值:」 · consume-result 返回值不得裸丢弃(须接住或显式 (void)) 老名对照:R1=err-type、R2=timeout-name、R3=return-doc、R4=consume-result。 同步面:脚本里的规则码与 38 条基线键的字段**一起换**(中途不跑 --prune,否则改名会被 读成"38 条全还清 + 38 条新账"),换完 --emit-baseline 复核仍是 38/38/0、RC=0。现状类 文本全部换新名:AGENTS.md、pre-commit使用说明.md §2.27(新增"检查项叫什么"一行含老新 对照与撞号说明)、bsp_err.h 边界纪律、报告 §0.4 / §9 / 附录 A,以及代码注释里的 5 处 (gd5f2gq5ue.c/.h、nand_ftl.h、ui_menu_tab.h)。已提交的变更记录行里的老名按"冻结历史 不回改"处理,以 §2.27 对照行为准。ui_menu_tab.h 带 BOM,Edit 又剥掉、已按原样补回。 写说明文字时又被编号门禁拦了一次:我为了举例"老名撞车",在那段话里写了两个编号字面, 被判"未紧邻文档名 / 未登记前缀"共 3 类新账——连批评自造编号的段落本身都在自造编号。 改法是不写号、只描述形态并指向权威条目。 验证:@build **0 错 0 警告**,Program Size 与上一笔**逐字段相同**(Code 213,676、RO 18,860、RW 1,188、ZI 131,500)→ 证明确为注释与文案,故不跑 @test(无指令变化); pre-commit run 全绿 **17 项 Passed**;check_doc_links --all --sections-strict 147 篇 0 问题(中途我自己写的 §4.3 没紧邻文档名,被判成本文自引用,已补全路径);9 个文件字节 卫生 0 异常(UI 头 BOM 仍在、无 CR、无 U+FFFD、均有尾换行)。 |
||
|
|
d9b887c0af |
feat(S-5 R4): 返回值不得裸丢弃成为机械检查,并修掉照出的第一处 ECC 静默降级
B 的第一步(加规则)与第二步(修最小最值的那处)。用户裁定"B 按你说的进行"。 **为什么只能项目侧自己做**(两条实测负结果,已写进 §2.27 与 bsp_err.h 边界纪律 4): · ARMCC v5 对 __attribute__((warn_unused_result)) 不生效(凌晨在 rs485_transmit 上实测:属性 在位、调用点裸调不接返回值,构建仍 0 错 0 警告); · cppcheck 也照不出来——按本仓自己的 --enable=warning,style,performance,portability 跑 gd5f2gq5ue.c,对 14 处丢弃 HAL_SPI_* 返回值、以及"未初始化局部量直接进 P_FAIL 判定" **一条不报**(它假定 gd5f_read_status(&status) 会写出参,而失败路径根本不写)。 结论:**丢弃底层传输的返回值,今天没有任何工具会响**。 **R4 上线即照出 37 条存量丢弃**:gd5f2gq5ue.c 29(14 处 HAL + 15 处自家原语)、net_socket.c 8、 ui_net_sink.c 4(net_close)、rs485.c 2、nand_ftl.c 1。按增量拦截老规矩入基线、只减不增—— 这是覆盖面扩大新暴露的历史债,不是新增违规;基线 19 → 1 → **38**。 **R4 自己被修三次(逐条记下,都是"规则比我想的更脆")**: ① 首版漏判"裸调用行尾带注释"这种写法(正则的 \s*$ 卡死),而那恰是最常见的"带说明的丢弃" → 补可选注释分支,并做 6 条正/反例自检; ② 把 net_set_errno 八处误判成丢弃(它是 setter,返回 int 是状态回声不是调用成败)→ **按真实 反例把规则收窄**(谓词 + setter 两类豁免),而不是塞进基线洗掉假阳性; ③ must-use 名单只从**头文件**取,gd5f_set_feature 这类 static 原语整个在视野外——而下面那处 真窟窿恰恰是 static、靠人工才发现 → 名单扩到 .c 内 static 定义(同一套启发式),补完立刻 又照出 gd5f_bbt_save#1:**运行时标坏后 BBT 落盘结果被丢弃 = 坏块记账可能没持久化**,已入 基线记为待还,本步不动行为。 另修一处自己的排版误判:R3 原本要求注释块出现字面"返回值",规范 V1.0 允许列对齐写法 「返 回 值:」→ 改为忽略空格匹配(门禁不该规定排版)。 **修掉的第一处真窟窿**:bbt_rebuild 的注释写着"无论扫描成败都须恢复 ECC,否则后续 I/O 在无 ECC 下运行",而它下面 gd5f_set_feature(0xB0,…) + gd5f_wait_busy(100) **返回值全丢**:SPI 一抖 ECC 就保持关闭,且 gd5f_check_ecc() 在 ECC 关闭时恒读"无 ECC 错"——整条链再没有任何信号。 补法复用 init 路径的 E-10(写完回读 B0h 验 ECC_EN 位真生效,"写了但没生效"与写失败同罪), 并把"关 ECC 失败"改成**立即中止**(此时 ECC 仍开着,是安全侧;带着 ECC 去扫 spare 反而会把 厂标好块误判成坏块,"重建 BBT"变成制造坏块)。恢复失败时按 (void)gd5f_bbt_load() 回读盘上 副本,避免 RAM 留着"比盘上更新"的表而下次上电又退回旧表。 **一条定位纠正(差点把修复记大)**:查链接映射发现 Removing gd5f2gq5ue.o (i.gd5f2gq5ue_bbt_rebuild), (604 bytes)——该函数在产品镜像里**根本没被链接**(产品构建无调用 者),只由 ENABLE_STORAGE_RECOVERY_TESTS 的恢复构建经 testTask 调用。所以本次修的是**恢复流程 自身的安全**(跑恢复时若 ECC 没恢复,设备会带着"已恢复"的结论在无保护状态下继续跑),不是 现场日常路径;相应地镜像 Code 的 +28 B 也不是它带来的(未逐字节追,线索是 gd5f_private_get_feature 由单调用点变多调用点、内联决策可能变化)。验证因此不能只跑产品构建: 临时打开 TEST_SUITE_STORAGE + ENABLE_STORAGE_RECOVERY_TESTS 编一次(0 错 0 警告且该函数不再 出现在 Removing 列表),随即还原两处开关并重编产品构建核对(test/board 无残留差异)。 **编号门禁把我自己的自造编号拦下了**:我起初用 B-1/B2/B3/B4 标记这批缺陷,id-check 报"未登记 前缀"13 处(且 B2/B3/B4 会与真实里程碑号撞号)→ 全部改写成平实语言。这是那条"注释与文档里 不许用没人检索得到的内部阶段编号"的规则第一次在提交前拦住我,规则是对的。 验证:@build 0 错 0 警告,Code 213,648 → **213,676**、RO 18,860、ER_IROM1 **232,536 B / 22.2%** (ROM 合计 233,724 B;RAM 逐字段不变);ram_report 重生成 + ram_baseline.py --update + 配图常量 同步(mem-numbers Passed);@test **PASS**(到 net init: OK 682 ms、[ERR] 0);R4 两处**探针 实证**(临时加一条裸调用 → 立刻成为新增并 RC=1;还原 → 38/38/0);--prune 点名本次还掉的 2 条 已删;pre-commit run 全绿 15 Passed;check_doc_links --all --sections-strict 147 篇 0 问题; id-check --strict 增量拦截通过。 文档:AGENTS.md 与 .pre-commit-config.yaml 的 bsp-err 口径(四条规则、基线 38); pre-commit使用说明.md §2.27(R4 全量口径 + 两条负结果 + 名单来源与豁免形状 + 单行限度); bsp_err.h 边界纪律 4;报告 §0.4 / §5.1 / §9 / 附录 A + 变更记录;存储维护与容量恢复.md 新增 §4.3(运维判据:出现 ECC restore FAILED / ECC not enabled 即本次恢复**不算完成**,须断电重启 重走 init,不许接着 nand_ftl_format() 当作已修好;并记明该函数产品镜像不链接)——顺带修该文 结构缺陷:原 ## 5. 变更记录 误插在 §4.1 与 §4.2 之间,会让 §4.2 变成 §5 的子节。 |
||
|
|
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 与变更记录两条(还债一条、更正一条)。 |
||
|
|
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 状态改为 🟡 主体已落地。
|
||
|
|
8d3349c587 |
docs(存储维护): 记录自检台账清零与 flushchk 孤儿清理,修表格断行
设备侧动作(不动仓库内容)与文档同步,一并落在这里。 **① 自检台账清零,重建 S7 审计基线**:`/log/selftest.log` 里 29 条历史记录(**16 × `watchdog` `reset` + 13 × `heap low water`**,跨度 `2026-10-10 09:24:12 → 2026-10-14 06:55:02`,看门狗 引入期与堆迁移期留下,两案后来都已结案)按用户决定删除。**安全判据**:`sys_log_mcu.c` 是每条 记录开-写-关(`f_open(FA_WRITE|FA_OPEN_APPEND)` → `f_write(64)` → `f_close`),**不持常驻 句柄** → FTP 侧 `DELE` 不会撕裂,下次真有故障时自动重建。删前把导出副本留档(1856 B = 29×64)。 **② 顺带清掉一个孤儿**:`/log/flushchk.bin` 32,768 B,全库零引用——已退役的 flush 诱导实验 留下的(`sys_log_mcu.c` 注释里"旧称 flush 诱导"就是它)。**删前先 RETR 出来验过内容**:256 种 字节值全部出现、前 64 字节全 `FF` → 一份 0..255 图案的写盘校验填充,无任何产品语义;留档后再 `DELE`。判据同 §4.1:**先知道删的是什么,再删**。 **清理后 `/log` 只剩三条、全是产品数据**:`run.log` / `wdev.log`(掉电取证)/ `soe.log` (B9 SOE)——这三条是固件在写的活文件,容量护栏由固件自己管,**别手删**,文档里也这么写。 **验收(两条判定路径都要过才算基线干净)**: · `--require-selftest-pass`(默认 `this-run`)→ PASS,`SELFTEST_BASELINE: 既有记录 0 条` (`status=absent`); · `--selftest-scope all`(累计硬口径)→ **PASS**:`selftest: no fault record`——**清零前这条是 `29 fault record(s)` FAIL**,也就是那条判据在台架上从来没可能通过;现在真的能用了。 · 之后再跑 `@test` → PASS(699 ms、`[STACK] heap` 等锚点齐全、无 `[ERR]`/Fault)。 设备侧无此文件时 lftpd 回 450,被按"台账干净"处理——这条映射过去是假绿来源,现在是有意的正常 路径(见 `../架构设计/STM32F4-Base存储架构说明.md` §10.3 与 S7 判定表)。 **修自己的排版 bug**:上一笔(`23daa3d`)把 §4.1 的变更记录行拆成了三行——Markdown 表格行必须 单行,会渲染成断裂的表。已合并;本次两条变更行(§4.1、§4.2)都是单行。 门禁:`check_doc_links.py --all --sections-strict` 146 文档 0 问题;`check_ids.py --strict` 增量 拦截通过(可判定 82.82%);文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格 / 尾换行。本轮无 固件与脚本改动,故不跑 `@build`(`@test` 已跑,镜像仍是 `23daa3d` 那笔的构建)。 |
||
|
|
23daa3d9ad |
chore(台架): 删零引用的一次性探针、修可信判据工具登录、清理口径落文
两件事一起做:把"没人用又已经失效"的一次性探针清掉,把"台架现场脏"这件事的口径写进文档。 **删除(5 个文件,全库零引用)**:`MDK-ARM/ftp_retr_abort_probe.py`(其职能已被 `test/host/ftp_abort_probe.py` 取代,Trap 记录引用的也是后者)与整个 `MDK-ARM/bench_diag/` (4 个诊断脚本:abort_gap / pcref / recover / sink_recycle)。删除判据三条同时成立:① 全库 `git grep` 除自身外零引用;② 自 2026-10-11 设备启用 FTP 鉴权起,脚本里写死的匿名登录必然 530 → 已经跑不动 11 天;③ 都是"跑一次拿现场"的诊断件,价值在结论而结论已沉淀进 Trap 记录。 需要时从 git 历史取回(`test/host/README.md` 与 `MDK-ARM/README.md` 都写明了这一点)。 **刻意保留并修好**:`MDK-ARM/ftp_freeze_probe.py`——它不是一次性件: `CH395F_Trap_Records.md` 拿它当"177 轮 SHA-256 全匹配"的**可信复现配方**、 `BSD_Socket_API_使用指南.md` 把它列进排障判据、`Drivers/BSP/NET/lftpd/README.md` 也指它。 删掉它等于删掉文档指定的真值工具。改为 `--user/--password`(默认出厂 admin/admin,模块常量 `FTP_USER`/`FTP_PASS`,两处登录点都读它),实测一轮:`ROUND 1 size=4096 stor=OK retr=OK hash=MATCH`、`SUMMARY pass=1 fail=0`——修前它连 530 都过不去。 **台架设备清理(不动仓库内容)**:`/` 根目录实测残留 **27 个探针文件 / 262 KB**(`AB000x`、 `A000x`、`R000x`、`SZ2K~64K`、`E12A`、`DIAG_A`、`PP0~PP4`——正是刚删的那批一次性探针留下的)。 逐个 `DELE` 后剩 `LOG` + `CONFIG.INI` + `PROT_SET.BIN` 三条目。**判据:只删显式点名的文件, 绝不 glob**;保留清单必须含 `/config.ini`(网口 + FTP 鉴权)、`/PROT_SET.BIN`(保护定值 blob, `PROT_SET_BLOB_SZ` = 176 B,删了等于定值清零)、`/log` 目录。清完两步验收:`@test` PASS (630 ms,定值巡检与配置加载都在这条链上)+ `ftp_list_fill_test.py` PASS(29 行 / 1851 B / 完成回包 226,26 个新文件全列出)。口径写进 `docs/测试说明/存储维护与容量恢复.md` 新 §4.1 (明确它是"动作 D 清文件",与 A 重建 BBT / B 全片擦除 / C 容量恢复区分——那三个会丢卷内容)。 门禁:`py_compile`(freeze 探针)通过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题(删除的 5 个文件确认无任何文档引用);`check_ids.py --strict` 增量拦截通过(可判定 82.82%);`layer-deps` 通过;4 个改动文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。 本轮无固件改动,未跑 `@build`(设备侧清理后的 `@test` 已跑,镜像仍是上一笔的构建)。未推送。 |
||
|
|
8b0ea17b71 |
fix(lftpd): LIST/NLST 满块续发——大目录不再被静默截断并回 550
现象:`ftp_test.py` 的 `LIST 150->226` 断言失败——1512 B 数据全收到了,完成回包却是 **550**。 **根因**(`send_list`/`send_nlst` 各一处):定案三把"整份目录一次性批量发送"落进 static 1536 B 缓冲时,顺手留下了"装满就 `break` 并回 −1",`cmd_list` 据此回 550。于是目录一旦超过一块,客户端 拿到的是**静默截断**的清单**外加一个错误码**——而清单看起来完全像"目录里就这些"。这比单纯报错 更危险:没有人会去数条目个数。 **台架实测(同一台设备、同一个根目录,改动前后各测一次)**: · 改前:1512 B / **24 条目** / 完成回包 **550**(停在 A0007.BIN,后面 6 个文件被吞) · 改后:1880 B / **30 条目** / 完成回包 **226** **修法:满块续发**,而不是把缓冲区加大。新增 `list_batch_flush()`——装满一块就整块发出、清空 继续装,收尾再发最后一块。为什么仍要"批量":定案三的本体是实测结论——同一连接上背靠背 ~60 B 小段写会毒化 CH395F 的 TX(芯片报 SEND_OK 但对端全丢,无 RST 无 DISCONNECT),而 RETR 的 1 KB 满块连发安全;所以这里只发大块,绝不退回"每行一发"。单行真装不下时**明确失败**而不是少发条目 (8.3 短名 + 定长格式远小于 1536,真发生说明格式被改坏)。两条出口的 `lftpd_io_closedir` 收敛到 循环后单点:卷锁持有至 closedir,任何提前 return 都会漏锁。 **防回退**(关键):修完之后,**目录清空的设备上这颗雷再也暴露不出来**,所以补了 `test/host/ftp_list_fill_test.py`——自造 26 个 8.3 短名文件把清单撑过一块,三条判据(STOR 全 226 /LIST 全文里每个名字都在/完成回包 226 而非 550,用 ftplib `retrlines` 让非 2xx 完成直接抛), 跑完逐个 DELE 还原。真机实测 `收到 56 行 / 3544 字节`(跨 3 块,满块续发被真正走到)→ 三条全 PASS。用法登记进 `docs/测试说明/FTP_Test_Guide.md` §4.5,机理与增补写进 `docs/问题记录/CH395F_Trap_Records.md` 定案三。 **顺带记录的已知偏差(未修)**:`cmd_list`/`cmd_nlst` 忽略命令参数、恒列会话 CWD (`client->directory`)。主流客户端都是 `CWD` + 裸 `LIST`,实际影响小,先记在案。 体量:`Code` 213,264 → **213,436 B(+172)**、RO 不变 → `ER_IROM1` **232,088 B(22.1%) **、 `ROM 合计` 233,276 B;**RAM 一字未动**(`RW_IRAM1` 75,264 B 57.4% / `RW_CCM` 57,376 B 87.9%)。 数字联动照做:`data/ram_report` 重跑、`ram_baseline --update/--write`、 `check_mem_numbers --write`、 FLASH 配图常量重算重生成(桶合计 = 232,088 与 `Code+RO` 相符)、报告 §1.3/§5.1/§5.4 与部署 §3.3。 门禁:`@build` 0 错 0 警告;`@test` PASS(692 ms);`ftp_test.py` → **TEST PASSED**(含此前失败 的 `LIST 150->226`);`ftp_up_dl_test.py` → TEST PASSED;`ftp_list_fill_test.py` → TEST PASSED; `doc-links --all` 146 文档 0 问题;`id-check --strict` 增量拦截通过(可判定 82.82%); `mem-numbers` / `mem-budget` 通过;改动文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。未推送。 |
||
|
|
4c21cff753 |
fix(lftpd): 对端行为不再打 [ERR]——新增 lftpd_peer 级别并划清级别边界
现象:抓自检台账基线那次 FTP 会话一结束,设备就打 `[ERR] [FTP] error reading next command`, 本次 `@test` 直接 FAIL(Forbidden 含 `[ERR]`)。查下去发现 lftpd 把**全部**异常路径一律按 error 级打,其中九处其实由外部输入决定:客户端关窗口、连上就走、PASV 后不建数据连接、 运维改 IP 触发监听重建。两条代价:① 任何人"连 21 端口一下就断开"就能把一次启动测试打成 FAIL;② 同样的动作可以**无限制造固件级 ERROR 日志**——日志面与门禁面都可被外部驱动, 工业现场过不了审。 **做法**:`private/lftpd_log.h` 新增 `lftpd_log_peer()`(info 级,带 `[FTP]` 前缀),而不是 就地改成 `DBG_INFO`——让"谁才算真故障"在读代码时一目了然,也防止后来者顺手把对端行为升回 error。降级的九处:控制通道被对端关闭(原 `error reading next command`,改为 `control channel closed by peer (ret=%d)`,措辞原先把正常收尾写成故障、误导过排查方向)、 发送 220 前对端已走、`listener rebuilt`(网络重建,原因由 accept 层自己记)、四处 `data accept failed`(PASV 后没来数据连接)、`accept timeout`。 **刻意留在 error 的**:create/bind/listen/取端口失败、写通道失败、文件打开失败、 `DOWNLOAD/UPLOAD FAILED`(对端中途退出确实让一次传输没走完 = 数据完整性事件, `../问题记录/CH395F_Trap_Records.md` 的中止家族要靠这些锚点定位)、 `data listener disarmed`(控制连接还活着而我方数据监听已不在服役 = 本侧异常,就地注明)。 **实测划清的边界**:客户端**干净收尾**(发 `QUIT` 或 FIN 半关)现在整条链路没有任何 `[ERR]`; 但 **RST 硬拆**(`SO_LINGER` 清零 / 进程被杀 / 拔线)仍会留一条 `[ERR][NET] sockN TIMEOUT`—— 那是 net 层的套接字超时锚点,网络自愈架构判据要读它,不在本次降级范围。所以"采集窗口内别跑 `data listener disarmed`(控制连接还活着而我方数据监听已不在服役 = 本侧异常,就地注明)。 **真机验证**:`@build` 0 错 0 警告;`@test --require-selftest-pass --device-ip` → **PASS** (基线 29 条 → 本次无新增;`net init: OK` 726 ms);专门写了 RST 复现脚本(登录后 `SO_LINGER` 清零直接关)→ 串口得到 `[FTP] control channel closed by peer (ret=-1)`,**不再是 `[ERR]` 行**; 同一份日志里 QUIT 收尾的那次会话完全没有错误行。 体量:`Code` 213,236 → **213,264 B(+28)**、`RO` 18,580 → **18,652 B(+72)**(措辞变长), 同一份日志里 QUIT 收尾的那次会话完全没有错误行。 `ZI` 131,500、`RW_IRAM1` 75,264 B 57.4% / `RW_CCM` 57,376 B 87.9% 全不变)。数字联动照做: 两份 `data/` 快照、`ram_baseline --update/--write`、`check_mem_numbers --write`、FLASH 配图 常量重算重生成(桶合计 = 231,916 与 `Code+RO` 相符;越界复查 0)、报告 §1.3/§5.1/§5.4 与 部署文档 §3.3 同步。 口径落文:新增 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.3「FTP 日志级别划分:对端行为 vs 固件故障」(含两类的判据表与边界),并更新 `docs/测试说明/自动化测试流程.md` 的采集窗口警示。 顺手把 `private/lftpd_log.h` 工作区的 CRLF 混用归一回 LF(仓库里 blob 本来就是 LF,diff 无影响)。 门禁:`doc-links --all --sections-strict` 146 文档 0 问题、`id-check --strict` 增量拦截通过 (可判定 82.82%)、`mem-numbers` / `mem-budget` / `layer-deps` 全绿;改动文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。未推送。 |
||
|
|
af5bad5609 |
fix(测试工具): 自检台账判据改为只看本次启动新增(修假绿)
接上一笔(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 个一次性
探针脚本仍写匿名。未推送。
|
||
|
|
15d01d4dc1 |
fix(测试工具): FTP 脚本与 @test 抓取改用设备鉴权账号(修 11 天假绿)
设备 FTP 自鉴权落地起就**没有匿名通道**(实测 `USER anonymous` → 530、`admin`+错口令 → 530、`admin`/`admin` → 230;`lftpd` 源码里也没有 anonymous 特判),但三个老主机脚本、 `@test` 的自检记录抓取、以及测试指南都还写着匿名——同目录的 `ftp_auth_bench.py` / `ftp_abort_probe.py` 反而早就记着"匿名一律 530",可见当时只改了新增件。 **改动**: · `test/host/ftp_test.py`、`ftp_up_dl_test.py`、`ftp_speed_test.py`:写死的 `USER anonymous` / `PASS test@test.com` 改为 `--user` / `--password`(默认出厂 admin/admin);**USER 的期望码从 230 修成"230 或 331"**——要口令的服务器在 USER 后 回 331,脚本原先要求 USER 直接给 230,这是第二个 bug(就算口令对也过不了);登录 失败时打印口径提示(`/config.ini` 的 `[ftp]`、无匿名通道、连续 5 次失败锁 60 秒)。 · `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`:`ftp.login()`(匿名)→ `ftp.login(user, pass)`,新增 `--ftp-user/--ftp-pass` 与 config 的 `ftp_user`/ `ftp_pass`(默认 admin);函数文档与 `--device-ip` 的 help 去掉 "(anonymous)"。 · `docs/测试说明/FTP_Test_Guide.md`:§1 的"匿名登录(`USER anonymous` / `PASS` 任意)" 改为鉴权口径(`user` / `pwd_hash` = SHA-256(user‖password) / `write`,出厂 admin/admin,默认口令在用有启动告警,5 次失败锁 60 秒,**无匿名通道**),§4 参数说明 补两个新参数,§4.1 期望改为 `USER admin`→331、`PASS admin`→230,FileZilla 的用户 由 `anonymous` 改 `admin`。 **这才是真正的危害**:`@test --require-selftest-pass` 的抓取走匿名登录 → 必然 530 → 落 进 `RUN_TEST_WARN … treated as no record (S7 PASS path)` → **每次都判 PASS**。也就是 说这条"联合判定自检记录"的门禁从写下那天起就没真正看过文件(假绿)。修完凭据后第一次 真抓到 `/log/selftest.log`(1856 B),立刻报出 29 条故障记录——而它们的**时间戳全是 2026-10-10 ~ 10-14**,没有一条来自本次启动:`/log/selftest.log` 是 512 条环形累计历史, 而 S7 的判据是"有任何 64B 记录就 FAIL" → **在任何做过故障注入或长跑的设备上,这条 门禁都不可能 PASS**。判据本身要改成"只看本次启动新增",已单独提出,不在本笔。 **验证(全部真机实跑)**:`ftp_up_dl_test.py` → TEST PASSED(10 项全过,含 STOR/LIST/ RETR/逐字节比对/DELE);`ftp_speed_test.py --size 128` → TEST PASSED(上行 225.8 KB/s、 下行 250.9 KB/s,131,072 B 逐字节一致);`@test` → PASS(625 ms 到 `net init: OK`); `@test --require-selftest-pass --device-ip 192.168.1.100` → `SELFTEST_FETCH: … (1856 bytes)`,抓取路径真跑通(旧代码在此步必 530)。 `py_compile` 四个脚本全过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题; `check_ids.py --strict` 增量拦截通过;5 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无行尾空格。 **顺带暴露、未在本笔处理**:① `ftp_test.py` 的 `LIST 150->226` 断言失败——lftpd 的 LIST 数据本身正确(1512 B 全收到),但传输结束后控制通道回的是 **550** 而不是 226,需单独查 收尾路径;② 设备 `/` 根目录残留约 24 个旧探针文件(AB*/E12A/SZ*/A000x/R000x/DIAG_A); ③ `MDK-ARM/` 下 4 个一次性探针脚本仍写匿名,现在必然登录不上。未推送。 |
||
|
|
e7f28502bc |
refactor(NET): netMsgQueueHandle 收进 net_socket 私有,改为创建后绑定
结构债 S-2 的最后一步——**该族的 24 处手写跨模块裸 extern 就此全部清零**。 队列对象仍由 CubeMX 在 `MX_FREERTOS_Init()` 创建(**创建点不可外移**: `osKernelInitialize()` 之前的任何创建类 API 会杀死启动,见 `docs/架构设计/项目启动顺序管理.md`);改为创建后立刻在 `Src/freertos.c` 的 `USER CODE BEGIN RTOS_QUEUES` 区调用 `net_msg_queue_adopt(netMsgQueueHandle)`, 把句柄交给唯一使用者。`net_socket.c` 内改用 `static osMessageQueueId_t s_net_msg_queue`(21 处引用改名),BSP 对该全局变量的裸 `extern` 消失——现在 `netMsgQueueHandle` 只出现在生成文件自己的两处(定义 + 创建)。 两个刻意的设计决定: ① **不新建第二个队列**。自建队列 + 删 `.ioc` 里的 netMsgQueue 也能收口,但要 CubeMX 重生成(Ask-first),过渡期还会同时存在两个队列,白占 CCM 堆里 8 × `net_msg_t` 的空间——CCM 正是当下 87.9% 的红线。故选"创建后绑定"。 ② `adopt()` **拒绝重复绑定、保留首次句柄**。真出现两处绑定时,静默换句柄会让 已入队的请求留在旧队列里永不处理;拒掉第二个反而更容易暴露。绑定前的判空 是真实防线:丢了那一行,后果是"所有 net_* 请求被丢弃、网络全线静默不通", 不是崩溃。 **防回退**:`check_cubemx_survival.py` 新增存活项 `net-msgq-adopt`(region `RTOS_QUEUES`、`must_contain=["net_msg_queue_adopt"]`),易失清单 29 → 30 项全部 到位;基线已重建(有意的 USER CODE 区改动,按脚本文档流程 `--write`)。 **体量**:`Code` 213,204 → **213,236 B(+32)**、`ER_IROM1` **231,816 B(22.1%)**; `RW +4 / ZI −4` → `RW_IRAM1` 恒 **75,264 B(57.4%)**、`RW_CCM` 恒 **57,376 B (87.9%)**、`RW+ZI` 总量 132,688 B 不变(没有多占一个字节 RAM)。 **验证**:`@build` 0 错 0 警告;`@test` PASS——624 ms 到 `net init: OK`、`i104: listening on 2404`、无 `[ERR]`/Fault、`heap free=15800 min_ever=8464` 逐项不变。 另在真机跑了一次 FTP 控制通道往返:220 → `USER admin` 331 → `PASS admin` 230 → `PWD` 257 → `TYPE I` 200 → `QUIT` 221。每条命令都是一次过队列的 net_recv/net_send,证明绑定路径真在跑;`.map` 里 `freertos.o(MX_FREERTOS_Init) refers to net_socket.o(i.net_msg_queue_adopt)` 证明镜像含该行。 ⚠ **顺带揪出一条测试工具漂移(本次未修,等定夺)**: `test/host/ftp_up_dl_test.py:123/126` 仍写死 `USER anonymous` / `PASS test@test.com`,而设备自 N-19(2026-10-11)起按 `/config.ini [ftp]` 校验 admin/admin(SHA-256 `pwd_hash`)→ 该脚本必然登录失败。它是手工工具、不在任何 pre-commit 钩子里,所以 11 天没人发现,而 `docs/测试说明/FTP_Test_Guide.md` 把它 列为"推荐入口"。建议加 `--user/--password`(默认 admin/admin),并连 `ftp_speed_test.py` 一起核。 数字联动:两份 `data/` 快照重跑、`ram_baseline --update/--write`、 `check_mem_numbers --write`、FLASH 与任务栈两张配图常量重算并重新生成(逐文本按 字号与锚点复查越界 = 0)、报告 §1.3/§5.1/§5.4 与部署文档 §3.3 同步。报告 §0.4 的 S-2 行按「完成即销项」改写为已闭环,§9 拆出独立 S-2 行标 ✅。 门禁:`@build` 0/0、`@test` PASS、`layer-deps` / `cubemx-survival` / `mem-numbers` (本次改到台账受管区,scan 已过)/ `mem-budget`(57.4% / 87.9%)/ `task-table` / `doc-links --all`(146 文档 0 问题)/ `id-check --strict`(增量拦截通过、可判定 82.84%、存量债 640)全绿;14 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 / 无尾空格。 未推送。 |
||
|
|
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`。未推送。
|
||
|
|
7e77af08a0 |
fix(BSP): SPI1 完成标志收回 GD5F 私有,HAL 回调改契约转调
推荐清单第 4a 步(结构债 S-2 剩余面里唯一带"静默失效"属性的那条)。 **问题不是风格**:HAL 的 SPI 完成/错误回调是**全局唯一符号**,整个工程只能有一份 实现,当前由 CH395F 代管并按 `hspi->Instance` 分发——于是 GD5F 的完成标志 `g_spi1_dma_done` 被 CH395F 跨模块裸 `extern` 直接写,**GD5F 出错时的"释放等待者" 寄居在别人的 .c 里**。谁哪天做"CH395F 清理",把那两个 `else if (SPI1)` 分支当无关 代码删掉:编译零报错,GD5F 的 DMA 等待在出错时只能干等 `GD5F_SPI_TIMEOUT` 兜底。 **改法(时序一字不动,只换所有权)**: · 标志收为 GD5F 文件内 `static volatile uint8_t s_spi1_dma_done`(6 处引用同步改名) · GD5F 导出 `gd5f_spi1_notify_done()` / `gd5f_spi1_notify_error()`,头文件里写明 "SPI1 分支必须转调这两个函数,否则本模块的 DMA 等待在出错时只能靠超时兜底" · CH395F 删除裸 `extern`、include `gd5f2gq5ue.h`、两个 SPI1 分支改为转调并就地注明 "勿删:少了这行 GD5F 的 DMA 出错只能靠超时退出" `HAL_SPI_AbortCpltCallback`(`HAL_SPI_DMAStop()` 会触发)本次**刻意不做**——它会改 变中止语义、需要真机专项,当前超时兜底可用(已在报告 §0.4 记为观察项)。 **体量代价**:`ch395f.o` −8 B、`gd5f2gq5ue.o` +24 B → `Code` 213,188 → **213,204 B (+16 B)**,RO/RW/ZI 全不变,`ER_IROM1` 231,768 → **231,784 B(22.1%)**。16 B 换 掉一个静默失效类,划算。 **真机 `@test` PASS**:无 `[ERR]`/Fault,复位原因 software;`[STACK] heap free=15800 min_ever=8464` 与改前**逐项相同**;各任务实测峰值 default 2152 / net 1116 / ftp 764 / ui 4052 / timesync 616 / adc 952 / iec104 540 / protect 624, **静态最坏链全部未变**(3024/1912/1456/4052/1264/1320/1456/872)→ 9/9 PASS。 ⚠ 记一条读数陷阱:测试脚本打印的 "boot to net init: OK = 7,907 ms" 是采集窗口 锚点造成的度量偏差,真机日志里 `boot: syslog init OK → net init: OK` 实测 ≈699 ms, 与改前一致,**不是性能回退**。 **数字联动**:`data/ram_report_20261022.md`、`data/stack_report_20261022.md` 重跑; `ram_baseline.py --update/--write`;`check_mem_numbers.py --write`(RAM 方案受管区 + 台账);`gen_memory_svg.py` 常量更新后重新生成 FLASH 与任务栈两张配图(逐文本按 字号与锚点复查越界 = 0;RAM 图无需变更);报告 §1.3/§5.1/§5.4、部署文档 §3.3 的 Flash 数字同步;§0.4/§9 的 S-2 口径改为"24 处手写已消 23 处,只剩 `net_socket.c:77`"。 门禁:`@build` 0 错 0 警告;`cubemx-survival --check`、`mem-numbers`、`mem-budget` (57.4% / 87.9%)、`task-table`、`doc-links --all --sections-strict`(146 文档 0 问题)、 `id-check --strict`(增量拦截通过、可判定 82.83%)全绿;字节卫生 BOM/CR/U+FFFD/尾 空格全清;未提交 UV4 抖动的 `.uvprojx`(本次未被改写)。未推送。 |
||
|
|
36a3c3449c |
refactor(task): 19 处任务句柄裸 extern 收拢为 task_handles.h 引用契约
推荐清单第 3 步(结构债 S-2 剩余面),用户批准"按你推荐做"。 新建 `App/task/task_handles.h`——**只有声明、没有 `.c`**:头文件不需要双端注册, 且 `../App/task` 本来就在 uvprojx 的 `<IncludePath>` 里,所以这一步零构建面。 - `App/task/default_task.c:52-69` 的 18 行局部 extern(9 个句柄 + 9 个 attributes) 删除,改 include;栈水位观测用的任务清单表本体仍留在该文件 - `App/task/adc_task.c:125` 的 1 行同法收口 - `Src/main.c` 函数内的 `extern volatile uint32_t g_rcc_csr_latched;` 移进 `App/app/app_main.h`(`main.c` 本来就 include 它),USER CODE 区内只留"读 RCC_CSR → 写 RMVF 清除"两句,顺序约束的原注释保留 为什么值得做:原先每加一个任务都得记得往 `default_task.c` 再抄两行 extern,抄 漏一行 = 读别人的栈(静默,无报错)。现在只有一个改动点;而且 CubeMX 若把句柄 改名,报错会落在链接期而不是静默通过。 **镜像逐项体积不变**:`Code=213,188 RO-data=18,580 RW-data=1,184 ZI-data=131,504`,`@build` 0 错 0 警告 → 行为中性。 `check_cubemx_survival.py` 对这次有意的 USER CODE 区改动报"漂移",已按其文档流程 `--write` 重建基线(`.cubemx_survival_baseline.json` 仅 1 个指纹变化;顺手把该脚本 写文件带来的 CRLF 归一回 LF,符合 `* text=auto eol=lf`),`--check` 复跑通过、易失 清单 29 项全部到位。 报告 §0.4/§9 的 S-2 口径同时改成**精确分类**:该修的手写跨模块裸 extern **24 处, 今天已消 22 处,只剩 2 处**(`ch395f.c:177` 的 `g_spi1_dma_done` 与 `net_socket.c:77` 的 `netMsgQueueHandle`);`stm32f4xx_it.c:241-256` 那 16 条是 CubeMX「External variables」生成块(删了会被重生成写回来,**不算债**),`:183/195/207/220` 那 4 条 是手写在 `__asm` Handler 里引用汇编标签(ARMCC `__asm` 取不到 C 数据符号, **合法例外**)。上一版写的"25 处"把这三类混在一起了,一并更正。另更正 §0.4 的 S-1 一格:不是"运行时属主纪律被破"(UI 只投递 `net_cfg_save_request()`,写盘在 defaultTask),也不是"进不了主机侧单测"(模拟器已跑整条链),要修的只是静态分层 方向。 门禁:`@build` 0/0;`cubemx-survival --check` 通过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题;`check_ids.py --strict` 增量拦截通过、可判定 82.83%;`mem-numbers` 0。字节卫生:新增头文件与各改动文件 BOM 无 / CR 0 / U+FFFD 0。未推送。 |
||
|
|
67fbdd9897 |
refactor(结构债): S-4 删零消费点占位宏;S-2 消两处与 Inc 重复的 extern
用户批准「先只跑 1+2」(报告 §0.4 结构债三项 S-1/S-2/S-4 的评估结论)。 **S-4 闭环**:删除 `App/app/app_cfg.h` 里 4 个**全库零消费点**的宏 (`APP_NET_ENABLE` / `APP_ADC_ENABLE` / `APP_RS485_ENABLE` / `APP_SYS_TIME_SRC`),文件降格为「决策占位说明」——修改记录 v1.2 写明为什么删、 将来在真实初始化点接入门控时怎么接回;同时去掉 `app_main.c` 里只为它存在的 include。留「看着能裁剪、其实不生效」的宏比不留更糟:它不产生任何编译期效果, 却会让评审误信模块可裁剪——此前为了对冲这个误解,AGENTS 之外还得在 6 处文档各 写一句「未接线占位,别当开关用」。 **文档口径同步 17 处**:`App/app/README.md`、`App/README.md`、根 `README.md`、 `项目架构总览.md`、`项目部署环境要求.md`(§3.2 两行 + 配置表一行)、`产品需求 设计文档.md`(B0 结论 + 配置表)、`基线与待确认.md`、`排期详述.md` C5、`登记簿 .md` N-23 残余、`RAM使用分析与优化方案.md` 三处。 **顺带纠出另一个「文档里有、代码里从来没有」的东西**:`RAM使用分析与优化方案 .md` 把 `APP_CCM_ENABLE` 写成「加在 `App/app/app_cfg.h`」,全库只有那篇文档提 到它,从未落地——已就地标注「未实现,重开本簇时一起落地」,免得下一个读者以为 CCM 有回退开关。 **S-2 第一步**:删 `gd5f2gq5ue.c:47`、`sd2506.c:20` 两处与 `Inc/spi.h` / `Inc/i2c.h` 重复的 `extern`;GD5F 改 `#include "spi.h"`(沿用 `ch395f.c:22` 的既有做法)。 **镜像体积逐项不变**:`Code=213,188 RO-data=18,580 RW-data=1,184 ZI-data=131,504` 与改动前完全相同,`@build` 0 错 0 警告 → 两步均为行为中性。 报告 §0.4/§9 同步为实测口径并**认错**:S-2 不是旧写的「4 处」而是 **25 处** (`App/task/default_task.c:52-69` 一次就 18 行任务句柄/属性 extern; `stm32f4xx_it.c` 的 `fault_entry_hard` 属 ARMCC `__asm` 合法例外,不该「 修」);S-1 不是「语义层错位」而是**真实的 BSP→App 反向依赖**(NETCFG 10 处 include `App/cfg/cfg_file.h` + UI 3 个文件)。这两个旧数字是 v3.0 重写时沿用 未测所致。 途中被自家门禁正确拦下一次:在 .c 注释里写「S-2a」→ `+1 [校验1] 未登记编号 `,改成平实语言后转绿(正合既有纪律「代码注释禁用不可检索的内部编号」)。 门禁:`@build` 0/0;`check_ids.py --strict` 增量拦截通过、存量债 650 → 640、 可判定比例 82.83%(地板 82.6%+0.1pp);`check_doc_links.py --all --sections-strict` 146 文档 0 问题;`pre-commit` 相关文件全 Passed;字节卫生 15 个文件 BOM/CR/U+FFFD/行尾空格全清(`end-of-file-fixer` 修掉 app_cfg.h 的一个 多余空行)。未推送。 |
||
|
|
538c44c1cc |
docs(报告): 项目审查报告 v3.0 全量重写,按 2026-10-22 实测重判并删过时条目
用户裁定:报告很久没更新,过时的删除、重新写。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,未推送。
|
||
|
|
504db26698 |
docs(内存): 阶段子项改层级编号 4.阶段.项,消除与顶层章节同号撞面
根因:`RAM使用分析与优化方案.md` 顶层有 §1 背景 / §2 现状测量(2.1~2.6),而 §4 「方案」下"阶段 N"的子项也用裸号 0.x / 1.x / 2.x——阶段 2 的 2.1/2.2/2.3 与顶层 2.x **完全同号**,阶段 1 的 1.x 又长得像"第 1 章第 x 节"。`doc-links` 对本文自 引用按设计直接放过,于是门禁和人一起看错:AGENTS.md 把"栈余量台账"写成 §1.4, 而 §1.4 其实是阶段 1 的 lftpd 静态化项(上一笔提交的信息里我把它误判成断链, 已 amend 更正)。 改法:阶段子项一律改层级编号,阶段号不变、只加 `4.` 前缀 —— 0.x→4.0.x、 1.x→4.1.x、2.x→4.2.x。共改 14 个标题;文内自引用与实施序列表(S4~S9 那套)、 暂缓清单、§5 预期效果列里的裸号一并补成 §4.x.y。 外部引用同步 15 处:AGENTS.md、lftpd/README ×2、菜单页面设计、RAM预算台账 ×2、 项目架构总览 ×3、FTP_Test_Guide ×3、boot_log_data/README、部署环境要求、审查 报告、ram_baseline.py ×3。最有说服力的两处歧义:审查报告与 RAM预算台账都写 "缩堆见 …§2.2"——既可读成顶层 §2.2(静态大项排行)也可读成阶段 2 第 2 项, 现均为 §4.2.2(阶段 2 第 2 项"缩堆")。 另修 4 处把 §1.4 当"栈台账"的错指:采样有效性校验设计说明 :441/:593/:650 改 指 §2.3(任务栈配置与实测峰值)与 §4.0.6(静态最坏链与判定);id_aliases.json 的 note 改指 §4.0.6 并标注"原 §1.4,2026-10-22 层级编号"。 冻结与示例:其它文档**变更记录行**里的旧编号按项目纪律不改写(项目架构总览 :367、RAM预算台账 :98),改在被引文档 §4 开头留一张换算表 + 约定说明;把旧号 当语法示例写的 7 处一并换掉(文档规范 ×3、check_doc_links ×3、rename_ref ×1), 否则示例本身指向已改名的锚点。主文档版本行 bump 到 v1.3 / 2026-10-22。 门禁:check_doc_links.py --all --sections-strict → 146 个文档 0 问题(章节引用 2513 处、机器可判定 1491);check_ids.py --strict 增量拦截通过;mem-numbers、 ram_baseline --gate(SRAM1 57.4% / CCM 87.9%)、task-table 全绿;本轮未改任何 .c/.h。16 个文件字节卫生自查 BOM/CR/U+FFFD/行尾空格全清。已提交本地,未推送。 |
||
|
|
f4b9bd57d5 |
docs(部署): 删任务栈副本表改指针;纠 CCM/FPU 事实并刷新配图基线
范围:AGENTS.md 一处指针、部署环境要求 §2.2/§3.1/§3.3、RAM 方案 6 处现状 数字、内存配图生成器 + 3 张 SVG、两份报告脚本的行尾与路径修正、新增两份 .map/.htm 数据快照。未改任何 .c/.h(仍跑 @build 复核:0 错误 0 警告)。 部署环境要求.md - §3.3 删"任务栈预算"副本表、改为指针:该表已缺 adcTask/iec104Task/ protectTask,defaultTask/timeSyncTask 两值停在旧数,注释却写"由 gen_task_table.py 同源"——该钩子的校验面只有 README.md / AGENTS.md 两处受管块,抄出来的副本没有门禁、改栈后必漂。指针指向 Src/freertos.c + 两份受管表 + RAM 方案 §2.3/§0.6,并留一条"别再抄"的成因说明。 - §2.2 链接布局补 `RW_CCM 0x10000000`(scatter 给 0x0000FF00 = 65,280 B, 2026-10-11 起承载 FreeRTOS 堆;另有 256 B UNINIT_CCM 尾区),写明 CCM 不可 DMA。原文"SRAM3/CCM 故意未映射"与 STM32F407-Demo.sct 直接矛盾。 - §3.1 MCU 行更正为**单精度 FPU(VFPv4-SP,无硬件双精度)、无 TrustZone** (原写"双精度 FPU / TrustZone"是芯片事实错误,也正是报告 F-6 双精度软 浮点被长期误判的土壤);SRAM 行改为"两块都在用,仅 SRAM3 未映射"。 - §3.3 资源预算表补 CCM 行,并把"主 SRAM 125,104 B / 95.4% / 极度紧张" 更正为 75,264 B / 57.4%(余 55,808 B);红线改标在 CCM(余 7,904 B)。 - 变更记录补 2026-10-22 行(含上一提交里的资源表刷新那一笔)。 AGENTS.md:任务表注里 "RAM使用分析与优化方案.md §1.4" 是**指错小节**——§1.4 确实 存在(该文档 §4「方案」下的阶段 1 子项,讲 lftpd 大局部缓冲静态化),但栈余量 台账在 §2.3(任务栈配置与实测峰值)与 §0.6(静态最坏链与判定),故改指这两处 再加 内存内容分配策略.md §6。根因是该文档里 §1(背景)与 §4 下的 1.1~1.5 同号 撞面,人和 doc-links(自引用放过)都会看错——本会话我上一轮就被它带错过一次。 配图与数据(draw-diagram 流程:改生成器顶部数据 → 重跑 → 逐文本核越界) - 新增 data/ram_report_20261022.md、data/stack_report_20261022.md;后者 运行时水位取自 2026-10-22 真机 [STACK] 心跳(8 任务高水位 + heap free=15,800 / min_ever=8,464),静态侧来自同日 .htm(全局最深链 3,024 B)。 - gen_memory_svg.py:三组图数据从 2026-09-21 / 10-10 快照换成 2026-10-22 实测;任务栈 6 → 9 行,堆总量 48KB → 56KB,判定 6/6 → 9/9 PASS,新增 AboveNormal 配色;Flash 子条改为"窄段不压字 + 下方四列图例";任务栈 画布 440 → 620 高。CCM 段总量按 scatter 用 65,280 B(与台账同口径)。 - 三张 SVG 重生成;用"CJK≈1.0em / 其余≈0.58em + 锚点"逐文本估算边界, 修掉 5 处文字越界/压字(生成器内置 ElementTree 自检亦通过)。 - ram_report.py / stack_report.py 两处工具性修正:write_text 未指定 newline 参数 → Windows 下把 data/ 快照写成 CRLF(与 .gitattributes 及既有快照 不符);stack_report 的 relative_to 在传相对 -i 时抛 ValueError,且会把 本机绝对路径写进入库快照 → 统一走 resolve+relative_to 的相对路径回退。 RAM 方案:6 处"当前状态"随 .map/[STACK] 更正——RW_IRAM1 89,168 → 75,264、 RW_CCM 49,184 → 57,376、FreeRTOS 堆 48KB → 56KB(两处)、CCM 余量 ~16KB → 7,904 B、min_ever 4,848 → 8,464 B、6 任务全过 → 9/9 PASS;S-1 历史句保留。 门禁:pre-commit 对全部改动文件 Passed(doc-links --sections-strict、 task-table、mem-numbers、mem-budget、id-check 增量拦截);字节卫生自查 BOM / CR / U+FFFD / 行尾空格全清。已提交本地,未推送。 |
||
|
|
d4867ddbe2 |
fix(algo): Q10 定值宏改单精度;收口 Flash 优化簇并更正审查报告三处误判
影响面:一个头文件宏(App/algo/prot_set.h)+ 七份文档口径;固件行为逐位 等价,无功能变化。门禁全绿后提交,未推送。 验证链 - @build:0 错 0 警;Code 213,520 → 213,188(−332 B),RO/RW/ZI 不变。 - 真机 @test PASS:戳 fw build: Oct 22 2026 10:36:15 suite=NONE;无 [ERR]/ERROR/FAIL/Fault;708 ms 到 net init: OK;protect task start、 i104: listening on 2404、[STACK] 三面水位齐。 - 主机单测:prot_set 90/90、prot_elm 47/47、iec104 97/97。 - 等价性穷举:宏内 42 个字面量;int32 全域 ÷1024 往返 4,494,305 例零 差异;×1024 方向 20 万随机 float 零差异(×/÷1024 是 2^10 指数平移, 不产生尾数舍入)→ 定值默认值/上下限、校验和、104 上送值全部不变。 F-6 双精度软浮点(已落地) - 两个 Q10 宏去掉显式 (double)、1024.0 → 1024.0f:保护判据每 20 ms 每 元件 3 次定值读取改走 F407 单精度 FPU,不再软件模拟。 - .map 里 dflti/d2f/f2d/dfixi/fepilogue 共 300 B 消失;自研侧对双精度 helper 的引用归零。 - 更正报告归因:全库 0 处 %f(扫 App/Src/Inc/Drivers 共 173 个 .c/.h), 剩下 dadd/dmul/ddiv/depilogue/dfixul = 1,018 B 由 MicroLIB printfa.o 的 _printf_core → _fp_digits 无条件引用,只要还用 snprintf 就删不掉; 报告"把 %f 改整数刻度"没有可执行对象。 F-4 LVGL 死代码(判定不做) - 报告列的五项字节数逐字节仍准(合计 10,060 B),但没有一项有开关: 根因是 lv_draw_sw.c 的 execute_drawing() 无条件 switch 引用全部 task 类型,链接器 --remove 无从下手。 - 唯一总开关 LV_DRAW_SW_COMPLEX 被 ui_deco.c 的"浅 V"3 px 粗斜腿依赖 (斜段与其所需 mask 核心都在该开关内);2026-09-03 已实测失败并把两端 =1 固化 → UI_设计架构.md §1.4 第 7 条。 - 报告原"mask/grad 是文字与矩形绘制必经路径"证伪:字形走 blend 层 mask_buf,不经 lv_draw_sw_mask_*。 - 剩余路线需给 Lib/lvgl 打本地补丁(上限 ≈16 KB),而 Flash 仅占 22.1%、 无 OTA/双镜像预算 → 收益不抵代价;图片链同理不打补丁。 F-5 uvprojx 空 TU(降级为观察项) - 实测 326 个 .c / LVGL 201 个 / .map 零产出 127 个;但增量构建 4~5 s、 空 TU 只在全量重建或改 lv_conf.h 时才重编,且远程 CI 刻意不跑 @build。 - 报告原"编译时长 −40%"无基线记录,不复述;真要做用 Keil 原生 <IncludeInBuild>0</IncludeInBuild>,且排在任何 lv_conf.h 改动之后。 文档与台账 - 审查报告 §0.1/§0.4/§1/注册表/§5.1/§5.4/§6.4/W4 行按实测更正,补 2026-10-22 变更记录行与 F-4/F-6 记录去处行;登记簿 N-28 改写为"三项 已判定、仅剩 F-5 观察项"。 - 更正 FTL 写回缓存旧口径(sys_log_mcu.c 注释、运行与自检记录设计说明 §4.4、实机测试实施指南 §9.1):f_close 经 sync_fs → CTRL_SYNC → ftl_flush_cache 已落盘,"写另一个文件逼出脏页"不是必要条件;2026-10-02 那次"复位后写入整段丢失"根因是 dhara resume 锚错 checkpoint。 - 部署环境要求 §3.3 资源预算由 2026-09-11 快照刷新为 2026-10-22 实测, 并补 CCM 红线行(旧"SRAM 95.4%/极度紧张"的说法早已失效)。 - ram_baseline.py --update/--write、check_mem_numbers.py --write 已回填: mem-budget(SRAM1 57.4% / CCM 87.9%,阈值 92%)与 mem-numbers 转绿。 |
||
|
|
ee25b10053 |
chore(编号): 硬件网表报告里的 Trap05 统一为 TRAP-NET-05
- 6 处裸形 Trap05(V0.0 报告 5 处 + V0.1 报告 1 处)→ TRAP-NET-05,序号不变只补模块码 - 语义已核对:属主条目 docs/问题记录/CH395F_Trap_Records.md 的 TRAP-NET-05 就是 CH395F 与 RTL8305 自动协商不兼容,同名不同义的担心排除掉了 - 别名表登记 Trap05 → TRAP-NET-05(scope 限定这两个文件,写进真容器 aliases) - rename_ids.py 解除对 docs/硬件平台/ 的排除(芯片手册仍排除) - 注册表 TRAP 族注记改为事实:该目录仍不进门禁扫描语料——粗测约 4200 处 R/C/D/J/X/U/T 位号,位号类型识别做完前纳入只会让 --strict 为位号天天拦提交 验证:id-check --strict 无台账外新账;doc-links 失效与待确认均 0; 改名文件 BOM=否、CR=0、U+FFFD=0;未改 C/H,不需 @build |