Skip to content

Commit aa93cee

Browse files
committed
test(ui): 登记表判据按档位分流并复核不变量
`surfaceTagRegistry.test.ts` ⑪ 的二档判据从「按 `LEGACY.has(file)` 分文件」改为「按 `tier` 分档」, `REG_DUP` 的语义跟着 `(file, tier)` 键走(同文件**不同档合法**、同键才红),并给键的二元性加一条 防真空正控。由此**开出**「legacy 文件内新增一处 `<Surface>`」的合法路径:加一行 `tier:"new"` + 抬 `FROZEN_SURFACE_TAG_TOTAL` + 同步 `SURFACE_TAG_ANCHOR.entries`。 牙口逐条未松:牙 1 = `实测 − new 档登记值之和 ≤ legacy 档登记值`(且 ≠ 0);牙 2 = `实测 − legacy 档 登记值之和 == new 档登记值之和`(不是 ≤);牙 3 的 legacy 和锁**按档位**求和(旧写法在同文件两行时 会把 new 档算进 legacy 面);牙 4/5 原样。 不变量复核(改前/改后逐字段对拍差异 = 0):域内 `.ts/.tsx` 335 文件(含 T8 在飞件)· `<Surface>` 开标签 47 · 命中文件 29 · 登记行 29 · Σ 登记值 47 · legacy 9 行值之和 14 · 锚 entries 29 + `TaskConversationView.tsx` = 3。冻结常数一个未改。
1 parent 08b1afd commit aa93cee

1 file changed

Lines changed: 54 additions & 23 deletions

File tree

