docs(规范,门禁): 卷锁五态去裸形——本仓第一次真正还第二根护栏的债,并写清还债的算法

最反直觉的是结果里的差值:36 处裸形态号全部清零,绝对数只从 1,162 降到 1,156(−6,余量 4 → 10 格)。
作者没把差值糊过去而是查到底:check_ids 只在字面量**落不进任何已登记族**时才计不可判定,撞进已登记族的
23 处本来就记在可判定侧,删它们分子分母同缩、比例反而从 86.74% 涨到 86.77%;真正在盲区里的只有族外
7 处,其中 1 处还被变更记录行豁免 ⇒ 账面 −6,算式对平(±1 属同窗别道在飞)。**这四条机制我写进
docs/门禁说明/id-check.md §6.10 并署名作者**——它是「怎么还这根护栏的债」的第一份操作口径。

改动面:App/fs/vol_lock.c 10 处、.h 6 处、vol_lock_test.c 14 处、卷锁单元测试说明.md 3 处 ⇒ 同口径
改后 0 处。四态语义只留 vol_lock.h 的 vol_lock_stats_t 注释一处,其余三处只描述断言并指回头文件,
不抄第二真源。除 4 行声明外全是注释文字,代码行为零改动。顺带消掉一个**同形两义**的撞号源。

