fix(cfg): 视图守卫补内容判据——同尺寸全 0 旧映射块也能拦(#73)
CI / 静态门禁 (pre-commit) (pull_request) Failing after 6m33s
CI / 主机侧单测 (MSVC) (pull_request) Successful in 35s
CI / 静态门禁 (pre-commit) (push) Failing after 5m7s
CI / 主机侧单测 (MSVC) (push) Failing after 43s

台架(PR #82 head d9303e05,常授权烧写验证)在三点校验版守卫上继续复现
出打回形态:卷层给出的旧映射块与目标文件恰好同长(1064B)且整块全 0,
读长 == st.size 与读前后复 stat 全部通过,解析零错误、RAM 照样打回默认
(15:21 决定性轮:盘文件 RETR 校验七段完好,重载窗口内 s_sntp 三字节段
全 0)。纯令牌层校验对"内容坏"是盲的,必须补内容判据。

处置(引擎侧,与三点校验同族):
- 新增 s_view_bad:重载/保存读到的视图若为空读、或 [0,len) 内任意偏移
  含 NUL 字节即判坏——INI UTF-8 文本约定下任何偏移都不得含 0x00;解析
  器按行切分遇 NUL 即断行,零块之后的内容本就不可达(静默截断同源故障),
  拒之不误伤任何可解析文档。不要求首字节 ';'(手写文档可直接段名起始)。
- poll:坏内容 = 与长度/令牌不符同处置——拒绝应用,RAM 与基线不动,2s
  重试自愈,首次 ERROR + 自检留痕,每 30 趟一条 WARN(限速)。
- save:坏内容同族拒存返回 -1,原文件一字节不动,调用方 dirty 保留重试。
- 边界如实保留:内容合法但陈旧的整卷视图(长度/令牌/内容三者皆一致滞后)
  引擎层无法检出,属卷层(dhara/FTL)课题,重启必然收敛;"改过配置后
  重启一次"在卷层根治前仍是最稳口径。

测试:
- TC-CONFIG-061(poll 同尺寸全 0 块 HELD + 视图刷新自愈 + 零回调)、
  TC-CONFIG-062(save 遇全 0 块拒存、文件字节/写次数不动、追平后重试
  成功);桩新增 vfs_mark_stale_zeros() 注入同长全 0 旧视图。
- cfg_file 套件 259/259;iec104 179/179、run_par 67/67、adc_cal 117/117、
  sntp 98/98、cfg_file_store_mcu 34/34 复跑全绿。
- /keil-build 0 错 0 警:Code 254,784 -> 254,820 B(+36 B);台账与同码
  凭证已同步。台架实录见 PR #82 评论:NUL 注入块实机 HELD(16:55:46)
  + WARN 节拍 30/60 趟命中(16:56:42/16:57:41)+ 视图转新鲜后重载自动
  追平;残余打回事件均为"内容合法但陈旧"整卷视图(15:19:30/16:34:59/
  16:58:38),属已声明守卫边界,盘文件全程未吞段,重启收敛实机确认
  (17:04 RAM 回文件值)。

文档:
- 《配置文件设计说明》§4-(3)/§5.3 守卫描述补内容判据与 15:21 实证形态、
  风险表行更新、变更记录 v1.17 行同步;
- 《配置文件单元测试说明》TC-CONFIG-061/062 行 + 当前结果 259/259 +
  扩展指南补 vfs_mark_stale_zeros()。
This commit is contained in:
2026-11-05 17:08:25 +08:00
parent d9303e05a4
commit 1a2ca0467d
7 changed files with 146 additions and 33 deletions
+70
View File
@@ -66,6 +66,10 @@
* 基线保留 2s 重试、视图追平即自愈应用 + 回调;拒绝期间无回调无二次 ERROR
* TC-CONFIG-060 读中改写(读后复 stat 令牌变)-> HELD 后重新沉降自愈;
* 持续不一致:全程仅 1 ERROR + 1 留痕,WARN 每 30 趟一条(日志限速)
* TC-CONFIG-061 同尺寸全 0 旧映射块(长度/令牌双检全过)-> 内容判据拦下:
* HELD RAM 保旧、视图刷新后自愈应用 + 回调
* TC-CONFIG-062 保存链遇同尺寸全 0 旧视图 -> 拒存 -1,文件字节/写次数不动,
* 视图恢复后同一保存重试成功落盘
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
@@ -355,6 +359,15 @@ static void vfs_mark_stale(void) {
s_view_stale = 1;
}
/* 同尺寸全 0 旧视图(2026-11-05 台架 15:21 实证形态:dhara 旧映射块恰与
* 目标同长,长度/再 stat 双检全过,只有内容判据能拦):read 返回与真实
* 盘同长的全 0 块 */
static void vfs_mark_stale_zeros(void) {
s_vfs_stale_len = s_vfs_len;
memset(s_vfs_stale, 0, s_vfs_stale_len);
s_view_stale = 1;
}
/* 视图追平盘上真身 = 卷层视图恢复(守卫自愈路径) */
static void vfs_clear_stale(void) {
s_view_stale = 0;
@@ -1210,6 +1223,62 @@ static void tc_poll(void) {
}
}
/* 内容层坏视图(2026-11-05 台架 15:21 实证形态:dhara 同尺寸全 0 旧映射块,
* 长度与再 stat 双检全过,仅内容判据可拦) */
static void tc_view_content(void) {
printf("[TC-CONFIG-061] poll zero-block same-size stale view -> HELD + self-heal\n");
{
vfs_reset();
register_std();
vfs_put("[net]\r\nu8=42\r\n");
s_net_data.u8 = 5u; /* RAM = 旧值:非默认(7)非新值(88) */
cfg_file_poll(); /* 基线(台架时序:先基线后变更) */
vfs_put("[net]\r\nu8=88\r\n"); /* 盘上真实变更(与旧视图同尺寸) */
vfs_mark_stale_zeros(); /* 视图 = 与 stat 同长的全 0 块 */
cfg_file_poll(); /* st 变:stable=1 */
s_syslog_count = 0;
g_cfgtest_err_count = 0;
s_net_cb_count = 0;
cfg_file_poll(); /* stable=2 -> 长度/令牌双检过,内容判据拦下 */
CHECK(s_net_data.u8 == 5u, "zero-block view held: RAM keeps old value");
CHECK(g_cfgtest_err_count == 1, "zero-block first reject logs one ERROR");
CHECK(s_syslog_count == 1, "zero-block first reject self-test logged");
CHECK(s_net_cb_count == 0, "zero-block view never reaches callback");
cfg_file_poll(); /* 视图仍旧 -> 持续 HELD 静默 */
CHECK(g_cfgtest_err_count == 1, "zero-block repeats stay silent");
CHECK(s_net_data.u8 == 5u, "zero-block view still never touches RAM");
vfs_clear_stale(); /* 卷层视图恢复 */
cfg_file_poll(); /* 重试 -> 一致视图 -> 应用 */
CHECK(s_net_data.u8 == 88u, "self-heal: applied once zero view refreshed");
CHECK(s_net_cb_count >= 1, "callback fires on healed apply");
}
printf("[TC-CONFIG-062] save on zero-block view -> abort, file untouched\n");
{
int w_before;
size_t l_before;
vfs_reset();
register_std();
vfs_put("[net]\r\nu8=88\r\n");
s_net_data.u8 = 9u; /* 待保存的脏值 */
vfs_mark_stale_zeros(); /* 保存链读到同长全 0 旧视图 */
w_before = s_write_count;
l_before = s_vfs_len;
s_syslog_count = 0;
g_cfgtest_err_count = 0;
CHECK(cfg_file_save() == -1, "save aborts on zero-block view");
CHECK(s_write_count == w_before, "no atomic write happened");
CHECK(s_vfs_len == l_before, "file size untouched (no shrink)");
CHECK(strcmp(vfs_get(), "[net]\r\nu8=88\r\n") == 0, "file bytes untouched");
CHECK(s_syslog_count == 1, "save reject self-test logged");
CHECK(g_cfgtest_err_count == 1, "save reject logs one ERROR");
vfs_clear_stale(); /* 视图恢复 -> 同一保存链重试成功 */
CHECK(cfg_file_save() == 0, "save succeeds once view converges");
CHECK(strstr(vfs_get(), "u8=9") != NULL, "dirty value persisted on retry");
}
}
int main(void) {
printf("=== Config File Tests ===\n");
@@ -1225,6 +1294,7 @@ int main(void) {
tc_load();
tc_save();
tc_poll();
tc_view_content();
printf("\n=== Config File Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);