‎app/src/ui/primitives/surfaceTagRegistry.test.ts‎

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@
99
* 登记进逐文件表**(登记即计数),从而在保留全部防漂移牙齿的前提下允许合法增长。
1010
*
1111
* ★ 五颗牙(**登记制不是「把表抬高就完事」**;牙号与任务书逐条对应)
12-
* 牙 1 **既有文件一格都不许涨**:表里既有的 9 个文件逐文件 `实测 ≤ 登记值`(主牙齿);
13-
* 牙 2 **新条目只许给「原本不在表里」的文件 ∧ `实测 == 登记值`(不是 ≤)** —— 防「先把表抬高再看」;
12+
* 牙 1 **既有(legacy 档)面一格都不许涨**:逐文件 `实测 − 该文件 new 档登记值之和 ≤ legacy 档登记值`(主牙齿);
13+
* 牙 2 **new 档必须 `实测 − 该文件 legacy 档登记值之和 == new 档登记值之和`**(不是 ≤)—— 防「先把表抬高再看」;
1414
* 牙 3 **总数三连通**:`FROZEN_SURFACE_TAG_TOTAL == Σ 登记值 == Σ 实测`,
15-
* 且 legacy 9 行的和恒等于 `SURFACE_TAG_FROZEN_LEGACY_COUNT`(堵「降 legacy 值腾地方」),
15+
* 且 **legacy 档**(按 `tier` 筛,不按文件)的和恒等于 `SURFACE_TAG_FROZEN_LEGACY_COUNT`(堵「降 legacy 值腾地方」),
1616
* 锚(`SURFACE_TAG_ANCHOR`:条目数 + 一个具名文件的登记值与实测值)须同步;
1717
* 牙 4 **未登记文件的命中仍然 = 0**(`surfaceRatchet.test.ts` ⑤ 原样保留,本件再查一次);
1818
* 牙 5 **登记必须带理由**(`reason` ≥ 12 字,写明哪个视图 / 为什么用 `<Surface>`)**且防僵尸登记**
19-
* (登记的文件必须**真实存在**于扫描面内 ∧ **此刻仍命中 == count**)。
19+
* (登记的文件必须**真实存在**于扫描面内 ∧ **此刻仍命中 > 0**)。
20+
*
21+
* ★ T19(键 `file` → `(file, tier)`):**同键(同文件 ∧ 同档)才 `REG_DUP` 红**,同一文件**可以两行**
22+
* (`legacy` + `new` 各一行)⇒ 「legacy 文件内新增一处 `<Surface>`」有了合法路径:加一行 `tier:"new"`
23+
* (值 = 新增处数)+ 抬 `FROZEN_SURFACE_TAG_TOTAL` + 同步 `SURFACE_TAG_ANCHOR.entries`;🔴 **不许**抬
24+
* legacy 面(`SURFACE_TAG_FROZEN_LEGACY_COUNT` 之和锁当场顶红)。
25+
* 🔴 **不改窄任何牙**:牙 1/2 的分档算术是「同文件两行」下**唯一**能同时表达「legacy 只许降」与
26+
* 「new 恰等于实测」的口径;legacy 和锁**按档位**求和(旧写法 `LEGACY.has(file)` 在双行文件上会把
27+
* new 档算进 legacy 面 ⇒ 那是改键后**必须**跟着改的一处口径,不是放宽)。
2028
*
2129
* ★ 仪器:与 `surfaceRatchet.test.ts` ⑦ **共用** `surfaceScan.ts` 与 `sliceScan.ts`
2230
* (域谓词 / 剥注释 / 开标签提取都只有一份实现)—— 两件判据读的是同一把尺子。
@@ -50,7 +58,7 @@ const SURFACE_TAGS = scanSurfaceTags(FILES, textOf);
5058
* 登记表的**键** = `(file, tier)` 二元组(T19)—— `REG_DUP` / 形状断言 / 字典序断言共用**这一处**实现,
5159
* 故「键退回 `file`」这类变异会同时打到三条断言上(变异体 M1 的红点即在此)。
5260
*/
53-
const keyOf = (e: SurfaceTagRegistryEntry): string => `${e.file}|${e.tier}`;
61+
const keyOf = (e: Pick<SurfaceTagRegistryEntry, "file" | "tier">): string => `${e.file}|${e.tier}`;
5462
/** 登记表的逐**键**读出(同键只许一条 —— 重复键会让「Σ 登记值」与「逐文件比对」两套口径漂移) */
5563
const REG = new Map<string, number>();
5664
const REG_DUP: string[] = [];
@@ -59,9 +67,18 @@ for (const e of SURFACE_TAG_REGISTRY) {
5967
if (REG.has(k)) REG_DUP.push(k);
6068
REG.set(k, (REG.get(k) ?? 0) + e.count);
6169
}
62-
/** 逐文件的登记值**之和**(T19 起同一文件可有两行 ⇒ 逐文件比对必须求和,不能只取一行) */
63-
const REG_BY_FILE = new Map<string, number>();
64-
for (const e of SURFACE_TAG_REGISTRY) REG_BY_FILE.set(e.file, (REG_BY_FILE.get(e.file) ?? 0) + e.count);
70+
/**
71+
* 逐文件**按档位**聚合(T19:同一文件可以两行 ⇒ 逐文件比对必须先分档,不能只按文件求和):
72+
* `legacy` = 该文件 legacy 档登记值之和(`≤` 上限)· `next` = new 档登记值之和(**恰等于**实测减去 legacy 面)。
73+
*/
74+
interface TierAgg { readonly legacy: number; readonly next: number }
75+
const BY_FILE = new Map<string, TierAgg>();
76+
for (const e of SURFACE_TAG_REGISTRY) {
77+
const cur = BY_FILE.get(e.file) ?? { legacy: 0, next: 0 };
78+
BY_FILE.set(e.file, e.tier === "legacy"
79+
? { legacy: cur.legacy + e.count, next: cur.next }
80+
: { legacy: cur.legacy, next: cur.next + e.count });
81+
}
6582
/** legacy **档**的文件集(判定一律看 `e.tier`;此处只用于「legacy 集合非空」这条非真空自证) */
6683
const LEGACY = new Set(SURFACE_TAG_REGISTRY.filter((e) => e.tier === "legacy").map((e) => e.file));
6784
/** 实测逐文件读出(0 命中的文件不进 `SURFACE_TAGS` ⇒ 显式补 0,好让「表里有、实测无」被抓住) */
@@ -81,6 +98,16 @@ describe("⑪ `<Surface>` 新增调用点登记制(T15a 判据修正:允许
8198
expect(keys, "登记表的键(`file|tier`)必须按字典序(与 FROZEN_*_BY_FILE 同范式)").toEqual([...keys].sort());
8299
expect(SURFACE_TAG_REGISTRY.length, "登记表空了 ⇒ 本组判据空真").toBeGreaterThan(0);
83100
expect(LEGACY.size, "legacy 集合为空 ⇒ 牙 1 与 legacy 和锁都会空真").toBeGreaterThan(0);
101+
// 🅿️ 正控(防真空):键**确实是二元组** —— 同 `file` 不同 `tier` 必须给出**两个**不同的键。
102+
// 把 `keyOf` 变异回 `e.file` ⇒ 这两行折叠成 1 ⇒ 本行红 ⇒ 钉住「`REG_DUP` 的语义跟着键走」
103+
// (改键前 `REG_DUP` 判的是同 `file`;改键后判的是同 `(file, tier)`)。
104+
expect(
105+
new Set([
106+
keyOf({ file: "views/zz-key-probe.tsx", tier: "legacy" }),
107+
keyOf({ file: "views/zz-key-probe.tsx", tier: "new" }),
108+
]).size,
109+
"同 file 不同 tier 被折叠成同一个键 ⇒ 键退回了 `file`(改键失效)",
110+
).toBe(2);
84111
});
85112

