From 1c3335f0c9dd37e234155c477ec69a560b3b9d72 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 20 Sep 2026 05:22:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E7=AC=AC=E4=BA=94?= =?UTF-8?q?=E4=B8=AA=E8=90=BD=E7=82=B9=20=E2=80=94=E2=80=94=20=E5=88=A4?= =?UTF-8?q?=E6=8D=AE=E5=86=99=E5=AF=B9=E4=BA=86=EF=BC=8C=E5=8D=B4**?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E4=BB=BB=E4=BD=95=E4=BA=BA=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=AE=83**=EF=BC=88check-file-modes.sh=20=E7=A9=BA=E8=BD=AC?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi `4fc16f66` 报的是 rc=2 那半个缝(M18)—— 我复核后**已经在 `e99a657` 修好了**, 而它的**父提交正是 pi 报信时读的 `6ee9902`**。又是同一形状的竞态。 但照 pi §四 那句「**每条分支的前提本身也要能被构造**」做审计时, 我发现**第五个落点,而且这一格的判据本身写得完全正确**: `deploy/check-file-modes.sh` = 源文件权限政策的**唯一**判据。 它甚至专门修过自己那份 `[ -x ]` 恒真的 root 陷阱(`b7dc9e9`),改成直接读权限位 —— 修得对。 **但它从来没有被任何东西执行过。** 全仓 `grep -rn "check-file-modes"` 只有: · `check-deploy-drift.mjs` 3 处**注释**(谈分工) · `run-all.mjs:951` **注释**(讲 root 陷阱) · `summary.py:77` **注释**,且**说法与实现不符** · `summary.py:431` 一句 `print(...)` 的**文案** ⇒ 唯一一处非注释提及是一句 print 的文案。没有 install.sh / redeploy-* / hook / CI / cron 调它。 **而它当时正红着**:2 个文件是 600 + `jobs/` 缺属主 x 位。 ★ 为什么这一类**只能靠判据守**(实测): `git status` 看不见、`git diff` 看不见、`git ls-files -s` **只记 100644/100755** (组/其他读位**不进版本库**)⇒ 把受跟踪文件 `chmod 600 ↔ 644` 两次 `git status` 都空。 套件也看不见(`jobs/` 缺 x 位时实测 `diag=none`、`ran=48`,全绿)⇒ **没有第二条通道**。 0600 在"跑的人恰好是属主"时不炸,换身份就是 EACCES,而那串报错**看起来像代码问题**。 修法: · 接进 `deploy/install.sh`(check-shared-libs 之后),**走 `--check` 累积通道** (照既有 npm_rc/CHECK_GATE_RC)。⚠️ 我第一版**直接调**,而门禁红时它 `exit 1`、 install.sh 是 `set -e` ⇒ 当场吞掉后面所有诊断:**实测**接线后 `构建 Gateway` 在日志里命中 **0 次**(接线前 2 次)—— 正是 install.sh:141-157 刚修过的同一个毛病, 我自己又造了一遍。改累积后回到 2 次,门禁报 `[FAIL] 权限政策没过(退出码 1)`。 · 修那两个文件:600 → 644 ⇒ 权限政策现在绿(rc=0)。 · 新增判据 `criteria-hygiene` 第 7 条:政策门禁必须被入口脚本在**可执行位置**调用。 ★★ 变异验证时**我自己先假绿了一次**(单记):第一版用 `code()` 剥注释,而 `lib/read.mjs` 的 `stripComments()` 只认 `//` 与 `/* */` —— 那是 **JS** 的注释, **`install.sh` 是 shell,注释是 `#`** ⇒ 对 shell 文件**原样返回** ⇒ 判据读到了 **我写在它上面那段解释里的** `` # `check-file-modes.sh` 红时 `exit 1` ``。 变异①(接线整段删掉)⇒ 第一版 **rc=0(假绿)**;自剥 shell `#` 后 **rc=1** ✓ 变异②(只在 echo 里提一句)⇒ rc=1 ✓ 变异③(gates 恒空 ∀x∈∅)⇒ rc=1(`只找到 0 个`)✓ (③ 第一次 python 锚点没匹配上、变异没施加,我重测并**先打印"变异已施加"**才算数。) ★ 射程如实标出:只管 `deploy/check-*.sh`(政策门禁族);**不管** `check-deploy-drift.mjs` 这类**按需手动工具**(自带 `--self-check`、文档写明"事后自查")——要求它进入口是**错的**。 它实测同样 0 处调用,但**本判据不覆盖它,也没修它**。判"有没有接线",不判"接得对不对"。 ★ 通用纪律(CRITERIA.md §16.1.2):**判据自己也要能"被证明它真的在看代码"。** 若判据读的**语言**与 stripComments 实现的**语言**不一致(shell vs JS、Python vs JS), 那"剥注释"是**假的** ⇒ 判据消费散文,而**变异验证是唯一能戳破它的东西**。 全套 checks=516→**517** pass=512 fail=5 red=5 broken=0 verdict=red(5 条红都是别的会话的); mutants=48 ran=48 skipped=0 diag=none baseline=7/7✓;5 个自检各 rc=0。 --- client/electron/test/CRITERIA.md | 85 +++++++++++++ .../electron/test/criteria-hygiene.test.mjs | 113 ++++++++++++++++++ client/electron/test/run-all.mjs | 2 +- deploy/install.sh | 39 ++++++ 4 files changed, 238 insertions(+), 1 deletion(-) diff --git a/client/electron/test/CRITERIA.md b/client/electron/test/CRITERIA.md index 7fc4865..5642c4e 100644 --- a/client/electron/test/CRITERIA.md +++ b/client/electron/test/CRITERIA.md @@ -969,6 +969,91 @@ pi 报的第 1–3 例都修好之后,我顺着同一条线**审计全部 61 > 同族的第三个自查问(pi 归纳):"守着它的判据,它自己的样本由谁守"; > 这一条是它的**镜像**:"**被守的那个东西没挂上时,谁来说**"。 +#### 16.1.2 ★★ 第五个落点:**判据写对了,却没有任何人执行它**(dsh 2026-09-19) + +同一个形状的又一格,而且这一格的判据**本身写得完全正确**: + +`deploy/check-file-modes.sh` = **源文件权限政策的唯一判据** +("受跟踪文件不得比 0644 更严;可执行与否按 `git ls-files -s` 记的那位判")。 +它甚至专门修过**自己那份 `[ -x ]` 恒真的 root 陷阱**(`b7dc9e9`),改成直接读权限位 —— 修得对。 + +**但它从来没有被任何东西执行过。** 全仓 `grep -rn "check-file-modes"` 的结果: + +``` +deploy/check-deploy-drift.mjs:147,149,442 ← 都在注释里谈"分工" +client/electron/test/run-all.mjs:951 ← 注释(讲 root 陷阱) +client/electron/test/mutants/summary.py:77 ← 注释,且**说法与实现不符** +client/electron/test/mutants/summary.py:431 ← 一句 print(...) 的**文案** +``` + +⇒ **唯一一处非注释提及是一句 `print` 的文案。** 没有任何 `install.sh` / `redeploy-*.sh` / +hook / CI / cron 调用它。**而它当时正红着**: + +``` +[FAIL] 权限过严:docs/DSH-0.1.5-MAIL-CHANNEL-ROOTCAUSE.md 是 600 +[FAIL] 权限过严:scripts/repair-legacy-spliced-ids.mjs 是 600 +[FAIL] 目录不可进入:client/electron/test/mutants/jobs(drw-r--r--,缺属主 x 位) +``` + +★ **为什么这一类只能靠判据守、不能指望"顺手看见"**(实测): + +| 手段 | 能否发现 600 vs 644 | +|---|---| +| `git status` | **不能**(两次都空) | +| `git diff` | **不能** | +| `git ls-files -s` | **只记 `100644`/`100755`** ⇒ 组/其他读位**不进版本库** | +| 套件(`jobs/` 缺 x 位时) | **不能**(实测 `diag=none`、`ran=48`,全绿) | + +⇒ 这一类**没有第二条通道**。0600 在"跑的人恰好是属主"时不炸,一旦换身份(`install.sh` +自己会切身份判可写性;套件里还有**以 `nobody` 降权跑**的判据)就是 EACCES, +而那串报错**看起来像代码问题**。 + +**`summary.py:77` 那句"那个只在部署时跑"是实现与说法不符**(实际从没跑过)—— +按本仓口径:**说法与实现不一致 ⇒ 以实现为准**,故以实现(从没跑)为准并把它接上线。 + +**修法**:接进 `deploy/install.sh`(`check-shared-libs.sh` 之后)。 +⚠️ **接线必须走 `--check` 的累积通道**(照既有 `npm_rc`/`CHECK_GATE_RC`): +门禁红时它 `exit 1`,而 `install.sh` 是 `set -e` ⇒ 直接调会**在那里中止**、 +后面 `build-stamp`/钩子/收尾诊断**一行都不打**。 +**实测(我第一版就是直接调的)**:接线后 `构建 Gateway` 在日志里命中 **0 次** +(接线前 2 次)—— 正是 `install.sh:141-157` 刚修过的同一个毛病,我自己又造了一遍。 +改成累积通道后 `构建 Gateway` 回到 2 次,门禁报 `[FAIL] 权限政策没过(退出码 1)`。 + +**新增判据**(`criteria-hygiene.test.mjs`,第 7 条):政策门禁(`deploy/check-*.sh`) +必须至少被一个入口脚本在**可执行位置**调用。 + +★ **射程如实标出**: +- 只管 `deploy/check-*.sh`(政策门禁那一族);**不管** `check-deploy-drift.mjs` + 这类**按需手动工具**(自带 `--self-check`、文档写明是"事后自查")—— + 要求它也进入口是**错的**(把工具当门禁,然后逼人硬挂上去)。 +- 判"**有没有**接线",**不判"接得对不对"**(挂累积通道是另一条纪律)。 +- `check-deploy-drift.mjs` 同样不在任何入口被调用(实测 0 处), + 但它是**工具**不是门禁 ⇒ **本判据不覆盖它,也没修它**。 + +★★ **变异验证时我自己先假绿了一次(值得单记)**: + +第一版用 `code()`(剥注释)读入口脚本 —— 而 `lib/read.mjs` 的 `stripComments()` +只认 `//` 与 `/* */`,那是 **JS** 的注释,**`install.sh` 是 shell,注释是 `#`** ⇒ +对 shell 文件它**原样返回**。于是判据读到了**我写在它上面那段解释里的** +`` # `check-file-modes.sh` 红时 `exit 1` `` ⇒ **变异验证①(把接线整段删掉)判据仍绿**。 + +| 变异 | 第一版(用 `code()`) | 修后(自剥 shell `#`) | +|---|---|---| +| ① 接线拆掉 | **rc=0(假绿)** | **rc=1** ✓ | +| ② 只在 `echo` 里提一句 | — | **rc=1** ✓ | +| ③ gates 恒空(∀x∈∅) | — | **rc=1**(`只找到 0 个`)✓ | + +⇒ 修法:自己剥 shell 注释(行首/空白后的 `#`;**故意不碰 `${VAR#pat}`**), +并抹掉 `echo`/`printf` 开头的散文行。**已知限制**:引号内的 `#` 会被当注释起点, +方向是**假红**(漏掉接线),不是假绿 —— 假红当场看得见。 + +> **纪律(这一格的通用形式):判据自己也要能"被证明它真的在看代码"。** +> 一条"在源码里找某个写法"的判据,若它**读的那种语言**和 +> `stripComments` **实现的那种语言**不一致(shell vs JS、Python vs JS、模板 vs 源码), +> 那么"剥注释"是**假的** ⇒ 判据会**消费散文**,而**变异验证是唯一能戳破它的东西**。 +> 本仓 `stripComments` 自己那段注释早警告过"判据开始消费散文", +> 但警告的是**字符串**,**没覆盖"另一种语言的注释"** —— 这一格是那个洞的实例。 + ### 16.2 ★★ **同一优先级写了两遍 ⇒ 两个顺序**:不变式只在**对角线**上被验过(pi + dsh 2026-09-18) §16.1 讲"结论到不了决定颜色的那一格"。这一节是它的**镜像**: diff --git a/client/electron/test/criteria-hygiene.test.mjs b/client/electron/test/criteria-hygiene.test.mjs index cf10124..a32cd9f 100644 --- a/client/electron/test/criteria-hygiene.test.mjs +++ b/client/electron/test/criteria-hygiene.test.mjs @@ -585,3 +585,116 @@ test('★ AGC 真身从未进过远端历史(泄露之后响的闹钟,不是 + ' 3) 查清是哪次 push 绕过了 `.githooks/pre-push`(`--no-verify`?没装的机器?)。\n' + ` 命中:\n ${published.join('\n ')}`); }); + +/* + * ★★ 政策门禁不许"写了但没人跑"(dsh 2026-09-19)。 + * + * 形状(这是本仓第 5 次同一个形状,前 4 次见 `CRITERIA.md §16`): + * `deploy/check-file-modes.sh` 是**源文件权限政策的唯一判据**, + * 逻辑写得对(`b7dc9e9` 还专门修过它自己那份 `[ -x ]` 恒真的 root 陷阱), + * **但全仓没有任何地方执行它** —— 唯一的非注释提及是 `mutants/summary.py` + * 里一句 `print(...)` 的**文案**。而它当时**正红着**(2 个文件是 600、 + * `jobs/` 缺属主 x 位)。判据在,但走不到。 + * + * 为什么这一类**只能靠判据守**、不能指望"顺手发现": + * `git ls-files -s` 只记 **100644 / 100755**(只含可执行位), + * **组/其他读位不进版本库** ⇒ 实测把受跟踪文件 `chmod 600 ↔ 644`, + * `git status` 两次都空、`git diff` 也不动。没有任何"顺带"的通道。 + * + * 判什么:**宣称是"政策/门禁"的 `deploy/check-*.sh`,必须至少被一个入口脚本 + * (`install.sh` / `redeploy-*.sh`)在**可执行位置**调用**。 + * + * ★ 射程如实标出(别让它冒充比它更宽的东西): + * · 只管 `deploy/check-*.sh`(**政策门禁那一族**),**不管** `check-deploy-drift.mjs` + * 这类**按需手动工具** —— 后者自带 `--self-check`、文档里明确是"事后自查", + * 要求它也进入口是**错的**(会把工具当门禁,然后逼人把它挂上去)。 + * · 判的是"**有没有**接线",**不判"接得对不对"**(挂在 `--check` 累积通道里 + * 而不是直接调,是另一条纪律,由 `install.sh` 自己的注释与 141-157 行守着)。 + * · 数的是**可执行位置**的出现,所以**注释里提到不算**(本仓纪律: + * "注释说明禁令 ≠ 违反禁令")—— 用 `code()` 剥注释,与上一条同形。 + */ +test('★ 政策门禁(deploy/check-*.sh)必须被入口脚本真正调用,不许"判据在但走不到"', () => { + const DEPLOY = join(REPO_ROOT, 'deploy'); + const ENTRIES = ['install.sh', 'redeploy-gateway.sh', 'redeploy-plugin.sh']; + + /* + * ★★ 为什么**不能**直接用 `code()`(本判据第一版就是这么写的,**假绿了**): + * + * `lib/read.mjs` 的 `stripComments()` 只认 `//` 与 `/* *\/` —— 那是 **JS** 的注释。 + * `install.sh` 是 **shell**,它的注释是 `#`。于是 `code()` 对 shell 文件**原样返回**, + * 包括我写在它上面那段解释里的一句 + * `# \`check-file-modes.sh\` 红时 \`exit 1\`,而本脚本是 \`set -e\`` + * ⇒ 变异验证(把接线整段删掉)**判据仍然绿** —— 它读到的是**我自己的解释文案**。 + * 实测: + * 变异①(接线拆掉)⇒ 期望红,实际 **rc=0(绿)** + * `code(install.sh).includes('check-file-modes.sh')` ⇒ **true**,命中的是 `#` 开头那行 + * 这正是本仓那条纪律的又一次现身("注释说明禁令 ≠ 违反禁令"), + * 也是 `stripComments` 那段注释自己警告过的形状:"判据开始消费散文"。 + * + * ⇒ 自己剥 shell 注释。**已知限制,方向如实标出**: + * · 只处理"行首(可带空白)的 `#`"与"空白后的 ` #`"; + * `${VAR#pat}` 这种**没有空格**的写法因此**不会**被误当注释(正是想要的)。 + * · 引号内的 `#`(`echo "a # b"`)会被当成注释起点 ⇒ 该行后半被截掉。 + * 对本判据**方向是安全的**:截掉只会**漏掉**接线(假红), + * 不会**凭空造出**接线(假绿)——而假红是会被人当场看见的那一侧。 + */ + const stripShellComments = (src) => src.split('\n').map((line) => { + const t = line.trimStart(); + if (t.startsWith('#')) return ''; + const m = line.match(/(^|\s)#/); // 行首或空白后的 #(避开 ${VAR#pat}) + return m ? line.slice(0, m.index) : line; + }).join('\n'); + + /* + * ★ 还要排掉"散文行":真接线长这样(命令位置) + * "$REPO/deploy/check-shared-libs.sh" + * bash "$REPO/deploy/check-sandbox.sh" ... + * 而散文长这样 + * echo "药方:跑 deploy/check-file-modes.sh" + * ⇒ 抹掉 `echo`/`printf` 开头的行。**不做字符串剥离** —— + * 真接线里文件名**就在引号内**(`"$REPO/deploy/x.sh"`), + * 剥字符串会把真接线一起剥掉(那是把判据弄瞎,不是弄准)。 + */ + const entryCode = ENTRIES + .map((e) => ({ e, src: stripShellComments(code(join(DEPLOY, e))) })) + .filter(({ src }) => src.length > 0) + .map(({ e, src }) => ({ + e, + src: src.split('\n') + .filter((l) => !/^\s*(echo|printf)\b/.test(l)) + .join('\n'), + })); + + /* + * ★ 反空真护栏:本判据的结论是"每个门禁都有人调" —— + * 如果 `deploy/check-*.sh` 一个都没列出来(路径写错、目录挪了), + * `∀x∈∅` 会让它**看起来和真判据一样绿**。本仓纪律:"空真看起来和真判据一样绿"。 + */ + const gates = readdirSync(DEPLOY) + .filter((n) => /^check-.*\.sh$/.test(n)) + .sort(); + assert.ok(gates.length >= 3, + `只找到 ${gates.length} 个 deploy/check-*.sh(期望 ≥3)—— 大概率是路径/目录变了,` + + '而不是"门禁真的变少了"。空真必须报红,不许静默通过。'); + + assert.ok(entryCode.length >= 2, + `只读到 ${entryCode.length} 个入口脚本(期望 ≥2)—— 同上,反空真。`); + + const unwired = []; + for (const g of gates) { + // 在**入口脚本的代码里**找这个文件名(含 `$REPO/deploy/` 前缀那种写法) + const called = entryCode.some(({ src }) => src.includes(g)); + if (!called) unwired.push(g); + } + + assert.deepEqual(unwired, [], + `这些政策门禁**写了却没有任何入口脚本调用**(判据在,但走不到):\n` + + ` ${unwired.join('\n ')}\n` + + ' 为什么这条要紧(实测形状):`deploy/check-file-modes.sh` 就这样空转了 ——\n' + + ' 它的权限政策判得**完全正确**,而它当时**正红着**(2 个文件 600 + `jobs/` 缺 x),\n' + + ' 全仓唯一的非注释提及是另一个文件里一句 `print(...)` 的文案。\n' + + ' ⇒ 药方:在 `deploy/install.sh` 里调用它。\n' + + ' ⚠️ 挂在 `--check` 下要**走累积通道**(照 `npm_rc`/`CHECK_GATE_RC` 的既有做法),\n' + + ' **不能直接调** —— 门禁红时它 `exit 1`,而 `install.sh` 是 `set -e`,\n' + + ' 会在那里中止、后面所有诊断一行都不打(`install.sh` 141-157 行刚修过同一个毛病)。'); +}); diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 99b1fde..f7814b2 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -112,7 +112,7 @@ const SUITE = [ ['test/debt-visibility.test.mjs', [], 1], ['test/commit-hygiene.test.mjs', ['--experimental-strip-types', '--no-warnings'], 4], // 判据目录自身的卫生:读文本必须走 test/lib/read.mjs 的具名入口 - ['test/criteria-hygiene.test.mjs', [], 6], + ['test/criteria-hygiene.test.mjs', [], 7], // 用户管理页(P4c 同批):动作↔服务端调用同名 / 门禁只认严格 admin / // 启停只发 status / 「受限」徽标口径 / 页面零写死色值 / 接线(纯逻辑真被调用) ['test/harmony-admin.test.mjs', ['--experimental-strip-types', '--no-warnings'], 30], diff --git a/deploy/install.sh b/deploy/install.sh index acc7605..d0ac6b9 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -280,6 +280,45 @@ echo "==> 校验插件共用模块同源" # 一侧改了另一侧没改,几个平台的行为就会悄悄分叉。 "$REPO/deploy/check-shared-libs.sh" +# ★★ 源文件权限政策(dsh 2026-09-19 接线)。 +# +# 为什么单独一条、为什么**必须挂在这里**:这一类**只有它一个判据**,而且 +# `git` 根本看不见 —— `git ls-files -s` 只记 **100644 / 100755**(可执行位), +# **组/其他读位不进版本库**。实测:把受跟踪文件 `chmod 600 ↔ 644`, +# `git status` **两次都空**、`git diff` 也不动 ⇒ 没有任何"顺带"的通道会发现它。 +# +# 后果不是"本地难看":这些是**源码**,会被构建/测试/部署**以不同身份**读 +# (`install.sh` 自己会切身份做可写性判定;`client/electron/test` 里还有 +# **以 nobody 降权跑**的判据)。0600 在"跑的人恰好是属主"时不炸,换个身份就是 +# EACCES —— 而那串报错**看起来像代码问题**。 +# +# ⚠️ 这条判据**写出来很久了却从没被任何东西调用过**(`grep -rn` 全仓:唯一的 +# 非注释提及是 `mutants/summary.py` 里一句 `print(...)` 的文案), +# 而它**当时正红着**(3 条:2 个 600 的文件 + `jobs/` 缺 x 位)—— +# 正是本仓反复消的那个形状:"**判据在,但走不到**"。 +# 它自己那份 `[ -x ]` 恒真的 root 陷阱也修过一版(`b7dc9e9`),修得对, +# 但**没有任何人会跑到那一行**,所以修与不修没有可观察差别。 +# +# ⚠️⚠️ 接线时必须走 `--check` 的**累积**通道(`CHECK_GATE_RC`),不能直接调: +# `check-file-modes.sh` 红时 `exit 1`,而本脚本是 `set -e` +# ⇒ 它会在**这里**中止,后面 `build-stamp`/钩子/`--check` 的收尾诊断**一行都不打**。 +# 我第一版就是直接调的,**实测**:接线后 `构建 Gateway`/`启用并启动` 等在日志里 +# **命中 0 次**(接线前 `构建 Gateway` 命中 2 次)—— 那正是本文件 141-157 行 +# 刚修过的同一个毛病("第一道红吃掉后面所有诊断"),我自己又造了一遍。 +# ⇒ 照 `npm_rc` 的既有做法:真装路径仍 `set -e` 立刻中止(装机器时不该带着红往下走), +# 干跑路径**记下来、跑完、最后 `exit "${CHECK_GATE_RC:-0}"`**。 +if [[ $CHECK_ONLY -eq 1 ]]; then + _fm_rc=0 + "$REPO/deploy/check-file-modes.sh" || _fm_rc=$? + if [[ $_fm_rc -ne 0 ]]; then + echo " [FAIL] 权限政策没过(退出码 ${_fm_rc})—— 上面标了 [FAIL] 的那几处就是" >&2 + # 只记**第一个**失败码(既有做法):`CHECK_GATE_RC` 是"干跑退出码"的单一来源 + [[ -n "${CHECK_GATE_RC:-}" ]] || CHECK_GATE_RC="$_fm_rc" + fi +else + "$REPO/deploy/check-file-modes.sh" +fi + # 插件的纯函数测试(自动转发去重等)。 # 插件不参与构建产物,但它的逆行为会直接变成用户收件箱里的重复邮件, # 因此也纳入部署前的门禁。zod 已在 node_modules 里就不重装。