实测:1,162(03:15:08)→ 1,156(03:21:56),写变更记录后复量仍 1,156(本笔没吃余量);--strict exit
0;vol_lock 套件 45/45;**上限未动、刻意不跑 --update-baseline**(作者反对成立:重锚会把只减不增的线
按这次 −6 重画,白丢一次真还债的账面记录);未改 id_registry.json / 台账,未跑 --all-files。
This commit is contained in:
2026-10-27 05:14:11 +08:00
parent b1494eb10c
commit f1d0b5c2d9
5 changed files with 41 additions and 26 deletions
+10 -9
View File
@@ -6,7 +6,8 @@
* 创建日期:2026-10-02
* 修改记录:
* v1.0 2026-10-02 王建锋 创建
* v1.1 2026-10-26 DTU 项目组 道G task-17:① 新增 4 个观测计数(T-a/T2/T3/T4,
* v1.1 2026-10-26 DTU 项目组 道G task-17:① 新增 4 个观测计数(持锁 / try 超时 /
* 分片退让 / 零互斥 四态,
* 共 16 B 静态)与 vol_lock_get_stats() 只读口 —— 不取锁、
* 零外部调用、零局部变量,13 处调用点一字未动;
* ② 修 :38「返回值」注释与代码相反的错(fail-open 原被
@@ -34,10 +35,10 @@ static osMutexId_t s_lock; /* 卷串行化互斥锁(vol_lock_init 创建)
* 并发:多个任务可同时自增同一个计数,竞态下少计不会多计(判据是"是否 > 0 / 是否还
* 在增长",不是精确值),所以不为此上临界区 —— 上临界区反倒把观测做成了故障本身。
* volatile:跨任务读写且无锁保护,禁止把读侧重复读优化掉。 */
static volatile uint32_t s_st_acquired; /* T-a 真取到互斥 */
static volatile uint32_t s_st_try_timeout; /* T2 try(timeout=0)失败:卷被别人占住 */
static volatile uint32_t s_st_blk_timeout; /* T3 分片等待超时:ftpTask 正常退让 */
static volatile uint32_t s_st_failopen; /* T4 锁不存在却返 1:谎报成功那一态 */
static volatile uint32_t s_st_acquired; /* 持锁态:真取到互斥(四态定义见 vol_lock.h) */
static volatile uint32_t s_st_try_timeout; /* try 超时态:timeout=0 没取到,卷被别人占住 */
static volatile uint32_t s_st_blk_timeout; /* 分片退让态:分片等待超时,ftpTask 正常退让 */
static volatile uint32_t s_st_failopen; /* 零互斥态:锁不存在却返 1,谎报成功那一态 */
/* 函数定义区 */
@@ -73,18 +74,18 @@ int vol_lock_wait(uint32_t timeout_ms) {
if (s_lock == NULL) {
(void)vol_lock_init();
if (s_lock == NULL) {
s_st_failopen++; /* T4:返 1 但零互斥 —— 谎报成功那一态 */
s_st_failopen++; /* 零互斥态:返 1 但零互斥 —— 谎报成功那一态 */
return 1; /* 锁创建失败:fail-open(不阻塞,保持启动段可用) */
}
}
if (osMutexWait(s_lock, timeout_ms) == osOK) {
s_st_acquired++; /* T-a:真持锁 */
s_st_acquired++; /* 持锁态:真持锁 */
return 1;
}
if (timeout_ms == 0u) {
s_st_try_timeout++; /* T2:压根没等(喂狗点/绘制路径只有这一态会发生) */
s_st_try_timeout++; /* try 超时态:压根没等(喂狗点/绘制路径只有这一态会发生) */
} else {
s_st_blk_timeout++; /* T3:等了一会儿没等到(分片退让),语义与 T2 不同 */
s_st_blk_timeout++; /* 分片退让态:等了一会儿没等到,与 try 超时态语义不同 */
}
return 0;
}
+8 -7
View File
@@ -72,13 +72,14 @@ int vol_lock_wait(uint32_t timeout_ms);
/*
* 卷锁观测计数(道G task-17「卷锁 5 态可观测性」)。四个字段就是四态,语义**不得**合并:
* T-a `acquired` = 真取到互斥的次数
* T2 `try_timeout` = timeout=0 的 try 失败次数(卷被别的任务占住;defaultTask/uiTask
* 的喂狗与绘制路径只有这一态会发生)
* T3 `blk_timeout` = timeout>0 的分片等待超时次数(ftpTask 100 ms 分片正常退让,
* 语义与 T2 不同:那是"等了一会儿没等到",这是"压根没等")
* T4 `failopen` = 锁不存在却返 1 的次数(**谎报成功**那一态,见 wait 的函数头)
* T1「从未调用过」不占计数:三个计数全 0 就说明这条路径没跑过——区分"没跑过"与
* `acquired` 持锁态 = 真取到互斥的次数
* `try_timeout` try 超时态 = timeout=0 的 try 失败次数(卷被别的任务占住;
* defaultTask/uiTask 的喂狗与绘制路径只有这一态会发生)
* `blk_timeout` 分片退让态 = timeout>0 的分片等待超时次数(ftpTask 100 ms 分片正常
* 退让;与 try 超时态语义不同:那是"等了一会儿没等到",
* 这是"压根没等")
* `failopen` 零互斥态 = 锁不存在却返 1 的次数(**谎报成功**那一态,见 wait 函数头)
* 「未调用」这一态不占计数:三个计数全 0 就说明这条路径没跑过——区分"没跑过"与
* "跑了但一直忙"正是本卡的目的(此前两者在日志上完全一样)。
* ⚠ 本层**刻意不记时刻**:记时刻就得读 tick,等于给 defaultTask 的取锁链添一个
* callee 与几字节帧,而这条链的余量我们整天都在省着花(诊断不值那几个字节)。
@@ -141,8 +141,8 @@ test/host/vol_lock/vol_lock_test.c
| **TC-VOLLOCK-009** | fail-open 之后 `release` 无害:`osMutexRelease` **未被调用**(判空静默返回) |
| **TC-VOLLOCK-010** | 已创建时多次 `wait` **不重复创建**(`osMutexNew` 恒 1 次);每次 `wait` 各等待一次 |
| **TC-VOLLOCK-015** | 成功路径只涨 `acquired`:`try_timeout` / `blk_timeout` / `failopen` 必须仍为 `0`(异常计数不得被正常路径污染) |
| **TC-VOLLOCK-016** | `timeout=0` 未取到 → 只进 `try_timeout`,且 `blk_timeout` 与 `acquired` 保持 `0`(T2:压根没等,卷被别人占住) |
| **TC-VOLLOCK-017** | `timeout=100` 未取到 → 只进 `blk_timeout`,且 `try_timeout` 保持 `0`(T3:等了没等到;**与 T2 互不冒领**是本卡唯一"语义合并"风险) |
| **TC-VOLLOCK-016** | `timeout=0` 未取到 → 只进 `try_timeout`,且 `blk_timeout` 与 `acquired` 保持 `0`(try 超时态:压根没等,卷被别人占住) |
| **TC-VOLLOCK-017** | `timeout=100` 未取到 → 只进 `blk_timeout`,且 `try_timeout` 保持 `0`(分片退让态:等了没等到;**与 try 超时态互不冒领**是本卡唯一"语义合并"风险)。四态定义只在 `App/fs/vol_lock.h` 一处,本表只描述断言不重定义 |
| **TC-VOLLOCK-018** ⭐ | fail-open 仍返回 `1`(口径不变,同 TC-VOLLOCK-008),但 `failopen` 计数自增而 `acquired` 保持 `0`;连续两次 → `failopen == 2`(消费方按差值 latch,一个 episode 一条) |
| **TC-VOLLOCK-019** | `vol_lock_get_stats(NULL)` 无害;重复读取**不消耗证据**(计数不因读而归零) |
+13
View File
@@ -204,10 +204,23 @@
- 不要为了绿而往台账加 reason;确实该欠的债要写清楚原因(默认禁止顺手加)。
- 台账文件自身不是语料(见 §6 第 6 条),改动它之后要单跑一次确认没有自循环新账。
### 6.10 「删裸形」不等于按出现次数还债(道G task-45 实测,2026-10-27)
本仓第一次真正还第二根护栏的债(绝对数 1,162 → 1,156),**还掉的格数远少于删掉的裸形数**,机制值得写死:
1. **只有落不进任何已登记族的裸形才计不可判定**。撞进已登记族的那些本来就记在**可判定**一侧,删它们分子
分母同缩、**可判定比例反而会上升**(实测 86.74% → 86.77%)。「清了一大片、绝对数只降几格」不是算错。
2. 要真腾余量得挑**无族可归**的裸形态下手;撞族的删了只是稀释分母——与 §7.6 那根护栏防的稀释同源、方向相反。
3. 更省事的正解是**写注释与文档时一开始就别用裸形**:改用语义短语,或写全号 + 紧邻属主文档名。同日还有一
次反向教训:为「省余量」把在册族的号改成散文,那才是真把护栏当敌人(见 §7.7)。
4. 附带收益常比余量值钱:这次顺带消掉一个**同形两义**的撞号源(同一裸形既像自造状态号又撞已登记族)。
## 9. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-27 | **本仓第一次真正「还」第二根护栏的债**(道G task-45):卷锁观测计数旁自造的四位状态号(一字母+一数字的裸形写法)全量换成语义短语——`App/fs/vol_lock.c`、`App/fs/vol_lock.h`、`test/host/vol_lock/vol_lock_test.c`、`docs/测试说明/卷锁单元测试说明.md` 四处共 36 处出现清零,四态语义只留 `App/fs/vol_lock.h` 一处。改前实测不可判定绝对数 1,162(03:15),改后 1,156(03:22),余量从 4 格变 10 格;`--strict` exit 0、卷锁套件 45/45 未掉、未动 `undecidable_total_ceiling`。**顺带查清一件口径事,比这次还债本身更值得记**:绝对数只统计**落不进任何已登记族**的裸形;撞进已登记族的写法(台架/测试那一族的族模式只覆盖到该字母加一位数字的 0~3)本来就记在**可判定**一侧——于是这次 36 处里只有 6 处真正减了绝对数,另 23 处删掉是同时缩分子与分母(比值几乎不动:86.74% → 86.77%)。⇒ 「删裸形」不等于「按出现次数还债」;要腾余量得优先清**无族可归**的裸形态,而写文档时一开始就别用裸形才是正解。另注:写在模块头修改记录行里的第 7 处本来就被「变更记录行豁免」挡住不计数,所以账面是 6 不是 7。 |
| 2026-10-27 | 新增 §7.7:实测拆解「不可判定绝对数」的构成(裸形盲区 834 + 噪声白名单 326 + 真·硬不可判定 2 = 1,162,精确闭合),并写入每批自证没吃余量的五项式做法(含「不得用 stash/checkout 回退别人在飞改动」与 --update-baseline 只下调);同批撤回一条 Lead 错指令——「怕吃余量而把编号写成散文」恰好是 §7.6 这根护栏要防的稀释行为 |
| 2026-10-26 | **方案 B(同日续批)**:登记簿系 `N-x`/`S-x`/`T-x`/`A-x` 与 UI 簇 `U1~U5` 共 20 个号停发、约 418 处引用转语义短语(字面量净减 443:8,946 → 8,503);新增第二根护栏「不可判定引用绝对数上限」(`undecidable_total_ceiling`,只减不增),并把比例基线下调——理由与实测(比例跌、绝对数不涨)见 §7.6;`CHANGELOG_ROW` 补「版本号 + 日期」表头的变更记录行 |
| 2026-10-26 | **清号批次**(用户裁定:字母+数字编号太多、没人认得):全仓删除「属主已死 / 从未启用」的编号约 1,100 处,规范 §4.1 收成全活族 + §4.1.1 列已清除族,§4.2/§4.5/§7.1/§9 作废压缩。门禁口径不变(族仍登记,只拦新增引用);扫描面 / 字面量总数 / 台账 / 比例基线按本批实跑重打(见 §3、§7)。新增 §7.5 讲清"比例上升 ≠ 卫生变好"的读法 |
+8 -8
View File
@@ -32,9 +32,9 @@
* TC-VOLLOCK-014 wait(0) 的 timeout 透传为 0(defaultTask 的 try 语义)
* —— 观测计数(道G task-17「卷锁 5 态可观测性」)——
* TC-VOLLOCK-015 成功路径只涨 acquired,三个异常计数必须为 0
* TC-VOLLOCK-016 timeout=0 未取到 -> 只进 try_timeout(T2,没等)
* TC-VOLLOCK-017 timeout=100 未取到 -> 只进 blk_timeout(T3,等了没等到),
* 与 T2 互不冒领
* TC-VOLLOCK-016 timeout=0 未取到 -> 只进 try_timeout(try 超时态,压根没等)
* TC-VOLLOCK-017 timeout=100 未取到 -> 只进 blk_timeout(分片退让态,等了没等到),
* 与 try 超时态互不冒领
* TC-VOLLOCK-018 ⭐ fail-open 仍返 1(口径不变)但 failopen 计数自增并可累加
* —— 把"谎报成功"那一态变成事后可查的证据
* TC-VOLLOCK-019 观测口:NULL 无害、重复读取不消耗证据
@@ -222,8 +222,8 @@ static void tc_release(void) {
/* ===================== TC-VOLLOCK-015..019:观测计数(道G task-17) =====================
* 这一组是"5 态可观测性"的机器证据:
* T-a acquired / T2 try_timeout / T3 blk_timeout / T4 failopen 各自独立自增,
* T2 与 T3 不许互相冒领(那是本卡唯一"语义合并"风险),T4 从"谎报成功"变成可查。
* 四态 acquired / try_timeout / blk_timeout / failopen 各自独立自增(定义只在 vol_lock.h),
* try 超时态与分片退让态不许互相冒领(本卡唯一"语义合并"风险);零互斥态从"谎报成功"变成可查。
*/
static void tc_stats(void) {
vol_lock_stats_t st;
@@ -237,7 +237,7 @@ static void tc_stats(void) {
&& (st.failopen == 0u),
"TC-VOLLOCK-015b acquired only: a successful lock never inflates any anomaly counter");
/* 016 T2:timeout=0 的 try 失败 -> 只进 try_timeout */
/* 016 try 超时态:timeout=0 的 try 失败 -> 只进 try_timeout */
fakes_reset();
(void)vol_lock_init();
s_wait_ok = 0;
@@ -246,7 +246,7 @@ static void tc_stats(void) {
CHECK((st.try_timeout == 1u) && (st.blk_timeout == 0u) && (st.acquired == 0u),
"TC-VOLLOCK-016b timeout=0 miss counts as try_timeout and NOT as blk_timeout (feed point never waited)");
/* 017 T3:timeout>0 的分片等待超时 -> 只进 blk_timeout(与 T2 分道) */
/* 017 分片退让态:timeout>0 的分片等待超时 -> 只进 blk_timeout(与 try 超时态分道) */
fakes_reset();
(void)vol_lock_init();
s_wait_ok = 0;
@@ -255,7 +255,7 @@ static void tc_stats(void) {
CHECK((st.blk_timeout == 1u) && (st.try_timeout == 0u),
"TC-VOLLOCK-017b slice timeout counts as blk_timeout and NOT as try_timeout (waited-but-missed is a different fact)");
/* 018 T4 核心:锁不存在时 wait 仍返 1(TC-VOLLOCK-008 的口径),但现在必须留下
/* 018 零互斥态(本组核心):锁不存在时 wait 仍返 1(TC-VOLLOCK-008 的口径),但现在必须留下
* 可查证据 —— 这一态此前被谎报成"取到锁",整机没有任何地方能发现它 */
fakes_reset();
s_new_fail = 1;