86113
it("牙 1/4/5b:既有文件不许涨 · 未登记文件的命中 = 0 · 登记过的防僵尸(实测 > 登记 与 实测 = 0 都报错)", () => {
@@ -90,16 +117,18 @@ describe("⑪ `<Surface>` 新增调用点登记制(T15a 判据修正:允许
90117
const zombie: string[] = [];
91118
const overflow: string[] = [];
92119
for (const [file, n] of measured) {
93-
const reg = REG_BY_FILE.get(file);
120+
const agg = BY_FILE.get(file);
94121
// 牙 4:未登记文件的命中仍为 0
95-
if (reg === undefined) { zombie.push(`${file}: 实测 ${n} 处,**未登记**(新调用点请登记:登记 + 抬高总数 + 同步锚)`); continue; }
96-
// 牙 1:既有文件一格都不许涨(新登记行由下一用例的「恰等于实测」管)
97-
if (reg < n) overflow.push(`${file}: 登记 ${reg} 处,实测 ${n} 处 ⇒ 登记值被超(legacy 只许降 / 新登记必须恰等于实测)`);
122+
if (agg === undefined) { zombie.push(`${file}: 实测 ${n} 处,**未登记**(新调用点请登记:登记 + 抬高总数 + 同步锚)`); continue; }
123+
// 牙 1:**两档之和**是实测的紧上界(新登记行由下一用例的「恰等于实测」管)
124+
if (agg.legacy + agg.next < n) {
125+
overflow.push(`${file}: 登记 ${agg.legacy + agg.next} 处,实测 ${n} 处 ⇒ 登记值被超(legacy 只许降 / 新登记必须恰等于实测)`);
126+
}
98127
}
99128
// 牙 5b:登记的文件此刻必须仍命中(防僵尸登记)
100-
for (const [file, reg] of REG_BY_FILE) {
129+
for (const [file, agg] of BY_FILE) {
101130
const n = hitsOf(file);
102-
if (n === 0) zombie.push(`${file}: 登记 ${reg} 处,实测 0 处 ⇒ 僵尸登记(迁走了却还挂着)`);
131+
if (n === 0) zombie.push(`${file}: 登记 ${agg.legacy + agg.next} 处,实测 0 处 ⇒ 僵尸登记(迁走了却还挂着)`);
103132
}
104133
expect(zombie, `未登记 / 僵尸:\n${zombie.join("\n")}`).toEqual([]);
105134
expect(overflow, `登记值被超:\n${overflow.join("\n")}`).toEqual([]);
@@ -120,15 +149,17 @@ describe("⑪ `<Surface>` 新增调用点登记制(T15a 判据修正:允许
120149
SURFACE_TAG_FROZEN_LEGACY_COUNT,
121150
);
122151
const bad: string[] = [];
123-
for (const e of SURFACE_TAG_REGISTRY) {
124-
const n = hitsOf(e.file);
125-
if (e.tier === "legacy") {
126-
// 牙 1:legacy 行 = ≤ 上限(只许降)
127-
if (n > e.count) bad.push(`${e.file}: legacy 实测 ${n} > 登记 ${e.count} ⇒ 既有文件不许涨(牙 1)`);
128-
if (n === 0) bad.push(`${e.file}: legacy 实测 0 ⇒ 已迁空,请从登记表收紧(legacy 行只许降,降完手工同步总和)`);
129-
} else if (n !== e.count) {
130-
// 牙 2:新登记行 = **恰等于**实测(不是 ≤)
131-
bad.push(`${e.file}: 新登记 ${e.count},实测 ${n} ⇒ 新登记必须**恰等于**实测(防「先把表抬高再看」)`);
152+
for (const [file, agg] of BY_FILE) {
153+
const n = hitsOf(file);
154+
if (agg.legacy > 0) {
155+
// 牙 1:legacy 面 = 实测**减去**该文件 new 档登记值之和 ⇒ `≤` 上限(只许降)
156+
const legacyPart = n - agg.next;
157+
if (legacyPart > agg.legacy) bad.push(`${file}: legacy 实测 ${legacyPart} > 登记 ${agg.legacy} ⇒ 既有文件不许涨(牙 1)`);
158+
if (legacyPart === 0) bad.push(`${file}: legacy 实测 0 ⇒ 已迁空,请从登记表收紧(legacy 行只许降,降完手工同步总和)`);
159+
}
160+
if (agg.next > 0 && n - agg.legacy !== agg.next) {
161+
// 牙 2:new 档 = **恰等于**实测减去 legacy 档登记值之和(不是 ≤)
162+
bad.push(`${file}: 新登记 ${agg.next},实测 ${n} ⇒ 新登记必须**恰等于**实测(防「先把表抬高再看」)`);
132163
}
133164
}
134165
expect(bad, `登记表与实测不符:\n${bad.join("\n")}`).toEqual([]);

0 commit comments

Comments
 (0)