From ac48c6d71651b3a93790cac4272c2d700891e5d2 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Tue, 22 Sep 2026 01:37:13 +0800 Subject: [PATCH] =?UTF-8?q?E-01=E5=AE=A1=E8=AE=A1=E9=97=AD=E7=8E=AF?= =?UTF-8?q?=EF=BC=9A=E4=BF=AE=E5=A4=8D=E7=95=99=E6=A1=A3=EF=BC=88d030cb0/7?= =?UTF-8?q?0f1cc8=E5=8F=B0=E6=9E=B6=E9=AA=8C=E8=AF=81=E5=85=A8=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=EF=BC=89=E4=B8=8E=E9=85=8D=E7=BD=AE=E5=BC=95=E6=93=8E?= =?UTF-8?q?=E8=B6=85=E9=95=BF=E5=A5=91=E7=BA=A6=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/配置文件设计说明.md | 13 ++++++++----- docs/问题记录/错误码传播审计.md | 8 +++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/配置文件设计说明.md b/docs/配置文件设计说明.md index c78d67a6..19b418ef 100644 --- a/docs/配置文件设计说明.md +++ b/docs/配置文件设计说明.md @@ -207,11 +207,13 @@ flowchart TD C --> K E --> K J --> K - K --> L["f_rename → /config.ini(原子替换)"] + K --> L["unlink 既有目标 + f_rename → /config.ini(原子替换;本 FatFs 移植 f_rename 不覆盖已存在目标,v1.12)"] L --> M["f_stat 更新变更基线"] ``` -**(3) 变更检测** —— `cfg_file_poll()`(defaultTask / 模拟器主循环,2s 节拍):`f_stat` 取 (size, mtime-epoch) 与基线比对 → 变化则重载 → 段变化则触发该段回调。 +- **超长守卫(E-01,v1.12)**:外科手术输出超 `CFG_FILE_MAX_LEN`(4096B)→ **拒绝保存返回 -1**(原文件不动 + 自检上报 `cfg save overflow`);`s_write_doc`(模板/外科同闸的唯一落盘出口)硬性拒写 >4096B —— "引擎永不产出超长 config.ini"是结构性质。超长文件一律按损坏处置(load/poll 同判,见 §5.3) + +**(3) 变更检测** —— `cfg_file_poll()`(defaultTask / 模拟器主循环,2s 节拍):`f_stat` 取 (size, mtime-epoch) 与基线比对 → 沉降后**先按真实 `st.size` 查超长**(E-01,v1.12):>4096B = 损坏 → 各段复位工厂默认 + 重建模板 + 自检留痕(`cfg poll oversized`),**不触发段回调**(保留 FTP/在线修复通道,下次保存或重启收敛);正常路径重载 → 段变化则触发该段回调 → 刷新基线。 **(4) 段变更回调** —— 未来模块的接入点: @@ -357,8 +359,8 @@ typedef struct { ... } cfg_section_reg_t; int cfg_file_register(const cfg_section_reg_t *p_sec); /* 注册段(首次 load 前;变更回调随段注册) */ int cfg_file_load(void); /* 0 = 正常(含回退默认)/ -1 = 严重错误 */ -int cfg_file_save(void); /* 0 = 成功 / -1 = 失败(原文件保持不动) */ -void cfg_file_poll(void); /* 变更检测 + 沉降去抖(defaultTask / 模拟器主循环,2s 节拍) */ +int cfg_file_save(void); /* 0 = 成功 / -1 = 失败(原文件保持不动;输出超 4096B 拒绝保存,v1.12) */ +void cfg_file_poll(void); /* 变更检测 + 沉降去抖(defaultTask / 模拟器主循环,2s 节拍);沉降后先判超长(E-01) */ void *cfg_file_section_data(const char *p_name); /* 取段 RAM 副本(未注册 = NULL) */ ``` @@ -372,7 +374,7 @@ typedef struct { } cfg_file_stat_t; int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread); /* 至多读 cap 字节 */ -int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len); /* 临时文件 + rename(原子) */ +int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len); /* 临时文件 + unlink 既有目标 + rename(原子;本 FatFs 移植与 MSVC CRT 的 rename 均不覆盖已存在目标,须先删,v1.9/v1.12 双端对称) */ int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st); /* 0 = 存在 / -1 = 缺失/失败 */ int cfg_store_remove(const char *p_path); @@ -584,3 +586,4 @@ void adc_cfg_init(void) | 2026-09-14 | v1.9:**P5 完成**(sim 0 警告):模拟器主循环接入 2s `cfg_file_poll`(与 MCU netTask 同周期);新增无头自检 `lvgl_sim.exe --cfgtest`(加载/迁移 → 建基线 → 伪造外部编辑 → 两次轮询沉降 → 断言 RAM 更新 + 外科保存回环校验注释/值无损);修复其暴露的两处缺陷:① MSVC `rename()` 不覆盖已存在目标(C1117)——模拟器后端先删目标再 rename(目标丢失时引擎自动重建模板兜底);② **外科保存截断 bug**——`s_split_line` 将 CRLF 行的 `\r`/`\n` 双双 NUL 化,旧 `s_restore_newlines` 只复原一个 `\n` 即遇 NUL 终止,保存产物只剩首行(P3 未触发"对已存在文件保存"故未暴露)——改为 Pass 1 用一次性副本(堆峰值维持约 9KB)、删除复原函数 | | 2026-09-15 | v1.10:**配置引擎属主任务变更** netTask → defaultTask(P1-3 跨任务竞态修复,P1-5 一并落地)——① 上电加载移入 defaultTask 的 STORAGE_READY 屏障之前(屏障语义 = 卷挂载 + 配置就绪);② UI 保存改跨任务交接:`net_cfg_save_request()`("待保存"标志 + `SYS_EVT_CFG_SAVE` 事件唤醒)→ defaultTask `net_cfg_save_run()` 执行写盘、成功触发"立即应用"(`SYS_EVT_NET_RECONFIG` → netTask 重建),保存行随交接状态显示 ENTER/WAIT/OK/ERR(写盘期间的新请求重位置位不丢失);③ 2s poll 收敛 defaultTask——引擎静态状态仅单任务触碰,竞态在结构上消除;④ netTask 只读 RAM 副本、不触碰配置文件引擎;D15-③ 单写者、§5.1~5.4 时序图与并发矩阵同步;双端构建 0/0、UI 五步门禁全绿 | | 2026-09-18 | v1.11:**P0-1 修复(临时缓冲改走平台堆)**——共享核心 4 处 `malloc`(读缓冲 4,097B / 模板输出 5,120B / 外科保存 Pass1 副本 + 输出 5,120B)此前落在 **MicroLIB C 堆**上,而该堆由 `startup_stm32f407xx.s` 的 `Heap_Size EQU 0x200` 限定为 **512 B**,真机必然分配失败 → 配置加载静默回落工厂默认(模拟器用宿主 CRT 堆,两端不一致,故双端 0 警告 0 错也照不出来)。① `cfg_file_store` 新增 `cfg_store_alloc/cfg_store_free/cfg_store_heap_free` 后端三件套(MCU = `pvPortMalloc/vPortFree`,与 `lftpd_io.c` 同模式;模拟器 = `malloc/free`),共享核心不再出现 C 库分配符号;② 新增编译期堆峰值预算 `CFG_HEAP_BUDGET = 10240B`(typedef 负数组实现,ARMCC 5 C99 无 `_Static_assert`),放大 `CFG_FILE_MAX_LEN`/`CFG_OUT_MAX` 直接编译失败;③ 四条失败路径(load 读失败 / 模板写失败 / 保存分配失败 / 保存写盘失败)增 `syslog_post(SYSLOG_SELFTEST, …)` 上报,不再只留串口行;`load: OK` 行附带 `heap free` 实测值;④ 属主任务注释同步为 defaultTask。map 复核:`malloc.o` 不参与链接、`Removing startup_stm32f407xx.o(HEAP), (512 bytes)`,静态区 126,584 → **126,064 B**(余 4,488 → **5,008 B**);双端 0 错 0 警、`--cfgtest` PASS | +| 2026-09-22 | v1.12:**E-01 超长守卫根治**(错误码传播审计第一档 #3,commit d030cb0)——poll 的"超长=损坏"检查写在截断后读长(`s_load_file` 读长钉死 4096)上**恒假=死码**,超长文件被截断当有效解析 → 界外静默回工厂默认 → 基线粘住 → 用户配置销毁链。① poll 沉降分支判定改用 `cfg_store_stat` 真实 `st.size` 且**先于读取**,超长走"工厂默认 + 重建模板 + `cfg poll oversized` 自检留痕",**刻意不触发段回调**(避免按工厂 IP 重建网络造成静默失联,保留 FTP 修复通道,保存/重启收敛)② `s_surgical` 输出上限 `CFG_OUT_MAX`(5120) 收紧至 `CFG_FILE_MAX_LEN`(4096),超限走溢出中止路径(原文件不动 + `cfg save overflow` 自检)——消除"合法写出 4097~5120B 文件 = 下次开机销毁"的雷 ③ `s_write_doc` 唯一落盘闸口硬拒 >4096B(结构性保证)④ load 超长分支补 `cfg load oversized` 自检;`cfg_file.h` v1.2 契约同步;模拟器 cfgtest 新增两回归锁(4160B 伪造 → poll 判损坏重建;4000B 注释文件 → save 拒写且原文件未动)。**台架验证暴露并修复**:`cfg_store_write_atomic` MCU 后端 `f_rename` 对已存在目标返回 `FR_EXIST` 不覆盖 → 设备端一切"目标存在的覆盖写"(UI 保存/模板重建)静默失败、仅首启建文件成功——v1.9 已为模拟器后端修过同款(MSVC `rename()` C1117),MCU 侧漏了对称修复;修复 = rename 前先 `unlink` 目标(commit 70f1cc8)。双端 0 错 0 警、cfgtest PASS、台架 FTP 5047B 用例 poll/load 双路径自愈 PASS(重建 523B 工厂模板 + selftest 留痕 + 不掉线)、`@test` 默认闭环 PASS、ST-LINK 全片回读一致 | diff --git a/docs/问题记录/错误码传播审计.md b/docs/问题记录/错误码传播审计.md index 4e6d87b8..243c60d7 100644 --- a/docs/问题记录/错误码传播审计.md +++ b/docs/问题记录/错误码传播审计.md @@ -32,7 +32,7 @@ E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺 | 编号 | 位置 | 一句话 | |---|---|---| -| E-01 | `App/cfg_file.c:1151` | `cfg_file_poll` 的"文件超长=损坏"检查是死代码(读长被钉死在阈值)→ >4096B 配置文件被截断解析当有效 → 界外配置静默回工厂默认 → 基线粘住错误状态 → 下次开机 load 守卫判"损坏" → **用户配置文件整体销毁** | +| E-01 | `App/cfg_file.c:1151` | `cfg_file_poll` 的"文件超长=损坏"检查是死代码(读长被钉死在阈值)→ >4096B 配置文件被截断解析当有效 → 界外配置静默回工厂默认 → 基线粘住错误状态 → 下次开机 load 守卫判"损坏" → **用户配置文件整体销毁**。**已修复(2026-09-22,commit d030cb0)**:poll 超长判定基于 `st.size` 前置到 `s_load_file` 之前(根治死码),走"工厂默认+重建模板+自检留痕"且刻意不触发段回调(保留在线修复通道);save 输出上限收紧 4096 + `s_write_doc` 写盘唯一闸口硬拒超长;load/poll 超长分支补自检留痕。台架验证 PASS;验证中另暴露并修复 `cfg_store_write_atomic` 的 `f_rename` 不覆盖既有目标缺陷(commit 70f1cc8,见 E-01 明细"关联新发现") | | E-31 | `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c:250-252` | `dhara_map_resume` **任何**失败(含单次瞬态页读失败)→ `dhara_map_clear` → **整卷逻辑映射静默丢弃** → FatFs 探针判卷无效 → 整卷重格式化 → 全部用户数据丢失;dhara 本可区分"真空白"(合法)与 ECC/损坏(必须上报),glue 层把错误分类全坍缩。**已修复(2026-09-21,commit da1a6e8)**:resume 失败按 `dhara_error_t` 分类(仅真空白判 fresh)、其余失败 mount 层自检上报 + 保留卷 + 主循环有界重试 | ### P1 速览(22 项) @@ -135,6 +135,8 @@ E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺 - **触发**:`/config.ini` 超过 4096B——文档化的 FTP 编辑工作流 + 外科手术式保存补写(`:774-797`,输出上限 `CFG_OUT_MAX`=5120 > 4096,无回检)都可达 - **影响链**(已逐环复核):poll 读被截断的前 4096B → 界外段/键当"缺失"静默回工厂默认(`:1164-1183` 原地段复位 + 重解析)→ 回调触发网络按工厂 IP 重建 → 基线刷新(`:1189-1197`)把错误状态"粘住"→ 任何后续保存只按 RAM 副本(截断视图)重写 → **界外内容永久丢失** → 下次开机 `cfg_file_load` 的 `st.size > 4096` 守卫判"损坏" → **整份用户文件销毁**重生工厂模板。全程零错误日志 - **修复方向**:poll 在 `s_load_file` **之前**查 `cfg_store_stat` 的 `st.size > CFG_FILE_MAX_LEN`,走与 load 相同的"工厂默认 + 重建模板 + **自检留痕**"分支;save 路径加 `out_len ≤ CFG_FILE_MAX_LEN` 守卫(超限拒绝保存并报错,而非写出下一颗雷) +- **已修复(2026-09-22,commit d030cb0,第一档 #3)**:① `cfg_file_poll` 沉降分支超长判定改用 `cfg_store_stat` 真实 `st.size` 且**先于** `s_load_file`(旧检查写在截断后的 `len` 上恒假=死码),命中走"各段复位工厂默认 + `s_write_template()` 重建 + `DBG_ERROR`(含实际字节数)+ `syslog_post(SYSLOG_SELFTEST, "cfg poll oversized")`",**刻意不触发段回调**——避免按工厂 IP 重建网络造成"静默失联",RAM=工厂与硬件现状的分歧在下次保存/重启收敛(重启必收敛:load 读到的已是重建后的合法模板);`p_buf/len` 声明收进正常分支,超长路径省一次 4KB 分配 ② `cfg_file_save`:`s_surgical` 输出上限 `CFG_OUT_MAX`(5120) 收紧至 `CFG_FILE_MAX_LEN`(4096),复用既有溢出中止路径(原文件不动 + 自检 `cfg save overflow`),消除"合法写出 4097~5120B 文件 = 下次开机被销毁的雷" ③ `s_write_doc`(模板/外科手术唯一落盘出口)加 `len > CFG_FILE_MAX_LEN` 硬拒——"引擎永不产出超长 config.ini"成为结构性质,新增调用路径免疫 ④ load 超长分支补 `syslog_post(SYSLOG_SELFTEST, "cfg load oversized")`(销毁用户文件属重大事件,不能只留串口)。`cfg_file.h` v1.2 契约同步(load 第 4 条 / save / poll 说明)。**台架验证(2026-09-22,commit d030cb0+70f1cc8 构建)**:FTP STOR 5047B → 4s 后 `[ERR][CFG] poll: file 5047B > 4096B, corrupt, factory template regenerated`,设备保持在线(无网络重建),RETR 实测文件已重建为 523B 工厂模板;重启 load 守卫同链触发(`load: file 5047B > 4096B...`);再重启 `[CFG] load: OK (523B)` 零 `[ERR]` 无销毁循环;`selftest.log` 落 `cfg poll oversized`/`cfg load oversized` 留痕;模拟器 `--cfgtest` 两个新回归锁场景(伪造 4160B → poll 判损坏重建 + 4000B 注释文件 → save 拒写且原文件分毫未动,修复前必挂)PASS;`@test` 默认闭环 PASS + ST-LINK 全片回读一致 +- **关联新发现(E-01 台架验证暴露,已修复 2026-09-22,commit 70f1cc8)**:`App/cfg_file_store_mcu.c` `cfg_store_write_atomic`——本 FatFs 移植的 `f_rename` 对已存在目标返回 `FR_EXIST`、不覆盖(`Lib/FatFs/ff.c:5235-5238` "new name already in use" 检查,上游同语义)→ 设备端**一切"目标存在的覆盖写"(UI 保存 / 模板重建)静默失败**,仅首启建文件(目标不存在)成功;模拟器后端为宿主 rename(POSIX 覆盖语义),cfgtest 照不出。首轮台架证据:poll 守卫触发后 selftest 落 `cfg template write fail`、RETR 文件仍 5047B。修复 = rename 前先 `f_unlink` 既有目标(非原子窗口掉电 = 文件暂失,开机按首启重建工厂模板,与函数失败语义一致)。本缺陷恰位于 E-01 自愈路径上(模板重建即覆盖写),不修则 poll 守卫只能报无法愈;与 E-07(`f_close` 返回值丢弃)同属原子写可靠性链 #### E-31 FTL:`dhara_map_resume` 任何失败 → 整卷映射静默丢弃 - **位置**:`Drivers/BSP/GD5F2GQ5UE/nand_ftl.c:250-252`(`if (dhara_map_resume(&s_map, &err) < 0) { DBG_INFO("No valid map found..."); dhara_map_clear(&s_map); }`);错误源 `Lib/dhara/journal.c:222-248`(find_checkblock 逐块扫描,单次读失败透传 `*err` 并继续,任一子步骤失败 → resume 返回 -1);分类能力 `Lib/dhara/error.h:20-31` @@ -367,7 +369,7 @@ E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺 |---|---|---|---| | 1 | **E-32**(降 P2) | SD2506 init:18H 改"先回读验证 0x82,≠ 则 write_enable→写 18H=0x82→回读→write_disable"(18H 非易失、出厂 0x82,正常路径保留原写);注释更正(0x20/ARST 误注 → 0x7B 为手册 L396 推荐写禁止态值;ARST 在 CTR3 非 CTR1);**已修复 2026-09-21(commit 41e111b:18H 回读校验 + 漂移时写允许窗口重置与回读验证;台架 RTC 套件 PASS)** | P2 | | 2 | **E-31** | `dhara_map_resume` 按 `dhara_error_t` 分类:仅 `DHARA_E_TOO_BAD` 判 fresh,其余返回失败给 mount 层 + 自检上报,禁止静默 clear;**已修复 2026-09-21(commit da1a6e8:journal.c find_checkblock/find_root/read_checkpage 保留真实 I/O 错误(扫描读失败不再覆盖为 TOO_BAD、读成功但 checkpoint 不符判 CORRUPT_MAP)+ nand_ftl 分类(非 TOO_BAD 返回 STA_NOINIT 给 mount 层)+ freertos.c bring-up 重构为 storage_bring_up() 并加 3×2s 有界重试自愈;构建 0 错误 0 警告)** | P0 | -| 3 | **E-01** | `cfg_file_poll` 前置 `st.size > CFG_FILE_MAX_LEN` 守卫(工厂默认+模板+自检留痕);save 加 `out_len ≤ 4096` 守卫 | P0 | +| 3 | **E-01** | `cfg_file_poll` 前置 `st.size > CFG_FILE_MAX_LEN` 守卫(工厂默认+模板+自检留痕);save 加 `out_len ≤ 4096` 守卫;**已修复 2026-09-22(commit d030cb0:poll 判定基于真实 st.size 前置到读取之前根治死码、超长不触发段回调保留在线通道、save 输出上限收紧 4096、`s_write_doc` 写盘唯一闸口硬拒超长、load/poll 超长处置补自检留痕;构建 0 错误 0 警告;台架 FTP 5047B 用例 poll/load 双路径自愈 PASS + 模拟器 cfgtest 双回归锁场景 PASS;验证暴露并修复关联 `f_rename` 不覆盖缺陷 commit 70f1cc8)** | P0 | | 4 | **E-34** | CH395F 字节级路径:`HAL_MAX_DELAY` → 有限超时(100~500ms)+ 返回值检查 | P1 | | 5 | **E-33** | CH395F DMA 传输加错误通道(返回状态/错误标志);失败 → net_send/recv 返回 -1;错误完成分支 INFO→ERROR | P1 | | 6 | **E-36/E-37** | RS485 契约修复:重武装不清零(或 getter 契约改"钩子内有效"并明示);`rs485_init` 返回码 + 失败日志 | P1 | @@ -523,4 +525,4 @@ E-60"write_enable WRTC2 与手册矛盾"经三方复核(手册表头 D7~D0 顺 --- -*报告完。59 项发现全部带 file:line 落点;P0/P1 核心机制经父审计组逐行复核(含 `docs/芯片手册/SD2506API-G.md` L392-398/L537/§4.8 原文核对与 `SD2506_Trap_Records.md` 硬件观测交叉验证)。2026-09-21 复核修订:E-32 由 P0 降 P2(充电器 P0 链被"出厂 82H"证伪)、E-39 机制改写(ARST 在 CTR3、驱动从未写过 CTR3)、E-60"write_enable WRTC2 与手册矛盾"经手册表头位序 + DFRobot GravityRtc 官方库/社区代码 + 实测三方复核证伪(0x84 本已含 D2=WRTC2,代码与手册 L392 一致)移除,见 §7-19;18H 寄存器事实经手册 §4.8 专用表复核(Bit7=EN_Charge、Bit1=Charge1、Bit0=Charge0,复位 0x03,出厂 0x82=EN+5k,总表 L119 该行转录有误以 §4.8 为准);§6 交叉引用补 P2-2/P2-6。审计期间未修改任何代码、未执行构建;2026-09-21 依第一档 #1 完成 E-32 修复(commit 41e111b:18H 回读校验 + 漂移时写允许窗口重置与回读验证、0FH 死写与误注移除、充电宏位订正;构建 0 错误 0 警告,台架 RTC 套件 Phase 1 6/6 + Phase 2 累计 17/17 PASS 含 set==readback,产品固件重烧后启动校验 PASS,ST-LINK 全片回读 No difference);同日依第一档 #2 完成 E-31 修复(commit da1a6e8:journal.c 三处错误源治理——扫描读失败不再覆盖为 TOO_BAD、读成功但 checkpoint 不符判 CORRUPT_MAP + nand_ftl.c 错误分类——仅真空白判 fresh、其余失败返回 STA_NOINIT 给 mount 层 + freertos.c 启动块重构为 storage_bring_up() 并加 3×2s 有界重试自愈策略;构建 0 错误 0 警告;产品固件台架回归 PASS:`@test` 闭环 Required(含 `Map resumed`/`FATFS mounted`)全中、Forbidden 零命中,既有卷 2441/96064 扇区 resume 成功、用户配置 523B 原样加载、FTP 屏障链通,ST-LINK 全片回读 No difference)。* +*报告完。59 项发现全部带 file:line 落点;P0/P1 核心机制经父审计组逐行复核(含 `docs/芯片手册/SD2506API-G.md` L392-398/L537/§4.8 原文核对与 `SD2506_Trap_Records.md` 硬件观测交叉验证)。2026-09-21 复核修订:E-32 由 P0 降 P2(充电器 P0 链被"出厂 82H"证伪)、E-39 机制改写(ARST 在 CTR3、驱动从未写过 CTR3)、E-60"write_enable WRTC2 与手册矛盾"经手册表头位序 + DFRobot GravityRtc 官方库/社区代码 + 实测三方复核证伪(0x84 本已含 D2=WRTC2,代码与手册 L392 一致)移除,见 §7-19;18H 寄存器事实经手册 §4.8 专用表复核(Bit7=EN_Charge、Bit1=Charge1、Bit0=Charge0,复位 0x03,出厂 0x82=EN+5k,总表 L119 该行转录有误以 §4.8 为准);§6 交叉引用补 P2-2/P2-6。审计期间未修改任何代码、未执行构建;2026-09-21 依第一档 #1 完成 E-32 修复(commit 41e111b:18H 回读校验 + 漂移时写允许窗口重置与回读验证、0FH 死写与误注移除、充电宏位订正;构建 0 错误 0 警告,台架 RTC 套件 Phase 1 6/6 + Phase 2 累计 17/17 PASS 含 set==readback,产品固件重烧后启动校验 PASS,ST-LINK 全片回读 No difference);同日依第一档 #2 完成 E-31 修复(commit da1a6e8:journal.c 三处错误源治理——扫描读失败不再覆盖为 TOO_BAD、读成功但 checkpoint 不符判 CORRUPT_MAP + nand_ftl.c 错误分类——仅真空白判 fresh、其余失败返回 STA_NOINIT 给 mount 层 + freertos.c 启动块重构为 storage_bring_up() 并加 3×2s 有界重试自愈策略;构建 0 错误 0 警告;产品固件台架回归 PASS:`@test` 闭环 Required(含 `Map resumed`/`FATFS mounted`)全中、Forbidden 零命中,既有卷 2441/96064 扇区 resume 成功、用户配置 523B 原样加载、FTP 屏障链通,ST-LINK 全片回读 No difference);2026-09-22 依第一档 #3 完成 E-01 修复(commit d030cb0:poll 超长判定基于 `cfg_store_stat` 真实 st.size 前置到 `s_load_file` 之前根治死码、超长走工厂默认+重建模板+自检留痕且不触发段回调保留在线修复通道、save 输出上限 CFG_OUT_MAX→CFG_FILE_MAX_LEN、`s_write_doc` 写盘唯一闸口硬拒超长、load/poll 超长处置补 SYSLOG_SELFTEST 留痕;构建 0 错误 0 警告;台架验证 PASS:FTP STOR 5047B → poll 守卫 4s 内触发并重建 523B 工厂模板(RETR 实测)、设备保持在线、重启 load 守卫同链自愈、再重启零 `[ERR]` 无销毁循环、selftest.log 双留痕、模拟器 cfgtest 两个新回归锁场景 PASS、@test 默认闭环 PASS、ST-LINK 全片回读 No difference;验证中暴露并修复关联缺陷 `cfg_store_write_atomic` f_rename 对已存在目标不覆盖致设备端一切覆盖写静默失败(commit 70f1cc8:rename 前先 unlink 目标),详见 E-01 明细"关联新发现")。*