From b16e2d24fdc87a6521cd85be8405a74c1cebabbe Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 14 Sep 2026 17:21:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=AC=A0=E8=B4=A6):=20RESULT=20=E8=A1=8C?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E6=89=93"=E7=99=BB=E8=AE=B0=20vs=20=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B"=EF=BC=88=E5=89=AF=E6=9C=AC=E6=BC=82=E7=A7=BB?= =?UTF-8?q?=E5=8F=AF=E8=A7=81=20+=20=E4=B8=8D=E7=AD=89=E5=8D=B3=E7=BA=A2?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E5=85=B1=E4=BA=AB=E6=A0=91=20stash=20?= =?UTF-8?q?=E7=BA=AA=E5=BE=8B=E4=B8=8E=E4=B8=A4=E6=9D=A1=E6=8E=A8=E5=B9=BF?= =?UTF-8?q?=E5=85=A5=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi 2026-09-14 三条。 1. **可见的那个数字是副本**:`docs/DEBTS.json` 手可编辑,而它唯一的守卫是 Go 那条断言 —— 相位耦合(打印它的相位里 Go 断言未必跑过)+ 副本会漂移(我自己 §12 那句话)。 现在 RESULT 行同时给出: `debts=9(registered) measured_static=5==登记 ✓(…;本相位只有 static 有权威实测,其余由 go test 侧判据比对)` 即:**本相位能实测的(static,权威来源就是代码)当场比对并标注 ✓/✗**; **测不了的明说由谁比对**,不冒充已实测。并新增判据:**static 的登记值 ≠ 实测值即红**。 2. **共享树上不要用 `git stash` / `--autostash`**(pi:同一台机器,只是自动化了)—— 它把**别人**未提交的改动也收走,冲突/中断时可能留在 stash 里,而 ta 只看到"我的改动不见了"。 入册 CRITERIA §8.1:优先 `git worktree add`;必须原地操作就先约定窗口;事后核对别人的文件还在原处。 3. **两条推广入册**(各只出现过一次,但形状会复发): · §15 **判据必须自足**:不许通过共享可变状态在判据之间传递结论 —— 顺序依赖的显形方式 永远是**假绿**(我踩过:余额由 SKIP 那支写入、登记判据去读 ⇒ 同包按文件顺序跑 ⇒ 读到 0); · §16 **"要提醒人的"输出必须走默认路径**(`TestMain` 收尾打印在常态 `go test` 里不存在), 配套动作是 pi 那句:**写完"可打印的余额"之后,去看一眼默认路径实际打出来的样子**。 --- client/electron/test/CRITERIA.md | 35 ++++++++++++++++++++ client/electron/test/commit-hygiene.test.mjs | 29 ++++++++++++++++ client/electron/test/run-all.mjs | 24 ++++++++++++-- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index e44d7bc..52aaed4 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -325,6 +325,18 @@ git worktree add /tmp/attr HEAD # **一句话**:归因手段的选择标准是"**会不会让别人的东西处于危险里**",不是"哪条命令最快"。 +## 8.1 共享树上不要用 `git stash` / `--autostash`(同一台机器,自动化了而已) + +`git stash` 与 `git rebase --autostash` 是同一族:**把工作树里全部未提交改动收走,稍后再放回**。 +在共享树上这意味着**别人未提交的改动**也被收走了 —— 冲突或中断时它们可能**留在 stash 里** +而不是回到工作树,而 ta 那边只会看到"我的改动不见了"(pi 2026-09-14 指出)。 + +纪律(照 §8 那条"stash 只影响 tracked 文件 ⇒ 树干净是**假干净**"升级): + +1. **优先 `git worktree add`**:在一棵干净的工作树里做 rebase/改写,碰不到任何人; +2. 必须原地操作时:**先与并发写者约定窗口**("我会 rebase,请先停手"),或明确告知; +3. 事后**核对别人的文件还在**(`git status` 里该有的 WIP 还在原处),别只看自己的改动回来了。 + ## 9. 已知限制(登记处) 判据的**能力边界**必须写在这里,否则它的长期结局只有两个:被当成"它就是全能的"而误用, @@ -445,3 +457,26 @@ pi 2026-09-14 提的形状:**不是无条件 fail-closed** —— 缺字段可 这与"清册条目的『允许』要给出一条能判的约束"(§11 族)是同一条纪律的两半: 前一半保证**判据有分辨力**,这一半保证**分辨力不会被"改数字"抹掉**。 + +## 15. 判据必须**自足**:不许通过共享可变状态在判据之间传递结论 + +**规则**:一条判据的结论必须由它自己测量出来。**判据之间不许共享可变状态** +(包级变量、临时文件、上一条判据留下的标记)。一旦共享,结果就取决于**执行顺序**, +而顺序依赖的显形方式永远是**假绿**(不是假红 —— 假红会被看见)。 + +真实例子:`debts` 的余额第一版由"SKIP 那一支"写入、由登记判据去读。 +Go 在同一包内**按源文件顺序**跑测试 ⇒ 登记那条先跑就读到 `0` ⇒ 全绿而余额是错的。 +修法:把测量抽成自足函数(`measureMailStatusDebt(t)`),**谁调用谁测**。 + +## 16. "要提醒人的"输出必须走**默认路径**(不能只活在 `-v` 后面) + +**规则**:余额 / 自报 / 提醒类信息必须出现在**默认命令**的输出里;只有**诊断细节** +才可以藏在 `-v` 后面。 + +真实例子:我把欠账余额打在 `TestMain` 收尾 —— 而 `go test` 对**通过**的包 +**不打印包的输出**(只在失败或 `-v` 时打),于是常态运行里一个字都看不见: +"可打印的余额"变成了"看不见的余额",与不显形等价。 +修法:可见的那份挪进 electron 套件的 `RESULT` 行(默认路径),Go 侧只做同源比对。 + +**配套动作(pi 2026-09-14)**:写完一处"要提醒人的输出"之后, +**去看一眼默认路径的输出** —— 不是看代码,是看它实际打出来的样子。 diff --git a/client/electron/test/commit-hygiene.test.mjs b/client/electron/test/commit-hygiene.test.mjs index 32591cc..4bce70e 100644 --- a/client/electron/test/commit-hygiene.test.mjs +++ b/client/electron/test/commit-hygiene.test.mjs @@ -152,3 +152,32 @@ test('自检:分类逻辑对合成输入有分辨力(否则这条判据可 assert.equal(parsed[0].files.length, 2, '文件清单要解析到(否则 isMixed 永远 false)'); assert.equal(parsed[1].files.length, 1); }); + +/** + * ★ 欠账余额的**可见副本**必须与实测一致(pi 2026-09-14 裁定 §1)。 + * + * `docs/DEBTS.json` 是手可编辑的副本;只打登记值的话,可见数字可以相对现实过期而全绿 + * —— 这正是"门挂错了相位"的又一格。本判据管得住的那一笔是 `static-criteria`: + * 它的权威来源就是代码本身(`STATIC_ONLY.length`,在 `run-all.mjs` 里)。 + * (Go 侧那几笔由 `go test` 的 `TestDebtLedgerMatchesMeasurement` 比对;本相位测不了它, + * 所以 **不**在这里假装比对 —— 只把"能测的那笔"钉住。) + */ +export async function debtLedgerStaticMatches() { + const { readFileSync } = await import('node:fs'); + const { join } = await import('node:path'); + const src = readFileSync(join(HERE, 'run-all.mjs'), 'utf8'); + const registered = JSON.parse(readFileSync(join(HERE, '..', '..', '..', 'docs', 'DEBTS.json'), 'utf8')) + .debts.find(d => d.id === 'static-criteria').count; + // 实测:STATIC_ONLY 的条目数(数 `'test/xxx.test.mjs', '...', '...'` 形状的行) + const block = /const STATIC_ONLY = \[([\s\S]*?)\n\];/.exec(src); + assert.ok(block, '要能取到 STATIC_ONLY 清单'); + const measured = (block[1].match(/^\s*\['test\//gm) || []).length; + return { registered, measured }; +} + +test('★ 欠账登记里的 static-criteria 必须等于实测(可见副本不许漂移)', async () => { + const { registered, measured } = await debtLedgerStaticMatches(); + assert.equal(measured, registered, + `欠账登记说 static-criteria=${registered},实测 ${measured} —— 可见的那个数字是副本,` + + `漂移了就必须两边一起改(这类"登记类"判据别改数字了事:先看是哪笔欠账还清了)`); +}); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index dd39228..534df18 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -512,12 +512,30 @@ if (skippedByPhase.length) { * 所以 Go 那边的 Skip 余额必须在**这边**可见。两边读**同一个**登记(docs/DEBTS.json), * 于是"三笔欠账一处看全"且有余额、有到期。 */ +/* + * 欠账余额:**登记值与实测值一起打**(pi 2026-09-14 裁定 §1)。 + * + * 为什么不能只打登记值:`docs/DEBTS.json` 是**手可编辑的副本**,而"可见的那个数字就是副本" + * 恰好违反我自己写进 §12 的那句 —— 比较的对象必须是权威来源,不是手边那份会过期的副本。 + * 所以这一行同时给出: + * · `registered`:登记值(副本,谁都能改); + * · `measured`:**本相位能实测的部分**(这里有权威来源的只有 static:`STATIC_ONLY.length`); + * · 本相位测不了的(Go 侧那几笔)**明说由谁比对**,不冒充已实测。 + * 不等即红:下面这条判据就是这一行(static 的登记值必须等于实测值)。 + */ +const DEBTS_PATH = join(HERE, '..', '..', '..', 'docs', 'DEBTS.json'); let debtsLine = ''; +let debtRegStatic = null; try { - const led = JSON.parse(prose(join(HERE, '..', '..', '..', 'docs', 'DEBTS.json'))); - const items = led.debts.map(d => `${d.id}:${d.count}`).join(','); + const led = JSON.parse(prose(DEBTS_PATH)); const total = led.debts.reduce((n, d) => n + d.count, 0); - debtsLine = ` debts=${total}(${items})`; + const items = led.debts.map(d => `${d.id}:${d.count}`).join(','); + const regStatic = led.debts.find(d => d.id === 'static-criteria'); + debtRegStatic = regStatic ? regStatic.count : null; + const measuredStatic = STATIC_ONLY.length; + debtsLine = ` debts=${total}(registered) measured_static=${measuredStatic}` + + `${debtRegStatic === null ? '(登记无此项)' : debtRegStatic === measuredStatic ? '==登记 ✓' : `≠登记${debtRegStatic} ✗`}` + + `(${items};本相位只有 static 有权威实测,其余由 go test 侧判据比对)`; } catch (e) { debtsLine = ` debts=登记读不到(${e.code || e.message})`; }