<!--
  © 2026 IHUI AI (智汇AI) · 版权所有者: 李春川 (Li Chunchuan) · https://aizhs.top
  Provenance-watermarked. 未授权商用可被溯源追责 (Apache-2.0 须保留本声明与 NOTICE)。
  [IHUI-AI-PROVENANCE]:⁠​‌​​‌​​‌‍‍​‌​​‌​​​‍‍​‌​‌​‌​‌‍‍​‌​​‌​​‌‍‍​​‌​‌‌​‌‍‍​‌​​​​​‌‍‍​‌​​‌​​‌‍‍‌​‌‌​‌‌‌‍‍‌‌​​‌‌​​‌‌‌‌​‌​‍‍‌‌​‌‌​​​‌​​​‌‌‌‍‍​‌​​​​​‌‍‍​‌​​‌​​‌‍‍‌​‌‌​‌‌‌‍‍‌‌​​‌‌‌​‌​​‌‌‌​‍‍‌‌​​‌‌​​​‌​​‌​‌‍‍‌​‌‌‌​‌‌‌​‌‌‌​‌‍‍‌​‌‌​‌‌‌‍‍​‌​​‌‌​​‍‍​‌​​​​‌‌‍‍‌​‌‌​‌‌‌‍‍​‌‌​​​​‌‍‍​‌‌​‌​​‌‍‍​‌‌‌‌​‌​‍‍​‌‌​‌​​​‍‍​‌‌‌​​‌‌‍‍​​‌​‌‌‌​‍‍​‌‌‌​‌​​‍‍​‌‌​‌‌‌‌‍‍​‌‌‌​​​​‍‍‌​‌‌​‌‌‌‍‍​‌​‌​​​​‍‍​‌​‌​​‌​‍‍​‌​​‌‌‌‌‍‍​‌​‌​‌‌​‍‍​‌​​​‌​‌‍‍​‌​​‌‌‌​‍‍​‌​​​​​‌‍‍​‌​​‌‌‌​‍‍​‌​​​​‌‌‍‍​‌​​​‌​‌‍‍​​‌​‌‌​‌‍‍​​‌‌​​‌​‍‍​​‌‌​​​​‍‍​​‌‌​​‌​‍‍​​‌‌​‌‌​⁠
-->

# AGENTS.md — IHUI-AI 项目 Agent 指南

> 作用域:`D:/IHUI-AI` 仓库根目录及所有子目录（2026-09-12 修正:仓库早已从 `G:` 迁到 `D:`，旧盘符路径已全部改为从脚本自身位置推导）。
> 历史案例归档见 `.ihui-agent/archive/AGENTS_history.md`。
> 本文件为精简版(2026-07-25 重构,原 783 行 → ≤400 行),保留所有强制规则核心条款。

---

## 1. 任务计划文档规则(强制)

- 项目**唯一**任务计划文档是 `PROJECT_PLAN.md`(根目录),所有任务计划、进度更新、待办清单、状态变更**只写**此文件,**不得**在 `.ihui-agent/`、`docs/`、根目录或其他位置新建计划/TODO/ROADMAP 文件。
- 完成任务后 `[ ]` → `[x] ✅(日期)`;新增任务追加到对应优先级(P0/P1/P2)末尾。commit message:`feat`/`fix`/`docs`/`chore`/`test`/`refactor` 前缀。
- **任务认领(2026-09-23 立)**:agent 开始任务前,在对应 `- [ ]` 后追加 `（进行中）` 标记(全角括号),例 `- [ ]（进行中）O20d 守门...`;完成后按上条改 `[x] ✅(日期)` 并删除 `（进行中）`。扫描 `PROJECT_PLAN.md` 可区分三态:无人认领(`- [ ]` 无标记)/ 进行中(`- [ ]（进行中）`)/ 已完成(`- [x]`)。派单前先扫进行中项,避免重复认领。扫描工具:`node scripts/check-task-claims.mjs`。
- **任务状态一致性(2026-09-26 立,守门 130 配套)**:计划文档被当台账用而**没有主键约束**,这是"完成的为什么还在这里、还有重复"的唯一根因 —— §1 上一条让每个 agent 追加登记,同一任务编号被不同批次各写一份,做完只翻自己那一批那份,其余副本永久烂成"未勾选";而既有防护链(门 71 防丢行 / 84 防写回旧版 / 100 防合并吞文件 / 工作区自愈)**全部单向防丢,没有一道判"状态分叉"**,并集合并当时又是"每行重数取 max",所以改写必然产出两份 —— **该口径已于 2026-09-29 由 G-814386 收窄**:带「重复登记副本」指针的行只许"从无到有带一份",不参与份数累加(同主键而正文已漂开的仍交人工);现行实现与四条落地断言见本节末「合并吞并的修复出口」条。三条规矩:
  1. **派单口径只认一个入口**:`node scripts/plan-tasks.mjs --open`(未勾选 − 同题已完成副本 − 自带作废声明的行 − 同一件事的第二条待办副本;四条判据 F1/F2/F3/F4 都以"编号 + 标题前缀逐字等值"的复合主键为准,不依赖行号顺序也不依赖相似度)。`grep '^- \[ \]'` 不是待办清单(2026-09-26 一次实测的**不重叠**分解:未勾选 205 = 已认领 44 + 其余排除 28 + 真待办 133;同刻 F1/F2/F3/F4 读数均为 0 —— **这些数是当次现读,派单前必须重跑,勿照抄**;报告里其余排除项的明细计数彼此可重叠,只能当诊断看,不能拿来减法核账)。归并出口:`node scripts/plan-tasks-merge.mjs --heal --commit`(F1/F2 翻勾并写明归并到谁;**F4 只加"重复登记副本"指针、绝不动勾选** —— 两条都是没做完的事,把没做的记成做过的比原病更响)。
  2. **一个编号只能有一行"当前状态"**:新登记同一件事时必须把旧行翻勾或就地改写,不得在别处再抄一份。判据是复合主键(编号 + 标题前缀**逐字**等值),不是相似度 —— 相似度只能报数,不配判红。
  3. **证据指针禁止写行号**:行号在任何一次 append 后都会挪位。实测 HEAD 面 27 处 `存活于 L<行号>` 式指针**复核通过率 0/27**(目标行已不是条目行)——上一轮登记"已识别、只标注未翻勾"的那批账,今天已无法按它自己给的证据复核。必须用内容锚点(编号 + 标题原文)。
- **行首裸编号是本仓登记行的默认形态,必须算主键(2026-09-27 O87 续,`scripts/lib/plan-task-index.mjs`)**:`TASK_ID_PATTERN`(门 71 导出的编号族)只认 `G-/D/O/B/P/W/V3/守门`,而 `titleOf` 又在正文第一个 `.` 处截断 ⇒ `- [ ]75. file_search…`、`- [ ] **86A. 证据流水…**` 这类行的复合主键恒为 null,**F1/F4/归并器对整族结构失明** —— 后果实测:75/51/62 三票的实现与常驻尺子都在 HEAD 且 RC=0,台账仍报未勾选,于是被再派一次。**判据(照抄别重新发明)**:① 编号必须落在剥掉复选框与状态装饰后的**正文开头**,② ≤3 位、可带**单个大写字母后缀**,③ `.`/`、` 之后第一个非空字符不得是数字(否则 `**12.3 万**` 这类量值开头会成百地冒成"第二次登记")。**两条同源缺陷各由一条测试钉住**:M14 钉族缺失本身;M15 钉"**放宽编号判据时,标题跳过必须走同一个出口**" —— 我第一版在 `titleOf` 里另抄了一份窄正则(不认 `**` 与字母后缀),结果标题仍被 cut 在编号后面那个 `.` 上、只剩 `"86A"`(3 字 <4)⇒ composite 仍 null,**刚修判据的那一族上自己失明**;变异取证:把 `stripLeadingNumeric` 退回窄版 ⇒ M14+M15 同时翻红(`pass 14 / fail 2`),逐字还原(`cmp`)后 16/16 绿。**这一族的"行首"判据与门 71 的"登记行"判据是两件事**:门 71 的防丢面仍按裸族认登记行,所以整块回写这类形态目前只有台账层(F1/F4)看得见,门 71 那一面要不要同步扩,归该门持有人决定,不得由本层顺手改。
  - 问责:`pnpm check:plan-task-state`(`--strict`,存量判红);提交链上跑的是**差值棘轮**(只拦"这次提交让分叉变多",存量永不拦 —— 与改动无关的恒红门只会逼人 `--no-verify`,连带废掉全部守门,§12e 同型)。修法只有**归并**(副本翻勾并注记归并到谁),§1 的"禁止无声删除"对已完成行仍然成立。
  - **新登记必须有交代(2026-09-27 立,G-254 / 守门 130 新增 F7+F8 两维)**:一条 `- [ ]` 只有满足三者之一才算"交代得清"——① 带租约 `（进行中@日期/持有者）`;② 正文写明它在等什么(归属四态:等人拍板 / 等环境条件 / 归他人持有者);③ 有新鲜锚点(行内日期或进展日期)。**三样都没有 = 无交代**,提交链按**差值棘轮**只拦"本次带进来的无交代行"(`git show <新提交>^..<新提交>` 与当下基准面逐行比对,存量不算在本次头上),因为与提交内容无关的恒红门唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e)。**登记一件活时顺手写下它为什么还开着,是这本账不再无限膨胀的唯一止血点** —— 现读:未勾选 313 行里 128 行无交代,而真待办只有 203 行,"越做越多"涨的是账不是活。
  - **寿命这一维不进提交链**:`node scripts/plan-tasks.mjs --stale`(或 `--strict`)才点名"超 21 天无进展"。行龄取 `git blame` 的作者时刻与行内日期的**较新者**(`scripts/lib/plan-line-age.mjs`,覆盖面由 `parseTaskRows` 一份判据闭合 ⇒ 量不到的行必须为 0,否则喊"未判定"),所以**不需要也不许**给人工回填"追溯出生日":回填等于替每行伪造一个新鲜时刻,寿命判据当场被架空。到期处置只有两条 —— 续进展(改日期)或写清它在等什么;**门绝不替你勾掉任何一行**。
  - **派单先看可派单口径**:`node scripts/plan-tasks.mjs --open --dispatchable`(= 未认领 ∧ 不是同题副本 ∧ 现在可做)。默认 `--open` 仍是全部无人认领行(向后兼容,不改变既有派单语义);两者差集就是"挂着但今天做不了"的账。数字一律现读,勿照本节点名。

### 归档机制

- 已完成任务条目(`### XXX(已完成 ✅ ...)` 标题)**禁止直接删除**,必须两步走:① 把完整任务条目(标题 + 正文)移动到 `.ihui-agent/archive/PROJECT_PLAN_YYYY-MM-DD.md`;② 在 `PROJECT_PLAN.md` 原位置留 HTML 注释占位:`<!-- 已归档(YYYY-MM-DD):XXX 任务,完整内容在 .ihui-agent/archive/PROJECT_PLAN_*.md -->`。
- **自动归档**:`scripts/archive-completed-tasks.mjs` 扫描**已完成**条目(默认阈值 0 = 完成即归档;`--days N` 是降速档,不是默认),post-commit 钩子自动 `--auto-commit`。归档 commit 设 `IHUI_ARCHIVE_COMMIT=1` 防递归。
- **排空步骤已在提交链上(2026-09-28 接)**:`.husky/post-commit` 在调归档器**之前**先跑 `node scripts/ledger-strip-open-rows.mjs --apply`。理由:"标题写着已完成、体内还裹着未勾选登记"的块是归档器**结构上不许搬**的那一类(搬走等于把别人正开着的活账归档),没有这一步,"完成即归档"只对干净块成立,而实测大部分块都不干净(台账是几百次并发登记写出来的)。三条安全前提写在这里,下一个人不必重新猜:① 该脚本走**对象空间**(`commit-tree` + CAS),而 `commit-tree` **不跑钩子** ⇒ 结构上不会自触发递归,与归档器同一形态;② 它自带五条零损失不变量(行数不变 ∧ 消失 0 ∧ 非空行净新增只可能是区头 ∧ 被拒块归零 ∧ 勾选两态不缩水),任一不成立就**整批不落**并 exit 非零,所以"钩子跑了"不等于"台账被改了";③ 它**排在归档器前面**,同一轮里排空完就能搬。应急开关 `HUSKY_SKIP_LEDGER_STRIP=1` 只关这一步,不影响归档(两级开关刻意分开;`HUSKY_SKIP_ARCHIVE=1` 关的是整块,含本步)。
  - **标题形态三处必须同形**(2026-09-25 G-182 实证):本节写的 `### XXX(已完成 ✅ ...)`、守门 13c 的提取式、归档器 `parseCompletedTasks()` 的匹配式,**任一处漂了另外两处不会响**。归档器曾按 `/^### \[x\]/` 找标题,而文件里 `^### [x]` 命中 0 次 ⇒ 连续 11 天每次跑都扫到 0 条、post-commit 静默无操作。现判据:含 `✅` 即算已完成条目,日期取标题里第一个 `YYYY-MM-DD`(真实形态日期常在 ✅ 之前);**无 `✅` 的小节标题(如 `### 已完成清单`)不算条目** ⇒ 归档器的搬运集必须是 13c 保护集的**真子集**,否则一边搬一边护就是互咬。
  - **大批量阀门只挡自动档,但"全批或不动"是错的默认**(2026-09-28 改):旧写法在积压 >25 条或 >256 KB 时整批拒绝,于是超阈值之后**每次自动档重算同一批再拒绝** —— 实测 HEAD 面积压 3 条 / 592,001 B,归档机制对存量结构性失效而账面一路绿(参照 §5c 水印门"单次缺口 >200 拒绝自动回写"同一条设计,但那条阀只能对**一次动作**生效,不能当常态)。现 `selectWithinBudget` 按**最旧优先贪心取前缀**(单批仍不超 500 段 / 256 KB —— 段数上限只是"格式又漂了一次"的哨兵,真正把关的是字节,余量点名并报数、下一轮继续),只有"最旧一条自身就超预算"才回到拒绝路径且不拆条目搬(2026-09-28 同日把段数上限从 25 提到 500:bullet 级纳入后一段常常只有一行,25 段/轮只搬得动 100 KB,卡的不再是它想防的"半本活文档被重写");退出码仍 0(自动档少做一件事不是错误,不得把钩子链弄红),`--allow-mass` 只放宽字节预算这一件事。
  - **归档粒度自 2026-09-28 起含 bullet 级 `- [x]`**:此前只认 `##`/`###` 带 `✅` 的条目标题,而台账里绝大多数完成状态写在 bullet 级(HEAD 面实测 2106 处),旧输出那句"不在 §1 条目粒度内,不搬也不护"就是计划文档只增不减(实测 4.9 MB)的唯一成因。`collectCompletedBullets` 把**连续**已完成登记并成一段(含缩进续行),**块内一遇任何 `- [ ]` 当场断开**,无日期不搬(不给没出生证明的行造生日);搬运仍走同一条结构等值自证 + 零损失闸 + 对象空间落地,别人在飞的磁盘副本逐字不动。`--no-bullets` 只作人工取证出口,不得当常态。
  - **占位必须带上随块搬走的"归并落账注记"(`〔【归并】…落账:复测 日期〕`)**(2026-09-28 立):台账门 130 的 F5 存续性棘轮数的就是这批注记,而它们原本挂在 `- [x]` 行上 —— bullet 级纳入归档后,行一进归档,计划文档面上的计数直接掉到 0,门按"整文件写回旧版"那一型喊红。它分不清"随块归档"与"被抹掉",而**这是归档器造成的形态,不该靠放宽门解决**(§12e:修红不得顺手削判据)。故占位新增字段「随块带走的归并落账注记: …」,注记正则取自台账 lib 的 `MERGE_NOTE_RE` 那一份实现;字段插在「完整内容在 …」之**前**,使 13c 反查文件名的"最后一个逗号段"形态一字不变,且无注记时占位与旧形态**逐字相同**(成对自检 S17b/S17c)。
  - **"标题写已完成、体内还有未勾"的假条目一律不搬,且 `--allow-mass` 绕不过**(守卫 `entryHasOpenRows`,与体积阀**正交**):立项当天抓到一条 —— `## P1 侧边栏底部 5 工具按钮…立并完成 ✅` 之后再无同级标题,整块一路吞到文件末尾 = 771 行 / 575,577 B、块内 `- [ ]` 237 条。旧阀按字节拒绝**纯属巧合救场**,而它给用户的出路正是 `--allow-mass`,谁按提示跑就把 237 件活账归档、派单口径静默少行。拒绝必须大声点名(标题、块大小、体内未勾数)。取证:归档器 `--self-test` 30 条(S12x 采集器正反例 / S13x 守卫含逐字取自真仓的真实形态 / S14x 贪心三态)+ 镜像 35 例(G:子弹真被搬走而同章 `- [ ]` 一字未动;H:连 `--allow-mass` 也搬不走假条目)。真仓首批现读:搬 12 段 / 69.8 KB / 76 条已勾行,未勾选 557 → 557,文档 4,926,467 → 4,857,252 B,门 71 与 13c 在新 HEAD 上 RC=0。
  - **归档锚点必须受版本控制**(2026-09-25 G-183):`.gitignore` 对 `.ihui-agent/` 的处理是"忽略其内容 + `!.ihui-agent/archive/` 例外",**不得改回整目录忽略** —— §1 的"完整内容在 `.ihui-agent/archive/PROJECT_PLAN_*.md`"和守门 71 的"archive 里找得到 ⇒ 不算丢"都以它为事实基础。旧状态(整目录忽略)下:历史那次 `b599edbba` 归档 11 条的 commit **只含 PROJECT_PLAN.md 一个路径**,即副本从未入库,而"A 机归档过"可以授权"B 机删行"。守门 71 曾仍读磁盘副本 ⇒ 尚未入库的本机归档文件可以授权另一次提交删行,**该洞已于 2026-09-25 由 G-184 关闭**:`archivedCopy()` 的归档清单与内容现在**同面**取(`--staged` 走 `git ls-files` 索引、全量走 `git ls-tree HEAD`),面里没有的路径不构成删行凭据;`runCheck` 必须把当次判定面喂给豁免(`archiveExemptFor(isStaged)`)。取证:自测四条组合 + 镜像端到端 **A/B**(同一份归档正文、同样删掉 `## O42`,唯一差别是一次 `git add`:未入库侧必 exit 1、已入库侧必 exit 0),外加一条反向回归锁(函数体内不得再出现 `readdirSync(` / `readFileSync(`,对 HEAD 旧版该锁 5 条断言全红)。**G-183 当时登记的"被门 118 结构性棘轮挡住"这个前提实测已不成立** —— 并行会话已把门 71 的内容面迁到 `scripts/lib/face-reader.mjs`,门 118 见 face-reader 导入即判合规,所以这里不存在"必须先整道迁移"的阻塞;登记这条是为了说明**沿用旧理由会挡住一件已经可以直接做的事**。
  - **归档 commit 必须带 pathspec**(2026-09-25 与上面同批修):旧实现第二步是 `execSync('git commit --no-verify -m "…"')` —— 不带路径 = 把当下共享索引里的东西全提交,而并发会话随时在索引里挂着 staged 内容,于是那枚"归档 commit"会把别人的在途改动打包带走(§12 污染型;它跑在 post-commit 里,没人盯着)。**它一直没炸,只因为匹配式漂了、这段代码从没真跑到过 ⇒ "让工具能用"和"修它的提交面"必须是同一条提交,否则修好匹配式的那一刻就是引爆那一刻。**现形:`execFileSync(GIT_BIN, […, 'commit', '--no-verify', '-m', msg, '--', <计划文档>, <归档文件>])`。A/B 实证:旧版在"计划文档 + 一个被 `git rm --cached` staged 的他人文件"场景下产出的 commit **确实含别人的文件**;新版只含声明的两路径(测试第 20 例钉成回归)。
  - **归档占位点名的文件必须真在审面里(守门 13c 的 A1/A2/A3,2026-09-25 立)**:`<!-- 已归档(…):X,完整内容在 .ihui-agent/archive/<FILE> -->` 是一句**可核验的指针**,不是修辞。A1 = 盘上有 `PROJECT_PLAN_*.md` 而审面(全量 HEAD 树 / `--staged` 索引)没有 ⇒ 红;A2 = 占位点名的具体文件不在审面里 ⇒ 红(写成 `PROJECT_PLAN_*.md` 通配的占位不点名,不参与判定)。**A3(同日 G-192)= 同一把尺子伸进归档件内部** —— 元归档层(归档文件正文里再写"完整内容在 archive/<另一个>")过去完全无人看守,实测 `2026-09-12` 那份里嵌着 **218 条**占位、点名 13 个文件,其中 12 个从未入库。A3 与 A2 共用"缺失⇒红 / 已登记⇒只报数 / 已入库仍挂台账⇒判清单腐烂"三段规矩,标签不串门,同一名字被 A2 与 A3 点到只计一次债;blob 取不到那一层 ⇒ 结论行喊"未判定",不静默通过。立项即抓到两条实测:`PROJECT_PLAN_dedup-2026-09-23.md` 只躺在本机(已随本票入库),以及 **8 个被 52 条占位点名的归档文件在仓库与本机两侧都不存在、其路径也从未出现在历史里** —— 即 §1 那句承诺对这批条目一直是空的。这 8 项进 `LOST_ANCHOR_LEDGER` **只报数**:它们是已发生的债,当场判红就是一台与任何提交都无关的恒红门(§12e 同型);台账是**待偿清单不是豁免清单** —— 修好了仍挂着 ⇒ 判"清单腐烂"红,删了行却没修好 ⇒ 照常红,两个方向各有一条自检钉死。按条目从计划文档历史版本逐字找回另计一票。
  - **台账已第一次真清偿(2026-09-25 G-191,8 项减到 4 项)**:找回的办法是**从 git 历史逐字取回,不是重写历史**。每批归档都由"把正文从计划文档搬走"的那枚提交产生,所以那枚提交**删掉的内容块**就是归档正文;落地判据只有一条 —— 该正文逐字存在于所引提交的父版本 `PROJECT_PLAN.md`,不成立就不落地(实测有 2 条 `2026-07-26` 的占位是在 `3a5b737bf8` 里**凭空新增**的纯 `+` 行,按裸标题 `git log -S` 证明那两个条目从未存在过 ⇒ 从别的提交"补"出来就是编造,已确认为找不回,不当干)。落地 4 份 / **28 块 / 567 行实质正文、0 行无出处**,取证用两把**互相独立**的尺子(逐块对应 23/23 + 格式无关的全量行级比对 567/567 —— 后者专门防"重建脚本自己给自己发合格证")。每份文件里保留逐条 `recovered from <sha>` 注释 + 一句"这是找回件,不是当时原写"的性质说明:**不写进文件,后来人就会把重建文本当一手记录。**
    **取出处时必绕过一个坑**:`b129e09482` 做过一次 `.trae-cn/archive/` → `.ihui-agent/archive/` 的**占位文本搬家**(它不删除任何内容),按"最旧一次 `-S` 命中"取出处会选中搬家那枚而非真正的删除,必须沿搬家链回走。
  - **递归那一格已做完(2026-09-25 G-192),并把看守面扩到归档层自身**:上面"218 条嵌在元归档里的占位"共点名 **13 个**文件,三类处置 —— ① **4 个真存在过**(躺在 `.trae-cn/archive/` 下,搬家时漏带),按 `git cat-file blob <出处>^:.trae-cn/archive/<名>` **原字节**落盘,判据两把:注入水印**之前** `git hash-object` 与出处 blob **同 SHA**,注入**之后**正文仍须逐行全等(合计 2,210 行 / 170,608 B);② **8 个从未作为文件存在过**(`git log --all -- <任意前缀>/<名>` 对 8 个名字全部 0 命中)⇒ 占位被写过、文件没被写过,结构上找不回,进台账只报数;③ 1 个由 G-191 已入库。**为什么必须两把尺子**:找回脚本自己写的是"剥水印→逐行全等",它剥窄或剥宽都会让自己的断言跟着错(§22c"镜像测试只复读实现就是复读机"同型),所以独立复核换成异形判据 —— "blob 每一行必须在落地文件里**同序、连续、逐字**出现",水印层天然落在窗口之外。过程中真抓到一次不等:出处带 **UTF-8 BOM** 而 `watermark.mjs inject` 重写时会去掉 ⇒ 容差只开在 BOM 这一处(字节同值已由注入前的 SHA 判据证明),并写进文件里的出处注释,不让它读成"逐字节永远同值"。**A3 是本格的真正产出**:归档件**内部**的占位同样点名,A1/A2 过去只扫计划文档,这一层零看守。台账 4→12 项(**全部只报数**,所以扩 A3 不产生新恒红面)。
  - **13c 自身的取材口径(同日迁移)**:此前 `ROOT = process.cwd()` + 裸 `git show` 用 `execSync` 拼字符串(无 timeout)+ 占位合规性看 `git diff` 文本,三处都与本仓现行口径相反(工作树滞后即换结论 / 服务账户 PATH 不通 / 别人同时改这文件时合规性跟着盘走)。现全部经 `scripts/lib/face-reader.mjs`,对照面是 `--staged`:HEAD→索引、全量:HEAD^→HEAD、`--worktree` 仅人工;整面取不到 ⇒ **exit 2 无法判定**,不冒红也不记绿。归档件正文也**同面**取(A3 用),单份 blob 取不到 ⇒ 在结论行喊"那一层未判定",不静默通过。另有两条是本门第一次自跑咬出来的:CRLF/LF 的同一条标题不得被读成"删除"(HEAD blob 是 LF、共享工作树常是 CRLF,不剥 `\r` 会把整批已完成条目误报成被删);**点名对象的归属必须拿该目录在审面上的"全集"判**,而不是只看 `PROJECT_PLAN_*` 形状 —— 归档目录里也放非该形状的跟踪件,按形状筛就会把"已入库"读成"落空"。
  - **本门自己修掉的两个"拦了却不说话"型缺陷(2026-09-25 G-192,都值得记住形态)**:① A0 阻塞分支写成 `if (deletedHeadings.length > 0 && !del.addedPlaceholders)`,而**空数组是真值** ⇒ 该分支永不执行,门对"直接删已完成条目"`exit 1` 且**零输出** —— 调用方只看到提交被阻止,看不到原因(现走 `del.compliant`,由 length 决定)。② 门按自身位置推导 `ROOT` 之后,镜像测试靠 `cwd` 定位夹具的那套调用**结构上失效了**:13 例里 11 例其实在审真仓,账面全绿而结论与夹具无关(守门 70 同型)。夹具改经显式 `--root <dir>` 通道进入,落点改回 `scripts/lib/scratch-dir.mjs`(§26,不再往 `os.tmpdir()` 写),并由两条**源码级反向锁**钉住(不得再出现"只靠 cwd"的调用形态、不得回到裸 `mkdtempSync`)—— 这类失效只有源码锁能防,加断言会跟着一起漂绿。取证现为 `--self-test` **17** 条 + 镜像 **20** 例(含 A1/A2/A3 端到端与"点名已入库的对象不该红"的反向对照,后者防的是把整层一律判红那种恒红门)。**本门没有应急跳过通道**(runner 条目无 `skipEnv`、脚本不读 env);登记这一事实,是因为本仓多次出现"文档写了一个跑不通的出路"。
- **手动触发**:`pnpm archive` / `--all`(全部)/ `--days 3`(自定义)/ `--dry-run`(预览);跳过用 `HUSKY_SKIP_ARCHIVE=1 git commit`。
- **守门**:`scripts/check-project-plan-archive.mjs` + pre-commit 第 13c 项。历史案例见 `.ihui-agent/archive/AGENTS_history.md`。
- **搬不走时读两把只读尺子,而不是放宽判据(2026-09-28 立,由"把所有已完成任务归档"这一轮逼出)**:
  ① `node scripts/archive-completed-tasks.mjs --list-blocked [--json]` —— 点名"条目标题写着已完成、体内还裹着未勾选登记"的块,并逐行给出被审面行号与原文(派单与人工裁决的输入)。分区判据是 `partitionPlanBlocks` **一份**实现,`main()` 与该出口共用:两处各写一遍必然漂开,而漂开的表现不是报错,是"日志说卡住 N 块、交给裁决的是另外 N 块"。`--json` 面必须是可 `JSON.parse` 的纯 JSON(人读行会把它打掉,第一版就犯过)。
  ② `node scripts/plan-tasks-merge.mjs --audit-pointers [--json]` —— 点名"整族每一行都带着 `【归并】重复登记副本` 指针"的主键族,并分两读数:按主键无终端 / **按编号也找不到任何代表**(后者才是真隐形)。两把尺子都**只读、只报数、不判红**:存量与任何单次提交都无关,挂 blocking 就是恒红门(§12e 同型),数字一律现读不得照文档派单。
  - **规矩:块内未勾选行必须"移出块",不得"改判据放过"。** 那些行按 §1 的 F4 口径「两条都是没做完的事、绝不动勾选」就是活账;把它们随已完成块搬进归档件 = 派单口径静默少行。本轮实测教训:520 行里 329 行带归并指针,一度被误判成"派单口径已逐出 ⇒ 归档器与派单口径不同形",据此要放宽 `entryHasOpenRows` —— 该改动作废并留档,因为"逐出派单口径"是为了**不重复派单**,不是说它做完了。唯一无损出路是把 `- [ ]` 行移到台账末尾的 `待办剥离区` —— **该动作已收编成常驻出口** `node scripts/ledger-strip-open-rows.mjs`(缺省档零副作用只出报告,`--apply` 才落地;落地面 = HEAD blob + 临时索引 commit-tree + CAS,**从不写工作树**,因为那份副本常年是别人的在飞现场)。它落地前五条守恒断言任一不过即拒绝写盘(区标题不得自认已完成 ∧ 非空行零消失 ∧ 新增只能是区头与移出行 ∧ 产出面被拒块归零 ∧ 未勾选与已勾选两态都不缩水),迭代到不动点(摘行会改块边界,一轮过会把"还剩几个"报成假 0)。它解的正是"归档器拒绝整块搬运 ⇒ 那些行永久挡住完成即归档"这一型死锁:只换位置,不删行、不改勾选、不动其余顺序,落地前四条守恒断言(区标题不得自认已完成 ∧ 行多重集零消失 ∧ 新增只能是区头 ∧ 派单口径未勾选择不缩水)必须全绿。
  - **这一轮连出的第四笔账(当前无判据、无修法,须同笔两处才能收)**:归档把"幸存者"搬走之后,留在台账里的副本指针就地失去终点 ⇒ **归档越干净,隐形族越多**。收口必须**同时**改两处:① `rewriteDup` 在加指针前先问该族是否还有终端代表;② 归并落地的验收断言从「F4 必须归零」升级为「F4=0 ∨ 剩余副本各有终端」。**只改 ① 会让归并器每次自我拒绝**(副本归不了零 ⇒ 停手 ⇒ 自动档永不生效),与恒红门同罪。这条收敛链属台账判据持有人裁决,不得由"排空台账"这一轮顺手改。
  - **同一轮的后半已经落地(2026-09-28,枚 7a075820d / 4064e68a6)**:① 活文档归并改成**搬运感知 max** —— `scripts/lib/ledger-move-aware.mjs` 复用归档器那一份块解析判"某行是否已被 `已归档` 占位代表",已代表的不再从对侧取回;失效方向刻意是**多带一行回来**(占位找不到 / 归属解析不出 / 该行被就地改写过 / 归档件取不到 ⇒ 一律照旧取回并打印未判定原因),因为少带一行 = 丢别人的内容。这解掉了"我搬完 → 别人那份滞台副本被合回来 → 台账又涨"的循环。② 守门 84 补 **R1r 行级复活判据**(判索引面、差值棘轮、锚点 = 该文件在 HEAD^→HEAD 那一步自身的复活数且祖先窗口必须剔掉 HEAD 与 HEAD^,否则棘轮被自己喂饱成假绿),`object-space-land` 的同一判据已提取成 `scripts/lib/stale-content-analysis.mjs` 供两处共用 —— 上线当场即点名 6 个路径正把已归档台账行搬进暂存,而旧的整 blob 判据对这一型全部沉默。

### 唯一例外

- `/goal` 模式:`.ihui-agent/goal-runtime/STATE.md` + `loop-run-log.md`(临时,目标结束后删除);skills:`.ihui-agent/skills/SKILL.md`(AI 工具配置,非计划文档)。

---

## 2. 项目概览

IHUI-AI 是全栈 AI 平台(TS Monorepo + pnpm workspace + Turborepo),8 端清单:

- `apps/api`(Fastify 5 + Drizzle ORM 0.38 + PostgreSQL)
- `apps/web`(Next.js 16.2.12 + React 19 + Tailwind 4 + shadcn/ui)
- `apps/ai-service`(FastAPI + LangGraph + LiteLLM + MCP)
- `apps/miniapp-taro`(Taro 4 + React)
- `apps/desktop` / `apps/extension` / `apps/mobile-rn` / `apps/cli`(各端独立)
- `packages/`(database / auth / types / ui / config / eslint-config / tsconfig)

---

## 3. 代码风格

- 做减法,最小化代码,零冗余。复用现有代码和模式,不创建文档文件(除非明确要求),不加 copyright/license header。

### TypeScript 类型零技术债(强制)

- **尽最大程度禁用 `any`**,优先用 `unknown` + 类型守卫 / `as const` / 泛型 / 条件类型 / 工具类型(`Pick`/`Omit`/`Record`/`Partial`/`Required`/`ReturnType`/`Parameters`)/ 精确接口替代,禁止把 `any` 当"类型兜底"逃避设计。
- **深度分析 TS 用法**:函数签名(入参/出参/泛型约束)、对象字段、API 响应、props、state 必须显式标注精确类型;能由 `tsc` 推断且可读性良好的局部变量可省略,但禁止"省略 = 不写类型"扩散到公共 API。
- **必须用 `any` 的例外(三选一)**:① 第三方库无 `@types` 或类型声明缺失;② 泛型推断失败且无法用 `unknown` + 守卫替代;③ 跨包循环依赖无法用类型导入断言解决。**必须**附行内注释 `// FIXME(any): 原因 + 移除计划 + 截止版本`,后续 PR 必须清理。
- **不留技术债**:新代码 `tsc --noEmit` 必须 0 错误,禁止"先 any 后修"占位;重构遗留 `any` 必须替换为精确类型,**禁止**复制粘贴扩散 `any`;PR 引入新的 `any` 必须在 PR 描述说明例外依据。
- **守门**(分层渐进):
  - **过渡期(当前生效)**:`@typescript-eslint/no-explicit-any: error`(packages/eslint-config/index.js,syntax-level 不需 type info,已生效)。lint-staged 对 staged 源码文件触发阻塞,新代码引入 `any` 会在 `pnpm lint` 报错。
  - **目标态(已评估,推迟启用)**:`@typescript-eslint/no-unsafe-assignment` / `no-unsafe-member-access` / `no-unsafe-call` / `no-unsafe-return` / `no-unsafe-argument` 五条规则设为 `error`。**2026-07-28 评估结论**:实测启用 `recommendedTypeChecked` + `projectService` 后,(a) 性能不可接受 — cli 包(最小)lint 时间 2.7s -> 51.9s(慢 19 倍),web/api 包预计 30s -> 600s+;(b) 历史错误多 — cli 包已报 30+ 处(`require-await` / `no-unsafe-assignment` / `no-base-to-string` / `restrict-template-expressions` / `no-unnecessary-type-assertion`),全量预计 300+ 处;(c) 配置陷阱 — `eslint.config.js` 不被 tsconfig include 需 `allowDefaultProject`,JS 文件需单独豁免 `no-unsafe-*`。**启用前置条件(全部满足才可启用)**:1. lint 性能优化方案落地(eslint cache 持久化 / 仅 staged 文件 typed-lint / CI 才跑全量 typed-lint);2. 历史类型错误清零(`require-await` 等非 unsafe 错误先修);3. `allowDefaultProject` 配置就绪。
  - **测试文件豁免**:`**/*.test.ts` / `**/*.spec.ts` / `**/tests/**` / `**/test/**` / `**/e2e/**` 路径下的 mock/stub 代码允许 `any`(mock 类型断言必需),启用 typed-linting 后通过 `files` overrides 关闭上述规则。
  - CI `pnpm typecheck` 全绿方可合并。

### 共享层优先(强制)

- **写新代码前必须先查共享层**,确认是否已有现成实现。禁止在端内(apps/*)重新实现 `packages/` 已提供的功能。
- **检查清单(按顺序)**:
  1. **hooks**: `packages/shared/src/hooks/` — 基础 hook(clipboard/debounce/countdown/form/mounted/pagination 等)和业务 hook(auth/chat/agents/articles/agent-runtime/confirm-dialog 等)共 16 个。各端 `hooks/` 目录应只做 re-export wrapper + 平台 adapter,不得独立实现。
  2. **utils**: `packages/shared/src/utils/` — 工具函数(date-utils/dangerous-command-detector/format/file-helpers/error-messages/jwt-utils/ai-skill-variables 等)。各端 `lib/` 目录应只做 re-export wrapper。
  3. **types**: `packages/types/src/` — 所有跨端类型(ChatMessage/MessageInputFile/WorkspacePermissionMode/User/ApiRequest 等)。禁止在端内重新声明同名类型。
  4. **api-client**: `packages/api-client/src/` — 所有 API 调用。禁止在端内直接用 `fetch`/`axios`/`Taro.request` 调后端,必须走 `@ihui/api-client`。端内可保留 re-export + 平台 adapter(如 AsyncStorage 持久化)。
  5. **stores**: `packages/shared/src/stores/` — 共享 store 工厂(createAuthStore/createThemeStore)。各端 store 应调工厂 + 注入平台 transport,不得重新定义 state shape。
  6. **constants**: `packages/shared/src/constants/` — 跨端常量(storage key/URL/locale key 等)。禁止在端内硬编码同名常量。
- **如果共享层没有**:
  - 评估是否跨端可用 → 如果是,先提取到 `packages/shared/` 再在端内 import,不得直接在端内写。
  - 如果确认平台特有(依赖 DOM/RN API/Taro API)→ 可在端内实现,但必须在文件头注释说明 `// 平台特有:依赖 [DOM/RN/Taro] API,不适合共享`。
- **工厂模式优先**:跨端 hook/util 用工厂函数 + 依赖注入(参考 `createUseClipboard` / `createAuthStore`),各端传入平台 adapter。不得用 `if (Platform.OS === 'web')` 条件分支在共享层处理平台差异。
- **守门**:PR review 时检查是否有端内文件重新实现了共享层已有功能。发现重复 → 要求改为 import 共享层。

---

## 4. 前端 UI 约束

- compact 紧凑、elegant 优雅。hover 用 subtle 颜色变化,**不要蓝色发光边框**。复用 `packages/ui-react` 的 Card/Button/Input/Dialog。每个页面 < 250 行。时间用 `Intl.DateTimeFormat`,头像用 initials。状态徽章:draft 灰 / published 绿。积分正数绿色,负数红色。

### 品牌 CTA / 主按钮色同源(强制,2026-09-24 收口)

- **唯一写法(2026-09-26 回翻定稿)**:主按钮、选中胶囊、悬浮加号这类"品牌实底 + 其上文字",RN/共享包写 `brand.cta`(底)+ `brand.ctaForeground`(文字)**成对**;CSS/类名侧用 `--color-cta` + `--color-cta-foreground`(小程序 `CategoryBar`/FAB 同值)。**这一档黑/白反转**:亮=纯黑底(`#000000`)/ 纯白字(`#FFFFFF`),暗=纯白底(`#FFFFFF`)/ 纯黑字(`#000000`),与 `--color-primary` 同一明暗行为,`.dark` 显式覆盖。对账由「跨端色值同源对账」守门负责(编号以 `scripts/guardian-runner.mjs` 现值为准,勿照抄文档)。
- **为什么保留独立档名而不并进 `--color-primary`**:primary 在 web 端兼任墨色(`text-primary` 1803 处),改它等于给全站正文染色;CTA 保持独立档名、取值与同主题 primary 全等,语义 = "品牌实底 + 其上文字"。(2026-09-24 曾取 `#4A7A96` 明暗同值以避开"大色块与页面反极",2026-09-26 用户明确要求回翻为黑/白反转,该设计已废。)
- ~~**唯一写法(2026-09-24 已废)**:主按钮/选中胶囊用 `brand.DEFAULT` + `brand.foreground`,CSS 侧 `--color-primary` + `--color-primary-foreground`。~~ 该行描述的正是被本票替换掉的旧档:DEFAULT 亮=纯黑 / 暗=纯白,大色块与页面反极。**现存唯一写法见本节第一条**(`brand.cta` / `--color-cta`)。保留本行仅为让早期条目里的 `brand.DEFAULT` 措辞能被追溯到已废。
- 真要新增品牌档:先在 `tokens.css` 落一个 CSS 变量,再到同源对账门的映射表登记依据;确属 RN 专属(如 `brand.dark` 品牌绿)才能进 `RN_ONLY_BRAND_KEYS` 并写明理由 —— 豁免项若已不存在同样算红(防清单腐烂)。
- 悬空引用由该门的"品牌键"判据直接拦(编译不一定红,运行时是 `undefined` 颜色)。并行会话的暂存区里若还残留 `tokens.brand.ctaFill/ctaText`,改法只有一行:`ctaFill` → `cta`、`ctaText` → `ctaForeground`(2026-09-24 改档;此前该行的指向是 `DEFAULT`/`foreground`,已随 CTA 独立成档而更新)。
- **禁止端内自立 CTA 档**:`brand.ctaFill` / `ctaText` 这类"浅色等于 web、深色另取一档"的**混血**键不得再加回 —— 当年删它不是因为"不许有 CTA 档",而是因为**它在端里自立、且两档取值互不相同**(那才是"手机上改了 web 没改"的成因)。`--color-cta` 走的是相反路径:先落 `tokens.css` 源头(2026-09-26 起 `.dark` 显式覆盖、黑/白反转)、再进同源对账门的映射表,所以它合法而旧键仍属禁止。要调主按钮观感就改 `tokens.css` 的 `--color-cta`(亮档)与 `.dark --color-cta`(暗档)**同一处**,三端一起动;不得在某一端单独覆盖。
- **成对即合规,不得再当债务**:主 CTA 的唯一写法是 `brand.cta` 实底 + `brand.ctaForeground` 文字(旧档 `brand.DEFAULT` + `brand.foreground` 已废,见上),所以这套配对**必须**被所有前景/容器对账判据认作合法 —— 按规矩写就红的门只有一个结局:逼人 `--no-verify`,连带废掉全部守门。拦的是**没有配对前景**的实底卡片与**跨档错配**(底取 `brand.cta`、字却取 `text.primary`/`surface.light` —— 前者黑/白反转下两态与底同色纯隐形,后者按档位法则本就非配对前景)。守门 83 的 R1_BG 已同时认 `brand.DEFAULT|brand.cta`,就是为了"判据必须覆盖门自己产出的形态"。
- **禁止端内自立 CTA 档**:`brand.ctaFill` / `ctaText` 这类"浅色等于 web、深色另取一档"的混血键已全部删除,**不得再加回**。要调暗色主按钮的观感(例如觉得纯白刺眼),改 `tokens.css` 的 `.dark --color-primary` **一处**,web / 小程序 / RN 同时动;不得在某一端单独覆盖 —— 那正是"手机上改了 web 没改"的成因。

> ⚠️ 本行已于 2026-09-24 被上面「禁止端内自立 CTA 档」新条目就地改写取代;保留原文只为不丢行,勿照它执行。

- **成对即合规,不得再当债务**(同日补):上面这条是主 CTA 的唯一写法,所以深色档案下"brand.DEFAULT 实底 + brand.foreground 文字"**必须**被所有前景/容器对账判据认作合法 —— 按规矩写就红的门只有一个结局:逼人 `--no-verify`,连带废掉全部守门。拦的是**没有配对前景**的白卡片与**跨档错配**(底取 brand、字取 `text.primary`/`surface.light`)。

> ⚠️ 本行已于 2026-09-24 被上面「成对即合规」新条目就地改写取代;保留原文只为不丢行,勿照它执行。

- **RN 侧 `dark:` 类必须与 App 主题同源才允许依赖**(强制):共享 preset 是 `darkMode: 'class'`,`dark:*` 工具类读 NativeWind 自己的 colorScheme store。`apps/mobile-rn/src/theme/color-scheme-sync.ts` + `ThemeProvider` 负责把 App 解析出的主题(light/dark/system→系统)落进该 store —— 缺这一步,`dark:` 只跟系统外观,与 App 内主题开关长期分裂(而前景/容器对账门把"同行有 `dark:` 变体"记为已配对,不同步时那些配对只是纸面正确)。**新增深色取用二选一**:走 `tokens.*`(模块级单例,重载 JS 生效,共享包默认),或走 `dark:` 变体(依赖上述同步);不得混用两套、更不得在端内自造第三个色源。回归:`apps/mobile-rn/tests/theme-colorscheme-sync.test.ts`。

- 真要新增品牌档:先在 `tokens.css` 落一个 CSS 变量,再到守门 90 的映射表登记依据;确属 RN 专属(如 `brand.dark` 品牌绿)才能进 `RN_ONLY_BRAND_KEYS` 并写明理由 —— 豁免项若已不存在同样算红(防清单腐烂)。
- 悬空引用由 R3 直接拦(编译不一定红,运行时是 `undefined` 颜色)。并行会话的暂存区里若还残留 `tokens.brand.ctaFill/ctaText`,改法只有一行:`ctaFill` → `DEFAULT`、`ctaText` → `foreground`。

### 描边不得取墨档(强制,2026-09-26 立)

- **规矩**:任何描边 —— RN/共享包的 `borderColor` / `borderBottomColor` / `border: \`1px solid …\``模板串、web 与小程序的`border-primary`/`ring-primary` 类名 —— **一律不得取墨档**。用户原话:「我们项目设计没有这样的纯黑色的描边」。
- **禁的四档**:`brand.DEFAULT`、`brand.foreground`、`brand.ctaForeground`、`text.primary`(亮=近纯黑,暗=近纯白)。它们兼任墨色与文字色,当描边用就是在容器外圈画一道硬黑线。
- **合法出路只有两条,都是项目已有档**:中性描边 → `border.light` / `border.medium`(= web 的 `border-border`);强调描边 → `brandAccent.light` / `brandAccent.deep`(= web 的 `border-brand-accent-deep`,亮 `#4a7a96` / 暗 `#a3c4d6`,端内在 R8 之前已有 7 处这么写)。不得在端内自拼 `#000` / `rgba(0,0,0,…)`,也不得为消红去改判据。
- **`brand.cta` 作描边不由本条判**:它是"主实底"档,与同色填充并用作描边时语义是加厚而非描边,且该形态早已计入守门 83 的 **R3** 计数面(`(backgroundColor|borderColor): *.brand.cta`)。同一笔债两道门各计一次会让两份基线互相顶掉。
- **染色档不算这一型**:`border-primary/20` 是 20% 染色、`border-primary-foreground` 是另一个键名,都不属"纯黑描边"。判据用尾部字符类同时排除两者,别放宽。
- **覆盖面(同日两次补,现行口径以这条为准)**:R8 判**三种书写形态**——RN style 对象(`border\w*:` 取墨档)、Tailwind 类名(`border-primary`/`ring-primary`)、**CSS 声明**(`border*: … var(--color-primary)`,含 TSX 内联字符串形态)。文件面**不再维护端名单**:`R8_DIRS = ['apps','packages']` 整面取,只靠 `e2e/tests/__tests__` 与 `*.test/spec.*` 排除叙述行;扩展名含 `tsx|jsx|ts|js|css|scss`。立项时前两种形态各自漏过一块(extension 不在 R5 端表里 → 类名形态在扩展端失明;.css 不在扩展名里 → **19 处** CSS 声明整面隐身),两处都已随同笔提交清零,基线 `inkBorderCounts` 仍为**空 = 零容忍**。**给守门扩面必须同批改两半**:内容判据的 scope 函数 + 枚举/预筛的目录表与扩展名;本条的第一版就只改了 scope,`git ls-files` 仍用旧表,结果**整块改动静默失效而门照报绿**。防线两条:① 拿真仓 HEAD 喂新判据,它必须点名已知存量(阳性对照);② 镜像测试锁源码——枚举面必须引用新表、旧表不得再出现、预筛常量必须含新形态。
- **历史成因(值得留着)**:本门自己的修复提示曾写着「`brand.DEFAULT` / `--color-primary` 只保留墨色、**描边**、文字色三义」—— 散文把这一档列为描边的合法语义,判据又只管"前景 vs 容器配对"、从不算描边取哪档,**两边一起漏**,所以这一型活了很久还一路报绿。现该措辞已就地改掉。
- **守门**:并入 83(见下方速查),新增判据 **R8**,基线键 `inkBorderCounts` 现为**空 = 零容忍**(立门当日 HEAD 实测 148 处:RN style 面 33 + web/小程序类名面 115,已全部并回上述两档)。行内豁免 `border-ink-exempt: <原因>` 必须带原因且**逐行**生效;上一行算豁免的前提是它是纯注释行。

### 圆角单一源头(强制,2026-09-23 全端收口)

- **唯一真相源**:`packages/design-tokens/src/radius.js` 的 `RADIUS_STEPS` = `xs 2 / sm 4 / md 6 / lg 8 / xl 12 / 2xl 16`(px;`DEFAULT`=8 对齐 web `--radius: 0.5rem`)。改档位只改这一处。`tailwind-preset.js` 必须写 `borderRadius: RADIUS_REM`,`tokens.css` / `app.css` 的 `--radius-*` 必须与之逐档同值。
- **"哪类元素取哪档"也有唯一真相源(2026-09-27 立)**:同文件的 `RADIUS_ROLES` = `tiny→xs / control→sm / chip→md / card→lg / panel→xl / hero→2xl`,JS 侧按 `rnRadiusFor.<角色>` 取,文档侧是 `docs/UI_GUIDELINES.md` §3.1 的展开。**立因**:项目此前只定义了档位**值**,从未定义角色→值,于是两端各按直觉选 —— 同一张 `UserInfoCard` 小程序 8px、App 12px,`ModelList` 一端 8 一端 12,而守门 77 一路报绿(它只判"有没有绕档位表写死数字")。§3.1 表格当时还写着 `rounded-sm`=2px、`rounded`=4px,与档位表差一档 —— 照文档写必错,那两行已随本表更正。**新增角色必须同笔有消费方**,不得先建表再等人用(与 `geometry.js` 同一条规矩)。
- **各端取用形态(不得自创写法)**:

  | 场景                                         | 唯一写法                                                                      |
  | -------------------------------------------- | ----------------------------------------------------------------------------- |
  | web / ui-react / extension(Tailwind v4 类名) | `rounded-xs` ~ `rounded-2xl`;CSS 里 `var(--radius-<step>)`                    |
  | miniapp-taro / mobile-rn(NativeWind v3 类名) | 同上,**禁止** `rounded-[24rpx]` 这类任意值                                    |
  | RN StyleSheet / 任意内联 style               | `borderRadius: rnRadius.lg`(`import { rnRadius } from '@ihui/design-tokens'`) |
  | `.css/.scss/.html` 任何端                    | `border-radius: var(--radius-lg)`,禁止 px/rpx 字面量                          |

- **类名语义按 web 对齐(修文档漂移)**:v3 preset 曾把 `rounded-sm` 定成 2px、web v4 是 4px,同名不同值即"手机上圆角和全局不一致"的根因;现 `sm=4px`,2px 由 **`rounded-xs`** 承载。裸 `rounded` 全端统一 8px(web 现实)。旧条目里"`rounded-sm`(2px)"作废。
- **禁止绕档**:每文件自定 `const *_RADIUS = <数字>`(含 `BAR_RX` 这类**名字不带 RADIUS** 的圆角常量)、用 `rpx()` 算圆角、`rounded-[任意值]`、StyleSheet 里写数字字面量、**以及写字符串形态 `borderRadius: '8px'` / `"6px 6px 0 0"`**(字符串曾是守门盲区,2026-09-23 对抗排查补上;多值串须拆成四个角属性引用档位)。
  - **上面那句"多值串须拆成四个角属性"曾只是散文**(2026-09-27 O81 票⑯实测推翻其覆盖声明):守门 77 的 B3 判 CSS 声明时是**整串判** —— 值里出现 `var(` 就整条放行,于是 `border-radius: var(--radius-xl) 24rpx 0 0`(第一个角引用档位、其余角写死字面量)这类多值声明**整型隐身**,HEAD 实存 4 处;另一型是 Tailwind **任意属性形态** `[border-radius:6rpx]` —— 判据的值前导字符类不含左方括号,整型隐身 1 处,而那个 6rpx(=3px)根本不在档位表上。**两型都是门 150 的"未判定"清单反手指出来的**(它按逐值读,所以看得见),即"两台尺子互相指认无人看守"这一缝在本仓第二次现形。现 B3 改为**逐值判**(每个角各自要求档位引用/豁免,`var(`/`calc(`/`0` 放过),值字符类同时排除右方括号(否则后半串 className 被当成一个值读),5 处站点同笔换成档位引用或 `rounded-full`。取证:门 77 自检 53→58 例(正反成对:多值必拦 / 全 var 不误伤 / 任意属性必拦 / 任意属性引档位放行)+ 镜像 8→10 例(夹具端到端逐条点名 `24rpx`、`6rpx`、`dot.tsx`,另两条源码形状反向锁:整串短路写法不得回来、前导字符类必须含 `[`)。
- **SVG 圆角同样受管(B5)**:`rx` / `ry` 会产生圆角,JSX 内联 SVG 必须写 `rx={rnRadius.xs}`;**静态 `.svg` 资产**没有 JS 通道,取值须等于档位值(偏档就近吸附、等距取小),确属形状需要则加 `<!-- radius-exempt: 原因 -->`。
- **生成式 HTML/CSS 字符串**(cli 分享页、`packages/shared/src/design/design-templates.ts`、扩展 content script、api 的 `swagger-theme.ts` 等拼 CSS 文本处):**不得写死数字**,用同表插值 `border-radius: ${RADIUS_CSS_PX.md}`(`import { RADIUS_CSS_PX } from '@ihui/design-tokens'`),不新增第二份真相。
- **真圆/胶囊豁免**:头像 / 装饰点 / 红点 / 进度环 / Switch 拇指 / 半高胶囊输入框**不得方档化把形状改坏**。优先 `size / 2` 表达式;确需保留数值必须在同行或紧邻上行写 `radius-exempt: <一句话原因>`(JS/TSX 用 `//`,CSS 用 `/* */`,静态 .svg 用 `<!-- -->`),不得静默写死。
- **守门**:`scripts/check-radius-single-source.mjs`(guardian 第 **77** 项,blocking)三判据 —— A 档位表四处对账(改一处忘改另一处即红)、B 端内取用必须引用档位(B1 数值与字符串形态 / B2 本地常量 / B3 CSS 字面量 / B4 任意值类 / B5 SVG rx-ry / **B6 引用 `rnRadius`、`RADIUS_CSS_PX` 却没在本文件 import** —— 本门判 HEAD 而 `pnpm typecheck` 只跑 worktree,悬空标识符属于"两边都不红"那一类)、C 覆盖面对账(含圆角的跟踪文件要么落在 SCAN_DIRS,要么在 `OUT_OF_SCOPE` 写明为什么不适用)。**B 的棘轮锚点是「该文件 HEAD 版本自身的违规数」**,不是手工清单 —— 只拦"这次改动把绕档加回来了",不拦仓库既有债。2026-09-24 换锚的起因:并行会话的索引层重建把 309 个路径整文件回写成迁移前的旧基线(**HEAD 积累 1179 处**),静态清单对此完全绿灯;同时全量审计改判 **HEAD blob 而非工作树**,因为滞后的旧草稿会被按磁盘读误记成本仓债务(一道与真实改动无关的红门只会逼人 `--no-verify`,连带废掉全部守门)。`scripts/radius-single-source-baseline.json` 降级为人工兜底,**现须为空**(镜像测试钉死)。判据**不锚定行首**,故 `width:16px; border-radius:50%` 同行多声明、TS 里生成的 CSS 一样可见。按文件自验:`--files <a> <b>`;紧急跳过 `HUSKY_SKIP_RADIUS_GUARD=1`;自检 `--self-test`(49 例,含真实表端到端对账、静态/内联 SVG 两种语义、NaN 防回归、HEAD 锚点四方向正反例、多行 import 不误伤),镜像测试 `node --test scripts/tests/check-radius-single-source.test.mjs`(4 例,含"锚点必须是 HEAD 而非静态清单"的装车证明)。容器纯圆违规仍由 `scripts/check-rounded-full.mjs`(第 11 项)管,两条互补不互替。

### Button 高度档位守门(强制,2026-09-07 立)

- **`@ihui/ui-react` 的 `<Button>` 禁止用 className `h-*` / `w-*` 覆盖高度宽度**,必须用 `size` 档位:`xs`(h-7 px-3 text-xs, 28px)/ `sm`(h-8, 32px)/ `default`(h-9, 36px)/ `lg`(h-10, 40px)/ `icon-2xs`(h-7 w-7, 28px)/ `icon-xs`(h-8 w-8)/ `icon-sm`(h-8 w-8)/ `icon`(h-8 w-8)。需要新高度先在 `packages/ui-react/src/components/button.tsx` size 表立档,禁止逐处打补丁。**2026-09-21 修正文档漂移**:`icon-xs`/`icon-sm`/`icon` 三档当前**同值**(均为 32px,仅保留名称以兼容既有调用),28px 图标请用 `icon-2xs`;此前本文档误写为 h-7/h-8/h-9 三档不同 —— 这正是"按文档用 `icon-xs` 期望 28px → 不达预期 → 再以 `className="h-7 w-7"` 覆盖 → 触发本守门"违规链的成因。守门脚本(`check-button-height.mjs`)的档位清单已改为从 button.tsx **动态解析**,不再手抄。
- **豁免(不属 Button token 体系)**:原生 `<button>` 自绘按钮(IDE 面板 / spec-panel / chat 密集工具条的 24px 紧凑档为有意设计)、`Input` / `SelectTrigger` / `Skeleton` / 图标 svg 等非 Button 元素;Button 上的 `h-5`/`h-6`(24px/20px 紧凑档,存量 45 处表格行操作钮/侧栏密集场景)暂豁免,后续统一时先立对应档位再迁移。
- 守门:`scripts/check-button-height.mjs`(精确 JSX 开标签解析,零误报;拦 h-7 及以上覆盖 + 校验 size 值合法性)+ pre-commit blocking(紧急跳过 `HUSKY_SKIP_BUTTON_HEIGHT_GUARD=1`)。

### 中文字体 + 图标垂直对齐硬约束(强制)

- **根治方案**:`apps/web/app/globals.css` 设 `--text-vcenter-offset: 0.3px` + 全局规则 `:where(button, a, [role='button'], [role='menuitem']):has(>svg):has(>span) > span { transform: translateY(var(--text-vcenter-offset)); }`,button/a 内 "icon + 中文 span" 同行布局自动应用,text-xs (12px) 用专用 0.7px 规则。配套:`apps/web/src/lib/nav-styles.ts` 5 个共享类 + `<CenteredText>` 组件(`apps/web/src/components/common/CenteredText.tsx`)。
- **编码强制 — icon + 文字必须包 `<span>`(2026-09-15 组件层根治)**:根因是补偿规则 `:has(>span) > span` 只能命中 `<span>` 元素,裸文本节点永远无法对齐(复发 3 次以上)。**根治:`@ihui/ui-react` 的 `<Button>` 已内置 `wrapRawTextChildren`,裸文本 children 自动包裹 `<span>`,使用 Button 无需手写。** 手写原生 `<button>`/`<a>`/`[role=button]` 时仍**必须**自行把文字包 `<span>`(自检口诀:**button 里有 svg,则每个直接文字都写 `<span>文字</span>`**)。守门:`apps/web/e2e/icon-text-alignment.spec.ts`(阈值 |delta| ≤ 0.15px)+ code review 必查项。
- **守门**:`apps/web/e2e/icon-text-alignment.spec.ts` 阈值 |delta| ≤ 0.15px,漏改 → CI fail。**严禁** `-mt-px` / `margin-top: -1px` 反向微调 hack。
- **数字计数徽章强制规范(2026-09-15 立,复发根治;2026-09-27 收掉模板自身的胶囊矛盾)**:按钮/标签内嵌数字计数(如待办数、队列数)**禁止**裸 `<span class="px-1">数字</span>` 手搓 — 行高、字形侧空、位数变化都会导致数字在色块内偏移。**必须**使用确定性居中模板:`inline-flex h-4 min-w-4 items-center justify-center rounded-md bg-*/… px-1 text-[10px] font-semibold leading-none tabular-nums`。要点:`inline-flex + justify-center` 保证任意位数水平居中;`h-4 + leading-none + items-center` 保证垂直居中(不依赖字体行高);`min-w-4` 保证单位数不掉宽;`tabular-nums` 保证多位数字等宽不抖动。
  - **该模板原写的是 `rounded`(=8px),这是一处自相矛盾**:2026-09-23 档位表收口后 `rounded` 等于 8px,套在 `h-4`(16px) 上正好是**半高胶囊** —— 强制模板在产出本节另一条绝对规则("禁止胶囊")所禁止的形态,而守门 11 的"红点底"豁免族替它背书,于是按模板写出来的徽章永远清不掉。现改 `rounded-md`(6px);**模板的中心一字未动**(确定性居中仍靠 inline-flex + justify-center + leading-none + tabular-nums + min-w-4),只是半径不再等于盒高一半。`apps/miniapp-taro/src/components/UnreadBadge.tsx` 与 `MessageTabs.tsx` 两处未读数徽章即照旧模板产出、已随本次收口。
- **flex 自居中 span 豁免 vcenter 补偿(2026-09-15)**:全局补偿规则(`packages/design-tokens/src/styles/tokens.css`)已改为 `> span:not([class~='flex']):not([class*='inline-flex'])` — 徽章等 inline-flex span 由 flexbox 自居中,整盒再被 translate 下推 0.3/0.7px 会"偏下"(用户实测复发)。**手写 flex 徽章 span 天然豁免,无需任何反向 hack;严禁给徽章再加 -mt/translate 反补**。

### 页头返回键单一源头(强制,2026-09-25 立)

- **起因(用户实拍)**:"小程序端所有返回按钮怎么是返回两个字"。web 端 2026-09-08 就把返回键收进顶栏唯一实现(`GlobalTopBar.tsx` 的 `TopBarBackButton`,lucide `<ChevronLeft />`,36×36 方块,守门 46 拦私接),**但小程序端没人跟着收口**:20 处把「返回」两个汉字当箭头渲染(各页自写 `tt('common.back','返回')` + 各自的字号/颜色)、8 处用字符 `‹` 当箭头。守门 102 当时的字符集只有右向 `›»→》` —— **一条门只管自己立项那一型,就是这一型的洞**(判据必须覆盖门自己产出的形态,同 §4 圆角那一条的教训)。
- **唯一写法**:小程序端一律 `<BackChevron onTap={…} />`(`apps/miniapp-taro/src/components/BackChevron.tsx`,内部 `LineIcon name="chevron-left"` 复用 `components/LineIcon/icons.ts` 既有素材 ⇒ 零新素材、零新色值;方块 72rpx=36px 与 web 的 36×36 同档)。返回动作(`navigateBack` / `switchTab` 降级 / 回登录页)语义**留在调用方**,组件只管外观。web/RN 侧对应写法是 `ChevronLeft`(lucide-react / lucide-react-native)。**禁止**在页头返回位写文字、写 `‹`,或端内自造第二种几何(负 margin 凑命中区、每页各自 `w-[80rpx]` 预留宽度 —— 后者会被组件行内 `width` 静默盖掉)。
- **文字标签的合法例外**:错误态卡片按钮、步骤回退链接这类**「返回」是按钮文案而非导航箭头**的位置保留文字,在**可点元素起始行或其紧邻上行**写 `back-label-exempt: <原因>`。它与 `glyph-arrow-exempt` 是两条独立通道,不得混用(给 GA1 开第二道口就等于没有那条锁)。带宾语的标签(`backHome` / `backLogin` / `prevMonth`)按判据结构上不纳,**不得为消红去改 i18n 文案**。
- **守门**:并入 102(见下一节末条),新增判据 **GA4** 与 S0 机制清单第 4 条(`components/BackChevron.tsx` 被摘线或无人 import 即红)。
- **其余三端的同一型(2026-09-25 收口;现值一律按当次实测取,勿照本行数字派单)**:`packages/app/src/features/**` + `apps/mobile-rn/src/screens/**` + `apps/extension` 的页头返回已全部收进 RN 侧唯一实现 **`packages/app/src/components/BackChevron.tsx`**(经 `@ihui/rn-app` 导出;lucide-react-native `ChevronLeft`,命中块 36×36 对齐 web `TopBarBackButton`)。**`colorScheme` 是必填 prop 而非 `= 'light'` 默认值** —— 守门 91 只把"带默认值的形参"认作静默脱主题开关,必填改由 `tsc` 强制,比门更严。HEAD 面实测 `<Text>{t('common.back')}</Text>` 仍有 42 处、`{'‹'}` 0 处:那 42 处是**错误态/空态卡片按钮、翻页、弹窗关闭** —— 那里「返回」是文案不是箭头,换裸箭头反而不表意,逐条带 `back-label-exempt: <原因> until YYYY-MM-DD`(该族已登记进守门 108 的 `FAMILY_LIFETIME_DAYS`,取 365 天:它是结构性定性不是待偿债务,短周期到期只会逼人删标记、删了又被 GA4 判红,两道门互咬)。**这两处"已知残余"已于同日由后续票收掉(2026-09-26 按 HEAD 复验;照旧执行会去找不存在的洞)**:① S0 现登记 5 条,含 RN 共享层那份 `packages/app/src/components/BackChevron.tsx`(该门注释原文即"只登记端内那一份时,共享层被摘线无人喊红");② 混合形态 `← 返回` 由新增判据 **GA5** 纳管,两处真站点的落地形态是 `ModelConfigDialog.tsx` **保留字形 + 带原因 `back-label-exempt`**(实测 `←`=1 / 豁免=1 / `ChevronLeft`=0 —— 正确处置是带理由豁免而非矢量化:该组件作用域内既无 `t` 也无 `tokens`,换矢量要改别人的组件签名)与 `Selecter.tsx` 删字形留文字 + 豁免注明"i18n 与矢量通道双缺"。

<!-- ⚠️ 本行是上面那条的**改写前原文**(2026-09-25 由提交 259fb1262d9 收口后已失效:其中"packages/app 223 处/168 文件""mobile-rn 5 处""共享层两个 NavBar 与 PayResultScreen 用 ‹"四项现值全变),照它派单会把已做完的 180 处重做一遍。保留只为不丢行(§12),勿照它执行。 -->

- **未收口的同一型(如实登记,不是已完成)**:`packages/app/src/features/**` 有 **223 处 / 168 文件**、`apps/mobile-rn/src/screens/**` 有 5 处仍是 `<Text>{t('common.back')}</Text>`;共享层两个 NavBar 与 `PayResultScreen` 用 `‹`。它们由 102 的**按文件 HEAD 棘轮**兜住"不得再加",清理另计一批。

> ⚠️ **上面那格的"223 处未收口 / 清理另计一批"已被 2026-09-26 的实测取代 —— 照它派单会去清一批不存在的东西。**
> 现读(HEAD 面,五个 UI 目录):`common.back` 共 **243 处**,其中 **194 处是 `label=` / `aria` / 配置字段**
> —— 那是喂给 `<BackChevron label={…}/>` 的**无障碍名称**,不是渲染出来的文字(设计要求:可见文案求箭头、
> 无障碍名称须脱离上下文成立)。剩下 **49 处**确实是 `<Text>{t('common.back')}</Text>` 这种"文字摆在按钮里",
> 但**逐条带 `back-label-exempt: <原因>`**(错误态/空态卡片按钮、翻页、弹窗关闭 —— 那里「返回」是按钮文案不是
> 页头箭头,换成裸箭头反而不表意,见本节"文字标签的合法例外"),抽查三处(`AgentDetailScreen.tsx:42`、
> `ActivityDetailScreen.tsx:50`、`miniapp plaza/detail/index.tsx:242`)标记都写在可点元素起始行的紧邻上行、
> 与判据要求的三个落点一致。所以守门 102 的 GA4 报 **0** 不是"判据没看见",而是"该收的收进了唯一实现、
> 该留的按理由留"。**这一批没有剩余清理项**;真要动这 49 处,得先逐条论证它其实是箭头位 —— 那属于改判据,
> 不属于清存量。`‹` 字符在 HEAD 面还剩 10 处,但都不在可证 affordance 语境(GA1 实判 0)。

### 区段头「更多」入口单一源头(强制,2026-09-24 立)

- **三端唯一实现**:RN = `packages/app/src/components/MoreLink.tsx`(经 `@ihui/rn-app` 导出);web = `apps/web/src/components/common/view-more-link.tsx`;小程序 = `LineIcon name="chevron-right"`(`components/LineIcon/icons.ts` 已含该图标,零新素材)。**禁止在任何端再自拼第四份**「标签 + 箭头」。
- **箭头必须是矢量图标,禁止用字符 `›` / `»` / `>` 充当**。字符与文字没有共用度量,`align-items:center` 居中的是各自行盒:实测「更」12px 墨迹偏上 1.0px,而 `›` 在 14/16/18/20px 档偏下 1.0 至 1.5px —— 是**相加**关系,所以"标签小、箭头大"的旧写法错位 2.0 至 2.5px。算式与完整根因见 `docs/UI_GUIDELINES.md` §4.6。
- **标签与箭头必须同一光学尺寸(RN/web 12px、小程序 24rpx)+ 同一缩放倍率**:旧写法箭头带 `allowFontScaling={false}` 而标签允许缩放,用户调大系统字号即分叉。RN 侧把倍率同时喂 `Text.maxFontSizeMultiplier` 与图标 `size`(clamp 1..1.4),并保留 `includeFontPadding: false`(Android 字体留白不对称,端内已有 8 处同写法)。
- **禁止反向微调补丁**:`marginBottom: -2` / `translateY` / 负 margin 凑数(与本节上方"严禁 -mt-px"同一条禁令,换机型即失效)。
- **文案统一 `common.more`**(更多 / More / もっと見る / 더 보기),不得再写"查看更多 / 查看全部 / 完整榜单";但 `accessibilityLabel` **刻意保留长口径**(如"查看更多模型")—— 可见文案求短,无障碍名称须脱离上下文成立。

<!-- ⚠️ 本行是守门 102 的**立项原文**(三类判据),已于 2026-09-25 被下方"四类判据"条就地改写取代;其中"字符集只有 ›»→》""三端唯一实现""--self-test 45 例"三项均已是旧值,照它派单会漏掉左向字形与 GA4。保留本行只为不丢行(§12),勿照它执行。 -->

- **守门**:`scripts/check-glyph-arrow-icon.mjs`(guardian 第 **102** 项,blocking)三类判据:**GA1** 整格字符箭头 `›»→》`(或 `{'>'}`)在可证 affordance 语境(自身或 ≤8 层祖先带 `on*Press/Click/Tap` 或 `role="button"`,或祖先为 `Link/Pressable/Touchable*/Button`)当图标;**GA2** 「更多」类标签与**同词干** `*Arrow*` 样式配对后箭头字号 > 标签字号(单位不同不判红,计入 undetermined);**S0** 三端唯一实现被摘线或无人 import(防"造好没装车")。口径同 77/83:全量判 HEAD blob、`--staged` 判索引、**棘轮锚点 = 该文件 HEAD 自身违规数**(行尾字符箭头存量 70 处只报数不拦,免得恒红逼人绕过钩子连带废掉全部门);行内豁免 `glyph-arrow-exempt: <原因>`(须带原因);取证 `--self-test` 45 例(含阳性对照与棘轮四向)+ 镜像测试 13 例。紧急跳过 `HUSKY_SKIP_GLYPH_ARROW_ICON=1`。

- **守门**:`scripts/check-glyph-arrow-icon.mjs`(guardian 第 **102** 项,blocking)四类判据:**GA1** 整格字符箭头 `›»→》‹←`(或 `{'>'}`)在可证 affordance 语境(自身或 ≤8 层祖先带 `on*Press/Click/Tap` 或 `role="button"`,或祖先为 `Link/Pressable/Touchable*/Button`)当图标 —— **左向 `‹`/`←` 于 2026-09-25 补入**(立项时只有右向四字,于是小程序 NavBar 与 7 个页面头拿 `‹` 当返回图标长期零判据);**GA2** 「更多」类标签与**同词干** `*Arrow*` 样式配对后箭头字号 > 标签字号(单位不同不判红,计入 undetermined);**GA4**(同日新增)整格子内容是「返回」类文案(`t('common.back')` 这类键名末段为 `back`/`back<数字>` 的 i18n 调用,或字面量 `返回`)且处在可证 affordance 语境 ⇒ 即"把返回两个字当返回箭头";**带宾语的标签刻意不纳**(`backHome`/`backLogin`/`prevMonth` —— 那些是按钮文案,换成裸箭头反而不表意);**S0** 共享唯一实现被摘线或无人 import(防"造好没装车"),现登记 4 条:三端「更多」箭头 + `components/BackChevron.tsx`。GA1 与 GA4 **共用同一遍遍历**(`walkAffordanceChildren`),两条对"可证 affordance"的定义必须同形 —— 各写一遍必然在栈深/祖先窗口上漂移。口径同 77/83:全量判 HEAD blob、`--staged` 判索引、**棘轮锚点 = 该文件 HEAD 自身违规数**(行尾字符箭头存量 70 处只报数不拦,免得恒红逼人绕过钩子连带废掉全部门);行内豁免**两条独立通道** `glyph-arrow-exempt: <原因>`(GA1/GA2,只认命中同行)与 `back-label-exempt: <原因>`(GA4,认命中行、可点元素起始行、及其紧邻上行)—— **宽严刻意不对称**且各由自检钉住:GA1 若也按块放行,一个标记就能救整棵子树,"一行救不了别处"那条反向锁就没了;GA4 若只认同行,人按直觉把标记写在可点块上就永远不生效(初版即如此,四处真实站点全部落空 ⇒ 恒红)。**原因不得由注释闭合符冒充**(裸标记 + `*/` 曾被读成"带了原因"⇒ 整行免检;该洞自 §22c 立项起就在 GA1 里,由 GA4 的自检反手抓出,现两条通道同锁,全仓零裸标记存量故改严无债务)。取证 `--self-test` 66 例(含阳性对照、棘轮四向、预筛必须是判据字面量超集的对账)+ 镜像测试 14 例(含"GA1/GA4 豁免宽严不得悄悄对齐"与"预筛漏字形=门对该形态全盲"两条锁)。紧急跳过 `HUSKY_SKIP_GLYPH_ARROW_ICON=1`。

### 禁止分割线(强制)

- 禁止 `<hr>` / `divide-y` / `divide-x` / 单边 `border-t/b/l/r` 当分割线。允许:容器完整描边(`border border-border`)、背景色对比(`bg-card` vs `bg-background`)、间距分隔(`gap-*`)。

#### 单边 border 的明确区分(解决两位开发者的分歧)

历史上出现两种冲突理解:一位开发者认为**所有** `border-t` 一律禁止;另一位认为显式 `border-t border-border/50` 一律允许。现统一为以下规则,**所有** `border-t/b/l/r` 用法必须归入下列两类之一,不得作为纯分割线使用:

- **允许(结构性边框,非分割线)**:
  1. 卡片/面板边缘:作为完整边框系统的一部分,例如带 `border` 的容器其 header 条用 `border-b` 作为容器底边(边框系统的收口,而非在两项之间画线);
  2. 引用块 / 语义强调左边框:`border-l-4` 等作为语义强调(如 blockquote、强调条),而非分隔两项内容的线;
  3. IDE diff 面板等编辑器内 chrome:有意为之的编辑器界面元素。
- **禁止(纯分割线)**:仅在**列表项之间**或**区块之间**用 `border-t/b/l/r` 画一条分隔线,此类场景必须用 `space-y-*` 间距,或 `bg-muted` / `bg-card` 背景对比替代。

> 注:低透明度单边边框(如 `border-t border-border/50`)是典型的"伪分割线",单独守门脚本 `scripts/check-no-divider.mjs` 对其作 **WARN** 告警(非阻塞),提示开发者确认是否为纯分隔用途;纯分隔线务必改用间距或背景对比。此后若确需单边边框,仅限上述"允许"三类情形。

### 禁止渐变遮罩(强制)

- 任何容器禁止 `mask-image` / `-webkit-mask-image` / `linear-gradient` 用作边缘淡出。用显式 UI 元素("查看更多"按钮 / 计数徽章 / 分页)替代。

### 禁用原生提示窗(强制)

- **禁止**使用原生浏览器提示:`title` 属性 / `alert()` / `confirm()` / `prompt()`。必须使用项目自有的 `Tooltip` 组件(`@/components/feedback`)统一提示样式。

### 图标统一用图标库(强制)

- **UI 图标一律用矢量图标库,禁止 emoji 充当图标。** 图标指导航项 / Tab / 按钮 / 状态指示 / 徽章 / 占位默认图标 / 关闭·发送·播放等操作图标 / 章节标题前缀 icon / 等级奖牌等界面元素。
- **web 端**:统一 `lucide-react`(`import { X } from 'lucide-react'`),渲染 `<X className="h-4 w-4" />`;配置数组里的 `icon: 'emoji'` 须改为 `icon: LucideIcon` 组件引用后在渲染处用 `<item.icon className="..." />`。
- **移动端(React Native)**:统一 `lucide-react-native`(`import { X } from 'lucide-react-native'`),渲染 `<X size={16} color={'#6b7280'} />`;颜色按原 `Text` 的 `text-*` 类或 style 颜色意图转十六进制,尺寸按原 fontSize(-xs→12 / -sm→14 / -lg→18 / -2xl→24)。
- **Taro 小程序端(miniapp-taro)**:统一 `apps/miniapp-taro/src/static/images/icons/` 下的 lucide 风格 SVG(24x24,stroke `#6366F1`),渲染 `<Image className="..." src="/static/images/icons/xxx.svg" mode="aspectFit" />`;动态切换用条件 src(如 `{liked ? heart-fill.svg : heart.svg}`);缺失图标用 `node scripts/gen-taro-lucide-icons.mjs <name>` 从 lucide 提取生成(别名如 bar-chart-3 自动解析)。
- **禁止**在 UI 图标位置使用 emoji(如 🏆 🤖 🔥 ✅ ❌ 🎤 🔔 等)。新增图标须先查 `lucide-react` / `lucide-react-native` / Taro `static/images/icons/` 是否有对应名称,无则用近义图标,不得回退 emoji。
- **例外(不强制)**:文档 / 营销页正文里的装饰性 emoji(提示 💡、技术栈列表、章节叙述中的 emoji)属于内容文案,不在此限;但同一页面应风格统一,优先用图标库。
- 守门:`scripts/check-no-emoji-icons.mjs` 扫描 UI 图标位 emoji(icon 字段 / JSX 渲染位 / 三元条件图标),豁免注释、i18n 参数、表情面板、`'★'.repeat` 评分字符串、表格布尔标记、docs/marketing 正文;guardian-runner 第 11h 项(pre-commit staged 模式阻塞 commit)。

### 品牌/平台图标必须是官方真实图标(强制)

- **展示第三方品牌 / 平台身份的图标(模型、推广平台、接入平台、社交渠道等),一律使用该品牌官方发布的真实图标,严禁手绘、自造、近似模仿或用字母占位图代替。** 手绘品牌图标属于品牌失真,被用户发现即为交付事故(2026-09 头条/WordPress/微博图标事故教训)。
- **官方图标获取链路(按优先级)**:
  1. 官网 favicon 直取:`https://<domain>/favicon.ico`;
  2. Google favicon 服务:`https://www.google.com/s2/favicons?domain=<domain>&sz=128`;
  3. 备源:`https://favicon.im/<domain>?larger=true`;
  4. 品牌官方 CDN / 官网 press-kit 资源。
     下载后必须人工目检(Read 工具查看图片)确认是官方真实图形,不是字母占位或第三方水印版。
- **存放与引用分离**:
  - 发布平台官方 favicon:`apps/web/public/publish-icons/`(引用方:`apps/web/src/components/publish/platform-icon.tsx` 的 `PNG_ICONS` 注册表);
  - 页脚/跑马灯 mono 白色剪影素材:`apps/web/public/footer/tuiguangpingtai/`(引用方:`apps/web/src/components/marketing/footer-data.ts`,带 `mono: true` 反色适配)。
    **两套素材禁止混用**:官方彩色 favicon 不得覆盖页脚 mono 剪影同名文件,反之亦然;新增图标先进 `publish-icons/`,不与 footer 素材重名。
- **深色模式可见性**:黑色系官方图标在注册表中标 `invertInDark: true`(深色下反色为白);彩色官方 favicon 无需反色,靠 `bg-primary/10` 容器衬托。
- **素材卫生**:被替代的旧图标文件必须同任务删除,不留孤儿资源;`.ico` 仅在拿不到 `.png/.svg` 时使用。
- 违反本条 = 未完成:用户要求"官方图标"时,交付物必须逐个可溯源到官方域名,可附来源 URL 清单。

### 圆角容器内 absolute 子元素避让

- 父容器 `rounded-xl` + `overflow-hidden` 时,贴边子元素**禁止** `h-full`/`w-full`,用 `top-<radius> bottom-<radius>`(纵向)或 `left-<radius> right-<radius>`(横向)替代。映射:`rounded-lg`→`top-2 bottom-2` / `rounded-xl`→`top-3 bottom-3` / `rounded-2xl`→`top-4 bottom-4`。
- 拖拽手柄用双层 div 结构(外层命中区 + 内层可见细线),**禁止** `before:` 伪元素方案。

### 浮动弹层内边距规范(2026-09-21 立,起因:context-usage-ring 弹层漏 padding 内容贴边)

新建弹层/浮层必须按内容类型对齐以下四档,**禁止自创中间值**:

| 类型          | 判定特征                                                            | 容器内边距                              | 参考实现                                                              |
| ------------- | ------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------- |
| 内容面板      | role=dialog,含标题/表单/信息块(permission、通知、设置卡)            | `p-3`                                   | permission-mode-popover、permission-history-panel、context-usage-ring |
| 菜单/选项列表 | role=menu / select 列表,纯按钮项                                    | `p-1`                                   | Select、TagsView 右键菜单、canvas-overlay                             |
| 轻提示/徽标   | tooltip、角标、状态胶囊                                             | `px-2 py-1`(mini 用 `px-1.5 py-0.5`)    | ByokIncomeChart、activity-bar                                         |
| 复合面板      | 输入行+列表的 overflow-hidden 面板(mention、selector、终端 overlay) | 外层零 padding,**行级自带** `px-3 py-*` | file-mention-popover、context-selector-popover                        |

- 反例存档:自定义 portal 弹层从 Radix 迁出时最易漏搬 `p-3`(PopoverContent 自带),新建 portal 容器必须显式写内边距档位。
- 同族组件必须同档:同一直弹层家族(如 permission 系列)不允许 p-2/p-3 混用。

### 跨端样式同步铁律(强制)

- web 与 miniapp-taro 视觉必须完全一致(除平台独占差异:登录页小程序端无、rem2rpx 自适应缩放、原生导航栏/tabBar 用 `Taro.setNavigationBarColor`/`setTabBarStyle` 而非 CSS var 等)。**任何一端改了样式/组件/主题,必须同步另一端**——这是交付门槛,不是可选项。
- **单一真相源**:design-tokens 在 `packages/design-tokens/src/styles/tokens.css`(`@theme` + `:root` + `.dark`)。miniapp-taro 的 `app.css :root/.dark` 由 `scripts/sync-miniapp-tokens.mjs` 自动同步(**禁止手改 app.css 的 token 块**;改 token 改源头 + 跑同步。2026-09-25 该生成器由端内 `apps/miniapp-taro/scripts/sync-design-tokens.mjs` **搬到根 `scripts/`**(旧路径已删,`pnpm --filter @ihui/miniapp-taro sync-tokens` 入口不变)—— 搬的理由是架构门 103 逼出来的:守门 36 与生成器必须共用**一份**取源实现,而取源住在 `scripts/lib/design-token-blocks.mjs`;端内脚本向上摸工具层是 D2 反向依赖(且 tooling 声明 `exported:false`),把实现下沉进 contract 包又让根工具层按路径穿透包实现细节(D3),而工具层**按包名解析不到 workspace 链接**(实测给根 package.json 加 `@ihui/design-tokens` 后 pnpm 写出的根链接是悬空的,门 78 当场判红,已回退)。摆在工具层 ⇒ 四个端的同名机制全在一处,零穿透零豁免。旧文档那句"根 `scripts/` 下没有同名文件"就此作废,照它跑 `node scripts/sync-design-tokens.mjs` 现在仍会报模块找不到 —— 新名字是 `sync-miniapp-tokens.mjs`)。另有 `scripts/check-miniapp-taro-design-tokens.mjs` 与 `scripts/check-miniapp-tokens-sync.mjs`(后者为 guardian-runner 第 36 项实际调用项)校验同步一致性。
- **主题系统**:miniapp-taro 主题根为 `ThemeRoot`(`@/components/ThemeRoot`,内部调用 `useThemeRoot()`),每个路由页 .tsx 顶层须 `<ThemeRoot>...</ThemeRoot>`。设置页切换主题必须调用 `@/lib/theme` 的 `setThemePreference`(同步原生导航栏/tabBar 配色 + 广播事件),**禁止**只用 `Taro.setStorageSync('theme', ...)` 而不同步原生 chrome(否则导航栏/tabBar 不变色)。
- **禁止深色科技风回潮**:app.css 不得再出现 `page{background:#121217}` / `*{font-family!important}` 等全局深色强制覆盖;页面/组件 CSS 不得硬编码禁用色板(`#00f2ff`/`#121217`/`#1f1f28`/`#1a1a2e` 等),一律改用 `var(--color-*`)。
- **禁止同名工具类冲突**:miniapp-taro 的 `app.css` 不得重定义 `.text-primary`/`.mt-*`/`.flex*`/`.align-*`/`.justify-*` 等与 web 端 Tailwind 同名同义类(语义冲突),局部样式用语义化类名 + `var(--color-*`)。
- **端内 CSS/色值副本一律是派生态,禁止手改、也禁止"只加拦红不写回"**(2026-09-25 立,由实测逼出):`packages/design-tokens/src/styles/tokens.css` 是唯一色值源;所有 v3 端副本(`apps/miniapp-taro/src/app.css` 的受管块、`apps/mobile-rn/global.css`、`packages/design-tokens/src/rn-tokens.ts`、`tailwind-alpha-plugin.js` 的 `ALPHA_USAGE`)必须由 `scripts/lib/design-token-blocks.mjs`(取 tokens.css 的**唯一实现**)+ 各自的 `scripts/sync-*.mjs` 派生,并挂在 `scripts/lib/pre-commit-hook.js` 的 `TOKEN_SYNC_TARGETS` 表上按**各自触发面**自动写回(token 类看 tokens.css,用量类看 v3 三端源码)。两条不可违反的写法:**① 原位写回,禁止整块替换** —— 副本里非受管的端内档(如 `global.css` 的 `.dark` 那批 `--rn-*`)与解释性注释必须逐字留在原位,值已等价时连字节都不改(这才是幂等);实测旧写法整块替换一次就抹掉 13 行在用深色档 + 6 段注释,那正是 RN 侧自动同步长期只拦红不回写的真实原因(接上必炸)。**② 取源只能有一份实现** —— 生成器与守门各写一遍取值逻辑必然不同形(实测:生成器只取 `@theme` 首个非贪婪块,漏掉后续 `:root` 块里的 `--color-*-rgb` 三元组,一次"同步"就删掉守门 93 R6 要求必备的 alpha 通道)。**新增跨端档位的正确顺序**:改 tokens.css 一处 → 跑对应 sync 脚本(或让提交链自动跑)→ 由门复核;不得在任何端手写色值、色名常量或第二张登记表。
- **小程序端有第 6 个受管区 = `app.css` 的 `@theme` 色档注册区**(2026-09-25 立,`f17b5fbdda`):它不是"又一份色值表",而是 Tailwind v4 的**注册动作** —— v4 只把 `@theme` 里的 `--color-*` 认作主题档,色值若只落在普通 `:root/.dark` 上,`bg-muted`/`text-foreground` 这一整族 utility **一条都不产出**(实测真构建 0 落地)。档位仍**唯一派生自** `deriveMiniappManaged(tokens.css, 'light')` 再剔掉 `-rgb` 三元组 —— `-rgb` 进 `@theme` 会长出 `.bg-muted-rgb` 这类脏档(v4 见 `--color-*` 就当颜色档)。三条不得照抄的形态选择:**只用普通 `@theme { }`**;`@theme inline` 会把亮档值烘进每条规则 ⇒ 深色档整族失效;`@theme reference` 会给每条规则附一份亮档 fallback(更贵,且把"没定义"洗成"默默用亮档")。守门 36 现同时核 `:root` / `.dark` / `@theme` 三面(无缺档 + 逐位同值 + 反向拦 `-rgb` 脏档)。**同批判死一条判据缺陷**:`check-miniapp-css-landing` 的 C1 原拿源类名直比产物类名,而 weapp-tailwindcss 会把类名标点转写(`[→_b ]→_B /→_f :→_c (→_p )→_P ,→_m .→_d +→_u`),于是 505 条"缺项"里 474 条是比对噪声;现按同表**正向**转写后再比,且分"原名直中/转写后中"两态计数(合计会把"表漏一条"藏起来)。**判落地的门如果比错形态,症状不是差一点,而是把真信号淹在噪声里** —— 上一轮正是靠"缺项恰好成族"才定位到色档整族没落地。
- **Tailwind v3 消费端的 `/alpha` 修饰符由共享 preset 负责**(2026-09-25 立):v3 的 `bg-<色>/<透明度>` 语法要求把颜色拆成 rgb 分量,而 preset 若只给 `var(--color-x)` 单值,这一整类类名在 miniapp-taro 端**根本不产出 CSS** —— 页面"样式没生效"却零报错,而 web/extension(v4,原生支持)一切正常,即"手机上改了 web 没改"的又一成因。修法唯一:`packages/design-tokens/src/tailwind-alpha-plugin.js` 为每个颜色键补 `<key>-rgb` 三元组并 `addUtilities`,挂在该 preset 的 `plugins` 上,**禁止**在端内自拼 `rgba(...)` 绕过。**新增颜色档必须同时进这个插件**,否则新档又缺 `/alpha`。**这条散文自 2026-09-25 起是机器判据,不再是约定**:守门「跨端色值 token 漂移对账」新增 **R6**(`scripts/check-cross-end-tokens.mjs`,编号以 runner 现值为准)扫三个 v3 消费端源码(`apps/miniapp-taro/src` + `apps/mobile-rn/src` + `packages/app/src`)里真实写过的 `bg|text|border-<档>/<数值>` 类名,逐条要求 ① 该形态在 `ALPHA_USAGE` 登记过、② 插件的 `buildAlphaUtilities` 真产出那个选择器、③ 每档在 `tokens.css` 有 `<key>-rgb` 三元组;反向也判 —— 登了却没人用的行按"清单腐烂"红(每条登记 = 主包里一条死规则)。判不出的形态(动态拼接类名)与默认色板(`bg-white/50`,v3 原生支持)只报数不判红;行内豁免 `alpha-plugin-exempt: <原因>`,须带原因且只救本行/紧邻上一行。口径同 77/83/98:全量判 HEAD blob、`--staged` 判索引⊕HEAD,**登记表与用量必须同面**(表读磁盘 + 用量读 HEAD 会在并行会话刚补行的瞬间产出假红/假绿);腐烂判据在 staged 面走**棘轮**(存量只计数不判红),否则一枚与之无关的提交会被别人欠的债钉红 → 逼人 `--no-verify`。取证:真 miniapp 源码上选择器 73 → 91、REMOVED 0 / ADDED 18(含任意值形态 `bg-muted/[0.12]`);NativeWind 侧装 release 包 v21→v22 逐指标 A/B 六项读数完全相同(零回归)。R6 上线首跑即抓出 `bg-muted/40` 是**从注释文本里 harvest 出来的假用量**(该文件头注的用量统计当时没剥注释),已删该行,现 17 形态。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/miniapp-taro build`,exit 0)的 154 个 wxss 里 **0 个产出规则**(`.flex{` `.items-center{` `.rounded-xl{` `.bg-muted{` `.text-sm{` 逐条为 0);而 **base/preflight 层是在的**(`border:0 solid;box-sizing:border-box;margin:0;padding:0`)⇒ 症状精确到"**只有 utilities 层为空**"。阳性对照:同一份 dist 里 `app.css` 手写类名 75/98 可查到规则。**机制不是** `config/index.ts` 那行 `tailwindcss: { enable: true, config: {} }` —— 实测把它改成真实配置路径后 A/B/C 三次构建产物**逐字节相同**(该键不是开关);真因指向 weapp-tailwindcss 5.2.9 **自带一份 vendored tailwind**(`dist/tailwindcss-*.js`)并由它自己的 generator 出 CSS,项目 `tailwind.config.ts` 的 content globs 没有喂进那条链。**后果**:本条 R6 判据守的是"源码用量 ↔ 登记表 ↔ 插件产出"三者一致,这三者都在**项目侧**;小程序实际产物由第三方 generator 决定,R6 的绿灯**不等于**到端生效。改这行配置的爆炸半径见 O62附③(主包余量 42,401 B 而 utilities 约 42,392 B,以及 `text-card` 同名双义会被注入白底白字)。**⚠️ 本条"0 个产出规则"的前提已于同日作废**(2026-09-25 深夜,`edb225c909` + `f17b5fbdda`):真产物落地率现测 **740/771 = 96.0%**,命名档与项目色档都已产出,主包余量 **113,504 B**;当年"整端一起变"的半径已被拆成两步分别验证。现行事实与判据见本节末"v4 色档注册区"条与 PROJECT_PLAN O62 附⑧/附⑨,勿照本行执行。
- ⚠️ **v3 端禁止裸 rpx 长度写进 `text-` / `border-` 任意值**(2026-09-25 立,守门 93 新增 **R7** 是它唯一的判据):`text-[28rpx]` / `border-[2rpx]` 会被 Tailwind v3 解析成**颜色属性**(`color: 28rpx` / `border-color: 2rpx`),整条声明无效且不报错。决定对错的是**单位不是前缀** —— `px`/`rem`/`em` 在 v3 下本就正确落 `font-size`/`border-width`,**不得**给它们加 `length:`(纯噪音);只有 rpx 这一族坏,因为 v3 的类型推断不认 rpx。正确写法:`text-[length:28rpx]`。颜色形态 `text-[var(--color-x)]` / `text-[color:…]` 不受本条约束。存量 1,331 处已清零,故 R7 在 HEAD 上是零容忍。
- ⚠️ **但 `apps/miniapp-taro` 实际跑的是 Tailwind v4.3.3,不是端内 `package.json` 声明的 v3**(2026-09-25 实测,详见 PROJECT_PLAN O62附⑤):`weapp-tailwindcss@5.2.9` 引用的是 **`@tailwindcss/postcss`**(v4 专属入口 —— v4 的 main 导出会**直接报错拒绝**被当 postcss 插件用),产物 base 层也带 v4 指纹(`border:0 solid` 合并写法、`--tw-gradient-position`,且无 v3 独有的 `--tw-bg-opacity`)。**取版本别只读 `package.json`** —— 那样会得到相反结论(本会话就错过一次:用 `resolve('tailwindcss/package.json')` 探到 `MODULE_NOT_FOUND` 就否掉了正确说法,真实依赖名是 `@tailwindcss/postcss`)。两条后果:① 上面 R7 拦的裸 rpx 错属性在 **v3 与 v4 上行为一致**(已用 v4 + 真实源码逐字复测),所以判据与那 1,341 处改写**不因版本换人而作废**;② 但"miniapp 的 `/alpha` 靠 `tailwind-alpha-plugin`"这个前提**尚未证实** —— v4 走 `color-mix` 是另一条路,插件的确定价值在 **mobile-rn(NativeWind v3)**。带真 theme 复测前,**不要把 R6 对 miniapp 那一侧的登记要求当成已证事实扩严**。
- ⚠️ **上面两条 `/alpha` 相关规则的实际适用范围已收窄,读的时候带上这条**(2026-09-25 实测,详见 PROJECT_PLAN O62附⑥):守门 R6 的"必须登记进 `ALPHA_USAGE`"只对**真 v3 消费端**(`apps/mobile-rn/src` + `packages/app/src`)生效 —— `apps/miniapp-taro` 跑的是 v4(见上一条),而 **v4 对 `/alpha` 是原生的**(带真 `@theme` 复测 7/7 命中,含项目自定义色与 `[0.12]` 这类任意值,声明体 `color-mix(in srgb, …)`)。收窄后量到的结论要如实说:**`tailwind-alpha-plugin` 至今零真实使用者**(全仓 64 处 `/alpha` 用量全在 miniapp,登记表已按用量归零为 `{}`),连 `--color-*-rgb` 三元组变量也**只有注释在引用、产物 0 处使用**。**别把它当已交付能力引用** —— 它是等 mobile-rn 真写 `/alpha` 时才生效的机制。要扩面先跑 `node scripts/sync-alpha-usage.mjs`(表由它导出,**不要手改**);R6 报"未登记"时的修复出口也是这条命令,不是"去 ALPHA_USAGE 加一行"。

- **守门**:`scripts/check-miniapp-taro-style-parity.mjs`(RULE-1~5:禁用色板 BLOCK / 其他 hex WARN / app.css 回归 BLOCK / 路由页 ThemeRoot BLOCK / tsx 内联 hex WARN / 已删除装饰类复用 BLOCK),接入 pre-commit(`HUSKY_SKIP_MINIAPP_PARITY=1` 跳过)+ `pnpm check:all`。新增路由页忘挂 `ThemeRoot`、改回深色科技风、复用已删装饰类,均会阻塞提交。

---

## 5. 后端约束

- Drizzle ORM 0.38 + postgres-js。用 Zod 校验请求参数。复用 `packages/auth` 的 authenticate 函数;admin 路由用 preHandler 统一校验(roleId >= 1)。幂等操作用 `onConflictDoNothing`。slug 从 name 自动生成。API 响应统一 `{ code, message, data }` 格式。

### 鉴权面公开化必须显式列举(强制,2026-09-23 立)

- **禁止**用兜底正则把 `/api/<前缀>/[^/]+` 这类"参数路由"形态当作公开面 —— 它会连同**静态子路由**一起放行。实测 `agents.ts` 的 `^/api/agents/[^/]+$` 让 `/agents/health` 游客可访问,而 `/agents/need-tasks` 的 handler 依赖 `request.userId`,游客走到它不是 401 而是 **500**:fail-open 直接崩在鉴权层后面,比 401 更难发现。
- 正确做法:**显式白名单列路径**(如 `/api/agents`、`/api/agents/list`、`/api/categories/list`),详情路由用正则时**必须**配一张静态段排除表(`AGENTS_PROTECTED_STATIC_SEGMENTS`)。**新增 `/agents/<静态段>` 的 GET 路由时必须同步登记该表**,否则会被当成游客详情放行。
- 游客视图的公开数据由 handler 自证:强制 `status=published` + `sanitizePublicAgent` 脱敏,并且测试要断言"未发布读不到 + 脱敏字段不出现",不得只断言 200。
- 改动 router 鉴权面前必须做**影响面核查**:全仓 grep 该路径(含 `packages/` 与各端)确认没有未登录调用方;本仓这两个端点的实际调用方为 0。
- 部署侧 nginx 与蓝绿 nginx 是两份配置:边缘限流(`limit_req_zone` / `limit_req_status 429` / `error_page 429`)改一处必须同步另一处,docker 侧 zone 名须带 `docker_` 前缀以免与 `deploy/nginx/conf.d/*.conf` 重名(Nginx 同 http 上下文重名 zone 会**启动失败**)。静态自检:`apps/api/tests/o5-nginx-edge-ratelimit.test.ts`。
- **对外能力白名单对账(2026-09-28 立,拍板②"按能力目录逐项放开"的尺子)**:`scripts/check-public-exposure-list.mjs`
  钉四件事 —— X1 每条 `@public-exposure` 申报必须配对一个**精确** `location =`(前缀/正则型一律红,AGENTS §5
  那条"兜底正则连静态子路由一起放行"的失效型就是它要防的);X2 站点壳与 docker 壳按 **(capability, method,**
  upstream)** 同批(两侧 zone/map 名必须 `docker_` 前缀错开,重名是 nginx **启动失败**不是警告;公网 path 命名空间
  分离:`/ai-service/api/mcp` 而非 `/api/mcp`,后者被 `packages/api-client` 的 MCP **项目** CRUD 占着);X3 资格
  (申报的 scope 必须在 `packages/types/generated/capabilities.json` 里 `host:'ai-service'` ∧ `thirdPartyEligible:true`
  ∧ `dataClass≠platform`);X4 每个开放 location 必须剥掉 `X-IHUI-Principal` / `X-Api-Key`(公网能携带内网身份头
  = 冒名面);X7 **已放开的每一条必须回查 ai-service 的 handler 真的解析身份** —— 身份出口名单从
  `app/core/jwt_auth.py` / `app/services/capability_gate.py` 的顶层函数**现读推导**(不抄第二份名字,空名单判
  "未判定"),允许同文件一跳委托,解析不到一律点名"未判定";刻意**只对开放集判红**(HEAD 面 266 条可解析路由里
  212 条不引用身份出口,全量判红就是恒红门)。
  **定级与问责入口(不要写一个跑不通的出路)**:手动 / 只读档 —— `node scripts/check-public-exposure-list.mjs`
  (缺省判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工),取证 `--self-test` 与
  `node --test scripts/tests/check-public-exposure-list.test.mjs`。**刻意不接提交链**(它判跨面一致性,一次只改
  一份配置的提交结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`,连带废掉全部守门,§12e 同型),
  这条定级由镜像测试 T6 钉住:**它一旦被接进 `guardian-runner`,那条测试就红** —— 想接必须先带着
  "为什么现在能接"的证据改那条测试,而不是悄悄接线。
### 认证不等于授权:身份只能从承载层显式入参进来(强制,2026-09-27 立,批 59/60)

- **规矩**:任何"按 id 读/改/删某条归属型记录"的入口,**令牌主体必须由承载层用显式入参传给被调方**,不得(a)取到手后原地丢弃,也不得(b)从"被操作记录"的字段里反推,更不得(c)接受请求体/参数里自报的同名字段。`app/routers/sessions.py` 的 13 个端点全部 `Depends(get_current_user_id)` 却**一次都没用** —— 那就是"过了认证等于没鉴权"的成批形态。
- **身份不得住在客户端可整写的字段里**:`threads` 没有 user_id 列,属主与角色只活在 `metadata` JSON 里,而 `thread/metadata`(merge=False)与 `POST /sessions/threads`(body.metadata)都是客户端可写的。实测三条后果(修复前):一次整写把 `userId` 抹掉 ⇒ 重启后线程**无属主** ⇒ 按"无从对账"语义任何登录连接都能继续用它对话;patch 里塞 `userId:"victim"` ⇒ 把会话**认领给别人**;自报身份**赢了**令牌主体。
- **唯一写法**:清单与规则只有一份 —— `app/services/session_store.py` 的 `IDENTITY_METADATA_KEYS` + `scrub_identity_keys`(创建时剥调用方自带值)+ `carry_identity_keys`(更新时以已落库那份为准)。引擎侧 `_restamp_identity` 与存储侧 `create_thread`/`update_thread_metadata` 都调它,**不得在端点里再抄一份键名判断**。跨线程待决表(`_permission_requests`/`_elicitation_requests`)一律存"带主记录"(future + thread_id + user_id),注册点盖章,结算点判定共用 `_principal_allows`。
- **可清除的是业务元数据,不是授权凭据**:codex 的 ClearableField(patch 值为 None 即删键)语义对这两个键刻意不适用;要清身份必须走显式的属主变更接口,而不是顺手 patch。
- **两条同形的拒绝口径**:`elicitationId` 这类"唯一把手"端点,"没这条"与"不是你的"必须**同形回包**(否则端点变成存在性预言机);而 `approval.respond` 那类请求自带 `threadId`,回 `foreign_thread` 不增加信息,可以区分 —— 差别写在处理器注释里,别抄成统一样式。
- **测试口径**:越权用例必须断言"**副作用没发生**"(future 未 done、待决表未动、`grant_spy == []`、删除返回 `deleted=False` 且 store 未被调用),只断言错误码会放过"先改了再抛 403"。同时必须成对留正向对照(同属主仍能完成、无属主线程行为逐字未变)—— 只留前者,门就可能只是把功能改坏了。
- 取证入口:`python -m pytest apps/ai-service/tests/test_thread_identity_immutable.py apps/ai-service/tests/test_engine_ownership_from_connection.py`;两相控制探针 `.ihui-agent/tmp/b59/probe_meta2.py`(同一份脚本跑修复前 HEAD 归档报 VULNERABLE、跑工作树报 SAFE)。

### 测试隔离铁律(强制,2026-09-12 立)

- **测试一律禁止连生产库**。pytest 用例不得对生产 PostgreSQL(`8810`)/ Redis(`8811`)产生任何写入副作用;需要 DB 的路径必须 mock 或注入隔离实例。
- 共享连接池(fastapi 侧 `app.core.db_pool.get_shared_pool`)是唯一生产入口,**所有**用例必须通过 `monkeypatch` 替换为 mock,不得直接 acquire。
- autouse fixture 只兜住"默认安全",用例体内若先于 `monkeypatch` 触发真实写入(如 `TestLoadActiveTests::test_duplicate_skill_skipped` 在 patch 前 `await create_test(...)`),仍会污染库 —— 新增用例必须自查执行顺序。
- 回归事故:`agent_ab_tests` 曾累计 17 行 `skill-a` 垃圾数据,并使 `len(stopped)==2` / `totalTests==3` 断言漂移。修复落点为 `apps/ai-service/tests/conftest.py::_isolate_ab_test_db`。
- 自检:<全量回归跑两次> + <跑前跑后 `agent_ab_tests` 行数不变> 才算通过。

---

## 5b. `.git` 存续治理(强制,2026-09-12 立)

**背景**:本机宿主 safe-delete 层会**整体删除**工作区 `.git`,累计触发 15 次(commit/husky、rebase、filter-repo、`reset --hard`、`git stash create`、本机直推 Gitee/GitCode 后 5s 窗口)。`CODEBUDDY_SAFE_DELETE_ENABLED=0` **已证明防不住**。

**现状结构(不可改回)**:

| 路径                                                                                                                                                                                                                                                                       | 角色                                                        |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `D:/IHUI-AI/.git`                                                                                                                                                                                                                                                          | **28 字节指针文件**(`gitdir: D:/IHUI-AI-git-repo`),不是目录 |
| `D:/IHUI-AI-git-repo`                                                                                                                                                                                                                                                      | 真实 gitdir(544MB),在工作区之外                             |
| `D:/IHUI-AI.git-backup-20260912`                                                                                                                                                                                                                                           | gitdir 完整备份,守护的本地恢复源                            |
| **守护**:计划任务 **`IHUI-AI git-guardian`**(每 2 分钟)→ `"C:/Program Files/nodejs/node.exe" D:/IHUI-AI/scripts/git-guardian.mjs`。本机**未安装 nssm**,故未采用 `--daemon` 常驻服务形态,改用脚本自带的 schtasks 兜底(2026-09-12 15:30 实测启用;16:12 实测**自愈已生效**)。 |
| 分层自愈:`指针 → 环境 → HEAD 语法 → 嵌套 ref → 本地备份 → 远端`;每步破坏性覆盖前先归档现场。实测自愈 **0.9s**(refs 自愈实测 **0.14s**)。                                                                                                                                   |

**守护**:计划任务 **`IHUI-AI git-guardian`**(每 2 分钟)→ `"C:/Program Files/nodejs/node.exe" D:/IHUI-AI/scripts/git-guardian.mjs`。本机**未安装 nssm**,故未采用 `--daemon` 常驻服务形态,改用脚本自带的 schtasks 兜底(2026-09-12 15:30 实测启用;16:12 实测**自愈已生效**)。
分层自愈:`指针 → 环境 → HEAD 语法 → 嵌套 ref → 本地备份 → 远端`;每步破坏性覆盖前先归档现场。实测自愈 **0.9s**(refs 自愈实测 **0.14s**)。
**守护**:计划任务 **`IHUI-AI git-guardian`**(每 2 分钟)→ `"C:/Program Files/nodejs/node.exe" D:/IHUI-AI/scripts/git-guardian.mjs`。改用脚本自带的 schtasks 兜底(2026-09-12 15:30 实测启用;16:12 实测**自愈已生效**)。

> **本句原先还写"本机未安装 nssm,故未采用 `--daemon` 常驻服务形态" —— 该前提已失真并于 2026-09-24 更正**:实测 `C:\Windows\System32\nssm.exe` 在位(331264 字节),且存在 NSSM 服务 **`IHUI-GIT-GUARD`**(`git-guardian.mjs --daemon`,进程自 2026-09-15 08:48:42 常驻),与上面那条每 2 分钟的计划任务**同时运行**。即"守护"当前是**双执行体并存**(常驻 daemon + 2 分钟 tick)。本票只如实登记、不动任何一侧:砍哪一侧属 `.git` 存续决策(§5b 的分层自愈依赖"每 2 分钟必有一次巡检"来兜底,而 daemon 侧是否等价覆盖尚未取证),且它**没有任何到人出口**(实测 `grep -niE "mail|smtp|resend|notify|ftqq|sct|pushplus|alert" scripts/git-guardian.mjs` 零命中),故不影响 §5e 通道口径。
> 分层自愈:`指针 → 环境 → HEAD 语法 → 嵌套 ref → 本地备份 → 远端`;每步破坏性覆盖前先归档现场。实测自愈 **0.9s**(refs 自愈实测 **0.14s**)。
>
> **本节三条"机器事实"于 2026-09-24 在 `G:` 那份 checkout 上被逐条测反,照抄会误诊(与 §15b 的盘符补注同族)**:
> ① **活 gitdir 就是工作区内的 `G:\IHUI-AI\.git`(实体目录 2.4GB,不是 28 字节指针)**,`refs-manifest.json` 在它里面;而 `G:\IHUI-AI-git-repo`(810MB)**没有 manifest**,是无人读取的残壳。守护自评 `node scripts/git-guardian.mjs --status` 对当前形态给 `pointerOk:true / gitdirOk:true`。⇒ §5b"必须外置 + 指针"与 §12d"禁止迁出工作区/改指针(**并行会话的恢复逻辑会把指针文件当损坏清除**,2026-09-10 立)"这两条在本机**互斥**;本票只如实登记、**不动任何一侧**——把 `.git` 迁出去是一次破坏性迁移(会连带移动 2.4GB 对象库并撞上清理层),不是"修复文档与现状不符"。
> ② `git remote get-url origin` 实测 **`ssh://git@ssh.github.com:443/IHUI-INF-AI/IHUI-AI.git`**,且 `ls-remote` / `fetch` / 后台 `git-push-guard` 推送全通;上方"origin 实测为 HTTPS `github.com`,`ssh://git@ssh.github.com:443` 形态在本机从未成立"在这一台机上**不成立**。
> ③ `git config --local http.proxy` 实测 **unset**;上方"本机已另配仓库级持久代理(因为钩子进程不继承 shell env)"在这一台机上**不成立**(ssh-over-443 通道不需要它)。
> **口径(本条的真正约束):凡「盘符 / gitdir 形态 / 远端 URL / 代理 / **服务承载体(nssm·计划任务·daemon) / PowerShell 解释器身份**」六类,每次使用前按当次实测取值**,不得把另一台机的现状当本机事实写进判据,也不得据此推断"通道坏了"。 —— **本行 2026-09-24 被并发旧基线回写带走过一次,同日原地补注使其重新成为"本侧修改"** —— 后两类是 2026-09-28 由本轮实测补进来的(G-320):本侧 checkout 量到 `Get-Service IHUI*`=0 条、`C:\Windows\System32\nssm.exe` 不存在,而 `powershell.exe` 实读 **5.1.26100.6584**、`pwsh.exe` 只在绝对路径 `C:/Program Files/PowerShell/7/pwsh.exe`(7.6.4)且不在该会话 PATH。**它们不属于原四类,所以四类口径守不住它们**——这正是"文档里的本机事实"最典型的腐烂方式:写的人没打算让它过期,读的人把它当成了不变量。

### 嵌套 ref 存续(2026-09-12 立,与 `.git` 同源问题)

**现象**:`refs/remotes/origin/main` 反复变 `[gone]`、`refs/tags/backup/push4-*` 反复"仅远端" → 守门 `30a`(commit 丢失防护)**抖动性阻塞**(刚 fetch 完绿,宿主一清理又红)。曾误判为"真的丢 commit"。

**机理(对照实验,可复现)**:宿主清理层删除 gitdir 下 **depth ≥ 2** 的嵌套命名空间目录:

| ref 路径                                       | 层级 | 结果                   |
| ---------------------------------------------- | ---- | ---------------------- |
| `refs/heads/main`                              | 1    | ✅ 存活                |
| `refs/tags/nightly-*`                          | 1    | ✅ 存活                |
| `refs/remotes/origin/main`                     | 2    | ❌ 目录被删 → `[gone]` |
| `refs/tags/backup/push4-*`、`refs/tags/<ns>/*` | 2    | ❌ 目录被删 → "仅远端" |

且 `git update-ref` 对这类嵌套 ref **返回 0 却不落盘**(静默失败)—— 所以"fetch 成功"不等于"ref 存在"。

**边界精化(2026-09-15 受控实验二次实证)**:

- `git update-ref` 对 **refs/heads 一级松散 ref 可靠**(新建 `refs/heads/<name>` 与覆盖既有 loose 均真实落盘);静默失败**仅限 depth ≥ 2 嵌套命名空间**(`refs/remotes/**`、`refs/tags/<ns>/**`)。
- 假成功链路:update-ref 先成功创建嵌套目录(如 `refs/remotes/origin/`)→ ref 文件被秒清 → 同命令内紧接的 `rev-parse` 读回 `packed-refs` 旧值。**"读回旧值"不代表 packed-refs 被改过**,勿朝 packed 损坏方向排查。
- bash 直写(`printf >` / `cp`)与 bash `mv` 对 gitdir 任意路径均真实落盘——清理层只针对 git ref 命名空间,不是文件系统级拦截;`git pack-refs --all --prune` 重写 `packed-refs` 本身可靠。
- 因此**对齐 `origin/main` 的唯一正解**:`git fetch origin main` → `node scripts/git-refs-heal.mjs`(FETCH_HEAD 权威值 → 固化 packed-refs)。**禁止**手工 `git update-ref` / 松散直写 `origin/main`(写了也会被清,且假成功会误导排查方向)。

**解法(载体替换,非 workaround)**:把嵌套 ref 固化进 `packed-refs`(gitdir **顶层单文件** → 清理不到),期望值另存 `refs-manifest.json`(同为顶层文件)。松散文件被删也照样解析。

- 期望值清单:`<gitdir>/refs-manifest.json`(含 `refs/remotes/origin/main`、`refs/tags/<ns>/*`)
- 固化:`git pack-refs --all --prune`(松散文件被 prune,只剩 packed)
- 手工命令:

  ```bash
  node scripts/git-refs-heal.mjs                   # 离线:按清单重建缺失 ref + 固化(无需网络)
  node scripts/git-refs-heal.mjs --status          # 只看健康
  node scripts/git-refs-heal.mjs --refresh-remote  # 联网:从 origin 校准全量 tag/heads 后固化
  ```

- 守护已在每个 tick 检查 `refsOk`,缺失即离线重建并写审计日志 —— **一般无需人工介入**。
- **`origin/main` 以 `FETCH_HEAD` 为准**:fetch 写入的松散 remote-tracking ref 在 **1 秒内**即被清理,
  若 `packed-refs` 残留旧值,同 sha 也会显示 `## main...origin/main [ahead 1]`。
  修复器已内建该规则(`git-refs-heal.mjs` / 守护 `healRefs()` 均读取 FETCH_HEAD 权威值)。
- **禁止**把嵌套 ref 的存续寄托在松散文件上(必被清理);**禁止**用 `HUSKY_SKIP_COMMIT_LOSS_CHECK=1` 绕过 30a —— 先跑 `git-refs-heal.mjs` 判定是真丢 commit 还是 ref 抖动。

**铁律**:

- **禁止**把 `.git` 改回目录形态:`git init` 必须带 `--separate-git-dir=D:/IHUI-AI-git-repo`;不带参数重建会把 544MB 实体拉回工作区,直接暴露给宿主删除。
- **禁止**删除/清理 `D:/IHUI-AI-git-repo`、`D:/IHUI-AI.git-backup-20260912`,以及两目录的 `*.broken-*` 归档。
- **现场归档一律落在 `gitArchiveDir()`(现 `D:\DevEnv\backups\git\`),手工抢修也不得例外**:把 `.git*` 的归档 `mv` 到**工作区根目录**等于把恢复现场送回宿主清理层的射程内(§5b 背景),而 §15b 已把项目根排除在备份落点之外。2026-09-24 03:13 一次手工 `mv` 就在一级目录留下 7 项 `.git.broken-remote-*`(3.2MB refs 快照 + 4 个 24 字节 `gitdir: G:/...` 旧指针),由守门 44(根目录整洁,blocking)检出后收口 —— 判据本身有效,缺的是"人工路径也必须走 `gitArchiveDir()`"这句。
- **禁止**手工 `git init` 抢修`:git` 消失 —— 先等守护(≤2 分钟;急可 `schtasks /run /tn "IHUI-AI git-guardian"` 立即触发),再查 `.workbuddy/git-guardian.log`(健康时**不写行**,别把「无日志」当没跑)。
- git 调用**不得依赖环境**:脚本一律 `execFileSync(<绝对路径 git>, ['-c','safe.directory=*', ...])`;服务账户(LocalSystem)与交互账户的 `safe.directory` 互不相通。
- **提交/推送**:**本机就是生产机**(`IHUI-API` 的 nssm `AppDirectory=D:\IHUI-AI\apps\api`、`IHUI-DEPLOYLOOP` 的 `AppDirectory=D:\IHUI-AI`,部署日志实测)。**【2026-09-24 深夜就地更正:这句在本侧 checkout 不成立 —— 用户明确指正"本机不是生产机,是开发机",三重实测印证:`Get-Service -Name IHUI*` 计数 0、`Win32_Service` 按 `DisplayName -Like *IHUI*` 零条、`Get-NetTCPConnection -State Listen` 对 8801/8802/8803/8810/8811 全部零命中(web/api/PG/Redis 无一在跑),而 `schtasks /fo CSV` 里仍有 16 条 `IHUI*` 计划任务 —— 形态是"任务在、服务与端口不在"。与 §5b/§15b 同族:上面那串 nssm `AppDirectory` 是 `D:` 那份 checkout 的历史事实,不得当本机现值照抄。后果是实打实的:本会话有 agent 据此把 D20/D29 的建表迁移按住,理由是"部署环会自动应用迁移=生产爆炸半径",该理由在本机不存在。口径:凡"要不要因为怕影响生产而暂缓"的判断,**先实测服务与端口**,查不到=开发机,按常规动作走;但反过来也别把"没有服务"读成"随便动" —— 无 PG 端口意味着迁移在本地连应用与验证都做不了,验收只能走守门 49 的离线判据(B1–B5)并明写"`--db` 模式本机不可用",不得让绿钩子冒充"迁移已验证"。取证陷阱:`powershell.exe -Command "... \$_.Name ..."`(双引号+转义)会**静默返回空**,看似"什么都没找到";判"零命中"必须换单引号包整段命令复测,否则把工具失效当成了事实。】**本行曾被 `978480078d0`(converge 的 worktree-preserving 合并)整文件回写吞掉一次,同日原地重打。`origin` 实测为 **HTTPS** `https://github.com/IHUI-INF-AI/IHUI-AI.git`,且 **HTTPS 直连 `github.com:443` 时通时不通**(同一分钟内 `git fetch` 可成功而 `git push`/`git ls-remote` 连拒 4 次)。唯一稳定通道是**本机代理 `http://127.0.0.1:7897`** —— `deploy/win/ihui-deploy.ps1` 每轮就是用它 fetch(日志行 `git 网络走代理 http://127.0.0.1:7897`),所以"部署环能取到远端而 agent 推不动"不是玄学,是**没走代理**。旧文档写的 `ssh://git@ssh.github.com:443/...` + 仓库级 `core.sshCommand` 形态**在本机从未成立**:`core.sshCommand` 未设置,`ssh.github.com` 握手可通但 `~/.ssh/id_ed25519`、`id_remote_control` 逐把 `git ls-remote` 均 `Permission denied (publickey)`(无任何已登记公钥,登记属账号侧动作)。⚠️ `~/.git-credentials` 在本机**并不存在**,不要按它取 token(HTTPS 走的是凭据管理器)。**agent 需要 git 网络时的两种写法**:临时用 `export http_proxy=http://127.0.0.1:7897 https_proxy=…`(或 `git -c http.proxy=…`);**本机已另配仓库级持久代理** `git config --local http.proxy/https.proxy = http://127.0.0.1:7897`(2026-09-23 立),因为 post-commit 的 `git-push-guard` 与其后台 worker 是钩子派生的 git 进程、**不继承任何 shell 的 env**,没有仓库级配置就仍然直连撞墙(此前"commit 成功、push 反复 failed"的真因即此)。该配置落在指针文件指向的 `D:/IHUI-AI-git-repo/config` 里,**不在工作树内 ⇒ 不受"旧基线整文件回写"影响**,也不会被并发会话抹掉;换网或代理关停时用 `git config --local --unset http.proxy` 撤销即可。新克隆/新机需重跑一次这条配置。本机**不直推 Gitee/GitCode**,交给 `mirror-to-cn.yml` 镜像收敛。自 2026-09-12 15:30 起 gitdir 已移出工作区(仅剩 28 字节指针),本地 git 写操作不再暴露给宿主批量删除层。
> **⚠️ 上面那句「无 PG 端口意味着迁移在本地连应用与验证都做不了」已被 2026-09-29 实测推翻，勿照它派单、也不要据此把票挂成「等环境条件」**：
> 那次实测列的是 **8801/8802/8803/8810/8811** 五个端口，而**本机 PG 与 Redis 从来不在那两位上**。2026-09-29 现读：
> `postgres`(PID 5416)在 **`0.0.0.0:5432`**(服务 `postgresql-x64-18` = Running/Auto，版本 18.6)、`memurai` 在 `127.0.0.1:6379`、
> 另有 `redis` 服务 Running;`node`(api)在听 **8802**、`python`(ai-service)在听 **8803** —— 只有 `web` 8801 不在听，
> IHUI* 服务形态仍为 0(所以「这台机不是生产机」那半句仍然成立)。权威入口是被审应用自己的连接串：
> `apps/api/.env` 与 `apps/ai-service/.env` 的 `DATABASE_URL` 一直写着 `postgresql://…@127.0.0.1:5432/ihui`、
> `REDIS_URL=redis://127.0.0.1:6379`。⇒ **库是真能连的**(实连成功:读到 `current_database()=ihui`、账本 286 行对 journal 296 条、`llm_call_logs` 26 列 68 行),**迁移也是真能应用的**(D172 那枚已在一次性临时库里执行两次、验过幂等与列/索引形状)。
> 但**别把这两句读成"守门 49 的 `--db` 档本机可用"**——它此刻仍判不了:候选表只有 $IHUI_PSQL、一个 D 盘硬路径和 PATH,而本机 psql 真身在 `C:/Program Files/PostgreSQL/18/bin`,于是报 `spawnSync psql ENOENT`。**这条 ENOENT 曾被下一轮会话读成"本机没有 PG"并据此把票挂成"等环境条件"**(D172 实错)—— 报错形状不等于原因,已另计票(补 C 盘安装目录探测 + 全落空时打印试过的候选清单)。`scripts/check-migration-from-zero.mjs` 能跑(它自带 `PG_CLIENT_DIR` 默认值),当次 RC=1 的原因不是"没有 PG",而是连接角色无 `CREATE EXTENSION vector` 权限,5 枚 vector 迁移失败并级联报出缺表缺列,与本票改动无关。
> `CREATE EXTENSION vector` 权限 —— 5 枚 vector 迁移失败并级联报出缺表缺列，那 5 枚与本票改动无关)。
> **口径(比原判据更窄也更可执行)**：判「某引擎在不在」必须 ① 先读该应用自己的 DSN/连接串，② 再看
> `Get-NetTCPConnection -State Listen` 上**那个地址**归谁;**不得拿 `docs/port-management.md` 里的约定端口当引擎存在性**
> —— 端口表描述的是部署形态，不是这台机装了什么。上面那条 2026-09-24 的更正只答了「这台机是不是生产机」，
> 没有答「这台机能不能验迁移」，而后者被顺手当成了否答案 —— 这正是 D172 一度挂「等环境条件」的唯一成因。
> 附带量到的一条同类陷阱:`powershell.exe` 在本机是 **5.1.26100**(§27 那句「本机 powershell.exe 实为 7.6.2」是
> D: 那份 checkout 的事实),且本机**没有 `pwsh`**;`#requires -Version 7` 因此在本机是**拦自己**而不是拦 5.1 的坑,
> 而 `psql` 不在 PATH(真身 `C:/Program Files/PostgreSQL/18/bin`,守门 49 的 `--db` 档据此判过「ENONET 跳过」)。
  - **🚫 agent 禁止手写 `git push`(2026-09-18 立,"已推完还在等"事故根治)**:①origin 的推送由 post-commit 钩子 `git-push-guard.mjs` 自动完成(内置 ahead 检测+推送+回读验证,幂等)——commit 落地即已推送,**手动盲推必撞 already-pushed 非快进报错**并诱发后台反复干等;②Gitee/GitCode 由 `mirror-to-cn.yml` CI 在 push 后自动镜像(+每日 2 次兜底),**本地手推镜像仓=违反架构**且制造 DIVERGED 竞态。③收尾核验同步状态**只允许** `node scripts/git-push-converge.mjs`(只读判定,六态 ALREADY/PUSHING/PUSHED/SKIP/BEHIND/DIVERGED,不做任何非必要推送);仅在 guard/CI 均失效的应急场景才人工推,且必须先跑该脚本确认状态。多会话并发期:本会话交付已被远端包含(merge-base --is-ancestor 验证)即为完成,本地 HEAD 落后不追、不与并发会话抢 reset/ff。
  - **⚡ 推送异步化(2026-09-18 立,根治第二段)**:guard 检测到 ahead 时默认 spawn 后台 worker 推送(pre-push 质量门不降级,实测单遍 216.8s)并立即返回——**commit 命令秒回,不再被推送拖住**;状态在 `.workbuddy/push-state.json`(running/done/failed,失败由下次 guard 自动重试),converge 读该状态显示 PUSHING;核验若见 PUSHING=正在推,等 1-2 分钟再查即可,勿手动干预。强制同步推送:GUARD_ASYNC=0。(2026-09-18 晚修复:异步 spawn 的 fd 误用 `out.close()` 必抛 TypeError→静默回退同步推送+双重推送竞态,已改 `closeSync`;push-gate 缓存改**内容指纹键控**(HEAD:apps/HEAD:packages 子树+脏文件内容 hash),合并/文档提交不再重跑全量门。)
  - **🔄 主动收敛(2026-09-18 晚立,根治第三段)**:推送遇 non-FF(并发会话推力)时**禁止手工 fetch/merge/push 循环**(实测 3 轮 25 分钟),统一跑 `node scripts/git-sync-converge.mjs`(默认 3 轮:fetch→祖先判定→`merge-tree --write-tree` 索引层合并(**零触碰他人未提交文件**)+commit-tree+update-ref→guard 推送,直至收敛;冲突才需人工)。只读核验仍用 `git-push-converge.mjs`。
  - **🪟 后台进程禁弹窗(2026-09-18 晚立)**:Windows 下 `spawn(detached:true)` + 控制台程序**必弹新 cmd 窗口**——所有 detached spawn 必须带 `windowsHide: true`(guard worker/锁心跳/desktop-dev-saas 已全量修复,仓库既有正例 dev-with-warmup.mjs)。**同理所有 `shell:true` 的 spawnSync/execSync 必须带 `windowsHide: true`**——shell 派生链无 CREATE_NO_WINDOW 时,pnpm -r typecheck 等会为每个子包拉起可见 cmd.exe(2026-09-18 WMI 实锤:一次全量 typecheck ~30 次闪窗,13 处派生点已全量修复)。新增派生点遗漏此参数=用户桌面反复弹窗事故。**⚠️ 默认终端=Windows Terminal 时上述措施全部失效(2026-09-18 深夜 WMI 二次实锤根治)**:WT 宿主忽略 SW_HIDE(windowsHide 无效)且不理 HKCU\Console 离屏档案,同一负载实测 135 个可见窗口样本;已把 `HKCU\Console\%%Startup` 的 DelegationConsole/DelegationTerminal 双双设为 `{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}`(Windows 控制台主机 conhost)——conhost 尊重 SW_HIDE 与离屏档案,复跑同负载可见窗口归零。**禁止改回 WT 委托**;排查弹窗先查此键,取证脚本=.ihui-agent/tmp/console-watch.ps1(WMI 事件+15ms 可见窗口采样,后台 PS 任务约 2 分钟即被杀,足够覆盖一次守门链)。
    - ⚠️ **上面那句给的取证脚本从未入库(2026-09-27 实测三面 + 历史全零命中)**:`.ihui-agent/tmp/console-watch.ps1` 在工作树不存在、`git ls-files` 零命中、`HEAD` 树零条、`git log --all --diff-filter=A -- '*console-watch*'` 也拿不到 ⇒ 它从来没有被版本化过,只是某次会话留在本机临时目录的产物,换机/重装/他人接手一律跑不通。这与本仓记过的 `pnpm c-drive:clean-ours`(根 `package.json` 从来没有这个脚本)、守门 46/11c/50 的假 `HUSKY_SKIP_*` 通道是同一条禁令的反面形态:**文档不得写跑不通的出路**。**在这条指针被换成真实入口之前**,弹窗这一维在提交链上只有**静态**那道门(`scripts/check-no-visible-spawn.mjs`,守门 52,判"派生控制台程序时有没有写 `windowsHide`"),而**静态判据绿不等于运行时不弹窗** —— 五次复发里至少有两次(WT 委托那一次、fork 风暴那一次)恰恰是"逐点参数都补了、现象仍在",只能靠运行时量窗口样本定性。登记事实:**"运行时可见窗口计数"这一维当前无版本化尺子**;补它的一票已立项,落地判据 = `git cat-file -e HEAD:<探针脚本>` 成立且该脚本真能报出"此刻开着的窗口"(阳性对照,不接受恒 0)。
  - **🧬 机器级根治 windowsHide 默认值(2026-09-20 立,"git 窗口一直弹"第四次复发收口)**:实测 Node v24 `child_process` 的 `windowsHide` **默认 `false`**;当父进程**无控制台**(detached+文件 fd 的 guard worker、计划任务、Qoder/Trae/WorkBuddy 等 GUI 宿主派生)时,派生任何控制台程序(git/cmd/pnpm/node)**必分配一个可见控制台**。逐点补参数不可持续(仅 `scripts/`+`.husky/` 热路径就 74 处漏,全仓 ~600 处,且新写脚本继续产生)。**根治**:`node scripts/install-console-window-hook.mjs --apply` —— 把 `scripts/lib/windows-hide-default.mjs` 复制到**仓库外**稳定目录(默认 `<TEMP 上级>/ihui-node-hooks`,本机 = `D:\caches\ihui-node-hooks`;放仓库外是因为 `NODE_OPTIONS` 指向的文件一旦缺失会让**全机所有 node 进程启动失败**,仓库移动/删除不得波及。钩子源文件**必须是 `.mjs`**:`.gitignore` 第 207 行 `*.cjs` 会把 `.cjs` 静默忽略,§23 同类陷阱,一旦被忽略其他机器就装不上),并以 `--import=file:///...` 写入 HKCU 用户级 `NODE_OPTIONS`。钩子给 7 个派生方法补默认 `windowsHide: true`,**显式传 `false` 一律尊重**;落盘前必先用暂存副本做"node 能否带此 NODE_OPTIONS 启动 + 7 个方法是否真被替换"双试跑,不通过则**拒绝写入**,原值备份于同目录 `node-options-backup.json`。实测**ESM 具名导入 `import { spawnSync }` 同样被拦**(loader 早于 builtin ESM facade 创建)。**换机/重装/新克隆后必须跑一次 `--apply`**;`--verify` 巡检不写盘,`--remove` 卸载。**不得因此省略逐点参数**:干净 checkout、他人机器、CI 没有此 env 时仍靠显式 `windowsHide: true` 生效,新增派生点照旧必须写。
  - **🔒 机制守门(2026-09-20 同日补,防第 5 次复发)**:`scripts/check-no-visible-spawn.mjs`(guardian-runner 第 **52** 项,blocking)——括号配平提取 `spawn/spawnSync/exec/execSync/execFile/execFileSync/fork` 调用,**首参可"肯定"为控制台程序**(白名单含 git/node/pnpm/cmd/pwsh/schtasks/ffmpeg…,并覆盖绝对路径与"路径+参数"两种形态)且 options 缺 `windowsHide` 即拦截。刻意**宁漏不误报**(未知变量名一律放过),避免阻塞他人提交;测试代码路径降为 warn(测试由终端 runner 派生,子进程继承已有控制台不新分配窗口)。`--staged` 供 pre-commit 用(runner 自动下发),`node scripts/check-no-visible-spawn.mjs` 为全量审计,`--self-test` 跑 14 例逻辑自检。§22c 镜像测试:`scripts/tests/check-no-visible-spawn.test.mjs`。
  - **🔗 异步推送配套适配(2026-09-18 晚)**:①check-push-sync(pre-commit #29)遇 push-state running(未过期)放行——否则每次 commit 后 270s 窗口内的下一次 commit 必被 #29 阻塞→--no-verify→80 项守门全跳过;②guard worker 串行化:在途 worker 未落定时新 worker 先等后跑,杜绝多个 270s typecheck 并行打满 CPU。③**推送门中断分类(2026-09-18 深夜)**:typecheck 进程被外部杀死(CTRL_C 注入/宿主清树,exit 3221225786/141/143/130/137,实测日志 39 次)≠ 类型检查结论——check-typecheck 按**临时失败 exit 75** 退出→guardian-runner 原样传播→pre-push 写 `.workbuddy/push-gate-last-result.json` 标记+exit 75→guard worker 见新鲜 75 标记**先带 hook 重试**拿真实结论,仍失败才按用户规则 --no-verify 兜底(此前"被杀"被误判成"他人代码失败"直接绕过真实门禁)。④降级判据第三兜底 `head-diff`:push-scope 与暂存区均为空时用 `git diff --name-only origin/main HEAD` 推断推送范围(实测 4 例"无可判定改动范围,无降级"硬拦);⑤push-state 死 pid 自愈:guard 启动发现 running+死 pid → 顺手改写 failed 终态。⑥**typecheck 再入守卫(2026-09-18 深夜,WMI 实测 24.6 万 cmd/2min fork 风暴根治)**:根包 `pnpm typecheck`=typecheck-full.mjs,pnpm -r 一旦把根包纳入执行即无限递归(每层再起 pnpm -r,指数级进程树,实测 ~2000 cmd/秒持续 26 分钟,即用户"一直闪弹 cmd"的主源);typecheck-full 已加 `IHUI_TYPECHECK_FULL_CHILD` 环境变量再入守卫(子层检测到立即 exit 0),勿删。
- **工作区存续自愈(2026-09-23 立,与 `.git` / 嵌套 ref 同源问题)**:宿主清理层同样会**成批删除工作区里的已跟踪目录**(实测同日三轮 137 → 27 → 1 个,命中 `tests/`、`__tests__/` 整目录与安装器位图资源)。缺失只体现为 `git status` 一片 ` D`,而**下一次提交就会把这些文件从版本树里删掉**(等价一次静默回滚)——`.git` 与 refs 早有分层自愈,工作区存续性此前无人管。自愈脚本 `scripts/heal-worktree-tracked.mjs` 判据三条同时成立才恢复:① 工作区缺该文件;② **索引 blob == HEAD blob**(⇒ 无人对它暂存过任何改动,含 `git rm` 的暂存删除);③ HEAD 中该路径存在。故恢复内容按定义零独有数据,他人已暂存的删除**只报数、不代裁**。触发点挂在 `git-guardian` 巡检的**健康轮次早退之前**(计划任务实跑 `main()` 单轮,`startDaemon` 未启用 —— 挂错位置等于永不执行);`--check` 口径保持零副作用,派生一律带 `windowsHide`(§5b)。手动:`node scripts/heal-worktree-tracked.mjs [--dry-run|--json|--self-test]`;跳过 `IHUI_SKIP_WORKTREE_HEAL=1`。故障演练:删 `scripts/brand-foreground-baseline.json` → 跑一轮守护 → 文件自动找回并写审计行「✅ 工作区存续自愈:恢复 1 个被外部删除的跟踪文件」。
- **同一自愈的第二、三层(2026-09-23 补)**:`alignDrifts()` 对齐**幻影漂移**(索引==HEAD 且 工作区内容==该路径某祖先版本 ⇒ 才动;判据直接复用守门 84(原 76),单一真相源);`refreshStaleIndex()` 刷新**落后索引** —— CAS / converge 用 commit-tree + update-ref 推进 HEAD 却**不动主索引**(`git status` 首列 `M `,实测同日 14 个路径),此时一次不带 pathspec 的普通 commit 就把整批文件写回旧版;三条判据同时成立才刷新(① index≠HEAD ② 索引 blob 确为该路径历史版本 ③ 工作区==索引),且**只逐路径 `update-index`,绝不做全局 `git reset`**(会连带 unstage 他人真正的暂存)。挂点:`git-sync-converge` 的成功出口自动调 `--align-drift`(真仓实测已生效)。`--self-test` 共 12 例,含三条反向对照:真编辑不覆盖 / 暂存后又有改动不刷新 / 他人真暂存不刷新。
- **本地恢复源必须增量刷新(2026-09-24 立,补 §5b 唯一的空白层)**:`git-guardian` 只**读**恢复源(`backupOk` 判存在 + `cpSync(BACKUP → GITDIR)`),此前**没有任何环节更新它**。实测 2026-09-24 04:40:`G:/IHUI-AI.git-backup-20260912` 的 main 停在 `f481c39a0`(09-23 20:13),本机 main 已前进 **97 个提交** ⇒ 宿主再删一次 `.git`,从该源恢复等价于**回滚 97 个提交**;若其中含未推送提交,即 09-23 15:49"15 条未推送 commit 对象永久丢失"的同型事故重演。刷新器 `scripts/git-backup-refresh.mjs` 对备份做**增量 fetch**(`+refs/heads/*` + `+refs/tags/*`、`--update-head-ok`,备份是 `.git` 的非裸 cpSync 副本,默认会被 git 硬拒)、`read-tree --reset HEAD` 重建其索引(不触碰任何工作树)、并复制 `refs-manifest.json`(使离线恢复后立刻具备嵌套 ref 自愈)。取证:`--self-test` 9 例(第 7/8 例专测非裸副本形态 —— 真仓首跑就是被它咬出 `refusing to fetch into branch 'refs/heads/main' checked out`;裸仓测不到)、`--check` 在追平前 exit 1、真刷后 exit 0。触发点:计划任务 **`IHUI Git Backup Refresh`**(每 15 分钟,经**纯 ASCII** `scripts/git-backup-refresh-hidden.vbs` 以 SW_HIDE 派生,注册前已用 `cscript //nologo` 实跑预检)。手动:`node scripts/git-backup-refresh.mjs`;判健康:`node scripts/git-backup-refresh.mjs --check`(零副作用)。
  - **触发点已改挂守护 tick(2026-09-24 同日补)**:实测 `Get-ScheduledTask` 全量列表里**已经没有** `IHUI Git Backup Refresh`(与 §26 记的 `IHUI C-Drive AutoMaintain` 凭空消失同型,而同目录其余 `IHUI*` 任务全在位可列 ⇒ 不是查法失效),恢复源因此又落后 100+ 枚提交。现由 `git-guardian` 每轮 `refreshRecoverySource()`:先 `--check`(零副作用、便宜)早退,判落后才跑增量刷新。**教训:关键自愈层不得挂在一个会自己消失的东西上** —— 计划任务可以当兜底,不能当唯一挂点。
- **禁止 `git pull --rebase`**:2026-09-12 15:2x 一次 rebase 崩溃导致真 gitdir 目录被原生删除。同步一律用 `git fetch <remote> main` + `git merge --ff-only FETCH_HEAD`。

**诊断**:

```bash
node scripts/git-guardian.mjs --status      # pointer/gitdir/git/HEAD/dirty/备份/嵌套ref 全量健康
node scripts/git-guardian.mjs --check       # 只检查,异常 exit 1(CI/巡检用)
node scripts/git-refs-heal.mjs --status     # 嵌套 ref 与清单比对(缺失即 exit 1)
schtasks /query /tn "IHUI-AI git-guardian"  # 守护任务实况(本机无 nssm;急用 schtasks /run 立即触发)
schtasks /query /tn "IHUI-AI git-guardian"  # 守护任务实况(另有 NSSM 服务 IHUI-GIT-GUARD 常驻 --daemon,见 §5b 上方更正;急用 schtasks /run 立即触发)
tail -20 .workbuddy/git-guardian.log        # 自愈审计流水(健康时不写行)
```

---

## 5c. 溯源水印与生成产物(强制,2026-09-12 立)

**三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 文件末尾独立不可见行。
**判据(2026-09-12 加严)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。四态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ `未覆盖`。后三者 `watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。

- 新建源文件后必跑:`node scripts/watermark.mjs inject <file>`;然后 `node scripts/check-watermark-coverage.mjs`(pre-commit + CI 门禁,只统计 git 跟踪文件)。
- **门禁是自愈式(2026-09-12 立,根治"恒红只能靠跳过"的历史痛点)**:`check-watermark-coverage.mjs` 检出缺口后**自动 `clean+inject` 回写并 `git add` 回暂存区**,修复后 `exit 0`。因此**任何**来源(生成器 / `sed` / 批量脚本 / 手工编辑)产出的无水印或载荷损坏文件,都不可能进入提交 —— 无需再 `HUSKY_SKIP_WATERMARK_GUARD=1`。
  - 纯判定不改文件(CI / 审计):`node scripts/check-watermark-coverage.mjs --no-fix`。
  - 安全闸:单次缺口 > 200 个时**拒绝**自动回写并报错(疑似批量改写事故,须先排查来源再整体 `inject`),避免静默整体重写。
  - CI 侧仍用 `node scripts/watermark.mjs verify` **严格判定**(不自动修复),保证"本地能自愈、远端不掩盖"。
- **生成器仍应自带注入(更早一步,非唯一防线)**:任何 `writeFileSync` 产出 git 跟踪文件后,必须紧随一次水印注入,失败即 `process.exit(1)`:

  ```js
  execFileSync(process.execPath, [resolve(repoRoot, 'scripts/watermark.mjs'), 'inject', outFile], {
    stdio: 'inherit',
  })
  ```

  - 用 `process.execPath` + 绝对路径,**不要**裸 `node` / `spawn('node')`(依赖 PATH,服务/CI 下会失败)。
  - 参考实现:`apps/miniapp-taro/scripts/gen-i18n-compressed.mjs`。
  - 事故:该生成器此前不写横幅 → 产物 `src/i18n/generated/remote-locales.gen.ts` 长期缺载,使 `check-watermark-coverage` 对已跟踪文件恒红,提交只能靠 `HUSKY_SKIP_WATERMARK_GUARD=1` 绕过。

- **禁止对含载荷文件做文本级批量改写**(reflow、空白归一、正则替换、`sed -i`、编码往返、批量重写):`U+200B`/`U+200C`/`U+200D`/`U+2060` 属 Unicode **Cf 类**不可见字符,会被这类操作静默改写。
  - 事故:`apps/ai-service/**` 等 **144 个已跟踪文件、218 处载荷**被破坏,解码成 `PROVENCE-2026` / `IHUHU-AI` / `IIUIUIUI-AI` / 混入控制字符 —— 旧版门禁只验存在性,损坏长期隐形。修复后 `verify` 新增"载荷损坏"计数并阻断。
  - 需批量改名/改动时:先 `clean` → 改 → 再 `inject`;或直接 `inject`(工具已能识别损坏并清洗重注)。
- 注入是**幂等**的(同一载荷 → 同一字节),重复生成不产生 diff;`clean → inject` 往返**零漂移**(已回归验证:可见内容逐字节不变)。
- `clean` 按**行首锚定**(`© YYYY IHUI AI` / `Provenance-watermarked.` / `[IHUI-AI-PROVENANCE]:`)识别横幅,**不会**误伤源码里的 `BANNER_ID` 常量或正则定义。
- **禁止**为加水印而整体重写文件内容(会破坏零宽溯源链),一律用 `watermark.mjs`。

---

## 5d. 模型密钥引导与 `.env` 回填(强制,2026-09-12 立)

**密钥不入仓、不入聊天记录。** 本机模型密钥的唯一权威来源是百度网盘同步目录 `BaiduSyncdisk/密钥/模型/`(每厂商一个 txt,内容为裸 token)。**盘符不得写死**:本机实测真实库在 **`F:/BaiduSyncdisk/密钥/模型/`**,而 `D:/BaiduSyncdisk` 根本不存在 —— 旧版本节与两处脚本都按 `D:` 写,结果是"读不到文件"被下游门禁报成"凭据失效"(2026-09-24 镜像活性假故障的根因)。解析统一走 `scripts/lib/key-dir.mjs`(`resolveKeyDir('模型')` / `resolveKeyDir('git仓库')`,按 F→D→E→G→C 取第一个存在者);目录仍可用环境变量 `IHUI_MODEL_KEY_DIR` / `IHUI_MODEL_KEY_DIR_GIT` / `IHUI_SECRETS_ROOT` 或 `--key-dir` 覆盖。

- 回填流程:先 `node scripts/env-backfill-model-keys.mjs --verify` 巡检(不写盘),确认无误后 `--verify --apply` 写盘。
- 硬性约束:只写 `.env` 中**值为空**的键;已有值一律跳过、**绝不覆盖**;apply 前自动备份到 `.ihui-agent/env-backup/`(已被 `.gitignore` 忽略)。
- 输出恒为脱敏态 `前6位***后2位 (len=N)`,任何情况下不打印完整 key。`.env` 由 `.gitignore` 第 98 行忽略,**永不入库**。
- 多候选消歧:同一源文件出现多把 key 时(如 AGNES 曾有两把),`--verify` 会逐把请求厂商官方 `/models` 端点,取**真能鉴权通过**的那把 —— 不靠猜测、不靠索引。
- 判读规则:`网络不可达` **不等于** key 无效(本机访问不到 Google,`GEMINI_API_KEY` 探测必然超时);只有 `鉴权通过` 才是有效判据。
- 换机器 / 重装后若 ai-service 静默降级,首选动作就是跑一次本脚本巡检。

---

## 5e. 运维到人通知 = 邮件单通道(2026-09-24 收口)

- **告警/运维到人只有邮件一条通道**。此前并行的第三方推送腿(免费额度 5 条/天的推送网关 + 其凭据环境变量/密钥文件/状态文件)已**整体摘除**:代码、环境变量读取、状态文件读写、文档段落均不留残余;摘除原因与演进记录只存在于 PROJECT_PLAN/归档。第三方凭据文件按策略不删(删凭据不是本仓动作),只是不再被任何代码读取。
- **配额模型 = 只按身份去重、无总量封顶**:同一条告警(alertname+instance 指纹)在去重窗口(默认 4h)内只寄一封,不同告警一律照寄。"每日 N 封"计数闸全部禁止 —— 第三方额度是**他人配额**,撞顶即静默丢投递,自保才有意义;SMTP 是我们自己的,自设总量上限等于把"告警静默"再复制一遍。显式开关(`BRIDGE_MAIL_ENABLED` 这类)只允许关"要不要发",不得关"发几封"。
- **失败必须响**:第三方推送时代"邮件只是兜底、失败可以忍"的前提已不存在。邮件寄不出去必须留可诊断痕迹(日志吼 + UNDELIVERED 标记文件,参照 `scripts/check-credential-health.mjs` 的 `UNDELIVERED` 机制与 bridge `alert-bridge-mail-UNDELIVERED.json`),下一次成功投递自动清除;不得静默。
- **发信一律经 `apps/api/scripts/notify-deploy-failure.ts`,不得在任何脚本里自拼 SMTP/Resend**(版式唯一真相源 `apps/api/src/services/email-templates.ts`,由守门 81 `check-brand-email-channel.mjs` 硬拦)。**通道与 From 的硬事实**:① 优先 SMTP,且经 `smtp.qq.com` 中继时 **From 的邮箱段必须等于登录账号**,否则被 550 拒 —— 旧规则"发件人统一 `智汇AI官方 <IHUI-AI@aizhs.top>`"配 QQ 账号正是本条成因(SMTP 恒被拒 → 恒回落纯文本 Resend → 用户收到的邮件永远没样式);② `aizhs.top` 只在 **Resend(已验证域)** 通道作 From,回落链 SMTP → Resend 且 payload 必带 `html`;③ 多行中文正文必须走 `--message-file`(无 BOM UTF-8),命令行参数会过一层控制台代码页(GBK);④ 调用方**一律不得传 `--env-file`**(tsx v4 会劫持它转发给 node,路径不存在时 node 直接 `exit 9`)。运维告警收件人由 `apps/api/.env` 的 `ALERT_EMAIL_TO` 控制(缺该键 ⇒ `alert-notification-service` 的 email 通道被整条排除,含数据库备份缺失 / 24h 错误超阈 / AI 资讯源失败 / 转发配额四类告警,且只在全通道皆空时才 warn)。
- **凭据残留的两类处置边界(2026-09-24 实测划定)**:① **服务环境块**里已无人读取的推送键要**事务式**摘除 —— `nssm set … AppEnvironmentExtra` 是**整块覆盖**语义,同块往往还挂着别的关键值(本机 `IHUI-DEPLOYLOOP` 块内就有 `IHUI_ADMIN_PASSWORD`),写坏等于把部署环打回"凭据过期"那次两天冻结。正确姿势 = 先 `reg` 读整块 → 备份到 `D:\DevEnv\backups\env\` → 只滤掉目标前缀 → **逐条逐字节比对剩余项全等**才 `SetValue` → 写回后**再读再比**,不等即从备份整块还原(本机实测:`IHUI_ADMIN_PASSWORD` 摘前/摘后 SHA 同为 `43CA897ACBFC`,条数 2→1)。② **HKCU 用户级同名键不得摘**:它不是本仓遗留,`~/.workbuddy/skills/serverchan` 与 `~/.agents/skills/serverchan` 的 `SKILL.md` 声明 `env_vars: SERVERCHAN_SENDKEY`、`scripts/send.sh` 直接读它 —— 删了是**删别人工具的凭据**,§5e 说的"本仓代码不再读"从不等于"全机无人读"。同理,孤儿运行态文件(如 `deploy/win/.sct-notify-state.json`,实测 mtime 停在生成方被移除的那一刻、全仓唯一引用是一道**断言它不得存在**的反向测试)可删,而"名字像垃圾"不构成删除依据。
- `apps/api/src/services/alert-notification-service.ts` 的 7 条 IM/pager 腿(钉钉/企微/飞书/Slack/Teams/PagerDuty/generic)**不在本节的摘除范围内**:它是**对外产品能力**(用户给自己的中转站/监控站配告警出口),不是运维到人通道;env 未设 ⇒ 今天不可达,而 email 腿已走 `renderSystemAlertEmail` + `html`(实测 `:29/:448/:471`)。**能力清单的取舍属用户职权,不得按"通道收口"顺手摘**。
- agent 侧长任务完成/阻塞待决策等**交互回复走会话本身**;需要"手机也收到"的事件统一走上述邮件通道。
- **"这条通道是否已摘干净"必须按"服务实际执行的是哪份文件"取径,不得按 `git ls-files`**。判据:`nssm get <svc> AppParameters`(逐个服务),顺着那个路径看内容 —— `deploy/prod-bundle/` 被 `.gitignore:383` 整目录忽略,里面的运行副本**可以完全没有入库源**,对跟踪文件做的 grep 对它零覆盖。实测教训(2026-09-24):两票按跟踪文件做出的"零残留"证明都成立,而 `IHUI-MONITOR` 跑的 `deploy/prod-bundle/monitor.ps1` 仍内联着第三方推送凭据常量与三条纯文本推送通道,其唯一通道当日已被对端配额打死(`monitor-alerts.log` 累计 55338 行推送失败),期间它判出的 `api(8802) 未监听`/公网 500 一封都没到人。现该文件已改为**入库源的转发器**(与 bridge 同一收敛法),真身 `deploy/win/ihui-monitor.ps1`。新写运维脚本若落在 `deploy/prod-bundle/`,必须同时落一份入库源 + 转发壳,否则等于写进盲区。
- 到人链路的**现役生产者清单**(改通道要逐个覆盖,缺一不可):`ihui-alert-bridge`(→`monitoring/alertbridge/alert-webhook-bridge.cjs`)、`IHUI-DEPLOYLOOP`(→`deploy/win/ihui-deploy.ps1`)、`IHUI-MONITOR`(→`deploy/prod-bundle/monitor.ps1` 转发 `deploy/win/ihui-monitor.ps1`)、`scripts/check-credential-health.mjs`、`scripts/git-guardian.mjs`、`.github/workflows/blue-green-deploy.yml`。**改完必须重启对应服务**才生效:PowerShell/Node 都在启动时把脚本读进内存,改文件不动运行中的进程 —— 只 commit 不重启 = 线上仍跑旧逻辑(旧版含每日封顶),而本地一切看上去已修好。
  - **`scripts/git-guardian.mjs` 这一行此前是"设计意图",不是现状**(2026-09-24 补齐):实测该文件里 `mail|smtp|resend|notify|alert` **零命中** —— 它每 2 分钟巡检、判红只落 `.workbuddy/git-guardian.log`,所以"守护发现问题 = 到人"从来不成立。现已接上派发出口(经 `notify-deploy-failure.ts`,不自拼 SMTP,受守门 81 管),按 alert 身份 + 内容指纹 4h 去重、未送达 30min 退避、**无每日总量封顶**,发信失败不改自愈与退出码;核验入口 `--notify-dry-run` / `--notify-test <名>` / `--status` 的 `.notify` 字段。**教训同 §26**:清单里写"某生产者会喊人",必须实测它真有一条到人的出口,否则记账与事实分叉,而分叉形态永远是"安静"。

---

## 6. 验证命令

```bash
pnpm turbo build typecheck lint test          # 全量验证(必须全绿)
pnpm --filter @ihui/api typecheck             # 单独验证后端
pnpm --filter @ihui/web typecheck             # 单独验证前端
cd apps/ai-service && mypy app --ignore-missing-imports --strict   # Python 类型(app 源码路径 apps/ai-service/app,继承 pyproject.toml [tool.mypy])
pnpm dev                                       # 启动所有服务(web + api + ai-service,端口见 docs/port-management.md)
```

---

## 7. 删除/重构安全规则(强制)

删除任何 git 对象(分支/stash/commit/文件)前必须回答:① 该内容承载的**功能**是什么?② 当前 monorepo 中是否有**等价的功能实现**?③ 没有 → **不可以删除**,必须先迁移/开发替代。禁止基于"路径不兼容"或"看起来是垃圾"擅自 drop。stash drop / branch -D 同样适用。

---

## 8. goal 模式工作流(强制)

触发 `/goal <目标条件>` 时按本节流程执行。

### 目标条件硬门槛(单条最大 4000 字符)

必须同时包含:核心任务 + 验证标准(命令退出码/测试输出/文件状态/HTTP 响应)+ 约束边界 + 质量要求 + 异常处理。缺一即拒绝启动。示例见对话上下文。

### 运行时文件(强制)

进入 goal 模式第一轮执行前必须在 `.ihui-agent/goal-runtime/` 创建:

- `STATE.md`:目标条件 + 状态机(`active`/`paused`/`achieved`/`blocked`/`budget_limited`)+ 当前轮次 + Token 累计 + 最近评估结论 + 硬性指标清单。
- `loop-run-log.md`:逐轮追加(轮次号 + 执行摘要 + 工具调用统计 + 评估结论 `yes|no` + 一行理由)。

### 7 步执行循环

1. 目标解析与初始化(拆分硬性/软性指标,初始化 STATE.md)
2. 单轮任务执行(聚焦核心问题,输出执行摘要)
3. 独立评估校验(基于真实结果,禁止模型自评 yes)
4. 循环判定(yes → 第 5 步;no → 续跑;连续 3 轮 no 无进展 → blocked)
5. 最终交付校验(逐条核对硬性指标)
6. 状态清除与交还控制权(输出交付报告)
7. 整合与清理(目标摘要追加到 PROJECT_PLAN.md,删除 STATE.md + loop-run-log.md)

**子命令**:`<目标条件>` 启动第一轮;`(无参数)`/`status` 查询;`pause`/`hold` 暂停;`resume`/`continue` 续跑;`clear`/`stop`/`off`/`reset` 终止清理;`budget <数值>` 设 Token 上限;`log`/`history` 输出日志。

### 红线规则

- 单目标最大自动迭代 **20 轮**,超出 blocked。
- 高危操作(删分支/强推/删库表/影响生产)**必须暂停**请求人工确认。
- 严格围绕目标,禁止扩展需求、做无关重构。
- 每轮**完整承接上下文**(压缩后必须重读 STATE.md)。
- 连续 5 轮工具失败 → blocked。

### 失败回滚

- `blocked` / `budget_limited` 状态下**禁止** agent 自主执行 `git reset --hard` / `git checkout .` / `git clean -f`。
- 必须在 PROJECT_PLAN.md 记录:已修改文件 + 当前分支 + 起始 commit sha + 未完成原因。
- 回滚决策权归属用户。

---

## 9. 多端同步开发强制规则(强制)

- **默认全端连通**:每一个任务默认 8 端(web/api/ai-service/desktop/extension/mobile-rn/miniapp-taro/cli)同步开发,"匹配连通好"= 代码同步(共享 types/UI/schema 跨端一致)+ 链路打通(跨端调用无契约/类型/路由/404 错)+ 验证齐绿(各端 typecheck+build+test 全绿)。禁止只改一端交付、单端验证声明完成、分期交付(平台独占豁免除外)。
- **平台独占豁免需显式标注**:仅天然只属特定端(desktop 系统托盘/extension 上下文菜单/miniapp-taro 微信支付/cli 终端集成/纯文档守门脚本)可豁免,必须在 PROJECT_PLAN.md 标注"平台独占"或"单端文档/脚本",未标注按全端同步执行。
- **多端并行派单**:主 agent 优先用 §11 多 Subagent 并行模式按端拆分,每个 subagent 管自己端的代码+typecheck+build;主 agent 负责跨端契约对齐(共享类型/API 路由/schema)和全链路连通验证,不得下放给单个 subagent。
- **登录/SSO 链路跨端自动同步(强制,2026-09-04 立)**:凡改动登录相关 UI/文案/逻辑,必须**主动**同步所有相关端,不得等用户提醒。关键结构事实:① 共享登录 UI 仅覆盖 mobile-rn(`packages/app/src/features/login/LoginScreen.tsx`),**miniapp-taro 登录页是独立实现**(`apps/miniapp-taro/src/pages/login/login.tsx`),两端都要动;② SSO deep-link scheme:mobile-rn/desktop 用 `ihui://sso/callback`,miniapp-taro 用 `ihui-miniapp://sso/callback`,后端白名单由 `apps/api/.env` 的 `SSO_ALLOWED_DEEP_LINK_SCHEMES` 控制,新增 scheme 必须同步登记;③ web OAuth 中转页 `apps/web/app/(auth)/callback/OAuthCallbackHandler.tsx` 的 `redirect=mobile-rn` 分支负责跳 `ihui://oauth/callback` 回 App(支付宝回调参数为 `auth_code`);④ web 端 SSO 出口页为 `apps/web/app/sso/*`(支持 custom scheme 回跳)。
- **守门**(warn-only):`scripts/check-multi-end-sync.mjs` 检测 staged 跨端分布 4 场景(纯豁免目录→pass;触及 packages/* 未标注→warn;触及≥2 端→pass;触及 1 端未标注→warn),集成于 pre-commit 第 21 项。

---

## 9b. 单分支开发强制规则(强制)

- **禁止创建乱七八糟分支,所有改动统一往 main 合并**。除 main 之外**不允许**新建任何本地/远程分支(`feat/*` / `fix/*` / `hotfix/*` / `add-*` / `rescue/*` / 自定义前缀全部禁止)。**前提已于 2026-09-27 改变(2026-09-28 就地补注,勿照旧文执行)**:origin/main 开了分支保护(受保护检查上下文 = `CI / lint-typecheck-test (push)`,`enforce_admins: false`)。这条在本机有**两半事实,不得只读一半**:① 持 admin 凭据的会话**仍可直推 main**(2026-09-28 实测:提交后 push 成功并 `git ls-remote` 回读一致);② 走 PR 的通道受那条检查约束,而 **main 自身 CI 一红就把整条 PR 通道堵住**(同日实测 `main@3fab392c7` 的 `lint-typecheck-test` job failure)—— 所以"main 的 CI 红"从账面难看升级为全队交付阻塞。按本条字面执行(任何旁支都判红)会让每台每次提交都被守门 41 拦住,唯一结局是各会话 `--no-verify` 连带废掉约 186 道门(§12e/§12f 同型)。现行口径:本条防的是**旁支长期滞留**,不是"存在旁支"—— 在飞窗口(默认 48h,`IHUI_SINGLE_BRANCH_GRACE_HOURS` 可调)内的 PR 工作分支不判红、超窗仍判;`backup/` 与 `ihui-backup/` 引用**永不判红**(§5b 明令禁止删除备份、§22 要求备份引用本地+远端双留,门喊"删掉它"等于替人犯 §5b 的禁令)。两类豁免都只报数并**逐条点名**,不静默。
- **唯一例外**:`/goal` 模式目标条件强制要求独立分支时,允许创建 `goal/<目标名>` 临时分支(AGENTS.md §8);`goal/*` 完成后**必须立即删除**,不留历史快照。
- **必要分支判断标准**:**单次任务无法在 main 上原子完成**才允许创建(如 8 端并行多 subagent、紧急 hotfix 需独立回滚通道);**普通功能开发、Bug 修复、refactor、文档/守门脚本改动一律禁止创建分支**,全部在 main 上直接 commit。
- **已合并分支立即删除**:任务合并后**本会话内**完成 `git branch -d <已合并>`(本地) + `git push origin --delete <已合并>`(远程),不留"历史快照"分支污染 main 分支列表。
- **删除未合并分支前必须 tag 备份**(AGENTS.md §22 配套):`git tag backup/cleanup-<date>-<branch> <branch>` → `git push origin --atomic refs/tags/backup/cleanup-*` → 再 `git branch -D`。tag 必须本地+远端双备份,防 git gc 清理。
- **fetch + prune 是日常**:`git fetch origin --prune` 在每个 push 周期跑一次,清理已删远程分支的本地 stale 引用。
- **守门**(2026-07-30 立,2026-08-02 落地):
  - `scripts/check-single-branch.mjs`:检测 `git branch -a` 列表中除 main / upstream 外的分支,发现任意 1 个 → exit 1 阻塞 commit。**2026-09-28 口径已改**:先过"在飞窗口 + 备份引用"两类豁免(逐条报名不静默),剩下的才判红 —— 阳性对照 `IHUI_SINGLE_BRANCH_GRACE_HOURS=0 node scripts/check-single-branch.mjs` 必须把全部旁支翻回判红(**条数一律当次现读,不得照任何文档里的数字派单**)。同批修掉该门自带的两处"报绿却不看守":① `--self-test` 的登记函数写成 `!!ok`,而 13 条用例全部传箭头函数 ⇒ `!!fn` 恒真 ⇒ 它一路打印"13/13 通过"却连一条都没判过;② 在飞年龄把**秒差**除以 3,600,000(每毫秒的小时数)⇒ 结果小 1000 倍,48h 窗口实际约等于 5.5 年,即它自己那条"超窗仍判(豁免不得变成永久放行)"的反向对照在生产面**永不触发**。两处必须同枚修:只修①会让那条恒绿的反向对照当场翻红,只修②则没有任何尺子看得见它。现由 `scripts/tests/check-single-branch.test.mjs`(§22c 镜像 13 例,该门此前没有镜像测试)的 T4/T6/T13 钉住,三条各有变异对照(还原旧写法即翻红)。
  - 集成位置:`scripts/guardian-runner.mjs` id 41(blocking),守门不通过则禁止 commit + push。
  - 豁免:§8 goal 模式临时分支(必须带 `goal/` 前缀,且在 `.ihui-agent/goal-runtime/STATE.md` 标注 `active` 状态才算合法)。2026-09-28 起与之并列的还有:linked worktree 的 checkout 分支及其 `origin/` 镜像(§12d)、镜像远端与幻影引用(既有)、**在飞窗口内的 PR 分支**、**`backup/` 与 `ihui-backup/` 引用** —— 后两类只报数并逐条点名,因为"让门喊人删备份引用"与 §5b 的禁删令直接冲突。**不得**把当下那几条旁支名抄进门体当白名单消红(名单必然腐烂,§4 对 `RN_ONLY_BRAND_KEYS` 记过同型;已由镜像 T10 反向钉死)。
- **历史教训**(2026-07-30 立):仓库曾积累 12 个分支(本地 6 + 远程 7 + 1 upstream),其中 `add-ihui-ai` / `goal/*` / `rescue/*` 等 13 个无价值分支全部已合并或已被 main 覆盖;3 个未合并分支的内容(LLM 三提供商/i18n 五端/console.log→logger/awesome-prs)均已在 main 后续 commit 中包含或演进,merge 会回退 main 功能。教训:**分支不是"工作单元",是"协作单元"**——单 agent 单任务无需分支,直接 main 提交即可。

---

## 10. 交付报告一致性硬约束(强制)

同一份 .md 报告中**不得**同时出现:"无后续建议" / "完整收尾" / "对话可关闭" 与 "P1-P5" / "优化项" / "TODO" / "后续任务"。守门:`scripts/check-delivery-report-consistency.mjs` + pre-commit 第 12 项。

---

## 11. 多 Subagent 并行开发强制规则(强制)

### 任务分配格式(强制)

派发子任务时必须用以下格式,缺一拒绝执行:

```
## 任务目标
<一句话>

## 受影响文件(绝对路径,只允许以下文件)
- <repo-root>\path\to\file1
- <repo-root>\path\to\file2

## 禁止修改
- 任何不在上述清单的文件

## 验证命令(子任务完成后必须自行运行)
- pnpm --filter @ihui/web typecheck
- pnpm --filter @ihui/api test

## 约束边界
- <API 契约/类型/样式/行为约束>

## 交付物
- 完整代码 + 自验通过 + 一句话总结
```

### 联动规则

- 与第 7 节(删除安全)协同:subagent 不得删除非任务清单内文件。
- 与第 16 节(push保护)和 §20 协同:subagent 完成后由主 agent 统一 push。
- **subagent working tree 自检(2026-07-30 立,真实事故)**:subagent 完成任务后**必须**执行 `git status --short` 自检,确认 working tree 状态符合预期——只有任务清单内文件被修改/新增。如果发现意外文件(其他 agent 改动被 `git stash pop` 带回 / lint-staged 副作用 / IDE 自动 stage / 文件被清成 1 行等异常),**立即停止**并报告主 agent,**禁止**继续 commit/push/补救。主 agent 收到异常报告后按 §12 + §22 处理(revert / 隔离 add / `--no-verify` 跳过其他 agent 代码问题)。
- **stash 操作后强制 Read 验证(2026-07-30 立)**:subagent 在执行 `git stash push` / `git stash pop` / `git stash apply` 后,**必须**用 Read 工具验证任务清单内文件内容完整(防止 stash 误操作把文件清成 1 行或吞掉内容)。Read 返回内容与预期不符 → 立即停止,报告主 agent,**禁止**基于未验证的"假设文件完整"继续操作。

---

## 12. 多会话并行操作同一仓库强制规则(强制)

- 多会话/多 agent 在同一仓库并行工作时,**禁止**任何破坏性 git 操作:`git restore` / `git stash push` / `git clean -f` / `git reset --hard` / `Remove-Item` 删除其他 agent 创建的文件(包括"看着像垃圾"的 `commit_msg.txt` / 临时测试文件 / 调试日志)。
- commit 阶段**只 add 本任务相关文件**:`git add <file1> <file2>`,**禁止** `git add .` / `git add -A` / `git add -u`。
- **提交活文档前必须做"工作树 ⊇ HEAD"行级对账(2026-09-24 立,一夜三次自伤换来的)**:`README.md` / `AGENTS.md` / `PROJECT_PLAN.md` 这类多会话共写的文件,工作树副本**常年滞后于 HEAD**(实测分别少 57 / 48 / 54 行),而 `safe-commit` 的 Step ④ 是 `git commit -- <pathspec>` —— 按路径取**工作树**版本,于是"只 add 本任务文件"的规范提交照样把别人已入库的行整批写回旧态,`git status`、diff 行数、typecheck、守门 71 全都看不出来。动作:① 提交前 `node scripts/merge-live-doc.mjs --file <该文档>`(exit 1 = 存在真丢失);② 需归并时 `--apply`(它按锚点把 HEAD 缺失块插回工作树,并区分"被吃掉"与"被就地改写",后者不插回以免新老并存);③ 归并后必须报"仍判 lost = 0 且长行重复新增 = 0"。判据是**字符二元组** Jaccard ≥ 0.6 —— 前缀与按空格切词在中文里都会漏(工具自己栽过一次,README 里一度同时留下 `**第 93 项 X**` 与 `**守门 X**` 两行),已由 `--self-test` 8 例钉死。登记条目见 PLAN 的 O46④/O46⑩。
- **提交活文档前必须做"工作树 ⊇ HEAD"行级对账(2026-09-24 立,一夜三次自伤换来的)**:`README.md` / `AGENTS.md` / `PROJECT_PLAN.md` 这类多会话共写的文件,工作树副本**常年滞后于 HEAD**(实测分别少 57 / 48 / 54 行),而 `safe-commit` 的 Step ④ 是 `git commit -- <pathspec>` —— 按路径取**工作树**版本,于是"只 add 本任务文件"的规范提交照样把别人已入库的行整批写回旧态,`git status`、diff 行数、typecheck、守门 71 全都看不出来。动作:① 提交前 `node scripts/merge-live-doc.mjs --file <该文档>`(exit 1 = 存在真丢失);② 需归并时 `--apply`(它按锚点把 HEAD 缺失块插回工作树,并区分"被吃掉"与"被就地改写",后者不插回以免新老并存);③ 归并后必须报"仍判 lost = 0 且长行重复新增 = 0"。**但提交前的报告有时效边界**(2026-09-24 实测:`merge-live-doc` 报 0 丢失之后、`safe-commit` 落地之前,并发会话又推进了一轮 ⇒ 那次提交仍丢掉 2 条他人登记行)。所以顺序必须是四步:**报告 → 提交 → 立刻 `git show <新提交>^..<新提交>` 做行级对账 → 有丢失就地回补**(发现手段只能是提交后的 diff,`git status` 与提交前那次报告都看不见)。判据是**字符二元组** Jaccard ≥ 0.6 —— 前缀与按空格切词在中文里都会漏(工具自己栽过一次,README 里一度同时留下 `**第 93 项 X**` 与 `**守门 X**` 两行),已由 `--self-test` 8 例钉死。登记条目见 PLAN 的 O46④/O46⑩。
  - **回补过的行还要在"每次收敛/合并之后"复验它仍在 HEAD(2026-09-24 实测该文件第 4 次被顶掉)**:
    并发会话的 union 合并会把别人刚回补的行**再次吃掉** —— 实测
    `packages/shared/src/chat/handoff-package.ts` 的 `i18n-content-exempt-file:` 声明:被 `cfe8f65e4`
    抹掉 → `9e01f6aa9` 原样补回 → 几轮合并后 `git show HEAD:<该文件>` 又是 0 命中。"提交前对账 +
    提交后 diff"两道都保不住它,因为吃掉它的不是回补那次提交。复验是一行的事:
    `git show HEAD:<该文件> | grep -c '<该行稳定前缀>'` 应为非 0,为 0 即重新回补(工作树通常还留着,
    `git diff HEAD -- <该文件>` 立刻可见)。守门 70 的声明式出口是**随行内联**的 —— 行一丢,该文件
    49 处中文立刻判红:红点会等下一个碰它的人来吃,不会静默,但也只有那个人会以为是自己的错。
- 正确流程:预检(`git status --porcelain`)→ 隔离 add 本任务文件 → 验证 staged 仅含本任务文件。
- **任务完成必须自动 commit(2026-09-20 用户指令,强制)**:任务/批次完成且验证全绿后,agent **必须立即自动 commit**——不经询问、不等用户确认、禁止以"不擅自 commit"为由把已验证的工作留在未提交状态。push 仍按 §16/§20 执行(用户未要求时不主动 push)。commit 形态仍受本节约束(多 agent 并行必须 safe-commit.mjs;单 agent 直接 add 声明文件;禁止 `git add .` / `-A` / `-u`)。
- pre-push / pre-commit hook 失败因**其他 agent 引入的代码问题**(schema drift / 其他模块 TS/lint 错误 / 其他 agent 未完成 migration 等,不在本任务范围):**直接用 `--no-verify` 跳过 hook** 完成自己的 commit + push;**禁止**修改其他 agent 代码"帮他们修" / `git reset --hard` / 把"等其他 agent 修复再 push"作为交付结论 / 用 AskUserQuestion 询问用户;自己 commit + push 前只需保证**本任务改动文件** typecheck + lint + build 全绿即可;`--no-verify` 合法场景**仅限**"hook 失败原因是其他 agent 代码",若失败原因是**本任务自己代码**必须修复后正常 commit。

### 强制使用 safe-commit.mjs(2026-08-06 立,真实事故根治)

- **触发背景**:commit `aa15bec23` "fix(web): message-list 消息操作按钮..." 意外包含 `message-input.tsx`(其他 agent 改的 `rounded-t-xl`)。根因:`message-input.tsx` 在 pre-commit hook 执行**前**已被 IDE/其他 agent staged,`takeStagingSnapshot` 把它当成本任务文件,`restoreStaging` 不会 unstage。所有领域级守门(`check-commit-scope` / `check-staged-pollution`)都放过(同目录 `apps/web/src/components/chat/`,scope=web 匹配)。领域级守门**无法防御同目录文件级污染**。
- **强制规则**:多 agent 并行环境(≥2 个 agent 同时工作)下,agent commit **必须**用 `node scripts/safe-commit.mjs -m "<message>" -- <file1> [file2 ...]`,**禁止**直接 `git add <file> && git commit -m "..."`。
- **safe-commit.mjs 5 步法(零信任)**:① `git reset HEAD` 清空整个暂存区(无论谁 staged 的)→ ② `git add -A -- <声明的文件>` 只暂存自己声明的文件 → ③ 校验 `git diff --cached --name-only` === 声明文件(有意外文件立即 exit 1)→ ④ `git commit -- <pathspec>` 原生 pathspec 终极兜底 → ⑤ `git show --name-only HEAD` 验证 commit 内容只包含预期文件。
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**:旧版首次 commit 一失败就打印「按用户规则"hook 失败因其他 agent 代码 → --no-verify 重试"」并整批跳门 —— 它**从未计算过归因**。实测一轮 134 道门 111 通过 / 1 失败,红的是 `check-push-sync`(判远端态、与提交内容无关),措辞仍是"他人代码"。现流程:解析 runner 的失败门清单 → **逐道复跑** → 该门这次的输出**点名本次声明的文件 ⇒ 判"本任务自己的红",拒绝 `--no-verify` 并 exit 1**(必须修);一个都没点名 ⇒ 允许跳,但只说量到的话;批没跑完 / 解析不到 / 复跑全不可用 ⇒ 明写「未归因」,按应急路径落地并留痕于 `.workbuddy/safe-commit-attestation.jsonl`。**"批一道都没跑"从 2026-09-26 起不再是中性事实,而是一个必须先补证据的分支**:`scripts/lib/pre-commit-hook.js` 里 lint-staged 跑在守门批**之前**,于是一次 lint/prettier 失败 = 156 道门全不跑 = 归因永远 `unattributed` = 照样跳门,而账面读起来像"跑过了、只是与本次无关"(实测 `acaf8407cc` 与 `d379215126` 前后各一次;这正是"判据失效的表现永远是安静"那一型,守门 70/76/81 同族)。现由 `safe-commit` 在判到 `unattributed ∨ ranFullBatch===false` 时**自己把 `guardian-runner --staged` 跑一遍**,再把那一轮的输出喂回**同一条铰链**(`classifyHookFailure`)分流:点名本次文件 ⇒ `mine` ⇒ 拒绝跳门;一个都没点名 ⇒ 可跳但措辞必须写明"取证来自 safe-commit 自跑的那一轮,不是钩子内那一轮";自跑也没成功 ⇒ 照旧落地,而 `unattributed` 的措辞必须带上具体原因(runner 不在位 / 派生失败码 / 超时)。**刻意不把 `unattributed` 改成拒绝跳门** —— 那会让"别人把我挡在 lint 外"的应急路径消失,逼人手工 `git commit` 跳得更彻底且不留痕。**一条如实登记的覆盖面上限**:自跑发生在首次 commit 失败之后,而 lint-staged 失败时会回滚它自己动过的暂存区 ⇒ 新增文件此刻已退回未跟踪,自跑那一轮看到的暂存集可能少于声明集(与既有"逐道复跑"同一刻同一索引态,两份证据因此可互相比较)。应急提速:`IHUI_SAFE_COMMIT_BATCH_TIMEOUT_MS`(极小值 ⇒ 自跑判"未成功"并带原因入库,**不是**静默开关)。取证:镜像 13 例(含"汇总块在位时绝不自跑"的反向锁与装车证明)。**红在守门批「之外」也要点名,不得把已量到的批内结论丢掉(2026-09-25 补,G-185)**:钩子在 runner **之后**还跑若干独立 blocking 步(实测 `i18n 死 key 扫描`),它一红就 `exit 1` —— 那一轮 154 道门**全部跑完、blocking 失败 0**,而旧归因把这种组合写成「汇总里没列出失败门 ⇒ 归因未计算」,随后照样 `--no-verify` 落地(提交 `46a4c18ed` 即此型),等于**把已经拿到的守门结论整块丢弃**,账面读起来像"没跑守门"。现规则:解析到批外步骤名(`outsideBatchStep`)后仍按同一条铰链分流 —— 该步结论点名本次声明的文件 ⇒ 判 `mine`(拒绝跳门,必须修);一个都没点名 ⇒ 判 `not-ours` 但措辞必须是「批内 0 失败,红在批外这一步」并点名是哪一步、提醒随后清偿。**失效方向仍是"多要一次定向说明"**,不是"多放一次跳门",也不是"干脆不落地"。取证:C0/C0b/C1/C2/C3 五例(含"批内确有失败门时不得走这一支"的反向对照,否则批外步骤会替批内那道门洗责)。**刻意不建"机器态门 id 清单"** —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。 —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。取证:镜像测试 5/5(含装车证明 + 真实 `hook-logs/pre-commit.log` 取样校验夹具)+ 三条变异各自变红(ANSI 剥色那条登记为**等价变异**,不造假具硬凑红)。**因此:agent 不得再假设"钩子红了就能跳"** —— 被拒时那句红点名了哪个文件,就去修哪个文件;确属误判才可按 §16 显式说明后手工提交。
- **单 agent 环境豁免**:确认无其他 agent 并行时,可直接 `git add <file> && git commit`,但必须先 `git status --porcelain` 确认 staging area 干净(无其他已 staged 文件)。
- **pre-commit hook 配套提示层**(2026-08-06 立):hook 入口调用 `auditStagingFiles()` 打印 staged 文件清单(按目录分组)+ 同目录多文件警告 + 文件数 > 5 严重警告(warn-only,不阻塞)。提示层无法真正阻止污染,真正阻止污染的是 safe-commit.mjs 的 `git reset HEAD` 清空暂存区。
- **红线**:
  - ❌ 禁止多 agent 并行时直接 `git add <file> && git commit`(staging area 可能有其他 agent 残留)
  - ❌ 禁止用 `git add .` / `git add -A` / `git add -u`(会把其他 agent 改动一起 stage)
  - ❌ 禁止忽略 pre-commit hook 的 `📋 staged 文件清单审计` 警告(同目录多文件时必须核对)
  - ✅ 多 agent 并行时用 `node scripts/safe-commit.mjs -m "..." -- <files>`(自动清空暂存区 + 只 add 声明文件 + 校验)
- **git 写操作全局锁**(2026-08-06 立,`.git` 损坏事故根治):所有 git **写**操作(commit/push/pull/rebase/merge/stash/checkout/gc/repack/fetch)在同一仓库必须**串行化**,同一时刻只允许一个写操作单元执行。
  - **已自动生效**:safe-commit.mjs 整个 commit 流程自带锁(`Step 0/5 获取 git 写锁`);post-commit 钩子自动处理直接 `git commit` 场景。**agent 无需额外操作**。
  - **手动 git 命令**必须遵守:执行 `git pull` / `git rebase` / `git fetch` / `git checkout` / `git stash` 等写操作前,先 `node scripts/git-lock.mjs check`(exit 0 = 无锁可执行;exit 1 = 有其他写操作进行中,等待后重试)。
  - **禁止手动 `git gc` / `git repack` / `git prune`**:需要时用 `node scripts/safe-gc.mjs`(自动检查无锁后执行)。autoGc 已禁用(`gc.auto=0` + `maintenance.auto=false`),无需也不应手动触发 gc。
  - **锁异常处理(2026-09-18 心跳机制升级)**:持锁方 safe-commit/safe-gc 会 spawn 心跳子进程每 5s 续期 `meta.ts`——**活进程的锁绝不会被抢占**;仅当"锁年龄超 300s **且** 持有者 pid 已死"或超 1800s 硬上限(pid 复用兜底)才强制抢占。锁等待超时(safe-commit 15min / post-commit 3min)报错并提示持有者是否存活;紧急可删 `.git/ihui-git-write.lock`(先确认无 git 写进程)。绕过:`IHUI_GIT_NO_LOCK=1`(仅应急,禁用后自行承担并发风险)。
  - **新环境初始化**:重新 clone 后必须执行一次 `node scripts/git-hygiene-init.mjs`(恢复 gc.auto=0 / maintenance.auto=false 防护配置,这些是 local config,clone 不保留)。

### .git 整目录消失事故链根治(2026-09-10 立,当日 7 次事故复盘)

- **根因**:多会话共享同一工作区时,并发 git 手术互踩——会话 A 重建/删除 `.git` 期间,会话 B 的 git 命令撞上 `.git` 缺失,B 误判"仓库损坏"也触发重建/清理,互相把对方的 `.git` 删掉;refs 写入"被吞"多为重建窗口期读写竞态的表象。
- **强制规则**:
  1. `.git` 异常时**先诊断后动手**:`node scripts/git-lock.mjs check` 确认无其他会话持锁 + `ps`/审计日志确认无并行 git 进程,才允许删除/重建。
  2. 重建**只能**走 `node scripts/git-rebuild-local.mjs`(已接入 git-lock,锁被持有时快速失败),**禁止**手工 `rm -rf .git` + `git init` 自由发挥。
  3. 修复 refs 后必须**回读验证**:`git update-ref ... && git rev-parse <ref>` 输出一致才算成功;批量修复后 `git pack-refs --all` 持久化。
  4. **禁止把 `.git` 迁出工作区/改指针文件**(2026-09-10 已验证:并行会话的恢复逻辑会把指针文件当"损坏"清除,反而制造新事故)。
  5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。 **但"拿到了 sha"不等于能拿它做判据(2026-09-29 补,G-473 同窗口踩两次)**:`ls-remote` 只问引用、**不下载对象** ⇒ "用显式 SHA 操作"这条路上必须先插一步 `git fetch --no-tags origin <该 sha>`(或直接跑 `node scripts/git-sync-converge.mjs`,它先 fetch 再合)。漏了不报错,只会**误判**:`merge-base --is-ancestor <远端sha> HEAD` 因对象缺失而失败 ⇒ 被读成"不是祖先"(一次 fetch 被说成一次真分叉需人工);`rev-list --left-right --count <远端sha>...HEAD` 失败 ⇒ 被读成"无 ahead commit,可能是 shallow clone 等异常状态"(把没取回说成仓库形态坏了)。三处已改成显式"未判定 + 一步出路":`union-converge`(CLI 退 2,且本器绝不代跑 fetch)、`git-push-guard`(异常措辞前先 `cat-file -t`)、`git-sync-converge`("未判定"不再被并进"亦判需人工")。取证:`node --test scripts/tests/converge-object-presence-g473.test.mjs` 6 例(摘掉探测那一支的变异会翻红两条)。

### 部署/构建全局锁(2026-08-09 立,并发部署事故根治)

- **事故背景**(8-09 实锤):多 Agent/自动化任务并行触发 `build-next-prod.ps1` 时,两个构建同时备份/清理/写入 `apps/web/.next` → 8801 短暂 502 + 监控报警,产物存在损坏风险。原 `apps/web/scripts/check-lock.js` 只有 dev-vs-build 互斥,**没有 build-vs-build 互斥**;且 `build-next-prod.ps1` 曾引用不存在的 `scripts/check-lock.js`,锁从未生效。
- **锁机制**:`node scripts/deploy-lock.mjs acquire|release|check`(锁 = 项目根 `.deploy.lock` 目录,mkdir 原子性)。build 与 build/dev 全部互斥;dev+dev 共存;stale(10min)+ 超时(10min)自动兜底。
- **判活问的是"哪个 pid"——这一格在 2026-09-25 冻结过生产 11h50m**(登记 G-193):`acquire` 是一次性 CLI,打印"锁已获取"就退出,所以 meta 里那个 `pid` **不是持锁者**,拿它判活等于没有判据 —— 要么恒"已退出"(别人构建中途被抢),要么该号被系统复用给别的过程(实测:`meta.ts=11:16:32 pid=888`,而当时占着 888 的是 `C:\Windows\System32\nssm.exe`,**StartTime 11:19:13,比锁晚 160 秒**,一个进程不可能在它存在之前写锁)⇒ `alive=true` 永远成立 ⇒ 每轮白等 600s 后 exit=1,部署环从 11:16 起没产出新构建,而 `git status`、typecheck、154 道门全都看不出来。两条修法同时落地:**① `--owner-pid`**(调用方自己的 `$PID`,这才是这段锁真正的主人;`build-next-prod.ps1` 已传)**② 硬上限 30min**——"名义存活"与"锁龄超上限"同时成立只可能是复用 ⇒ 归档现场后抢占(与 §12 `git-lock` 的 1800s 复用兜底是同一条设计)。**判据矛盾时的失效方向**:宁可抢一把明显超时的锁(现场先归档,可复核),不可无限 wait(那等于把"没人能说清"变成"生产一直不更新")。`release` 在这一格**仍不代删别人的锁**,但必须点名"pid 疑似被复用"并指出出路是下一次 `acquire`。
  - **如实登记未完成的部分**:`apps/web/package.json` 的 `predev` / `dev:clean` / `dev:stable` / `prebuild` 四处**没法声明 owner**(npm 生命周期进程在 dev server 起来之前就退了),它们的互斥仍靠"CLI pid 多半已死 ⇒ 秒抢"这条弱机制;真正的解是心跳续期(照 `git-lock` 每 5s 写 `meta.ts`),而那需要一个能看见 dev server 生死的常驻方 —— 现在没有。所以这一段的事实是:**build 侧已收口,dev 侧靠硬上限兜底(最长 30min 让位),不是已有心跳**。
- **已自动生效**:`build-next-prod.ps1` [0/6] 阶段自动 acquire、[7/6] release;web 包 `prebuild`/`predev` 已接入。**agent 无需额外操作,直接跑构建脚本即可**。
- **手动构建必须遵守**:触发 web 构建前先 `node scripts/deploy-lock.mjs check`(exit 0=可构建;exit 1=有其他构建/部署进行中,等待后重试)。**禁止**绕过锁直接 `next build` 或并发触发 `build-next-prod.ps1`。
- **锁异常处理**:超时自动报错;持有者已退出的悬挂锁**不限锁龄**立即抢占(2026-08-27 判据);"名义存活但锁龄超 30min 硬上限"= pid 复用,同样归档后抢占(见上节 G-193);紧急可删项目根 `.deploy.lock`(先确认无构建进程)。`.deploy.lock/` 已 gitignore。
- **禁止**用 `-CleanCache` 或其它参数绕过锁;多 Agent 协作时若需排队构建,等待而不是强删锁。

### 12b. 协作收尾 SOP (2026-08-18 立, §22c 配套)

当发现其他 agent 正在并行做同一任务时(working tree 与 origin/main 不一致, 或远端新 commit 提到类似功能), 不要立即重写对方主体逻辑, 改走协作收尾路径:

1. **核查现状**: 用 `git diff HEAD --stat` + `git log origin/main..HEAD` 确认
   - 远端是否已 commit (a01fcf4 这种情况)
   - 本地 working tree 是否含对方未提交的改动
   - 对方改动的完整性与正确性
2. **不重写主体**: 仅修复对方代码中的明确缺陷(如测试用例失败、QUIET bug)
3. **最小化改动**: 用 `git add <仅自己改的文件>` 只 stage 自己的改动
4. **--no-verify 跳过**: 因为 hook 会被对方未 commit 的 working tree 改动误判为污染
5. **commit message 标注**: 标题含「(协作收尾)」后缀, 描述中明确
   「不动其他 agent 的主体逻辑」+ 列出具体修复点
6. **push-guard 自动捎带**: push 后 push-guard 会自动捎带其他 agent 的 commit,
   因为 git-pull-rebase --autostash 已同步

### 12c. 并发 commit 防混淆 SOP (2026-08-19 立)

当多个 agent 同时 commit 同一文件时, 可能出现:

- 你的 commit message 描述的是 A, 但 diff 里包含 B 的修改 (race condition)
- 因为另一个 agent 在你 commit 期间 `git add` 了相同/不同文件

防混淆措施:

1. **精确 add**: 用 `git add <具体路径>`, **禁止** `git add .` / `git add -A` / `git add -u`
2. **commit 前验证**: `git diff --cached --stat` 必须与预期文件清单完全一致
3. **commit 后审查**: `git show HEAD --stat` 立即检查 commit 内容, 若含无关文件 → `git reset HEAD~1` 重提
4. **避免同时改同一文件**: 如果发现其他 agent 在改相同文件, 等他完成后再动
5. **接受混合 commit**: 当混合 commit 已 push 到 main 且功能正确, 不要 force-amend, 加一个空 commit 标注"混合 commit 说明"即可

历史案例: d6e8906 + f028e5517b 期间出现"scripts 改动 + api 改动 + mobile-rn 改动"被同一 commit 收录, 后续审计应见此节说明。

### 12d. git worktree 多会话隔离规范(2026-08-31 立,stash 丢失 + 守门误伤事故根治)

- **触发背景**(真实事故链,同日三连):① 多会话共享 working tree,某会话 `git stash push/pop` 冲突导致另一会话已完成的 llm_gateway.py 拦截代码在提交中丢失(提交 diff 只剩 63+/58- 格式化差异);② 守门第 8 项 check-api-routes 全量扫描工作区,104 处"前端调用无后端路由"全部来自其他会话未完成文件,正常提交被阻塞;③ push 门全量 typecheck 报上千 TS 错误全部来自其他会话工作区噪音,推送反复被阻;④ 2026-08-31 补充事故:d22d233091 的 commit message 声称含 §12d 规范但 AGENTS.md 实际 diff 仅 1 行——§12d 本体在提交前被并行会话覆盖工作区而丢失,**commit message 声称的规范条目必须与实际 diff 一致**。根因:**多会话共享同一 working tree + 同一 git index**。
- **第一优先:单写者原则**——同一时刻只允许一个会话写 working tree;并行会话开工前先确认其他会话已收尾(无未提交改动、无进行中 stash)。
- **确需并行写时必须用 worktree 隔离**(与 §9b 单分支规则协同):
  - **落点(2026-09-30 改,用户拍板"项目产物不外流"):`git worktree add --detach .worktrees/wt-<任务名>`**(项目内 `.worktrees/`,已 gitignore;旧落点 `../IHUI-AI-wt-<任务名>` 即盘根散落目录,2026-09-30 已清理 13 个残留并立盘根卫生守门 `check-disk-root-hygiene.mjs` 防回潮)。detached HEAD,不占分支名,不违反 §9b
  - worktree 内正常开发 + commit(本地 sha 可引用;worktree 无 node_modules,hook 必败,可 `--no-verify`)
  - 完成后回主 worktree `git cherry-pick <sha>` 收编,随主 worktree push
  - 收编后立即 `git worktree remove .worktrees/wt-<任务名>` + `git worktree prune`
- **worktree 内约束**:venv/node_modules 各自安装;端口不得冲突(docs/port-management.md 注册表);共享 DB/Redis 时 schema 迁移互斥。
- **守门兜底(2026-08-31 已落地)**:即使未用 worktree,守门已支持 staged-scope 降级防误伤——① `check-api-routes.mjs`(pre-commit 第 8 项)仅收集暂存区前端文件调用点,暂存区无前端文件→跳过,暂存区为空(手动跑)→保持全量;② 新增 `scripts/check-typecheck.mjs` 包装 push 门全量 typecheck(**判据 = 本次改动范围**:优先 `PUSH_SCOPE_FILES`(pre-push 依 git 传入的 remote_sha..local_sha 计算),暂存区仅兜底;报错文件均不在改动范围内→降级警告放行;解析不到报错文件=tsc 未真正运行→按失败,宁误拦不放过);③ `.husky/pre-push` 第 2 段接入 `node scripts/guardian-runner.mjs --push-gate` 编排。自检:`node scripts/check-typecheck.mjs --self-test`(新增样例 8-12 覆盖 refspec push 与 Next.js 路由组括号路径)。
  - **2026-09-03 push-scope 修复(必读,曾致 push 反复被硬拦)**:原判据只用暂存区,而 `git push <sha>:<ref>` 这类 refspec 推送**不产生暂存区**,若此刻他人也没 staged 文件,降级直接失效 → 他人并行会话的半编辑态报错(实测 miniapp-taro TS1005、web TS2345,单独复验均 0 错误)会硬拦本次 push。故 pre-push 先缓冲 stdin(`PUSH_REFS="$(cat)"`)再回喂 git-lfs,并据 `remote_sha..local_sha` 计算改动文件导出 `PUSH_SCOPE_FILES`;改动文件 >300 时清空该变量退回暂存区兜底(env 有长度上限,截断会漏判→宁可不降级)。同修一处不安全缺陷:tsc 报错正则原排除括号,把 `app/(main)/xxx.tsx` 截断成 `/xxx.tsx`,导致范围内文件匹配不上而**误放行**,现改为「扩展名 + `(\d+,\d+):`」双锚定。
- **全流程已实战演练验证(2026-08-31,主仓零残留)**:worktree add --detach(9785 文件)→worktree 内 commit(--no-verify)→主仓 `cherry-pick --no-commit` 收编验证无冲突→`git restore --staged -- <file>` + 删除文件精准撤销→`git worktree remove` + `git worktree prune`。细则:① cherry-pick --no-commit 验证后**必须立即撤销**,验证/撤销对在同一 git-lock 单元内紧凑完成,防暂存文件被并行会话的 commit 卷入;② 演练/临时文件删除用 `Remove-Item -LiteralPath`(回收站式删除会失败)。
- **应急:主 index 写锁/损坏时用 GIT_INDEX_FILE 旁路提交**(2026-08-31 实战验证,d22d233091 即此法提交):症状为 `fatal: Could not write new index file.`(objects 可写、磁盘充足、无 index.lock)→ 主 index 被外部句柄锁定。手法:`$env:GIT_INDEX_FILE = "$env:TEMP\ihui-index-recover"` 后照常跑 safe-commit 全流程(写入临时 index,主 index 不被触碰);旁路期间 staged-scope 守门读到的暂存区恰为本次声明文件,反而更精准。收尾:`Remove-Item Env:\GIT_INDEX_FILE` 必须清除防污染后续命令;事后 `git reset` 修复主 index(`git write-tree` 应返回非空树)。
- **stash 清理零损失流程(2026-08-31 实例:backup/stash-temp-other-sessions-8e1863c)**:`git tag backup/stash-<名>-<sha7> '<stash-ref>'` → `git rev-parse` 验证 tag 与 stash SHA 一致 → `git stash drop '<stash-ref>'`。tag 指向原 stash commit,内容永不丢失,随时 `git stash apply <tag>` 可恢复。
- **stash 后当日 apply 落地(2026-09-08 立,stash 丢失事故根治)**:任何 `git stash push` 的内容**必须当日 `apply` 落地或 tag 零损失备份**——stash 是黑盒(不提醒/不过期/不可见),滞留 stash 在并行会话合流推进下必然造成"功能被回滚"假象(2026-09-08 实证:压缩入口整合 + IM 聊天室重写 775 行双双滞留 2-3 天)。守门:`scripts/check-stale-stashes.mjs`——guardian-runner 第 30b 项(blocking,≥48h 含源码改动未备份→阻塞 commit)+ dev-web.mjs/apps/web predev 启动 warn 扫描;跳过 `HUSKY_SKIP_STALE_STASH_CHECK=1`。
- **全面禁止 git stash(2026-09-18 立,机制级根治,取代"当日 apply"条——从源头禁止,不再产生新 stash)**:任何情况下禁止 `git stash push` / `git stash`(含路径限定、含 -a/-u),**推了就是要生效**——改动必须直接 commit(走 safe-commit.mjs 多 agent 流程)或用本节 worktree 隔离,绝不允许藏进 stash。已落地 git 层面硬拦截:`.husky/reference-transaction` hook 在 prepared 阶段对 refs/stash 新建事务 exit 1 → `git stash push` 直接 `fatal: reference-transaction hook aborted`(`--no-verify` 也无法绕过;git 无 pre-stash hook,refs/stash 写入走引用事务故可拦)。hook 只拦"新建"不拦"清理"(drop/pop/clear 放行),纯 sh 内建实现不依赖 grep/sed。唯一例外=清理存量 stash:`IHUI_ALLOW_STASH=1` 前缀执行,且必须先按上方零损失流程 tag 备份、在会话报告声明放行原因。
- **红线**:
  - ❌ 禁止一切 `git stash`(2026-09-18 起全面禁止,见上方"全面禁止 git stash"条款,hook 已硬拦截;历史事故:无路径限定全仓快照曾致 stash@{0} 沦为 1781 文件巨型快照)——改动直接 commit 或 worktree 隔离
  - ❌ 禁止在非 worktree 场景用 detached HEAD 承载长期开发(commit 游离无引用)
  - ❌ 禁止 commit message 声称未落盘的规范/代码条目——commit 前 `git show --stat`(或 `git diff HEAD --stat`)核对声称内容与实际 diff 一致(2026-08-31 d22d233091 事故教训)
  - ✅ worktree 收编前 `git log <sha> --stat` 核对内容,收编后 `git worktree list` 确认清理
  - ✅ 怀疑脚本被外部进程(水印/注入)污染时:`node --check` 取证 → `git show HEAD:<path>` 验证基线 → `git restore --source=HEAD --worktree -- <path>` 恢复 → 冒烟(2026-08-31 git-lock.mjs 修复实例:水印进程追加零宽 Unicode 行致 SyntaxError)

---

### 12e. workspace 加依赖禁用 `pnpm install --filter`(2026-09-23 立,自伤实测)

- 实测:为 `apps/extension` 加 `@ihui/design-tokens` workspace 依赖时跑 `pnpm install --filter @ihui/extension`,pnpm 按"只装被选中项目所需"重链接,**顺带剪掉根 `node_modules` 里未被该包引用的链接** —— `lint-staged` 就此消失,`.husky/pre-commit` 第一步即崩,每次 commit 都失败并逼出 `--no-verify`,连带 109 道守门全废(而 `git status` 与 typecheck 都看不出依赖树被削)。
- **规则**:本仓任何"新增/调整 workspace 依赖"一律跑**全量 `pnpm install`**(不带 `--filter`);改完必须验证 `node_modules/lint-staged/bin/lint-staged.js` 与 `node_modules/.bin` 关键入口在位,再提交。
- 排查同类问题的顺序:`grep "Cannot find module" .workbuddy/hook-logs/pre-commit.log`,先怀疑依赖树被动过,再怀疑守门判据。**但"根 `node_modules/.bin` 缺 shim"这一型既不看 package.json 也不报 Cannot find module** —— 它表现为 lint-staged 第一步 `'eslint' 不是内部或外部命令` → 每次 commit 必红 → 各会话合法 `--no-verify` → 约 110 道守门对全队同时失效(2026-09-24 实测连吃三次)。此型 `pnpm install` 与 `pnpm install --force` 都在 1.2 秒内回 "Already up to date"(pnpm 认为树是好的),**上一行那个 remedy 不起作用**;唯一生效的是 `node scripts/repair-node-bin-links.mjs`,且**判据只认实测版本号**:`node_modules/.bin/eslint --version` 与 `tsc --version` 必须都出版本号(或 `pnpm exec eslint --version`)。
- 排查同类问题的顺序:`grep "Cannot find module" .workbuddy/hook-logs/pre-commit.log`,先怀疑依赖树被动过,再怀疑守门判据。

### 12f. 恒红门优先级(强制,2026-09-27 立 —— 一天之内同型三道实测)

- **规矩:任何 blocking 检查红在"干净 HEAD"上(被它点名的文件都不脏),修它的优先级高于一切新增工作,包括你手上那张功能票。** 理由不是账面好看而是后果全局化:一道与本次改动无关的恒红门 ⇒ 每个会话每次提交都**合法地**走 `--no-verify` ⇒ 链上全部检查对每一次提交作废。实测 2026-09-27 一天内连出三道(影子副本漂移 / 注册的门体文件被外部清理层删掉后又被一次 `git add -A` 记成暂存删除 / mypy 红在已入库代码上),期间**这台机的每次提交都是零检查入库**,而 `git status`、typecheck、其余检查全部报绿 —— 失效形态是安静,不是吵闹。
- **判"这道红是不是我的错"只看一件事:它红在哪个面上。** 涉事文件 `git status --porcelain -- <文件>` 为空 ⇒ 是已入库缺陷,按上一条优先修;有输出 ⇒ 别人在飞,只报不动(§12 边界不变,不得为变绿去改别人的文件)。
- **跳门次数有两个来源,合起来才是总量:`.workbuddy/safe-commit-attestation.jsonl`**。①**跳门决策**由 `safe-commit` 写(kind=mine/not-ours/unattributed,含 `failedGates`、`ranFullBatch`、`declaredFiles`);②**旁路落地**由 `object-space-land.mjs` / `live-doc-edit.mjs` 写同一本台账(kind=bypass-landing,`gatesRun:false` + `landedSha` + `source`),因为 commit-tree + CAS **结构上不跑任何钩子** —— 2026-09-29 现读该文件当天 0 条而同窗口非合并提交 222 枚,旧写法"只有一个真值来源"会让人把"0 条"读成"今天没人跳门",比不写这句更糟。要回答"这周有多少次提交是在检查全废的状态下进的库",跑只读统计器 `scripts/plan-bypass-ledger-report.mjs --since <日期>`,它按日输出 normal/skipped/bypass/unknown 四态;**任一态取不到就点名"未判定",绝不以 0 冒充**(钩子日志过大只读尾部时 normal 是**下界**,这一维也必须被喊出来)。`ranFullBatch=false` 仍比跳门更严重(那一批检查压根没跑)。**仍开着的一格**:`plan-tasks-merge.mjs` 的落地站点未接②,它产出的每一枚提交今天仍会落进 unknown(承 G-725,见台账对应行)。所以"修好三道"完全可能只是"修好我恰好看到的三道"——门禁红的量算入口另有其物:`scripts/alert-volume-report.mjs`(该文件不在位即属**未交付**,调用方必须报"未判定",不得拿 0 当结论)。
- **"注册在位而门体文件不在"这一格所有接线判据都看不见**(守门 89 的候选集按"脚本在不在被审面"枚举,脚本不在就连候选都不生成),2026-09-27 已补成常驻判据 **R9**:注册面与存在性面**必须同面同轮**(`--staged` 读索引注册 ⇒ 存在性也读索引),注册表解析到 0 条 ⇒ 判"尺子失效"而不是通过。手工核验一句:`git cat-file -e HEAD:scripts/<门体>`。
- **修红不得顺手放宽判据。** 若真因是"被审代码换了写法",必须**同一枚提交里同时**改审它的正则 —— 否则"把它升级成 blocking"这个动作本身就是在给一台瞎掉的尺子背书(守门 117 记过同型:参数改成可选标注后正则只认旧形态 ⇒ 输出"未对齐 0 个"并 exit 0,一道刚升档的安全门变成永远绿灯的尺子)。

## 13. 文件修改持久化强制规则(强制)

- 任何文件修改后**必须立即用 Read 验证**修改已落地(防止文件系统缓存不一致)。
- 大文件(>500 行)修改后,Read 验证时**必须读取修改区域 ±50 行**,确认上下文完整。
- 若 Read 返回内容与预期不符(陈旧缓存),**必须**重读最多 3 次,仍不符则停止并报告用户。
- **禁止**基于未验证的"假设修改已成功"继续后续操作。

---

## 14. Agent 自主验证强制规则(强制)

- Agent **必须独立完成**它能完成的验证(browser_use / API 测试 / 文件检查 / 命令执行),**禁止**要求用户代为验证。
- **禁止**在交付报告中写"请你刷新浏览器查看效果" / "请你启动 dev server 验证" / "请你手动测试"等甩锅措辞。
- 验证失败时,记录失败原因 + 已尝试方法 + 建议下一步,**不**得假装验证通过。

---

## 15. 工作区卫生强制规则(强制)

**禁止项**:① 在 `G:\` 根目录创建任何文件;② 项目数据(扩展打包/Chrome profile/构建副本/临时 DB/临时配置)写到项目外路径;③ 硬编码 `C:\temp\ihui-*`/`$env:TEMP\ihui-*` 等项目外路径;④ agent 用 RunCommand/PowerShell/Out-File/Set-Content/New-Item 在项目外直接创建文件;⑤ 在 `G:\` 根目录运行 Qt 类外部工具或执行 pnpm 命令(会创建 `.pnpm-store` v11 冲突);⑥ 硬编码中文绝对路径(GBK 乱码)。路径推导用 `$PSScriptRoot`/`__dirname`/`import.meta.url`。唯一例外:纯系统日志(`debug.log`/`next-server.log`)可写 `$env:TEMP`。

**必须用项目内路径**(根 `g:\IHUI-AI`):扩展打包→`apps/extension/.output/chrome-mv3/`;Chrome profile→`.ihui-agent/tmp/chrome-profile/`;临时副本→`.ihui-agent/tmp/<任务名>/`;临时脚本→`.ihui-agent/tmp/<脚本名>.ps1`;临时文件统一放 `.ihui-agent/tmp/`(已 gitignore),任务完成后清理。

**守门脚本**:

- `check-workspace-hygiene.mjs`(第 25 项 BLOCKING:项目外路径写入;WARNING:硬编码中文路径)
- `check-parent-pollution.mjs`(第 26 项 BLOCKING:项目父目录递归 2 层+桌面根级+用户主目录巡查,命中=文件名强信号 `search_*.ps1`/`*_result.txt` 或内容双信号)。**2026-09-23 结构性加固**:新增 `CREDENTIAL_DIR_NAMES` 共 8 项,对 `secrets`/`secret`/`credentials`/`credential`/`密钥`/`certs`/`certificates`/`.pybcrypt` **整目录不扫不删**(目录名按 `toLowerCase()` 比对,大小写不敏感;既往逐 `USER_LEGIT_PATTERNS` 按 filename 豁免已被证明会漏第三把密钥);另新增 `BACKUP_DIR_NAMES` + `BACKUP_DIR_PREFIXES` 整目录豁免,防"卫生守门删掉数据备份"。
- `cleanup-external-junk.ps1`(G:\ 垃圾清理,16 目录+31 文件,`-Force` 跳过确认)
- `g-root-guardian.ps1` v2.0(G:\ 实时守门,FileSystemWatcher+白名单优先 5 层判定,~110-222ms 删除,Windows 计划任务自启)+ 配套 `g-root-blacklist.json`/install/uninstall/status 脚本
- post-commit 自动 `--auto-clean --quiet`(仅清文件名强信号);定时 08:00 巡查;跳过 `HUSKY_SKIP_HYGIENE=1`。**⚠️ `--auto-clean` 对强信号命中直接 `unlinkSync` 实删文件、无任何二次确认** —— 清理类任务的铁律:先跑不带 `--auto-clean` 的 `node scripts/check-parent-pollution.mjs` 看命中清单并逐项验明身份,**命中项落在凭据/密钥目录内或名字含 key/secret/token 的,一律先补豁免再清理,顺序不可颠倒**(2026-09-22 实测用户口令表 `D:/DevEnv/secrets/ihui-app-password.txt` 因 `ihui-` 前缀被判为 agent 垃圾 —— 若不先补目录级豁免而直接跑 `pnpm hygiene:parent:clean`,它**会被无声删除**;该文件现仍完好)。历史案例见 `.ihui-agent/archive/AGENTS_history.md`。

### 15b. 项目外落点唯一制(强制,2026-09-23 立)

用户规定:**任何文件都不得写在项目文件夹之外**,只允许下述四个经批准的落点;不再新增第五个。
禁止在家目录、盘根、`AppData` 下随手建目录 —— 本机曾因此散落 `D:\tmp-*`、`D:\c`、`D:\d`、
`D:\IHUI-AI-backup-*.tar`(8.3GB)等十几个游离项,已收口。

| 落点                                 | 用途                                                                                                                                  | 依据 |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| `D:\IHUI-AI\`(含 `.ihui-agent\tmp\`) | 源码、构建产物、一切临时物                                                                                                            | §15  |
| `D:\DevEnv\backups\`                 | **唯一备份目录**:`git\ sql\ deploy\ archives\ desktop\ pg\ env\ releases\`。项目内不得留备份(`backups/` 曾压 64MB DB 快照,已外迁)     | §15b |
| `D:\DevEnv\{cache,tools,runtimes}\`  | 工具链缓存唯一根;家目录里的工具状态一律 `robocopy /MOVE` + **junction** 改道(禁改 `HKCU\Environment\Path`),旧路径经 junction 仍可解析 | §26  |
| `D:\DevEnv\Temp\`                    | `TEMP`/`TMP`/`TMPDIR`(HKCU,需新开终端才继承)                                                                                          | §26  |

> **上表的 `D:` 是"D 盘那份 checkout"的历史值,不得当本机现值照抄**(2026-09-24 实测,登记于 PROJECT_PLAN —— **本行 2026-09-24 被并发旧基线回写带走过一次,同日原地补注使其重新成为"本侧修改"**
> 第三十三批 续五⑦):`gitArchiveDir()`(`scripts/lib/gitdir.mjs`)按**工作树所在盘**推导,工作树在
> `G:\IHUI-AI` 时它返回 **`G:/DevEnv/backups/git`**;而 `D:\DevEnv`(Temp/backups/kc-tools/logs)与
> `G:\DevEnv`(Temp/backups/cache/tools)**同时存在**,各自服务所在盘上的 checkout。
> **规则:凡脚本需要归档/备份/临时落点,一律 `import` 出口函数(`gitArchiveDir()` / `gitdirArchivePath()` /
> `resolveBackupDir()`),禁止再硬编码盘符** —— 硬编码的结果是"备份落点解析到不存在的路径",
> 表现为 `git-guardian --status` 的 `backupOk:false` 这类静默失效(§5b 已记过一次同型)。

**显式例外与已改道项(改道用 junction,故旧路径仍可用)**:

1. `D:\IHUI-AI-git-repo` —— 真 gitdir。§5b 实测宿主层会整体删除工作区内 `.git`,故必须在外;
   `scripts/git-rebuild-local.mjs:169` 硬编码该路径,`git-refs-heal.mjs` / `git-guardian.mjs` 依赖其
   `refs-manifest.json`。同族的 `IHUI-AI.git-backup-20260912` 与 `IHUI-AI-git-repo.broken-*` 由
   `scripts/lib/gitdir.mjs:250` 按路径主动选读,一律禁删。
   **但"必须在盘根"只是历史状态**:2026-09-23 起,gitdir 的**备份与现场归档**统一落
   `D:\DevEnv\backups\git\`(单一真相源 `gitArchiveDir()` / `gitdirArchivePath()`),
   因为旧写法 `${GITDIR}.broken-<ts>` 每次守护/重建归档都必然在盘根长一个新目录(实测累计 3 个 /
   1.94GB)。盘根因此从 6 项收口到 2 项(项目 + 活 gitdir);回归测试
   `scripts/tests/gitdir-archive-paths.test.mjs`(4 例,含"调用点必须真用该出口"的装车断言)。
   **改这类路径必须同批改 `resolveBackupDir`**:它曾只认写死的 `D:/IHUI-AI.git-backup-20260912`,
   目录一迁走就解析到不存在路径,表现为 `git-guardian --status` 的 `backupOk:false` —— 本地恢复源
   静默失效且无告警(与同日生产部署冻结同属"凭据/路径过期只以下游门禁失败形态出现")。
2. `F:\BaiduSyncdisk\密钥\`(盘符按 §5d 由 `scripts/lib/key-dir.mjs` 探测,不得写死)——模型密钥唯一权威源(§5d),不入仓、不入聊天记录。
3. 第三方 IDE/agent 自管家目录的**运行态**(`~/.workbuddy\binaries\PortableGit` 是
   `scripts/lib/gitdir.mjs` 的 `resolvePortableGitCandidates()`(候选序 = 环境变量 > versions 目录扫描 > 旧 1.2.0 兜底)解析 git 二进制的首选;`.qoder-cn` 承载本项目记忆与工作区状态)。
   这类不属"我们的产物",只登记、不搬动。
4. `~/.ihui`(1.2MB / 693 文件)—— 我们 CLI 的全局状态,**2026-09-23 已按 §26 的 junction 机制改道**
   到 `D:\DevEnv\cache\userhome\.ihui`(复制后逐文件校验 0 差异 → 删源 → `mklink /J`,**未改一行业务代码、未设任何环境变量**)。
   之所以不用"设 `IHUI_HOME`"这条路:`IHUI_HOME` 当前在本仓有**两套互斥语义** ——
   `apps/cli/src/plugins/paths.ts:31` 当它是**家目录**(再拼 `.ihui`),而
   `apps/cli/src/tools/mcp-oauth.ts:43`、`mcp-credentials.ts:26`、
   `apps/ai-service/app/core/message_history.py:33` 当它是**状态目录本身**;另有
   `workspace-ai-service.ts:38/252/2578`、`workspace-ai.ts:714/730/807`、
   `announcements/index.ts:117`、`refresh-cli-token.mjs:34` 与 `apps/cli` 内约 30 处裸 `homedir()`
   完全不认它。**设 env 必造双根分裂**;junction 则对硬编码路径同样生效。
   语义统一属独立技术债(动它需同步改 `plugin-marketplace.test.ts:114-129` 等被钉死的断言),不在本策略范围。

**新写文件前的三问**:① 是源码/产物吗 → 项目内;② 是备份吗 → `D:\DevEnv\backups\<类>\`;
③ 是临时物吗 → `TEMP`。三者都不是 → 停下来问用户,不得自建新目录。

---

## 16. Push 阶段跨 Agent 改动保护规则(强制)

- 本 agent 完成 commit + push 后,不再触碰 working tree,不执行 `git pull` / `git fetch` / `git rebase` / `git push --force`。
- 抹除其他 agent 改动 → **协作事故**;混入其他 agent 改动到自己 commit → **污染事故**;修改其他 agent 文件"帮他们修" → **越权事故**。
- `--no-verify` 跳过 hook 的合法性:见 §12 最后一条(hook 失败因其他 agent 代码 → 合法跳过;本任务代码 → 禁止跳过);`--no-verify` 不是流程事故,前提是本任务改动文件已通过 typecheck + lint + build。

---

## 17. UI 改动验证强制规则(强制)

**触发条件**:UI 样式/布局/交互改动(CSS/className/style/组件结构/Tailwind 类/shadcn props)。

**强制动作**(缺一不可,违反视为交付事故):

1. 改码前 browser ping `http://localhost:8801` 确认服务在线,不通则先启动 web+api+ai-service(端口见 `docs/port-management.md`)。
2. 改码后确认 web+api 服务在跑(browser 实际访问)。
3. 用 browser_use subagent 渲染目标页面,截图自验 4 状态:默认/hover/active/dark mode。
4. 读 DOM 数值验证样式生效(`getAttribute`/`getComputedStyle`,禁止只靠截图)。
5. 交付附 4 状态截图 + "已自验通过"声明;服务起不来禁止交付。

**commit message trailer**:含 `apps/web/**/*.css` 改动必须附 `Verified-DOM: http://localhost:8801/<path> (<DOM 属性=数值> ...)`,commit-msg hook 自动守门。

**Next.js CSS 缓存陷阱**:改 globals.css/styles 后 HMR 不一定重编译 CSS chunk,必须 curl 当前 CSS chunk 验证新值;`grep -c` 返回 0 → kill 旧 next-server 重启 `pnpm --filter @ihui/web dev`,等 15s 重新 curl 确认。

**工具故障应急**:dev server 永远只在集成终端内部运行(`RunCommand long_running_process`+`blocking=false`),禁止 `Start-Process` 派生独立窗口。RunCommand 连续 2 次返回空输出 → 判定失联 → 告知用户在集成终端面板手动执行。工具反复失败时先 Grep project_memory.md 查已知约束。

**豁免**(允许跳过 browser_use):① 纯后端 API(curl 验证);② 纯类型/工具函数(typecheck+test);③ dev server 30 分钟无法修复(降级单元测试);④ CI 环境(e2e)。历史案例见 `.ihui-agent/archive/AGENTS_history.md`。

---

## 18. 启动项目语义(强制)

用户说"启动项目" = 前后端全链路同步启动:web + api + ai-service(端口见 `docs/port-management.md`),必要时检查并启动数据库 / Redis。禁止只启动前端就交付。

---

## 19. i18n 约束规则(强制)

### 翻译文件语言纯度

- **zh-CN.json**:基准语言文件,其他 4 语言必须 parity(key 集合完全一致)。
- **zh-TW.json**:繁体中文,禁止简体字(pre-commit 第 2b 项,opencc 字形转换检测,阻塞)。
- **ko.json**:韩语,禁止中文残留(第 2c 项,字符范围检测,阻塞)。
- **ja.json**:日语,禁止中文残留(第 2d 项,warn-only,日文汉字词易误报)。
- **en.json**:英文,禁止中文残留 + 禁止破碎机翻(第 2e 项,阻塞)。

- **parity 绿不等于内容语种对(2026-09-26 立,由 D20 实测逼出;同日已配尺子 = 守门 133)**:上面五门判的是**键集**与"**有没有**中文残留",所以"某一块整体落成另一语言"这一型结构上看不见。实测抓到三种形态,当时全部门禁都是绿的:① `aiChat.org` 的 **ja 装了繁体、zh-TW 装了谚文**;② `chat.exportMenu.exportPdf` 是 **ja←zh-TW←ko←ja 的三向轮转**(判据必须按**码位**量,按字形肉眼判会把谚文 `을 익스포트` 与假名 `をエクスポート` 看成同一种东西);③ `aiChat.toast.orgSaved` 被机械写成 `orgSaved:{orgSaved:"…"}` **自套一层**,`t('toast.orgSaved')` 取到对象 ⇒ 功能成功但提示永远不响。**批量落 i18n 后必须做两件事**:逐语种量"这块内容是不是这门语言",以及全量扫"同名自套一层"。**尺子**:`scripts/check-i18n-locale-content-language.mjs`(唯一码位族表在 `scripts/lib/i18n-script-families.mjs`,五族区间两两不相交由 `rangesAreDisjoint()` 机器证明)。**三条能力边界要照抄进判断**:汉字族内部**简繁码位判不了**(日本新字体与中国简化字同码位:気/会/図/点),所以只在"族级矛盾"上判红;`en` 整门 L1 刻意跳过(品牌原文必须留汉字);超过 4000 叶的大块计"未判定"而非通过 —— `--strict` 因此**恒 exit 2 直到未判定被清**,那是它拒绝出合格证,不是仓库故障。**定级**:L1 默认档只报数、`--strict` 判红(建门实测候选 16 处,当场 blocking 就是恒红门 ⇒ 逼人跳门连带废掉全部守门,§12e 同型);L2 按每文件 HEAD 自身存量棘轮(建门实测**存量 5 处,不是本节先前写的"现仓 0 处"** —— 该数字已被下面这条实测推翻并就地改写,照旧文派单会去找一个不存在的零存量)。问责入口 `pnpm check:i18n-locale-content-language`(默认档,已串进 `check:all`)/ `pnpm check:i18n-locale-content-language:strict`。**建门当天顺带抓到两件事**:① 门自己第一版把"块级 4000 叶上限"误套在**文件**级 ⇒ `web/*.json` 22,702 叶只扫 4,000,连真事故都看不见;② 上面①②③三处内容**我曾按 `b4d2305c6` 改对过一次,被并发提交 `9ccfd31a8` 拿旧基线整文件写回,五道 i18n 门与部署环一路绿灯**,直到这道新门复验才现形(内容由 `55e772b78` 重新改对)。⇒ **通用教训:语言包这类多会话共写文件,"我改过了"不构成事实,判据必须在被审面上现读。**

### JSON 重复键禁止

- 同一对象内禁止重复 key(`JSON.parse` 时最后一个生效,前面被 shadowed)。
- 添加新键前 Grep 确认同级命名空间无同名块(models/nav/sort/market 等高频命名空间尤需注意)。

### 翻译策略(source of truth:`scripts/brand-glossary.json`)

- 品牌名/公司名/字体名/技术术语:优先 canonical 英文名(智谱清言→Zhipu AI, 宋体→SimSun, 物联网→IoT)。
- 人名:fictional/示例数据用拼音(李思涵→Li Sihan / 리쓰한 / リ・スハン);称呼符合目标语言习惯(李总→이 대표)。
- 占位符 `{var}` / `{{var}}` 必须原样保留。
- zh-TW 用繁体字形(简体→繁体);ko 用 Hangul;ja 汉字词允许(登録/確認/削除)但简体字残留改日文习惯;en 禁破碎机翻(如 AgentDevPlatform)。

**守门工具**:`scan-i18n-zh-residue.mjs <locale>`(zh-TW/ko 阻塞,ja warn)/ `check-i18n-broken-en.mjs`(en 破碎机翻,阻塞)/ `check-i18n-keys.mjs --staged`(key 完整性+parity+白名单+**含点键**+**同层重复 key**,后两项 2026-09-20 起阻塞)/ `brand-glossary.json`(canonical 映射表)+ `apply-brand-glossary.mjs [--dry-run]` / `i18n-diff.mjs`(差异检测,输出 pending.json)/ `i18n-apply.mjs [--check]`(应用器,按 zh-CN 重排 key+parity 校验)/ `check-i18n-namespace-passing.mjs`(检测 useTranslations('xxx') 限定命名空间 + 把 t 传给 @ihui/ui-react 共享登录组件的 bug 模式,warn-only)。

- **KR:语言包相对父提交丢键必须点名**(2026-09-27 立,`check-i18n-keys.mjs` 新增判据)。本门其余判据全是**横向**比(语言之间 parity)或查引用(键有没有人用),所以"五语言一致缩水"这一型结构上看不见 —— 而语言包少键的端上表现是**直接回显键名**(实测某端队列条渲染出裸串 `ai.pane.inputNotices.queue.denied.reorder`;本机在飞语言包副本 cli 一门少 **320** 键、extension 五语言各少 **23** 键,而 parity 全绿)。KR 只在 `--staged` 档判「索引 vs 该文件的父提交」——这是它的定义,不是"混面取数";全量档**刻意不判**(其"父"只能是 HEAD^,那会把历史上每一次正当清理天天重判成违规 ⇒ 恒红门,§12e 同型),仓库尚无父提交时打印"不适用"而不是判死。出口只有两条:① 把键补回;② 在 `scripts/data/i18n-key-removals.json` 按**文件**逐条声明 `file + keys + reason + until`(到期由 KR 自己判,镜像 KR-3 钉死;**刻意不做目录级通配** —— 一条理由不该给五个文件背书,而五个文件的丢键集合本来可以各不相同)。删键是允许的,**静默删键**不行。取证:镜像 `scripts/tests/check-i18n-keys.test.mjs` 的 KR-1…KR-4(KR-1 自带"同一份输入 parity 不红"的对照 —— 那才是本判据存在的全部理由;KR-4 是形状锁,防"函数在而无人调"),另有真仓 A/B/C 私有索引注入(注入未声明丢包必红 / 逐文件声明后不再计债 / 声明过期必红)。

**AI 翻译流水线**(强制,零用户算力):zh-CN.json 新增/修改 key 后必须执行:① `i18n-diff.mjs` 检测差异生成 pending.json;② AI agent 读 pending.json+brand-glossary.json 自行翻译;③ 写 i18n-translations.json(`{ translations: { [lang]: { [key]: value } } }`);④ `i18n-apply.mjs` 应用到 4 语言;⑤ `check-i18n-keys.mjs` 验 parity;⑥ `scan-i18n-zh-residue.mjs ko/zh-TW` 验无残留。

**守门集成**:web(第 2f-web 项 blocking,仅 staged 涉及 zh-CN.json 时);miniapp-taro(第 2f-miniapp-taro 项 blocking,`--target=miniapp-taro`);多 agent 并行时其他 agent 改 target locale 不触发阻塞。

---

## 20. 任务完成硬定义 — 杜绝"commit 后忘记 push"协作事故(强制)

### 任务完成的硬定义(5 条全满足才可声明"完成")

1. ✅ **本地有 commit**:`git log --oneline -1` 显示本次任务的 commit SHA
2. ✅ **工作区干净**:`git status --short` 无本任务残留 untracked / modified
3. ✅ **origin 同步**:`git push origin <branch>` 成功,stdout 含 `X..Y <branch> -> <branch>`
4. ✅ **HEAD 对齐**:`git rev-parse HEAD` === `git rev-parse origin/<branch>`
5. ✅ **守门脚本通过**:`node scripts/git-push-guard.mjs` exit 0

### 4 道自动防线

1. **pre-commit**:`check-push-sync.mjs`(guardian 第 29 项 blocking),commit 前检测本地 ahead(`git rev-list --count origin/<branch>..HEAD`),>0 阻塞;跳过 `HUSKY_SKIP_PUSH_SYNC=1`(不推荐);归档 commit `IHUI_ARCHIVE_COMMIT=1` 豁免。
2. **post-commit(主防线)**:`git-push-guard.mjs` 自动检测 ahead → push + 验证 local == remote,失败阻断提示手动 push;跳过 `HUSKY_SKIP_PUSH=1`(不推荐)。
3. **pre-push**:`.husky/pre-push` 第 2 段跑 `guardian-runner --push-gate`(2026-08-31 改版 / 2026-09-03 push-scope 增强:check-typecheck.mjs 包装,**判据优先 PUSH_SCOPE_FILES 本次推送改动范围,暂存区兜底**——报错文件均不在改动范围内时降级警告放行,防并行会话工作区噪音误伤,见 §12d),失败阻止 push(commit 仍本地保留);跳过 `HUSKY_SKIP_TYPECHECK=1`(不推荐)。
4. **手动兜底**:`node scripts/git-push-guard.mjs` 任何时候可手跑,打印 local vs remote HEAD,完全对齐 exit 0。

### 红线(违反视为协作事故)

- ❌ 禁止 commit 后只输出"已 commit"就声明任务完成,必须 push + 验证
- ❌ 禁止交付报告遗漏 "local HEAD == remote HEAD" commit SHA 对照
- ❌ 禁止用 `--no-verify` 绕过 `git-push-guard`(除非 typecheck 自验通过且显式说明)
- ❌ 禁止把 "git push 失败" 作为交付结论(必须修复后重推或显式说明阻塞原因)

### 交付报告必含证据

```
## Git 同步证据
- 本地 commit: <sha>
- origin commit: <sha>
- 同步状态: local == remote ✅ / 落后 N 个 commit ⚠️
- 守门脚本: node scripts/git-push-guard.mjs exit 0
```

### 工具失联处理流程

- **触发条件**:RunCommand 连续 ≥2 次返回 `{Exited, exit_code 0, 空输出}`(连 `Write-Output "test"` / `git --version` 都无输出),判定平台级故障。
- **红线**:禁止把 git 命令清单甩给用户作为交付物;禁止把"用户手动执行"作为完成结论;禁止用"工具失联"停止 retry;必须自己完成 commit+push+验证;工具失联时报告"blocked"状态不声明完成;工具恢复后立即执行 git 流程;唯一例外是用户主动说"我来手动执行"。
- **retry 策略**:首次失联用 `Write-Output "alive-test"` 探测 → 每隔 1-2 轮 retry RunCommand(可派 subagent 尝试) → 持续 retry 不放弃 → 恢复后立即执行完整 git 流程(add → commit → push → git-push-guard 验证)。
- 历史案例见 `.ihui-agent/archive/AGENTS_history.md`。

---

## 21. 功能开发同步更新 README 规则(强制)

### 触发条件

任务**新增 / 修改 / 删除**以下任一类别能力:

- 新功能模块(新增 P3 深度层 / IM 渠道 / 沙箱后端等)
- 现有功能重大调整(API 路由变更 / 架构重构 / schema 迁移)
- 守门规则 / 工程约束新增(本节本身即触发例)
- 项目对外能力清单变化(支持的平台 / 厂商 / 模型 / 端)

### 强制动作(缺一不可,违反视为交付事故)

1. **同步修改根目录 `README.md`**:在对应章节(功能特性 / 架构 / 平台支持 / 守门规则)更新文字 + 表格。
2. **README 改动必须与本任务代码同 commit 提交**:禁止"代码先 push、README 下一轮补"的分期模式。
3. **README 必须可被 git 远端可见**:commit + push 后 `git rev-parse origin/main` 必须包含 README 改动(由 §20 git-push-guard 自动验证)。
4. **交付报告必须含 "README 更新证据"**:列出修改的章节 + 行数变化。
5. **禁止以"下一步建议"形式把 README 同步留给下一轮**:本任务触发条件则 README 同步属本任务一部分,不得列为 P1/P2 遗留项或"最优下一步建议",违反视为交付事故。

### 豁免场景(允许不更新 README)

- 纯 bug 修复(不改变对外能力)
- 纯重构(不改变功能契约)
- 纯测试 / 文档 / 守门脚本改动(不改变运行时能力)
- 纯配置 / 依赖升级(不改变功能清单)
- 单端内部优化(不改变跨端契约)

### 守门(warn-only)

- `scripts/check-readme-sync.mjs`:staged 中有 `apps/` / `packages/` 下功能代码改动但 `README.md` 不在 staged → warn 提醒。
- 集成位置:`.husky/pre-commit` 第 22 项(warn-only,不阻塞 commit,只提醒)。
- 历史案例见 `.ihui-agent/archive/AGENTS_history.md`。

---

## 22. 防止 commit / push / merge 提交丢失硬性规则(强制)

### 触发背景(2026-07-25 立,真实事故)

reflog 记录 18:12-18:20 期间发生 **6 次 `reset: moving to HEAD~` 操作**,导致 3 个本地 commit 在 main 历史中消失:

- `15b984f90` "fix(api): P0 安全债并行修复"(ws-chat/ws-tasks IDOR + payment-gateway 金额反查)
- `5ef36e59d` "fix(web): sidebar 折叠按钮图标 16→20px" 第一次
- `b120c6e20` "fix(web): sidebar 折叠按钮图标 16→20px" 第二次

幸运的是这 3 个 commit 的工作内容已通过后续 commit(`ce3116ebd` merge + `ff7f744e0` 重做)重新整合到 origin/main,但 commit 本身已不可追溯,git log 不再显示原始 commit hash。

**根因**:多 agent 并行 + 某 agent 自动化流程使用 `git reset` 时,未考虑对其他 agent 本地 commit 的影响,导致 reset 把整个 commit 链(包括其他 agent 的工作)一并丢弃。

### 硬性规则(违反视为协作事故)

1. **禁止**在共享分支(任何已 push 过的分支,包括 main)使用 `git reset --hard`。
2. **禁止**使用 `git reset HEAD~N` / `git reset --soft HEAD~N` 撤销本地 commit(在多 agent 并行环境下,该 commit 可能被其他 agent 依赖)。
3. **禁止**使用 `git push --force` / `git push --force-with-lease`(已 push commit 的"撤销"必须用 `git revert`)。
4. **撤销已 push 提交必须用 `git revert`**:产生新 commit 撤销改动,保留原始 commit hash,所有 agent 都能看到完整历史。
5. **撤销本地未 push commit 推荐 `git revert`**(同样产生新 commit 保留历史);仅在确认 commit 内容无价值、且无其他 agent 引用时,才考虑 `git reset`(不推荐,需记录在 commit message)。
6. **禁止** `git stash drop` / `git stash clear`,除非先 `git stash show <id> --stat` 确认 stash 内容已合并到 working tree 或其他 commit;lint-staged 自动创建的 stash 必须保留至少到 commit 成功后下一次 git gc 周期(默认 14 天)。
7. **多 agent 并行 reset 前必须**:`git log --all --oneline | grep <other-agent-commit-sha>` 确认无其他 agent 引用;并在 `PROJECT_PLAN.md` 记录"reset 影响范围 + 已 tag 备份的 commit hash"。

### 已被 reset 丢失的 commit 永久记录(tag 备份)

3 个被 reset 的 commit 已用以下 tag 永久保留(防止 git gc 清理):

- `lost-commit/P0-security-debt` → `15b984f90`(P0 安全债)
- `lost-commit/sidebar-fold-btn-1` → `5ef36e59d`(sidebar 按钮第一次)
- `lost-commit/sidebar-fold-btn-2` → `b120c6e20`(sidebar 按钮第二次)
- `backup/pre-drop-recovery` → `251956eb6`(恢复前主分支快照)

可通过 `git show <tag-name>` 查看完整 commit 内容;`git tag -l "lost-commit/*"` 列出所有丢失 commit tag。

### 守门(blocking 升级已完成,2026-07-25)

`scripts/check-commit-loss-guard.mjs`(guardian-runner 第 30a 项,2026-07-25 升级为 blocking):

- ✅ **已升级 blocking**:`guardian-runner.mjs` 第 30a 项以 `node scripts/check-commit-loss-guard.mjs --blocking --filter-stash` 调用
- pre-commit 前扫描 `git reflog --all --date=iso` 最近 50 步(2026-07-26 扩),检测是否含 `reset: moving to HEAD~` 模式
- 扫描 `git fsck --unreachable --no-reflogs` 检测是否有悬空 commit
- 5 段检查流程(2026-07-26 强化):reflog reset / fsck 悬空 / lost-commit tag / backup tag / 远程 tag 完整性
- **仅远端 tag 已改为自愈式(2026-09-24)**:另一台机器推来的 `lost-commit/*` / `backup/*` tag 本地缺失时,本门自己 `git fetch --no-tags origin refs/tags/<t>:refs/tags/<t>`(分批 50、超时 60s,`IHUI_TAG_HEAL_MAX` / `IHUI_TAG_HEAL_TIMEOUT_MS` 可调),再 `git pack-refs --all --prune` 固化 —— 松散嵌套 `refs/tags/<ns>/*` 会被宿主清理层删掉,**不 pack 等于下次再红**(§5b)。拉回来即不红;离线 / 无凭据 / 超时只降级为警告并写明原因。理由:东西在**远端**、本地少一个引用根本不是 commit 丢失风险,而恒红的唯一结局是人人 `--no-verify`,把真正防丢的三条一起废掉。`reset` 检测 / 未备份悬空 commit / tag 对象不可达 **仍然 blocking**。
- 镜像测试 `scripts/tests/check-commit-loss-guard.test.mjs`(26 例)含端到端装车证明:本地 bare origin +「另一台机」推 tag → 守门须 exit 0、tag 必须真回到本机(证明不是"干脆不看远端"糊过去)、且落在 packed-refs。
- 发现 reset 操作或未备份悬空 commit → exit 1,阻塞 commit
- **悬空 commit 备份**:被 reset / drop 的 commit 一旦出现在 `git fsck --unreachable` 输出中且无 `lost-commit/*` tag 备份,即为"未备份悬空 commit",会阻塞 commit——需先执行 `git tag lost-commit/<name> <hash> -m "lost via reset"` 永久保留(防止 git gc 清理),再重新 commit
- 紧急跳过:`HUSKY_SKIP_COMMIT_LOSS_CHECK=1 git commit ...`
- 详细档案:见 [docs/lost-commit-archive.md](./docs/lost-commit-archive.md)

### 自动化 tag 同步(2026-07-26 立)

`scripts/sync-lost-commit-tags.mjs`(本任务新增)+ `.husky/post-commit` 第 5 段集成:

- **自动 push**:每次 commit 后自动 `git push origin --atomic refs/tags/lost-commit/* refs/tags/backup/*`,防止本地 git gc 清理 tag 后无远端备份
- **手动 fetch**:`node scripts/sync-lost-commit-tags.mjs --fetch` 一键从 origin 拉回所有 lost-commit/backup tag
- **手动 check**:`node scripts/sync-lost-commit-tags.mjs --check` 校验本地+远端 tag 一致性 + tag 对象可达性
- **package.json scripts**:`tag:sync` / `tag:sync:check` / `tag:sync:fetch` / `tag:sync:push`
- **紧急跳过**:`HUSKY_SKIP_TAG_SYNC=1`
- **触发背景**:2026-07-26 04:23 真实事故 — 本地 tag 被 git gc 清理,远端虽有但 fetch 失败(因为 fetch 默认不包含 tag,需要明确 refspec)
- **详细档案**:见 [docs/lost-commit-archive.md](./docs/lost-commit-archive.md) "🛡️ 防护机制" 段

**改道完整性已由机器看守(2026-09-24 立,守门 `check-home-junctions.mjs`)**:上面那三条人肉命令长期
等于没有校验 —— 实测 `AppData\Roaming\npm` 长成 **2.05GB**、`AppData\Local\pnpm-cache` **758MB** 都是实体目录
(同期 `~\.ihui`、桌面端两处 `com.ihui.desktop` 早已是 junction ⇒ 机制有效,**缺的只是回潮哨兵**)。
该门登记 16 项工具态,判三条:存在却不是指针 = `REAL-DIR` 红(且必须量出体积,否则报告写成"合计约 0 MB"的
假小量级)、指针目标不可达 = `DANGLING` 红、登记表被过滤空 = `EMPTY-REGISTRY` 红;非 Windows 如实报"未判定"
不计通过。**第三方 IDE 自管态(`.workbuddy`、`.qoder-cn`)刻意不进登记表** —— 由镜像测试反向钉死,
防止有人把它们加进来逼后人去挪别人的运行态(挪 `.qoder-cn` 等于丢记忆)。
同日已按 §26 机制把这两处收口(镜像复制→逐文件字节校验→源改名→`mklink /J`→经 junction 回读一致→才删源),
C 盘可用 44G → **45.75G**。

**但"能判"不等于"能修"(2026-09-24 补第二层)**:门 96 上线后当天,`D:\DevEnv\cache\userhome` 整棵被清
(13:15 前后,同分钟 `cache/Temp/tools` 被重建),16 项登记里 **已改道 0 / 违规 9**、4770MB 回到 C 盘。
门判红是对的,可它是 **blocking** 而修复只有人肉五步 —— 结果是**每一次提交都被逼成绕过钩子**,
一次绕过等于约 110 道守门对该提交全部作废(§12e 同型)。恒红门的唯一结局就是没人再守门。
所以补了幂等修复器 `scripts/re-home-junctions.mjs`( `--check` / `--apply` / `--reset-dst` / `--self-test` ):
登记表**复用门 96 的 `registryOf()`**、D 盘根**复用 `seal-c-root-stray` 的 `devEnvRoot()`**,不另立第二份表、
不写死盘符;流程仍是"镜像复制 → 逐文件(相对路径 + 字节)校验 → 源改名 → `mklink /J` → 经 junction
回读一致 → 才删源",任一步不符一律回退(源在校验通过前一字不动);robocopy 返回码 ≥8 视为失败。
**两条实测教训**:① 复制语义必须与校验语义一致 —— 要带 `/XJ /SL`,否则源内部的重解析点被 robocopy
展开成真实文件,而指纹两侧都跳过重解析点,永不收敛(`.cargo` 四轮稳定差 13 个即此;**我第一版把它
误报成"目标正被持续写入",是判据错、不是世界错**);② 被进程占用(EBUSY)的项由修复器自己记
30 分钟冷却,免得守护每 2 分钟重抄一遍 108MB 去撞同一个失败。触发点挂在 `git-guardian` 的
`healHomeJunctions()`(与 `healRootSeal` 同位、**早退之前**;每日 03:00 体检兜不住 23 小时空窗)。
实测:9 项违规 → 8 项已改道并逐字节校验一致(含 `.cargo` 947MB/21760 文件);剩 `~\.codex` 被别的会话
正在跑的 `codex-windows-sandbox-service` 占用 ⇒ 进冷却,该服务退出后 2 分钟内自动补回,**不杀别人的进程**。

**第三层:悬空目标必须能重建,冷却不得拦住人工窗口(2026-09-24 同日补,前向更正上面最后一句)**
上面"该服务退出后自动补回"是**错的**:`CodexSandboxService.OpenAI.Codex` 是 StartMode=Auto 的
LocalSystem 服务(实测 PID 6196,exe 在 `WindowsApps\OpenAI.Codex_*` 里、**不在 `~\.codex` 内**),
它**永不退出**,所以"等它释放"等于无限期恒红。处置是把窗口**造出来**而不是等:停服务 → `--apply` →
`finally` 里无条件启回 → 复核(服务 `Running`、事件日志 15 分钟内无 codex 相关报错、`Get-Item -Force`
的 `Attributes` 含 `ReparsePoint` 且 `Target` 指向 D 盘)。实测 **5732 个文件逐字节校验一致后改道**,
门 96 从"违规 1 / C 盘 108.8MB"到 **违规 0 / exit 0**。动手前必须先确认**只有该服务自身**匹配 codex
(无并发 CLI/IDE 会话),否则不 stop。

补这一层时暴露了两个真缺陷,都由自检抓出(不是演练抓的 —— **演练只能证明"会红",判据才证明"不会修"**):

1. **`repairOne` 的判序**:第一行是 `if (!existsSync(srcPath)) return 'absent'`,而**悬空 junction 的
   `existsSync` 返回 `false`**(它跟随重解析点,目标已被删)。于是"源不存在、无需改道"把这一整型吞掉,
   表现就是上面演练里"守护跑完什么都没补"。门 96 自己判的是 `!existsSync(p) && !isLink(p)`(两半都有),
   修复器只抄了前半 ⇒ **同一个判据在两处必须同形**,否则一边判红、一边判无需修,合起来是恒红且永不自愈。
   现判序改为先 `isLink`(lstat);并把"指针指向与登记表算出的目标不一致"单列 `link-moved` **判红交人工**,
   不擅自改指向 —— 那等于替人决定数据落点。
2. **冷却吞掉人工窗口**:第一次 `--apply --no-cooldown` 之前,上一轮 EBUSY 留下的 30 分钟冷却把服务
   已停好的窗口判成了"跳过"。故加 `--apply --no-cooldown`:**绕过判定但保留既有条目**(守护侧仍需拦住
   "每 2 分钟重抄 108MB 再撞同一个 EBUSY"),且本轮再失败时**不再续冷却**,否则下一个人工窗口照样被拦。
   两处都由镜像测试用正则钉死(`scripts/tests/re-home-junctions.test.mjs`),因为它们是行为分支,不是注释。

### 历史案例

`.ihui-agent/archive/AGENTS_history.md` 记录每次 reset 事故 + 已采取的 tag 备份措施。

---

## 22b. staged-typecheck 闸门:全量 include + 错误过滤(2026-08-18 立,根治改版)

### 触发背景

pre-commit 第 16 项「条件 typecheck 闸门」原策略:临时 tsconfig 只 include staged 文件 → 模块扩展(declare module 'fastify' 等)未被加载 → 报 TS2339 假阳性(如 `pushNotification` / `isMultipart` / `file`)。多 agent 并行时,任何 agent 改一下 ws-notifications.ts 都会让其他 agent 的 web 包 typecheck 失败 100% 误阻塞。

### 核心策略(根治)

`scripts/check-staged-typecheck.mjs`:

- **临时 tsconfig 沿用 package 原始 tsconfig 的【全量 include】**,完整加载所有模块扩展与全局类型,消除 TS2339 假阳性。
- **tsc 输出按行解析**,只把【错误文件属于 staged 文件】的错误视为失败;其他 agent 引入的非 staged 文件错误自动过滤、不阻塞。
- tsc 未能真正运行(如 pnpm/tsc 未找到、进程崩溃、空输出)按失败处理,**禁止静默通过**。
- 临时 tsconfig 清理加重试(Windows transient file lock 兼容);`.gitignore` 已加 `**/tsconfig.staged-typecheck.json` 防残留误入库。

### 守门集成

- `scripts/guardian-runner.mjs` 第 16 项 blocking(已有调用 `node scripts/check-staged-typecheck.mjs --staged`)
- 跳过方法:`HUSKY_SKIP_STAGED_TYPECHECK=1 git commit ...`(应急)
- 适用范围:任意 staged .ts/.tsx 文件(不限 apps/web)
- 自动跳过:apps/ai-service(Python,走 mypy)、apps/desktop(无 typecheck script)、packages/eslint-config / packages/tsconfig(纯配置包)等无 typecheck script / tsconfig 的 package

### 红线规则

- ❌ 禁止把临时 tsconfig 的 include 缩窄到 staged 文件(会回退到旧 partial-include 假阳性 bug)
- ❌ 禁止把 `**/tsconfig.staged-typecheck.json` 从 .gitignore 移除(Windows 偶发 unlink 失败可能残留)
- ❌ 禁止把 `sawAnyTscError` 移除(若 tsc 未能真正运行,必须按失败处理,不能静默通过)
- ✅ 修改 `filterTscOutputForStagedFiles` / `getOriginalInclude` 时必须同步更新 `scripts/tests/check-staged-typecheck.test.mjs`(镜像常量同步锚点:源脚本第 298-327 行 / 244-256 行)
- ✅ 修改源脚本 `check-staged-typecheck.mjs` 的核心函数时,必须同步:
  1. 测试文件 `scripts/tests/check-staged-typecheck.test.mjs` 中的镜像常量
  2. 跑 `scripts/check-staged-typecheck-mirror-sync.mjs` 验证指纹一致

---

## 22c. 镜像常量守门模式规范(2026-08-18 立)

### 背景

部分核心工具脚本(如 `scripts/check-staged-typecheck.mjs`)导出大量内部辅助函数,而测试文件 `scripts/tests/*.test.mjs` 由于路径隔离 / 模块副作用 / 静态导出冲突等原因,**无法直接 `import` 源函数**。沿用"测试文件里复制一份相同实现的镜像常量"做法虽然能跑通断言,但形成两套并行真相:任何对源函数签名的修改(参数顺序、返回值结构、过滤规则常量)必须**手动同步**到测试文件;一旦遗漏,测试将持续"假绿"(通过旧逻辑断言已不存在的字段),守门形同虚设。

### 根治路径(三步)

1. **源函数 export**:把核心函数从源脚本顶部 export 出去,确保测试环境可解析(注意 `.mjs` 必须用 `export` 关键字,且不引入副作用代码)。
2. **测试直接 import**:测试文件 `import { __test__ as <别名> } from '../source.mjs'`,消除"两份真相"。
3. **守门脚本检测 export 锚点**:`scripts/check-staged-typecheck-mirror-sync.mjs` 持续校验:
   - 源文件必须存在并 export `__test__` 对象
   - `__test__` 必须包含指定的函数键(本场景:`getOriginalInclude` / `normalizePath` / `filterTscOutputForStagedFiles`)
   - 测试文件必须存在对应 `import { __test__ as ... } from '../source.mjs'` 语句
   - 任意锚点缺失即 exit 1 报错(提示"镜像常量漂移,需走 §22c 协作收尾")

### 守门脚本工作机制(`check-staged-typecheck-mirror-sync.mjs`)

- **阶段 A · 检测源文件存在**:确认 `scripts/check-staged-typecheck.mjs` 存在于工作区(防止路径漂移)。
- **阶段 B · 检测 export const **test** 锚点**:用正则匹配 `export const __test__ = { ... }`,并校验三个键 (`getOriginalInclude` / `normalizePath` / `filterTscOutputForStagedFiles`) 均出现在对象字面量中(防止 export 但漏字段)。
- **阶段 C · 检测测试文件 import**:确认 `scripts/tests/check-staged-typecheck.test.mjs` 存在 `import { __test__ as ... } from '../check-staged-typecheck.mjs'` 语句(防止 import 路径写错 / 别名错配)。
- **触发场景**:任何对 `check-staged-typecheck.mjs` 的核心函数修改后,pre-commit hook 自动跑该守门脚本;若 export 锚点漂移,立即阻断 commit 并提示同步测试 import。

### 可复用模板

检测模式(正则 + 字符串匹配 + import 锚点)可复用到其他类似场景:

```javascript
// 伪代码:三阶段检测模板
const source = readFileSync('scripts/<source>.mjs', 'utf8')
assert(source.includes('export const __test__ = {'), 'phase B: missing __test__ export')
const requiredKeys = ['funcA', 'funcB', 'funcC']
for (const k of requiredKeys) assert(source.includes(`${k}:`), `phase B: missing key ${k}`)
const test = readFileSync('scripts/tests/<source>.test.mjs', 'utf8')
assert(
  /import\s*\{\s*__test__\s+as\s+\w+\s*\}\s*from\s*['"]\.\.\/<source>\.mjs['"]/.test(test),
  'phase C: missing __test__ import in test file',
)
```

可复用到:任何"测试文件无法直接 import 源函数"的工具脚本场景(如 `scripts/check-commit-scope.mjs` / `scripts/check-staged-pollution.mjs` 等)。

> 📌 配套使用: 本模板的入口守护需搭配 §22d `isDirectRun` 模式使用, 避免 import 时 main() 误触发。

### 红线规则

- ❌ 禁止在测试文件中**复制**源函数实现(产生镜像常量漂移风险)
- ❌ 禁止修改源函数签名后**不更新** `__test__` 对象的导出键(守门立即失败是预期行为,不是 bug)
- ❌ 禁止删除 `check-staged-typecheck-mirror-sync.mjs` 中的任何一项锚点检测(削弱守门强度)
- ✅ 修改源函数后必须 `pnpm test scripts/tests/check-staged-typecheck.test.mjs` + 跑 mirror-sync 守门脚本双验证
- ✅ 新增工具脚本若需要被测试直接 import,必须遵循"源文件 export `__test__` + 测试 import + 守门脚本三阶段检测"模板
- ✅ **判据的对象是某个真实文件的"形态"(标题式 / 路径式 / 字段名 / 分隔符)时,镜像测试至少一条用例的输入必须逐字取自那个真实文件**(或从 HEAD 现读一条),不得全部用自造夹具。实测于 2026-09-25 G-182:`archive-completed-tasks.mjs` 的匹配式与 `PROJECT_PLAN.md` 的真实标题形态漂了 11 天,而它的 15 个镜像测试**全绿** —— 因为夹具复刻的是实现的形状;更糟的是其中一条测试还把「必须以 `### [x]` 开头」当成规格去断言,**测试从防线变成了缺陷的掩体**。⇒ **镜像测试若只复读实现,它就只是复读机。**

---

## 22d. `isDirectRun` 模式规范:ESM 脚本"双形态"入口守护(2026-08-18 立)

### 背景

部分 `.mjs` 工具脚本(如 `scripts/check-staged-typecheck.mjs`)需要同时支持两种使用形态:

1. **CLI 直接执行**:`node scripts/foo.mjs`(命令行手动跑 / pre-commit hook 调用 → 必须自动执行 `main()`)。
2. **模块被 import**:`import { __test__ } from './foo.mjs'`(测试文件复用导出符号 → **绝不**触发 `main()` 副作用)。

ESM(`.mjs`)模块与 CJS(`.cjs` / `.js` + `"type": "module"`)的求值模型:**顶层代码仅在被 import 时执行一次**,但**直接 `node foo.mjs` 时同样会执行顶层代码**。这意味着 — 如果脚本顶层直接写 `main().catch(...)`,测试一旦 `import` 该模块,就会**连带触发 CLI 主流程**(批量跑 typecheck、写临时 tsconfig、调 git diff),把测试环境搞炸,或更糟 — 在测试用例之间留下真实副作用(临时文件未清理、staged 状态污染)。

### 根因:`import.meta.url` vs `process.argv[1]`

- **`import.meta.url`**:ESM 模块加载器注入,恒为**当前模块文件**的 `file://` URL(无论是被 import 还是直接 node 执行,值都相同:指向 `foo.mjs` 自身)。
- **`process.argv[1]`**:Node 启动时传入的第一个脚本参数,只在**直接 node 执行**时等于本模块路径;被 import 时为 undefined 或与本模块无关。

因此判定**"本模块是不是被直接 node 执行"** 可用:`import.meta.url === pathToFileURL(process.argv[1]).href`。两者相等 ⇒ 直接执行,触发 `main()`;否则 ⇒ 被 import,跳过副作用。

> ⚠️ **跨平台陷阱**:Windows 路径是反斜杠 `C:\foo\bar.mjs`,不能直接拼成 `file://` URL,否则 `import.meta.url`(始终是标准 `file:///`)与字符串永远不匹配。**必须**经 `node:url` 的 `pathToFileURL()` 归一化。

### 可复用模板

在 `.mjs` 脚本**底部顶层**(所有 export 之后)粘贴以下 4 行(已适配 Windows / Linux / macOS,推荐用项目现存形式而非手写字符串拼接):

```javascript
import { pathToFileURL } from 'node:url'

const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href

if (isDirectRun) {
  main().catch((e) => {
    console.error(`❌ ${e?.message ?? e}\n${e?.stack ?? ''}`)
    process.exit(2)
  })
}

export const __test__ = {/* 暴露给测试的核心函数 */}
```

位置约束:

- `__test__` 的 `export` 必须放在 **`if (isDirectRun)` 之后**(否则 ESM 提升可能在测试 import 时机前完成,语义仍正确但不直观;且与 §22c "镜像常量守门" 的 `import { __test__ as ... }` 锚点要求一致,便于 grep)。
- `main()` 自身声明应保持文件靠前位置(便于阅读),`if (isDirectRun)` 仅触发调用,**不**延迟或重写 `main`。
- `process.exit(2)` 复用 §22b 中"异常 vs 失败"的退出码约定(2 = 脚本自身异常,1 = 业务失败)。

### 与 §22c 的关系

`isDirectRun` 是 **§22c 镜像常量守门模式的使能条件**。没有它,§22c 的"源文件 export `__test__` + 测试 import"做法会因为 `main()` 顶层自动执行而不可行 — 测试一旦 import 就会触发 CLI 主流程,副作用污染测试环境(写临时文件、调 git 改 staged 状态、写控制台横幅噪音),`vitest` / `node --test` 都会在 setup 阶段立即崩。`isDirectRun` 把"CLI 入口"和"模块导出"两种用法的副作用隔离开,使 §22c 的 export / import 双真相消除得以落地。

两者协作的最小工作流:

1. 源 `.mjs` 脚本加 `isDirectRun` 守护(本节模板)。
2. 源脚本 export `__test__ = { fn1, fn2, ... }`(§22c 第 1 步)。
3. 测试文件 `import { __test__ as X } from '../source.mjs'`(§22c 第 2 步)— 因 `isDirectRun=false`,`main()` 不执行,仅拿到导出对象。
4. 守门脚本 `scripts/check-staged-typecheck-mirror-sync.mjs` 三阶段检测 export / key / import 锚点(§22c 第 3 步)。

### 红线规则

- ❌ 禁止在 **CommonJS / `.cjs`** 脚本中使用 `isDirectRun`(`.cjs` 没有 `import.meta`,需走 `require.main === module` 判定,本规范不适用;混用会导致 `SyntaxError: Cannot use 'import.meta' outside a module`)。
- ❌ 禁止把 `main()` 写成**同步函数**后用 `if (isDirectRun) { main() }`(同步抛错会抛出 toplevel,无法被 `.catch` 包裹,且 ESLint `no-top-level-await` 外的同步 throw 在 Node 18+ 表现不一致;必须保持 `main()` 是 `async` 或返回 `Promise`)。
- ❌ 禁止手写字符串拼接 `import.meta.url === 'file:///' + process.argv[1]` 替代 `pathToFileURL`(Windows 反斜杠永远不匹配,直接判定失败 → CLI 永不触发 main → 静默失控)。
- ❌ 禁止把 `isDirectRun` 守卫放到 `main` 函数**内部**(局部守卫无法阻止其他顶层 import 时副作用已执行的部分,如顶层模块加载就跑了 git diff / 写了临时文件)。
- ❌ 禁止把 `export const __test__` 移到 `if (isDirectRun)` **之前**且不放 `export`(否则测试 import 时拿到 undefined,§22c 守门 phase B 立即失败 — 这是预期行为,不是 bug)。
- ✅ 修改源脚本的 `main()` 调用约定时必须同步 §22c 守门(双验证 `pnpm test` + `node scripts/check-staged-typecheck-mirror-sync.mjs`),否则 `__test__` 导出语义与 import 时机解耦,可能让测试在 CI 通过而在本地 import 时副作用泄漏。
- ✅ 新增任何"需要双形态(CLI + import)"的 `.mjs` 工具脚本,必须沿用本节 4 行模板 + §22c export 规范,**不**为单次用例复制粘贴变体。

---

## 23. `.gitignore __*` 规则静默忽略 `__tests__/` 目录教训(强制)

### 触发背景(2026-07-25 立,真实事故)

`.gitignore` 第 154 行 `__*` 规则(本意是忽略"Agent 临时脚本",如 `__round1.log` / `__audit_dump`)会**静默忽略**所有以 `__` 开头的路径,包括合法的 `__tests__/` 测试目录。**关键陷阱**:`git status` 对 ignore 的文件**完全不显示**(既不 untracked 也不 modified),开发者肉眼完全看不到。

### 真实事故(2026-07-25 阶段 13 集成测试)

集成测试 subagent 在 `apps/web/__tests__/storage-adapter.test.ts` 写完测试文件,本任务合 stage 13 计划清单:

- 创建文件 → `git status` 不显示
- 编辑文件 → `git status` 不显示
- 准备 commit 时,`git add __tests__/` → 无任何文件可加(`__*` 规则吞掉)
- 险些导致整个 stage 13 测试集成**永久丢失**;直到最后 `git check-ignore -v apps/web/__tests__/` 才追查到 `__*` 规则源头

修复方法:把目录重命名为 `tests/`(避开 `__*` 规则)→ 后续 16 个 `__tests__/` 目录已审计,**全部未被 `__*` 规则命中**(未含 `.gitkeep` 且 `git check-ignore` 返回空),本任务仅作为守门+教训记录,不要求批量迁移。

### 硬性规则(违反视为事故)

1. **项目内测试目录必须用 `tests/`**(推荐)或 `spec/` / `__tests__/` + `.gitkeep`(不推荐);**禁止**新建无 `.gitkeep` 的 `__tests__/` 目录。`__tests__/` 命名只在"目录内全部文件被故意 ignore + 留 `.gitkeep` 占位"时使用。
2. **创建测试目录前必须** `git check-ignore -v <path>` 确认不被 ignore(空输出=安全,非空=被命中);`__` 前缀目录需逐个验证。
3. **CI / pre-commit 必跑** `node scripts/check-test-paths.mjs`(本任务新增,§守门脚本速查),命中 `__tests__/` 无 `.gitkeep` 且被 gitignore 吞掉 → 阻塞(退出码 1)。

### 守门脚本:`scripts/check-test-paths.mjs`

- 扫描 `apps/` + `packages/` + `scripts/` 下所有 `__tests__/` 目录
- 每个 `__tests__/` 调 `git check-ignore -v` 复核 + 检查 `.gitkeep`
- 同时检测 `*.tmp` / `*.bak` 目录 + 非白名单隐藏目录
- exit 0 (无阻断) / exit 1 (有 `__tests__/` 被吞掉阻断项)
- 详细用法:脚本头部 docstring

### 关联案例

- 阶段 13 集成测试 subagent:险些丢失 `apps/web/__tests__/storage-adapter.test.ts`
- 历史事故:曾出现"测试写了不显示 → commit 漏文件 → CI 假绿"模式,本节规则根治

---- **2026-07-26**:Commit 丢失防护机制强化。本地 lost-commit/* tag 被 git gc 清理事故暴露后,新增 sync-lost-commit-tags.mjs 自动 push + fetch 机制,创建 docs/lost-commit-archive.md 永久档案,check-commit-loss-guard.mjs 升级为 5 段检查(reflog 50 步 + 远程 tag 校验 + tag 对象可达性)。

---

## 24. 新增功能须用户确认强制规则(强制)

### 触发条件

任务类型 ∈ {新增功能模块 / 新增 API 路由 / 新增页面 / 新增端能力 / 新增对外能力清单 / 在 PROJECT_PLAN.md 中无对应条目的自发性功能开发}。

> 判定"新增功能"而非"修改/修复"的核心标准:**是否引入了 PROJECT_PLAN.md 当前任务清单之外的新能力**。是 → 触发本规则;否(属于现有功能的修复/重构/优化/适配)→ 不触发。

### 强制动作(缺一不可,违反视为越权事故)

1. **暂停开发**:识别到"新增功能"意图后,**禁止**直接动手写代码,必须先用 `AskUserQuestion` 工具向用户确认是否开发该功能。
2. **明确边界**:确认问题必须包含 ① 功能目标一句话描述 ② 受影响范围(端/文件/契约) ③ 是否纳入 PROJECT_PLAN.md(及其优先级 P0/P1/P2)。
3. **等待显式同意**:用户回复"同意/可以/做吧/开发"等肯定语义后才能开始;用户回复"不用/暂缓/先不做"则**禁止开发**;模糊回复需再次确认。
4. **补登记 PROJECT_PLAN.md**:用户同意后,将该功能追加到 PROJECT_PLAN.md 对应优先级末尾(遵循 §1),再开始编码。
5. **commit message 前缀**:新增功能 commit 用 `feat:` 前缀(§1 已规定)。

### 红线(违反视为协作事故)

- ❌ **禁止**未确认直接开发新功能(包括"顺手加一个"、"我看这里缺个"、"我加个小特性")。
- ❌ **禁止**把新功能伪装成"修复/重构"绕过确认(如新增路由写成"重构路由文件")。
- ❌ **禁止**在 `/goal` 模式下借目标执行之机擅自扩展目标范围外的新功能(违反 §8 红线"严格围绕目标,禁止扩展需求")。
- ❌ **禁止**用"已写完了,你看下要不要保留"代替事前确认。

### 豁免场景(允许不确认直接开发)

- **用户在本轮对话已明确要求开发该功能**(如"帮我加个 XXX 功能"、"做个 XXX")——用户意图已显式,无需再次确认。
- **/goal 模式目标条件内包含的功能**(目标已包含 = 已确认)。
- **纯 bug 修复 / 重构 / 类型修复 / 测试 / 文档 / 配置**(不引入新能力)。
- **守门脚本 / 工程约束自身的增量**(本节即触发例,属工程治理而非业务功能)。
- **PROJECT_PLAN.md 已有条目的细化执行**(任务已登记 = 已确认范围)。

### 与其他规则协同

- 与 §8(goal 模式)协同:goal 模式下不得借目标扩需求,新增功能必须先暂停 goal 询问用户。
- 与 §9(多端同步)协同:用户确认新功能后,默认按 8 端同步开发,除非显式标注"平台独占"。
- 与 §21(README 同步)协同:新功能开发必须同 commit 更新 README(§21 触发条件命中)。
- 与 §7(删除安全)协同:新功能开发中如需删除/重构现有代码,仍遵循 §7 三问。

---

## 25. `verify-*.mjs` / `verify-*.ts` 临时验证文件归档规则(强制)

### 触发条件

Agent 在调试 / 验证 / 探查某项功能时,常在 `apps/web/` / `apps/api/` 等源码根目录随手写一个 `verify-xxx.mjs` 脚本(如 `verify-permission-popover-v2.mjs` / `verify-permission-popover-v3.mjs` / `verify-login-tabs.mjs`)快速跑一次。这类临时文件**禁止**提交到 git,必须归档到 `.ihui-agent/tmp/<任务名>/`。

### 强制动作(缺一不可,违反视为协作事故)

1. **临时文件必须放 `.ihui-agent/tmp/<任务名>/`**:例如 `.ihui-agent/tmp/perm-popover-debug/verify-v2.mjs`。
2. _*禁止放 apps/* 根目录_*:`apps/web/verify-*.mjs` / `apps/api/verify-*.ts` 等位置**严禁** commit。
3. **禁止放 .ihui-agent/ 根目录**:`.ihui-agent/verify-*.mjs` 与守门脚本混在一起,难追溯。
4. **路径推导用项目内路径**:`$PSScriptRoot` / `__dirname` / `import.meta.url`,不写硬编码绝对路径(§15 卫生规则)。
5. **任务完成后清理**:`rm -rf .ihui-agent/tmp/<任务名>/`(已 gitignore,自动忽略)。
6. **commit 阶段禁 add**:`git add <本任务文件>`(§12 多会话保护),**禁止** `git add .` / `git add -A` 一次性把所有 verify-*.mjs 加进去。

### 红线(违反视为协作事故)

- ❌ **禁止** `apps/web/verify-*.mjs` 等源码根目录临时文件 commit(会被守门脚本警告 + 污染 main 分支)。
- ❌ **禁止** 用 "这是为了验证 XXX 功能" 借口把临时文件 commit 进来。
- ❌ **禁止** 留 `verify-v1.mjs` / `verify-v2.mjs` / `verify-v3.mjs` 等版本号后缀文件(版本号在 git history 里有)。

### 豁免场景(允许放源码目录)

- 守门脚本:`scripts/check-*.mjs` / `scripts/verify-*.mjs`(正式工具,有 README/CLI/help)。
- 测试文件:`*.test.ts` / `*.spec.ts` / `tests/` / `__tests__/`(符合 §23 测试目录规则)。
- 长期保留的 E2E 脚本:`scripts/e2e-*.mjs`(已纳入 CI,有意保留)。

### 守门脚本:`scripts/check-verify-tmp-files.mjs`

- 扫描 `apps/*/verify-*.mjs` / `apps/*/verify-*.ts` / `scripts/verify-*.mjs`(白名单外)
- 发现任意 1 个 → 警告(不阻断,只提示)
- 集成位置:CI / guardian-runner 后续项(暂 warn-only,后续按需升级 blocking)
- 退出码:0(有警告但通过)+ 输出警告列表

### 与其他规则协同

- 与 §12(多会话并行)协同:`git add` 阶段只加本任务文件,不批量加 verify-*.mjs。
- 与 §13(文件修改持久化)协同:Read 验证 verify-*.mjs 的修改生效。
- 与 §15(工作区卫生)协同:临时文件必须项目内路径(`.ihui-agent/tmp/`),不写 `G:\` 根目录或 `C:\temp\`。
- 与 §23(测试目录)协同:`verify-*.mjs` 命名 ≠ 测试文件,不能伪装成 `*.test.mjs` 绕过守门。

---

## 守门脚本速查(pre-commit 项,按类别)
- **自检用例登记侧的求值配对对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **156**,并发会话可能挪号;**定级 warn**(理由见下),`stagedTriggers=scripts/`,紧急跳过 `HUSKY_SKIP_SELFTEST_REGISTRANT=1`(由 runner 分发循环 honors,门体自身不读该 env):`scripts/check-selftest-registrant-evaluates.mjs` 拦的是**「登记一条自检用例的函数把实参存起来却从不求值,而有人正往它嘴里喂函数」**这一型 —— 那种 self-test 打印的"N/N 通过"是假的。判红必须两条同时成立:**R-REG** 登记不求值(裸存 `{name, ok}` / `!!x` / `Boolean(x)` / `x ? 'PASS':'FAIL'`;求值证据两种都认 —— 体内调用该形参,或存进去的键在全文被当函数调 `c.fn()`,后者就是 thunk 族合规的根据)+ **R-CASE** 同一**自检宿主**内确实有以该登记函数为名的调用、其第二实参是裸箭头函数或裸 function(`(() => {…})()` 自调用、`a === b` 比较式、单个标识符一律不算)。**只满足 R-REG 一律只报数**:接线当天现读潜伏 36 处 + 未判定 4 处(登记函数被抄出 29 种写法、55 个登记点,而被抄最多的恰是不求值那一族 ⇒ 是"没有共用出口、靠抄邻居"的结构问题,不是 36 个人的疏忽),当场把这 36 道判红就是与任何提交都无关的恒红门,唯一结局是各会话跳钩子、连带链上全部守门作废(§12f 同型),所以问责出口是 `node scripts/check-selftest-registrant-evaluates.mjs --strict`(有未判定即 rc=2,拒绝出具合格证;刻意不写 `pnpm check:*` 别名 —— 那要改此刻在他人手里的根 `package.json`,写一个跑不通的出路比不写更糟)。三型**不计潜伏也不判红**:`cond === true` 这类值比较族(传函数得 false ⇒ 当场翻红,不是静默绿)、形参被喂进别的调用(本门承认看不见那一层是否求值 ⇒ 未判定)、宿主函数之外的两参数 push(`walk` / `scanSource` / `route(method, handler)` 那一族数据收集器,现读 30 处 —— 不加宿主限定它们全被算成潜伏,而 `route('GET', () => …)` 是合法写法 ⇒ 假红)。射程只到仓库根 `scripts/*.mjs`(`lib/` 与 `tests/` 不在面内:node:test 的 `test(name, fn)` 本来就该传函数)。登记函数须声明在自检宿主体内(现读宿主名只有 `selfTest` / `runSelfTest` / `selfTestRun` 三类)—— **已知漏报**:宿主另取它名的文件不进射程。修复出口只有一条:把登记侧改成求值形态(体内调用,或改 thunk 族 `push({name, fn})` + `c.fn()`),**禁止把用例改成布尔去过门**。行内出口 `selftest-registrant-exempt: <原因>`(须带原因、只救本行;原因不得由 `*/` 冒充)。**残余两格,归下一票**:① 该豁免族尚未进守门 108 的存活期表(改的是 `check-exemption-expiry.mjs`,不在本票文件清单内);② 41→36 的差来自口径(本门把登记点限定在自检宿主内,并把值比较族/装箱族另档计),两个数不得互相顶账。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"、枚举到 0 个在射程文件判死不记绿;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(它不认正则字面量 ⇒ 那一型表现为"看不见某文件的登记函数",方向是少判不误判,由镜像的覆盖面自证兜住)。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像含 T4 真临时 git 仓的端到端三臂:注入必红 / thunk 必绿 / 同一形状只写进注释必绿,以及 T5"未注册不得被读成已装车 + 定级被改成 blocking 必读得出")。**立门凭据**是 `check-single-branch.mjs` 的实录:13 条用例全传箭头函数而登记侧写 `!!ok` ⇒ 恒真 ⇒ 一路打印"13/13 通过"却一条都没判过,同一文件一处单位错误(秒差 ÷ 3,600,000,把 48h 豁免窗算成 ~5.5 年)在该掩护下长期隐形(该门已由枚 `aea810c81` 修好,不在本门射程内,但它是本门存在的理由)。

- **名字承诺/实现兑现对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚取号器落地时给到 **137**，并发会话可能挪号；blocking，`stagedTriggers=apps/ packages/ scripts/ deploy/ monitoring/ sdks/`，紧急跳过 `HUSKY_SKIP_DIGEST_NAME_REALITY=1`)：`scripts/check-digest-name-reality.mjs` —— 拦的是**「函数或字段的名字承诺了摘要/脱敏，实现却没兑现」这一型**：读判据的人相信名字，而名字可以是张空支票。立项因由是本会话同轮量到的 4 处：`cross_account_guard._fingerprint_hash()` 文档写「计算指纹哈希」而实现是 `return "|".join(parts)`（明文，内含 UA/时区/伪经纬度），被原样持久化成设备图谱的跨账号比较键；`apps/cli/src/doom-loop-detector.ts` 的 `hashInput()` 字段全程叫 `inputHash`、实现是 `JSON.stringify(整个工具入参)`，随 `pattern` 明文 POST 进服务端长期落库；`deploy/scripts/ai-diagnose.mjs` 注释承诺「发送前一律抹掉」而正则只盖三族；`packages/types/src/device.ts` 写「指纹不可逆」而实现是有界字典上的 FNV-1a。**三条与门缺一不可**（只看"没有哈希调用"会把 Redis 的 hash 结构键名、显示档 `mask*`、JWT 的 `signature` 段名、内存内比较键满天误报）：① 命名承诺（**词元匹配非子串**，`hashtags` 不得被读成 `has`+`tags`=hash 承诺）② 可达返回/赋值面内无任何真散列调用 ③ 值流向持久化/出网出口或被当等值标识用。判不出的一律落 **未判定** 并报数（`digest` 在「散文摘要」与「密码学摘要」两义同形，机械分不开 —— 本仓最高频的失效型是"把没判写成判过了"，所以 `--strict` 下有未判定即 **exit 2，拒绝出具合格证**）。口径同 70/77/83/98/103/118：全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、枚举到 0 个候选判死；`--staged` 棘轮锚点 = 该文件 HEAD 自身存量。**行内出口 `digest-name-exempt: <原因>` 必须带原因、且只认声明行与其紧邻上一行**（写两行注释把标记顶到上上行 = 豁免不生效，本门落地当天主会话自己踩过并改正），已挂守门 108 的 30 天存活期档 —— 命名债一律**改名收口**而不是用豁免遮（`oauth2.ts` 那处 `hashed` 列表里混着明文 legacy secret，就是改名为 `storedCandidates` 而非豁免）。**假阳性清单如实登记**：首跑 3 处命中经逐条复核，`fingerprints`（哈希值的集合）与 `hashIdx`（URL 片段 `#` 的位置下标）是同词不同义、带原因豁免；判据有牙的证明是**阳性对照** —— 把修复前那枚父提交（`69a3d72f2`）的 `cross_account_guard.py` 与 `doom-loop-detector.ts` 原文喂同一判据 ⇒ 命中 2/2 文件，而修复后的 HEAD ⇒ 命中 0。取证 `--self-test` 35 条 + §22c 镜像测试（含"从注册表按大括号配对取出本门那一条再判 blocking+skipEnv"的装车证明、"摘线不得被读成已装车"的反向对照、"预筛必须是判据关键字超集"、"真散列形态必须落放过"）。手动问责入口 `pnpm check:digest-name`（走 `--strict`）。**两条写门过程中由主会话接手修掉的缺陷（F 代理撞轮次上限死在半途，记下来防再犯）**：① `JS_ASSIGN_RE` 定义了却从未接进 `findUnits` ⇒ `const digest = 拼接` 整型隐身，既不判红也不记未判定（自检 23 抓到的是"静默放过"），接上后候选声明 351→496；② `git grep` 的 `--cached` 被放在模式**之后** ⇒ `--staged` 档 `fatal: unable to resolve revision: --cached`，而 runner 给每道门追加 `--staged`，**接上就是每一次提交恒红**。
- **后台任务类型三面 parity**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地时取到 **136**,并发会话可能挪号;blocking,`stagedTriggers=apps/ai-service/app/services/` + `apps/ai-service/app/api/`,紧急跳过 `HUSKY_SKIP_BG_TASK_TYPE_PARITY=1`):`scripts/check-background-task-type-parity.mjs` —— V3 #51 立。钉的是"**声明了任务类型却没有执行器**"和"**有执行器却没人接线**"两型:声明面(`run_in_background` 广告给模型的 task_type 集合)↔ 实现面(`task_executors.py` 注册表)↔ 接线面(生产调用点)三面必须一致,任一面多出即红;回显型 executor(sleep/echo 冒充执行)直接判红。**存量按"在账未接线"报数不判红**(落地当天现读:六类中仅 2 类接线、6 类在账)—— 当场判红就是一台与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。取证 `--self-test` 30 条 + §22c 镜像 6 例(其中 T2 按**大括号配对取出本门那一条注册项**再判 `mode: 'blocking'+skipEnv+stagedTriggers —— 上一版取「脚本名前后各 400 字符」当条目范围,会跨进邻门:别人有 blocking 就算我有;它原先还断言 `blocking: true`,而本仓 runner 167 处全是 `mode: 'blocking'`,那条断言对任何正确注册的门都恒红)。 **注册曾被并发会话按旧副本整块写回**:落地当天 `grep -c check-background-task-type-parity scripts/guardian-runner.mjs` 由 1 变 0,而 AGENTS 仍点名它 ⇒ 门在、判据对、无人调度,且没有一道门会喊(它头注没自称已接线所以 R1/R2 不响,镜像 T2 的「未注册」分支也照样绿)。教训:接完门必须在 **HEAD 面**复现 grep 计数,不能只看落地器打印的成功;编号一律现读,不得照抄本文。**尚无 §22c 镜像测试,已登记为残余**。
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\``式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟`#`像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人`--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged`判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证`--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace`返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒`--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。

- **批量写计数诚实性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地时取到 134,并发会话可能挪号;`blocking`,`stagedTriggers=apps/api/src/routes/` + `apps/api/src/db/`,紧急跳过 `HUSKY_SKIP_BATCH_WRITE_COUNT_HONESTY=1`):`scripts/check-batch-write-count-honesty.mjs` —— 拦的是本仓 2026-09-26 一整批同型静默失真的**形状**:`db.delete/update ... where inArray(ids)` 之后把**请求侧数组的 `.length`** 当 `affected` / `deleted` / `restored` 回给前端。那一型的问题是"改了 0 行"与"改成功"在响应上完全同形 —— `where` 带 `eq(roleId)` / `eq(memberId)` 过滤时,传进来的 id 一个都不命中也照样回 `deleted: 3`,后台显示"已删除 3 项"而库里一行没动。当轮人肉清出 8 处(`chat.ts` batch、`admin-sys/role-routes.ts` cancelAll+selectAll、`admin-demand-square.ts`、`admin/_shared.ts` 的 `registerCrud` 批量删〔**19 处 admin 路由复用同一个工厂**〕、`message.ts` 批量删、`workspace.ts` batch-delete/batch-restore),统一出口是 `apps/api/src/utils/batch-outcome.ts`(`dedupeIds` + `batchWriteOutcome(requested, confirmed)` ⇒ `{requestedIds, affected, missedIds}`),db 层一律 `.returning({id})` 回报**库确认集合**,响应新增 `missedIds` 逐条点名(既有键名与 status code 逐字不变)。**四条放过通道,每条都有正反用例**:① 同一写链带 `.returning(`;② 计数根标识符 ≤2 跳可追到归属预查询(`business-card-routes.ts` 那种"先查 owned 再删"的**正确写法**不得判红);③ 该文件 import 了唯一出口;④ 行内 `batch-count-exempt: <原因>`(须带原因,裸标记不计)。**刻意不判的两型**:布尔 `deleted: true`(全仓惯例,HEAD 面实测 257 处、10+ 条用例当契约钉,要改属全 API 语义决策、交人定,不是这道门能顺手做的)与读查询 `count: rows.length`(不是写)。**判不了的形状如实登记**:写链在 db 层而计数在 handler(`workspace.ts` 那一型收口前看不见)、`send` 在嵌套箭头里、`deleted: (await …).length` 非点号链形态 —— 这些落 `未判定` 并报数,**`--strict` 下有未判定即 exit 2**(拒绝出合格证)。**口径同 70/77/83/98/103/118**:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"、枚举到 0 个候选判死;棘轮锚点 = 该文件 HEAD 自身违规数,所以存量未清时判红不会变成人人跳门。**两处遮噪方向不同**(本门自己踩过):判写链要"剥注释 + 保留字符串"(标识符落在字符串里的形态要看得见),判 `import` 说明符则连字符串一起抹会直接失明。取证 `--self-test`(35 条 = 构造面 27 + 临时 git 仓端到面 8)+ 镜像 10 例(含"禁 `process.cwd()` 定根 / 禁自派生 git 读内容 / 默认档必须判 HEAD"三条反向锁)。立门前阳性对照用**修复前的 HEAD blob** 喂同一判据:`_shared.ts ⇒ 342:deleted:idList.length`、`message.ts ⇒ 348:deleted:body.ids.length`,证明判据真能命中本仓实际产出的形态。
- **守门 134 的两次补强(2026-09-27,不改动上面那条历史登记的正文;其"35 条 / 10 例"是落地时读数,现值一律跑该门 `--self-test` 与 `node --test` 看末行)**:① **B2「委托假 ack」判据** —— 布尔 `deleted/removed/…` 所在函数体**没有**直接写链(B1 因此失明),但体内 `await` 了本仓一个具名导出函数,而**被调函数体**含 `db|tx|trx` 写链且链上没有 `.returning(`、体内也没走 `batchWriteOutcome(` ⇒ 判红。**只解析一跳**(沿 import 说明符,含 `.js→.ts` 与目录 `index.ts`),解析不到 ⇒ `b2Undetermined` 逐条点名,**既不冒红也不记绿**;调用方与被调方必须**同面同轮**取(一次 `catBatch` 读满),否则并行会话推进的瞬间会产出自洽却错位的尺子。② **布尔档键族从 `deleted` 扩到 `deleted|removed|restored|revoked|cleared`,并把棘轮锚点粒度下沉到「文件 × 判据 × ack 键」** —— 只到"文件 × 判据"层的锚点会让"把一处 `deleted` 假 ack 改写成 `removed` 假 ack"**净零逃逸**,那是扩键**新造**的通道(BK1 在临时仓把它钉成必红;变异回文件级锚点 ⇒ BK1 当场翻红)。扩键同时补四条"证据在别处"的放过认法:一跳委托的 RETURNING(按 ack 键筛被调体写动词,筛完无匹配退回旧口径**而不是判红**)、`db.execute(sql…)` 括号内的 `RETURNING` 字样(认这条要用"剥注释、保留字符串"的取材档 —— **证据就在字符串里**,连字符串一起抹等于把它抹掉)、接收者不在 `db|tx|trx` 白名单(内存 Map/store 是唯一状态源 ⇒ 无"库侧行"概念,**不属本型**)、同响应里另有简写形态的行数载体。**五处已逐体核实为诚实的落点由判据自己认出来,不是抄进豁免清单**(豁免清单必然腐烂)。**恒红门检查是这类扩面的生死线**:真仓 HEAD 面 `--strict` 必须 exit 0 —— 存量若已在同波次清偿,新判据落地时产出的任何红都只可能是"判据认不出某类形态",而不是"又发现一批欠账"。**通用规矩:给已有判据扩键/扩面,必须先清偿再收紧,并让"已诚实"的一侧由判据自身识别。**
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
  - **普查先于立判据,并且普查反手抓到尺子自己的假阴**:`UPDATE\s+"?[A-Za-z_]\b` 这种带动词词界的写法对 `UPDATE ai_model_config_models`(整型表名)**永不成立** ⇒ 真仓 HEAD 上 4 条整型写隐身;修尺子后同一把才认出 73 条写链、9 处与 ack 共处一体的候选,再逐条读体分成 W1(5 处已诚实)/ W2(1 处有 RETURNING 却弃之判定)/ W3(1 处无 RETURNING 且无条件回 true)。**"扫到 0"必须先怀疑尺子,再相信世界**(本仓第二次实录)。
  - **刻意没做的两格,都给了可判的触发条件而不是"视情况"**:① **B2 那一跳未接裸 SQL 维** —— 双向扫过 10 个具名裸 SQL writer × 全部 ack 落点,HEAD 面该组合 0 条,无存量可验就补属投机("加守卫前先证明坏状态可达");触发条件:任一 ack 落点的最小函数体 `await` 了一个"体内含无 RETURNING 裸 SQL 写"的具名函数 ⇒ 必补。② **`updated` / `created` 未进 `BOOL_ACK_KEYS`** —— 键族扩面属全 API 语义决策,且必须先逐键拍板再清偿存量(§12e 同型);触发条件:一旦拍板纳入,必须同时把"RETURNING 证据"从**体内任一处**收窄到**该条调用**,否则"UPDATE 带 RETURNING 但零命中走 `?? {updated:true}` 兜底"那一型仍然隐身。
  - 代码侧配套:`user-llm-configs-v2.ts` 的 UPDATE 补 `RETURNING id` 并按命中集取值、`ai-extended.ts` 的"没发写"与"零命中"两支由 `true` 改 `false`(键名与状态码一字未改;`git grep .updated` 实测无任何调用方读这两处的布尔)。

- **i18n**(2/2b/2c/2d/2e/2f/2g-web/2f-mobile-rn/2f-cli):check-i18n-keys(parity+白名单)/ scan-i18n-zh-residue(zh-TW/ko 阻塞,ja warn)/ check-i18n-broken-en(阻塞)/ i18n-diff(翻译流水线,2f-web + 2f-miniapp-taro 阻塞)/ check-i18n-namespace-passing(命名空间传递,warn)/ check-cli-i18n-parity(cli 端 parity,warn,2f-cli)/ validate-i18n-integrity(门 30,blocking,i18n JSON 截断防护——lint-staged prettier 写大 JSON 失败出截断版的事故门)
- **代码质量**(1/3/4/4b/4c/5/6/7/8/9/10):API key 泄露 / schema drift / 陈旧 dist / UTF-8 完整性 / lint-staged / sanitizer / dedupe / 路由一致性 / safeParse(warn)/ OpenAPI(info)
- **UI/样式**(11/11b/17/18/20/24a/24b/27/28/36/102):圆角 / 圆角溢出(父 rounded + 子 bg 贴边,warn,2026-08-06 立)/ CSS token / title tooltip / Tailwind 冲突 / 侧边栏宽度+端口注册表(warn)/ z-index 层叠+遮罩 z-index(阻塞,id 27/28,2026-09-22 加固:补 skipEnv + onFailHint,并在 id 27 新增第 5 项两组契约——桌面端自绘窗口控制三按钮挂 z-max(10003) 不能降(须高于 resize 抓手 z-loading=10000),`z-modal` 遮罩永远盖不到它,故等效压暗层 `data-window-controls`+`data-window-controls-dim` 与失焦非活动态 `data-window-inactive` 两组标记缺一不可,**两态弱化/瞬时规则必须写在 `apps/web/app/globals.css`**(入口 CSS 变更必然重编译;实测生产构建里组件内 Tailwind 任意变体未进 CSS 产物);判据有效性自查 `--self-test`;紧急跳过 HUSKY_SKIP_Z_INDEX_GUARD / HUSKY_SKIP_OVERLAY_ZINDEX)/ miniapp-taro design-tokens 同步(阻塞,防 app.css 漂移)
- **工程约束**(12/13b/13c/15/19/21/22/23):交付报告 / PLAN 体积(warn)+防误删 / 迁移完整性 / staged 污染(warn)/ 多端同步(warn)/ README 同步(warn)/ staged 清单(info)
- **Push/工作区**(25/26/29):项目外路径(阻塞)/ 父目录污染(阻塞)/ Push 同步(阻塞)
- **防提交丢失**(30a):reflog reset 检测 + fsck 悬空 commit 检测 + lost-commit/* tag 备份清单(AGENTS.md §22 配套,blocking);**仅远端 tag 缺失走自愈 fetch + packed-refs 固化,拉不动才降为警告**(2026-09-24)
- **对话流元素覆盖**(57):锚点 / SSE 契约双端 / 清单条目三条判据(blocking);**判仓库内容而非共享工作区快照** —— 已暂存取索引 blob、仅工作树脏取 HEAD blob,别人未提交的重写不得钉红无关提交(2026-09-24,与守门 77 同取向)
- **凭据外泄对账**(67):非 2xx 响应**不经** `response-sanitizer`(其 496 行 `statusCode<200||>=300` 直接 return payload),故把上游响应体拼进 error message 是脱敏旁路。判据 = **A∧B ＋ C/D/E/F 至少一条**:**A** 错误构造上下文含 4xx/5xx 响应 **与 `throw new Error(...)`/`XxxError(...)`** 两类(service 层走后者);**B** 外泄形态 = `JSON.stringify(X)` **或整对象插值** `${x}` / `${x.slice(…)}`(字段投影 `${json.error}` **不算**,那是推荐写法);**C** 凭据语义(变量名/声明右侧/对象 key/message 字面量命中 `token|secret|credential|password|api_?key|bearer|access_token|refresh_token|id_token`);**D/E 来源证据**:不认变量名,沿 `X=(await R.json()|R.text())` → `R=await fetch('<令牌端点>')` 回溯,以路由注册行为处理器边界、窗口 ≤40 行,越界放弃(宁漏不误报);**F 关键词兜底**:被调方是 **SDK 而非 fetch** 时 URL 不在文件里(D/E 结构上够不到),改以「消息自带凭据端点关键词(`AssumeRole`/OAuth/`device/code`/`gettoken`/`tenant_access_token` …)∧ 整对象被倒进 message(目标路径末段为 `body`/`data`/`payload` …)」双条件命中,单条件一律不判。其余非令牌端点的错误体透传只进低置信候选不计失败(现 30 处,含经 `callVendor`/`cozeRequest`/`callLuyala` 转发的动态 URL,调用点 path 均为推理接口;`throw` 形态新增的 4 处已逐个读明:1 处即下述 STS 真缺陷,3 处不含凭据——明细见 README 守门 67 节)。成因:同日实测出**五处**同族真缺陷——IMS 令牌体进 502(`tokenData`,C 抓)、GitHub `/login/device/code` 体进 400(变量名 `json`,`device_code` 按 RFC 8628 §1.5 是 bearer 凭据,C 全盲靠 D)、PayPal `/v1/oauth2/token` 原始文本进 `throw`(**既非 stringify 也非 status(),靠 A 扩展 + E**)、阿里云 STS `AssumeRole` 的 `response.body` 进 `throw`(走 SDK 无 URL 字面量 → 靠 F)、腾讯云 `oss-sts-service.ts` 的兜底 `errMsg = … ?? JSON.stringify(result)`(成功体含 TmpSecretKey / Token,且是"上行取体下行拼"的两行式 → 靠 F 的变量链配对)。**教训:每一代判据只会漏掉自己形态之外的那一种**,四通道是叠加不是替换;名字类判据必然漏掉名字最无辜的那一处。修这类缺陷**严禁**用加 `skipResponseSanitization` 消红——那是关掉脱敏保护。覆盖 **JS/TS 与 Python 两套语法**(`.py` 亦在 `apps/` 扫描内):`json.dumps`、f-string `{x}` / `{x[:200]}`、`raise XError(...)`、`status_code=4xx`、`#` 注释豁免都是独立的语法锚点,缺一条该语言整条空转;F 通道的关键词与 dump 只认两种配对:**同行**,或**关键词行插值的变量其声明右侧就是那记 dump(两行式)**;整窗任意配对会产出假阳性(实测 cnblogs/oschina/segmentfault 三处),已各留正反用例钉住。`--staged` 只收窄文件清单、内容一律读工作树(与 lint-staged 同形态),index 与工作树错配由 30c/65 负责。紧急跳过 `HUSKY_SKIP_CREDENTIAL_LEAK_IN_MESSAGE=1`,自检 `node scripts/check-credential-leak-in-message.mjs --self-test` + `node --test scripts/tests/check-credential-leak-in-message.test.mjs`
- **适配层接线**(64):miniapp-taro `adapters/*.taro.tsx` 未被 adapters **目录外**源文件从 adapters 路径 import 即拦截(blocking,2026-09-22 立)。补 `check-adapter-style-parity.mjs` 只守硬编码颜色、不守"是否被 import"的缺口——9 个屏级适配器 3078 行"造好没装车"直到删除始终无闸可挡,即本条成因。存量基线已清零(`scripts/adapter-wiring-baseline.json` = `unwiredAdapters: []`),任何新增未接线适配器一律直接拦截;判据必须限定 specifier,否则端内同名自有组件(`components/NavBar.tsx` 等)会造成假阳性放过死代码。紧急跳过 `HUSKY_SKIP_ADAPTER_WIRING=1`,自检 `node --test scripts/tests/check-adapter-wiring.test.mjs`
- **Python 类型**(35):mypy 检查(阻塞,防 ai-service Python 类型回退)
- **依赖治理**(38):solito 幽灵依赖回归守门(阻塞,防 P0 优化被回退)
- **迁移完整性**(39):mobile-rn screen 迁移守门(阻塞,防独立实现回升,白名单:Debug/DevEnter/SharedDemo/profileMenuData)
- **共享层重复**(40):端内重新实现 shared hook/util 检测(阻塞,防端内独立实现回升,白名单:web/useChat + web/useAuth + web/useAgentRuntime + web/useClipboard + web/useNotificationStore + mobile-rn/useAuth + mobile-rn/useNotificationStore(后二者均为 re-export wrapper + 平台 adapter,属 §3 允许形态;守门的 re-export 判定只看 export 那一行有无 `from`,函数型 wrapper 必须显式登记))
- **条件**(16/16b):staged-typecheck(任意 staged .ts/.tsx → 全量 include + 错误过滤,2026-08-18 根治);packages/database/src staged → build(脚本:check-staged-typecheck.mjs,详见 §22b)
- (16c):check-staged-typecheck-mirror-sync(源/测镜像漂移防御,blocking,AGENTS.md §22b 配套,2026-08-18 立)
- **React 事件闭包**(42):check-event-closure-leak(异步回调闭包访问 SyntheticEvent 属性检测,blocking,AGENTS.md §42 配套,2026-08-12 立)
- **桌面弹窗防护**(52):check-no-visible-spawn(派生控制台程序漏 `windowsHide` 检测,blocking,AGENTS.md §5b 机器级根治配套,2026-09-20 立;`--self-test` 14 例 + §22c 镜像测试)
- **工具名本地化**(55/56):check-tool-name-display-coverage(注册表 `_TOOLS` × 词表 × 五语言 taskStatus 三方比对,blocking,拦"新增工具不补功能名");check-tool-display-resolvable(91 个功能名在 shared + 5 端合并视图 + 小程序离线包逐语言解析,拦两类静默失败:端内取词缺键回显 `toolReadFile`、忘跑 `pnpm gen:i18n` 致离线包过期;`node --test scripts/tests/check-tool-display-resolvable.test.mjs` 自检)。紧急跳过 `HUSKY_SKIP_TOOL_NAME_COVERAGE=1` / `HUSKY_SKIP_TOOL_DISPLAY_RESOLVABLE=1`
- **快捷键声明与归属对账**(69):check-declared-shortcuts.mjs(blocking,2026-09-22 立并注册)——两类红点:① "声明未绑"(UI 标了 `Ctrl+X` 而全仓无处理器,含注册表"有键无消费者");② "同键被他功能接走"(`field` 类声明与全局注册表同键,但声明方拿不出持有证据;成因是 `view-switcher` 曾把 document/browser/figma/code-changes/agent 五项标成 `Ctrl+1-5`,而该族键位实际切 AI 对话模式)。合法证据三选一:本文件出现该条目 `event` 引号字面量 / 本文件自有同键 handler + `stopPropagation`(独占截断,`RichTextEditor` 的修法)/ 条目自身与注册表同义镜像(命令面板原样列出全局键位)。**刻意只认 `field`**:`<kbd>`/正文常合法描述**别的表面**的键位,纳进必假红。全量审计 `node scripts/check-declared-shortcuts.mjs`,自检 `node --test scripts/tests/check-declared-shortcuts.test.mjs`,紧急跳过 `HUSKY_SKIP_DECLARED_SHORTCUTS=1`
- **硬编码中文基线棘轮**(70):scan-hardcoded-zh.mjs(blocking,2026-09-22 接线)—— 扫 `apps/web/{app,src/components,src/hooks}` + `packages/{ui-react,shared}/src` + 2026-09-24 补的三端(`apps/mobile-rn/src`、`apps/extension/{entrypoints,src,lib}`、`apps/cli/src`),排除注释/metadata/useTranslations 行/admin/测试。**每文件命中数与基线额度比较**(`scripts/hardcoded-zh-baseline.json`):只拦"比基线更多",存量债(886 文件 / 13090 行)冻结、新增即拦;清理后 `--update-baseline` 下调额度(必须人工确认后再跑,禁止为过门而调高)。该脚本 2026-07-20 即存在但从未接入守门链,是"造好没装车"的第二个实例(第一个是同一批的 69)。`--staged` 由 runner 自动下发(runner 传 `--exit 1`,不带该参数时**永远 exit 0** —— 集成测试踩过),暂存集为空时回退全量防"空暂存恒绿"。**两处同日修**:① 14 例镜像测试自 §15「ROOT 由脚本自身位置推导」起 **13/14 恒红** —— 测试靠 `cwd` 定位夹具而脚本按定义忽略 cwd,14 例全在扫真仓(不是判据错,是调用方式失效);加 `--root <dir>` 显式测试通道(生产不带,语义不变)后 15/15 绿。② 命中判定漏剥**行尾 `//`** ⇒ PriceChart / TerminalTab / TerminalStatusIndicators 各多出 2/1/1 处假阳,把它们顶过基线额度、把本门钉成"谁碰这三个文件谁被拦";同时修 `'https://x/*'` 这类串内 `/*` 骗进块注释状态机的假绿。**遗留(已量化,未修)**:全量模式按共享工作树判,而共享工作树对约 2064 个路径落后 HEAD —— 同一份 HEAD 代码,临时 worktree 干净检出报 **1** 个文件越线(`packages/shared/src/chat/handoff-package.ts` 49 处 / 基线 0),本机工作树报 **281** 个。要么让全量模式改判 HEAD blob(守门 77 已验证的口径),要么把 `heal-worktree-tracked` 的刷新面扩大;前者是判据口径、后者是环境自愈,都不该靠"反正本地红着习惯就好"糊过去。紧急跳过 `HUSKY_SKIP_HARDCODED_ZH_GUARD=1`
- **计划登记行防丢**(71):check-plan-line-loss.mjs(blocking,2026-09-22 立,`stagedTriggers=PROJECT_PLAN.md`)—— 共享工作区里并发会话按"内存中那份旧计划文档"整文件提交,会把别人**已入库**的登记行按旧基线回写掉(2026-09-22 一小时内两次发生,本会话 10 条 G-152/G-166/D107b 进度行被抹)。既有 13c `check-project-plan-archive.mjs` 只认 `### XXX(已完成 ✅)` 任务标题行,条目内 bullet 登记行不在其视野,故补此闸。**登记编号族含 `G-x`/`Dx`/`Px`/`Wx` 与 `守门 NN`**(2026-09-23 补最后一族:各道闸门在计划里的登记行用的正是 `**守门 NN …**` 前缀 —— 实测一枚并发暂存版本整块删掉别人的守门 72 登记(40 行),只认 G/D/P/W 完全看不见);自愈结论一律写 `.workbuddy/plan-heal.log`(旧版丢 `/dev/null` 配 `|| true`,再叠加未 `.trim()` 的 sha,自愈静默失效一整天)。判据按**编号标记的原文前缀**在待提交内容里全文搜:整行消失 → 拦;只改写文案、保留编号 → 不报(不误伤正常编辑);原文能在 `.ihui-agent/archive/PROJECT_PLAN_*.md` 找到(§1 归档)→ 放行。写闸过程中真修掉一个自造假阳:标记若按"编号 + 后续文本"重拼,`D107b` 会被拆成源文本里不存在的 `D107 b`,使正常提交被误判丢失。自检 `node scripts/check-plan-line-loss.mjs --self-test`(9 例正反成对,含 missingFrom 双目标比对);**自愈面**:`.husky/post-commit` 第 6 段每次提交后扫最近历史自动回捞(并发会话 routinely 用 `--no-verify` 绕过 pre-commit,故这一层必须有),手动 `--heal` 只写工作区、`--heal --commit` 顺带前向提交(基线一律取 HEAD,不代收别人未提交的内容)。**2026-09-23 两处加固(都是"本地全绿也发现不了"那一类)**:① 自愈比对改成**工作区与 HEAD 分别判缺失** —— commit-tree 旁路(`git-sync-converge` 索引层合并、临时索引提交)**不跑钩子**,它把已入库行从 HEAD 合掉时共享工作区常还留着那行,单目标会"无缺失"提前返回 → HEAD 永久缺行(本次 G-154 登记行即此因),现由 converge 落合并提交后就地补跑一次自愈;② `rev-parse`/`hash-object` 未 `.trim()`,尾部换行使 `read-tree` 报 `Not a valid object name`,**自愈提交自上线起从未成功过**,而 post-commit 写作 `|| true` 所以毫无声响 —— 判据只能在**独立仓库**做端到端取证才暴露(临时 repo 造一次真旁路合行 → A/B:旧版判"无缺失",新版识别并建前向恢复提交)。跳过 `HUSKY_SKIP_PLAN_HEAL=1`;紧急跳过本闸 `HUSKY_SKIP_PLAN_LINE_LOSS=1`(会把别人的登记行写没,慎用)
- **Dockerfile 构建上下文对账**(72):check-dockerfile-copy-paths.mjs(blocking,2026-09-22 立)—— 堵的是"本地全绿也发现不了"的一类:提交 `79b906463f` 给根 package.json 加 `postinstall: node scripts/fix-expo-metro-junction.mjs`,而 `deploy/docker/Dockerfile.{api,web,cli,migrate}` 只 COPY 清单就跑 `pnpm install` ⇒ 镜像缺该脚本,CI 上 build-api/build-web 同时 `MODULE_NOT_FOUND`(本机无 docker,typecheck/lint/单测全都不会知道)。两条判据只用仓库内信息:**A** 凡 COPY `pnpm-workspace.yaml`(根上下文标记)的 Dockerfile,必须 COPY 钩子里 `node <file>` 引用的每个脚本(只 COPY 单文件,勿 COPY 整个 `scripts/` 以保层缓存);**B** 每条非通配、非 `--from=` 的 COPY 源必须存在于**构建上下文**,上下文由 `.github/workflows` 的 `context:`/`file:` 成对解析,解析不到即跳过并在结论行如实报 `B 核了 N/M`;**C** `pnpm --filter <spec> run <script>` 对闭包里**没有该脚本**的包是**静默跳过而非报错**,若被跳过的包自带 `build`(产出 dist)则下游按 `main`/`exports` 读 dist 必 `Module not found` —— 判据 = 从 workspace 包图展开闭包(`pkg`=自身、`pkg^...`=仅依赖、`pkg...`=自身+依赖,取反与上游方向放过),点名"有 build 却缺被调用脚本"的包。**A/B 修完 build-api 绿了,build-web 仍恒红就是 C 类**(Dockerfile.web 跑 `--filter @ihui/web... run build:static`,而 web 的 7 个可构建依赖全都没有 `build:static` ⇒ 一个都没构建 ⇒ `@ihui/api-client` 解析失败;同仓 Dockerfile.api 用 `run build` 人人都有故一直绿,两条只差脚本名)。**存在性一律按提交内容(`git ls-tree -r HEAD`)判,不按工作树**——并行会话可能删了文件但未暂存,按 `existsSync` 会产出与真实构建相反的假阳性(实测踩过)。`COPY . .` 与 `pnpm-lock.yaml*` 不参与判定。自检 `--self-test`(10 例,含 C 的 5 例正反对照)+ `node --test scripts/tests/check-dockerfile-copy-paths.test.mjs`(10 例,含"真仓包图上旧行必红且点名 api-client、修后必绿、api 作同仓对照"与 `expandFilterSpec` 三形态);紧急跳过 `HUSKY_SKIP_DOCKERFILE_COPY_GUARD=1`
- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 四条判据:**R1** 零豁免,同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);**R4** 同一关系被拆到**兄弟 key**(`retryBtn` × `retryText`)时按命名配对判,走基线棘轮;**R2** `surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json` 只减不增;**R3** 数 brand.DEFAULT 填充/描边但**不计 §4 成对 CTA**(同块或兄弟键用 `brand.foreground` 作前景)—— 见本节上方「品牌 CTA / 主按钮色同源」,按规矩写就红的门只会逼人 `--no-verify`;**无配对的白卡片照旧计**(自检里两条阳性对照钉住)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会在恒红/假绿间来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。

- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 四条判据:**R1** 零豁免,同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);**R4** 同一关系被拆到**兄弟 key**(`retryBtn` × `retryText`)时按命名配对判,走基线棘轮;**R2** `surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json` 只减不增;**R3** 数 brand.DEFAULT 填充/描边但**不计 §4 成对 CTA**(同块或兄弟键用 `brand.foreground` 作前景)—— 见本节上方「品牌 CTA / 主按钮色同源」,按规矩写就红的门只会逼人 `--no-verify`;**无配对的白卡片照旧计**(自检里两条阳性对照钉住)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会在恒红/假绿间来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。
- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— R1 零豁免:同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);R2 基线棘轮:`surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json`(现 13 文件 24 处,均为媒体上的合法浮层或已带 `dark:` 变体)只减不增。`--staged` / `--update-baseline` / `--self-test`(11 例);紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。
- **反回退对账**(84,原 76):check-stale-revert.mjs(blocking,2026-09-23 立)—— 堵共享工作区**静默回滚**:§12d converge 用 merge-tree/commit-tree 只推进 HEAD+index、**不 checkout**,工作区落后 HEAD 时 `git add <file>` 交的是旧基线(实测 503 文件落后 486 提交),等于把别人该路径的后续改动静默回滚,而 diff 只显"改了几行"。判据 R1 = 暂存 blob != HEAD blob **且字节级等于该路径某祖先提交版本** → 拦并点名回到的 commit;真新编辑不可能恰好等于历史 blob,故误报极低。三条护栏:merge/cherry-pick/revert 上下文整轮豁免、暂存删除只 warn(`git rm` 合法,机器分不清就被删)、判定文件 >300 跳过(性能护栏,防逼人 --no-verify 连带关掉全部守门)。取证 `--self-test` 8 例(含"写回 v1 必判红"阳性对照)+ 临时 index 端到端演练 3/3;**有意回退一律改用 `git revert` 生成前向提交**。紧急跳过 `HUSKY_SKIP_STALE_REVERT_GUARD=1`。

<!-- 合并归并说明:本行下方两条登记分属两个会话同日新增的闸门(78 workspace 依赖链接对账 / 79 提交内容含冲突标记),两侧均保留,不构成互斥。
     (2026-9-24 校正:原写法把冲突标记门写成 77,而 77 是「全 8 端圆角单一源头对账」——同日撞号正是本节要防的事,登记新门前请按守门速查逐条核对 id。) -->

- **workspace 依赖链接对账**(78):check-workspace-dep-links.mjs(blocking,2026-09-23 立)—— 堵的是同一族"**本地全绿、出事的是别人**"的故障,现共**五维**,每维的修复动作不同,红点会点名是哪一维:**① 声明↔链接**:`package.json` 声明 `workspace:*` 而 `node_modules` 里没有那条链接(§12e 的 `pnpm install --filter` 后遗症)。2026-09-23 实例:`@ihui/extension` 缺 `@ihui/design-tokens` → rollup `failed to resolve import` → `pnpm -r build` 连 4 次全红 → 部署进入 30 分钟冷却循环、线上停在旧提交,而 **typecheck/lint/单测全都不会红**(TS 走 tsconfig paths,不看 node_modules)。判据 = 每个包 dependencies/devDependencies/peerDependencies 里所有 `workspace:` 声明,必须在 `<pkg>/node_modules/<dep>` 或根 `node_modules/<dep>` 可解析(`existsSync` 跟随符号链接 ⇒ **悬空链接同样判红**)。**② 链接↔内容**(2026-09-24 补,`findGuttedLinks`):每条**符号链接**的目标必须是真包(有可 parse 且带 `name` 的 `package.json`)——`existsSync` 对"指向**空目录**的链接"仍返回 true,而 09-24 本机全机门禁停摆正是这一型:`node_modules/typescript`、`node_modules/eslint` 指向 `.pnpm` 里的空目录,`.bin` 只剩 16 项 ⇒ lint-staged 第一步 `✖ eslint --fix` 阻止提交 ⇒ **每一次提交都被迫 `--no-verify`,约 110 道守门对全队同时失效**,而 `git status`/typecheck/其余报告全都看不出来。刻意**不**比 name(pnpm 别名 `foo@npm:bar` 必产假阳);真目录(file:/workspace: 直连)不参与;`--strict` 档另把本维延伸到 `node_modules/.pnpm/*/node_modules/*` **传递闭包**(实测真仓浅扫 717 条 / 深扫 10,015 条,均 0 红,深扫 +1.3s);**深扫刻意不进提交链** —— 并发 `pnpm install` 半复制态会一次闪出上百条,而本门 blocking,恒红门的唯一结局就是各会话跳门、连带全部守门作废(本仓最高反面教训);`.pnpm` 里含 `_tmp_` 的安装中临时键**跳过并如实计数**,不判红。**③ shim 完整性(仅报数)**:直接声明的依赖有 `bin` 而该处 `.bin` 无同名 shim;不判红是因为它**在并发 install 窗口内会闪**(实测 0↔113 跳),判红落在 `pnpm check:dep-links:strict`(已串进 `check:all`)。稳态真仓实测 **0 条**缺失、25 个包各有 `.bin`(apps/api 45 项 / web 33 / extension 24 / shared 15 …)⇒ 旧条目写的"真仓 102 条含 apps/api 15 条""apps/api 根本没有自身 `.bin`"**是那次削损的现场读数,不是基线,已于 2026-09-24 全量安装后复测证伪并就地更正**;同时 pnpm 只为**直接声明的依赖**建 shim(根里 hoist 上来的 `expo`/`react-native` 不在根 `package.json` 内,按定义没有),`declaredFor(owner)` 就是为剔除这类假阳而存在。**④ 钩子命令可解析性(判红,不需基线假设)**:`lint-staged` 配置里必然被 spawn 的命令(现 `eslint`/`prettier`)必须在**根** `node_modules/.bin` 解析得到。两处刻意的窄口径:**不**接受"`node_modules/<cmd>` 包目录存在"作为通过(故障当天恰是"包体完好、shim 没了":`node node_modules/eslint/bin/eslint.js --version` 出 v10.8.1,而按 PATH 找 `eslint` 报「不是内部或外部命令」);**不**查全局 PATH(本会话 `where eslint` 能命中全局版,而钩子进程的 PATH 里没有 ⇒ 拿全局命中当"能解析"就是造一台**在故障现场报绿的尺子**)。**⑤ shim 指向的入口文件(判红)**:shim 在位而它 exec 的那个 `bin/*.js` 被删 ⇒ ④ 全绿、lint-staged 一 spawn 就 `Cannot find module`。`resolveShimEntry` 双路兜底(先剥 shim 文本的 `%~dp0`/`$basedir` 模板,再退回包清单的 `bin` 字段),两路都探不到一律记 `unresolved` **不判红**;两个写门时自己踩到的坑已由反向对照钉死 —— pnpm 的 `.CMD` **第一个候选是 `"%~dp0\node.exe"`**(存在性分支头),按"第一个命中"取值会让本门在**完好仓库**上恒红;`%~dp0` 本身就是 `.bin`,再拼一层 `..` 会多跳一级把目标算到 `node_modules` 外面。两条反假绿护栏:扫不到任何 workspace 包 → `exit 1`(不报绿);根 `node_modules` 不存在 → 显式提示"先 pnpm install"并**无法判定**(不记为通过);**扫到 0 条链接一律判红**(空扫不报绿,浅/深合计口径,默认档与改前逐字等值)。`--staged` **不**随暂存收窄范围,恒全量判定(全量 25 包约 0.2s)—— 这类破损与"本次改了什么"无关:手动删链接、他机跑过 `--filter`、清理工具动过依赖树,按 staged 收范围恰好放过整类。取证:`--self-test` + `node --test scripts/tests/check-workspace-dep-links.test.mjs`(例数以各自末行现测为准,不写死防漂移;含**装车证明** runner 里确有 id 78 + blocking + skipEnv、"真仓 eslint/prettier 第五维必须判绿"的**反恒红**对照、以及"深扫只在 strict 生效"的接线方向对照)。**修复动作按维度分**:① ② 靠全量 `pnpm install`(不带 `--filter`);③ ④ ⑤ 靠 `node scripts/repair-node-bin-links.mjs` —— 因为 **shim 不会被 `pnpm install` 或 `pnpm install --force` 重建**(两条都只回 `Already up to date`,1.2s 收工)。验收一律实测命令本身(`.bin/eslint --version` + `.bin/tsc --version` 都出版本号、`pnpm --filter @ihui/api typecheck` 真跑、门 78 全量 exit 0),只看 `node_modules/eslint` 在不在等于没测;`repair-node-bin-links.mjs --check` 在修复后的现读是「根依赖 16 个包 / bin 声明 9 个:`.bin` 完整,无缺失」。另留一条**归属纪律**:本机第一次补回 shim 的 mtime(11:22:56)与另一会话 `repair-node-bin-links.mjs` 入库(11:35)、与我自跑 `pnpm install --force` 都在同窗口,**归属未定 —— 只记事实,不记成果**。紧急跳过 `HUSKY_SKIP_WORKSPACE_DEP_LINKS=1`。

- **提交内容含冲突标记**(79):check-no-conflict-markers.mjs(blocking,2026-09-23 立)—— 2026-09-23 15:49 `.git` 被宿主清除,恢复期某会话在共享工作区跑真实 `git merge`,留下 103 个未合并路径与 94 个带字面标记的工作区文件;而全链守门**没有任何一道**看"被提交的内容含冲突标记",于是 `apps/cli/tests/file-edit.test.ts` 带着 `<<<<<<<` 一路进 HEAD 树,整轮 merge 结束都没被发现。判据 = 同文件内**成对**出现的行首 `<<<<<<< ` 与 `>>>>>>> `(中间允许夹整行 `=======`);**强制成对**是硬要求 —— 单行 `=======` 在 Markdown setext 标题下划线、表格分隔、ASCII 示意图里都合法,只判单行会满天假红(实测全仓 `^=======$` 命中远多于成对命中);未配对的孤立标记不计红但如实报数("只剩一半"= 标记被手删的强信号)。三模式:`--staged`(只判索引内容,`git show :<path>`,取不到退回工作区;路径清单含 `U` 未合并态,因 merge 冲突时 git 把该文件标为 unmerged,只按 ACMR 过滤会恰好漏掉本门要拦的那一类)/ 缺省(判所有跟踪文件工作区内容,`git ls-files`,实测 16561 候选 1.5s)/ `--rev <sha|HEAD>`(判提交树,`git grep -Ilz` 定位候选 + blob 复核,供事后核验)。护栏:自豁免(本门脚本与测试必含字面量,按文件名前缀跳过)、>2MB 大文件、二进制(前 8KB 含 NUL),三类**均在输出里如实计数**;退出码 0/1/2(2=git 候选解析失败等脚本自身异常,绝不静默放行)。取证 `--self-test` 23 例(含正反成对对照 + 真实 merge 未合并路径现场:成对判红 / 单行 setext 判绿 / 索引脏即红而仅工作区脏判绿 / rev 隔离 / 自豁免与大文件二进制计数)+ §22c 镜像测试 11 例。修复口径:**用 `git checkout --ours/--theirs` 或按 §12b 协作收尾重新归并,禁止手删三行标记当作已解决**;紧急跳过 `HUSKY_SKIP_CONFLICT_MARKERS=1`。

- **热路径 git 只读调用超时**(80):check-git-read-timeout.mjs(blocking,2026-09-23 立)—— 堵"提交像死掉了"这类无界挂起:`scripts/check-port-registry.mjs` 一处 `execSync('git ls-files')` 无 `timeout`,在共享工作区挂住 **80 分钟而 CPU 只用 2.84s**(等锁/等 IO 型),而 `git status`、typecheck 全看不出异常。全仓首参锚定实测 **159 处** git 派生调用**无一带 timeout** ⇒ 不是个别疏忽,是没有约束。口径刻意收窄三条(与门 52 同取向:宁漏不误报):① 只判钩子/守护链可达的 `HOT` 文件清单,不判测试夹具与端内代码;② 只判**动词为字面量**的调用 —— `git(args)` / `runGit(args)` 这类包装器动词未知,整体加超时会连带 bound 写操作,而 **`commit`/`add`/`reset`/`mktree` 中途被 SIGTERM 可能留下 `.git/index.lock`**,把一次挂起换成全局阻塞;③ 只判只读动词表内的调用。写动词与包装器**不判但如实计数**(输出行含 `写动词不判 N 处 / 包装器不判 M 处`),绝不静默成"看起来全绿"。字符串与注释内的命中一律丢弃(`markHidden`)——该缺陷是自检抓出来的:判据会把 `const fixture = "execFileSync('git', …)"` 这种测试夹具源码当真调用判红。`timeout` 两种写法都认(`timeout: <ms>` 与简写 `{ timeout }`)。**动词提取按 token 位置逐个走,并对 `-C <dir>` / `-c k=v` 这类带值选项连值一起跳**(`pickVerb`)——旧正则要求数组首元素即动词,于是 `execFileSync(GIT_BIN, ['-C', repoDir, 'status'])` 与 `['-c','safe.directory=*','for-each-ref']` 这两种最常见形态**根本匹配不到**:门恒报"0 处"而实际有 2 处无界只读调用(`git-push-guard.mjs`,已随本次清零)。判据盲区由并行会话的交付报告指出、以自检用例钉住;教训是**"扫到 0"必须先怀疑判据,再相信世界**。`HOT` 现 18 项(新增 `apps/cli/src/worktree.ts`)。**存量已随本门一并清零**(runner 3 处 + converge 4 处 + commit-loss-guard 默认值 1 处),不留基线债。取证 `--self-test` 12 例(含夹具/注释不判、简写属性不假红、写动词计数、-C 与 -c 带值选项取动词)+ `node --test scripts/tests/check-git-read-timeout.test.mjs` 7 例(含**装车证明**)。紧急跳过 `HUSKY_SKIP_GIT_READ_TIMEOUT=1`。 **登记新门前先查编号占用**(`grep -oE "^    id: .[0-9]+.," scripts/guardian-runner.mjs | sort -V | uniq -d` 应只出历史遗留项):同日多会话在数组同一位置各加一道门必然撞号 —— 实测 75/76 已各重复一次,本门最初登记的 79 也与并线进来的 check-no-conflict-markers 同号,故改号为 80 并在镜像测试里钉死"本门编号在 runner 中必须出现恰好一次"。
- **品牌邮件通道对账**(81):check-brand-email-channel.mjs(blocking,2026-09-23 立)—— 拦绕过品牌模板层的 ops 自发纯文本邮件("能发出去、typecheck/lint 全绿,用户收到的邮件没样式",与守门 72/78 同族):R1 `Send-MailMessage` 调用语句缺 `-BodyAsHtml` / R2 `api.resend.com/emails`(含 host+path 分行形态)同一发送上下文无 `html` 字段 / R3 有发信动作(`createTransport`/`sendMail`/`api.resend.com`)却不引用 `email-templates` 也不调用 `notify-deploy-failure`;范围 `deploy/**`+`scripts/**`(不含 tests)+workflows `*.yml`,注释行/裸域名不判(宁漏不误报),行内豁免 `brand-mail-exempt: <原因>`,存量走 `scripts/brand-email-channel-baseline.json` 只减不增(建门实测:ps1 自拼通道已被并行会话清除,副产品揪出第三条通道 `scripts/check-credential-health.mjs` 已入基线待迁移)。**修法的唯一姿势:ops 邮件一律经 `apps/api/scripts/notify-deploy-failure.ts`(版式由 `email-templates.ts` 单点决定),不得端内自拼传输层**。取证 `--self-test` 30 例(正反成对+临时仓双取材面)+ 镜像测试 8 例(含装车证明);`--staged` 暂存集为空/取不到 → 退化全量(守门 70 教训);紧急跳过 `HUSKY_SKIP_BRAND_MAIL_GUARD=1`。

- **品牌邮件通道对账**(81):check-brand-email-channel.mjs(blocking,2026-09-23 立)—— 拦绕过品牌模板层的 ops 自发纯文本邮件("能发出去、typecheck/lint 全绿,用户收到的邮件没样式",与守门 72/78 同族):R1 `Send-MailMessage` 调用语句缺 `-BodyAsHtml` / R2 `api.resend.com/emails`(含 host+path 分行形态)同一发送上下文无 `html` 字段 / R3 有发信动作(`createTransport`/`sendMail`/`api.resend.com`)却不引用 `email-templates` 也不调用 `notify-deploy-failure`;范围 `deploy/**`+`scripts/**`(不含 tests)+workflows `*.yml`,注释行/裸域名不判(宁漏不误报),行内豁免 `brand-mail-exempt: <原因>`,存量走 `scripts/brand-email-channel-baseline.json` 只减不增(建门实测:ps1 自拼通道已被并行会话清除,副产品揪出第三条通道 `scripts/check-credential-health.mjs` 已入基线待迁移)。**修法的唯一姿势:ops 邮件一律经 `apps/api/scripts/notify-deploy-failure.ts`(版式由 `email-templates.ts` 单点决定),不得端内自拼传输层**。取证 `--self-test` 30 例(正反成对+临时仓双取材面)+ 镜像测试 8 例(含装车证明);`--staged` 暂存集为空/取不到 → 退化全量(守门 70 教训);紧急跳过 `HUSKY_SKIP_BRAND_MAIL_GUARD=1`。
- **品牌邮件通道对账**(81):check-brand-email-channel.mjs(blocking,2026-09-23 立)—— 拦绕过品牌模板层的 ops 自发纯文本邮件("能发出去、typecheck/lint 全绿,用户收到的邮件没样式",与守门 72/78 同族):R1 `Send-MailMessage` 调用语句缺 `-BodyAsHtml` / R2 `api.resend.com/emails`(含 host+path 分行形态)同一发送上下文无 `html` 字段 / R3 有发信动作(`createTransport`/`sendMail`/`api.resend.com`)却不引用 `email-templates` 也不调用 `notify-deploy-failure`;范围 `deploy/**`+`scripts/**`(不含 tests)+workflows `*.yml`,注释行/裸域名不判(宁漏不误报),行内豁免 `brand-mail-exempt: <原因>`,存量走 `scripts/brand-email-channel-baseline.json` 只减不增(**2026-09-24 实测基线 `counts` 已为 `{}`** —— 建门时揪出的第三条通道 `scripts/check-credential-health.mjs` 已迁到同一派发器,未用 `brand-mail-exempt:` 糊过去)。**范围已两度扩面**(2026-09-24):原只扫 `deploy/**`+`scripts/**`,而告警邮件正文的实际出口在 `monitoring/alertbridge/*.cjs` —— 目录与扩展名**双重不可见**;现含 `monitoring/**` + `.cjs/.mts` + `apps/api/scripts/**`,并新增两条判据:**R3b** 接了唯一出口却自带一份 HTML 版式(三段与门:邮件版式标记 ∧ ±10 行样式指纹 ∧ 本文件确在邮件语境,注释行不计)—— R1/R2/R3a 只看"有没有走模板",对这一型全盲;**R4 告警接收面** `monitoring/alertmanager/**` 的 yml/yaml/tmpl 出现 `email_configs`/`smtp_*`(AM 原生邮件 = 无版式纯文本)、IM 中转 receiver(名字与 host/路径**都**认,`feishu-copy` 改名躲不过)、或 `webhook_configs` 里 url ≠ `BRIDGE_URL` ⇒ 红;注释行不判红但 `amCommentLiterals` 如实计数。**全仓不扫 yml**(根 compose / prometheus / loki 的历史注释里就写着 `dingtalk-webhook`,全仓扫必假红 —— 面外写进断言)。`stagedTriggers` 与扫描面**必须同步扩**,否则"只改 bridge/模板的提交"根本不唤起本门(判据存在而永不调用 = 没有,守门 70/76 同型;该字段今晚被并发整文件回写抹掉**三次**,由镜像测试"装车证明"抓回)。**修法的唯一姿势:ops 邮件一律经 `apps/api/scripts/notify-deploy-failure.ts`(版式由 `email-templates.ts` 单点决定),不得端内自拼传输层;Alertmanager 出口只有 bridge 一条**。取证 `--self-test` 61 例(正反成对+临时仓双取材面)+ 镜像测试 20 例(含装车证明与 `BRIDGE_URL` 与渲染器逐字等值的漂移即红断言);`--staged` 暂存集为空/取不到 → 退化全量(守门 70 教训);紧急跳过 `HUSKY_SKIP_BRAND_MAIL_GUARD=1`。
- **主题接线对账**(91):check-theme-prop-wiring.mjs(blocking,2026-09-24 立)—— 堵"顶栏深色 + 正文浅色"这类**同屏双色档案**:`packages/app` 213 个 theme-driven 组件的形参默认值是 `colorScheme = 'light'`,调用方漏传就静默脱主题,类型系统完全不红(`colorScheme?:` 可选)。三判据 —— **missing**(渲染点未传该属性)/ **literal**(写死 `'light'` 或 `'dark'`)/ **spread-unknown**(仅 `{...props}` 展开且回溯不出来源,判不出即如实报数,不静默放行)。建门实测:此前一轮用"JSX 元素文本里有没有 `colorScheme` 字样"统计出"漏传 0 处",**该结论是错的** —— 换成花括号深度扫描 + 组件清单自动推导后真实命中 **118 处 / 117 文件**,即 115 个屏一直在静默锁死浅色档案。**盲区已补(比漏修更值得记)**:写死的 `'light'` 有 5 处藏在**对象构造里**而非 JSX 属性上(`const props = { colorScheme: 'light' }; return <SharedX {...props} />`),"字面量"判据只解析 attrs 故从不判红,"未接线"判据见到 spread 就笼统归"判不出" —— 这 5 屏以"待人工核"的名义静默锁死浅色,**门一直是瞎的**。现由 `resolveSpreadThemeValue()` 回溯对象构造再判:对象里有字面量 → 判红;对象里确实没这个键 → 判 missing;props 来自函数形参且本文件无对象字面量 → 仍承认判不出,不猜。**基线 `scripts/theme-prop-wiring-baseline.json` 现须为空 `{counts:{}}`(零容忍)**,`--strict` 口径为"0 未接线 / 0 字面量 / 0 判不出"。行内豁免 `theme-wiring-exempt: <原因>`(须带原因)。取证 `--self-test`(**含阳性对照**:注释里写 `colorScheme: 'light'` 不得误判、不传 `src` 时行为与旧版一致)+ §22c 镜像测试 `scripts/tests/check-theme-prop-wiring.test.mjs`(含**装车证明**:runner 里必须真有 id 91 + blocking + skipEnv,且本门编号在 runner 中恰好出现一次)。**内容口径(2026-09-25 补齐,此前它是全链最后一道按磁盘判的主题门)**:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工逃生舱;枚举与内容同面同轮;取不到 ⇒ **exit 2「无法判定」**(不冒红也不记绿),`--staged --worktree` 同给判死,全量枚举到 0 个 .tsx 判死;git 面必须先 `prefetch`(一次 `cat-file --batch`)再 `read`,未预取即 read 抛错而非静默跳过。立项当场即由此把他人**未提交**的 `ChatScreen.tsx` 摘 prop 判成本仓违规(HEAD 其实合规)。取证 `--self-test` 的 F1–F4 在 `mkScratch` 临时仓里造"索引≠磁盘"现场,并由**三条变异**分别证明 F1/F4 不是恒真;紧急跳过 `HUSKY_SKIP_THEME_PROP_WIRING=1`。
- **HEAD 悬空具名导入对账**(98):`scripts/check-dangling-local-imports.mjs`(blocking,2026-09-24 立)—— 与守门 77 B6 同族的**另一半**:77 管"用了标识符却没 import",本门管"`import { X } from './y'` 而 y 根本不导出 X"。两者都只在编译期可见,而 `pnpm typecheck` 只跑共享工作区 —— 工作区恰好是旧基线时**两边都不红**。2026-09-24 一天内各中一次:`rnRadius['2xl']` 两处未 import 让真机 release 包启动即 SIGABRT(Metro 不查类型,打包成功≠能跑);`PermissionTierRow` 经 `git log --all -S` 证明**从未在任何提交里存在过**,而引用它的注释还写着"已抽到 ChatDisclosure"。判据 D1(具名导入无对应导出)/ D2(相对路径解析不到);口径与 77/83/90 一致:全量判 **HEAD blob**、`--staged` 判索引 blob、**棘轮锚点恒为该文件 HEAD 自身违规数**(把锚点写成 0 会让存量 300+ 文件整片报红 ⇒ 逼人 `--no-verify` ⇒ 全部守门作废);宁漏不误报 —— `export *` 目标不可枚举即放过,缩进/注释/模板字符串里的 import 形态一律不判。真仓 HEAD 实测 8042 源文件、悬空 6 处且**全在测试面**,生产代码 0 处。取证 `--self-test` 19 例 + §22c 镜像测试 5 例(含"存量只允许落在测试文件"与 runner 装车证明)。紧急跳过 `HUSKY_SKIP_DANGLING_IMPORTS=1`。
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob,策略表自身按 HEAD→索引→工作树降级取且**退到工作树会大声提示**;取不到判"无法判定",不冒红也不记绿。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 10 例(含 runner 装车证明与"同一代码 managed 两侧结论不同"的判据有牙证明)。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- **守门 77 B6 的括号形态盲区(2026-09-24 补)**:B6 首版的使用形态正则是 `rnRadius\s*\.`,而本门 `targetOf()` 对 2xl 档**规定的写法恰是 `rnRadius['2xl']`**(`rnRadius.2xl` 不是合法 JS)—— 门让你怎么写,门就看不见怎么写。现判据改为 `\s*(?:\.|\[)` 两种形态同视,`--self-test` 补 3 例成对正反对照,镜像测试补"括号形态必须被看见"的装车证明。教训:**判据必须覆盖自己产出的那一种形态**,否则它只拦得住别人、拦不住自己。

### 计划任务与 .vbs 的硬约束(2026-09-20 立,由本人引入的弹窗回归收口)

- **🚫 计划任务禁止直接执行控制台程序**:InteractiveToken 下 `/tr "node.exe xxx"` 会让 Windows **显示控制台窗口**(每 2 分钟闪一扇黑窗)。一律经 `wscript.exe "<name>-hidden.vbs"` 包装(`objShell.Run(cmd, 0, False)` = SW_HIDE);`-WindowStyle Hidden` 与 `powershell -WindowStyle Hidden` **仍会闪**,不作为豁免手段。现存正例:`git-guardian-hidden.vbs` / `cleanup-zombie-processes-hidden.vbs` / `kill-git-selector-hidden.vbs`。
- **🅰 `.vbs` 文件必须纯 ASCII**:`cscript`/`wscript` 按 **ANSI(本机 GBK)代码页**解码 `.vbs`,UTF-8 中文注释会被错切成伪引号 → **编译期**语法错 → wscript 弹 "Windows Script Host" 对话框,而 `schtasks` 仍报成功(与 §27 的 PowerShell/ANSI 同类陷阱)。`.vbs` 里**不得写中文注释**,说明一律英文;`git-guardian.mjs --install` 已内置"注册前用 `cscript //nologo` 实跑一次、非零退出即拒绝注册"的预检。
- **`WshShell.Run` 命令串引号规则**:裸程序名**不能**加引号(`"node.exe" arg` 会按字面文件名解析而失败),仅路径真含空格时才加引号;参数照常加引号。
- **`WScript.Shell.Environment()("不存在的键")` 返回 Null**,`Len(Null)` 亦为 Null,后续比较抛 "Type mismatch" 并静默中止。读环境变量必须 `& ""` 归一:`raw = shell.Environment("Process").Item("X") & ""`。
- **Electron 宿主无视 `NODE_OPTIONS`**:实测 Qoder/Trae 自带运行时内 `process.env.NODE_OPTIONS === undefined`(Electron 主动剥离),故机器级 windowsHide 钩子**只覆盖独立 `node.exe`**,覆盖不到 IDE 内部的 git 调用。后者若弹窗,只能关 IDE 自带 git 集成或改其调用方,不要误以为环境变量层能解决。

- **圆角容器背景溢出守门**(11b): `scripts/check-rounded-overflow.mjs`(warn) —— 拦"父 rounded-lg/xl/2xl/md 无 overflow-hidden × 子 bg-* 贴边无 rounded-* 无 margin 且非 absolute/fixed"六条件齐备的圆角冒角,--staged 只咬新增行,违规 exit 1(`scripts/check-rounded-overflow.mjs:457`,判定 `isViolation:229`);真跑全量 rc=0(3s);无 --self-test;紧急跳过 `HUSKY_SKIP_ROUNDED_OVERFLOW=1`(脚本自读 :56)。
- **TagsView 选中态描边定稿守门**(11c): `scripts/check-tagsview-visual.mjs`(blocking) —— 拦 TagsView active 态丢 `outline-2+outline-border` 定稿、全站出现 `outline-black`/`dark:outline-white` 回潮、pinned/inactive 背景 token 被换、测试反向断言被删,违规 exit 1(`scripts/check-tagsview-visual.mjs:188`,全站扫描段 `extractQuotedStrings:64`);真跑全量 rc=0(1s);无 --self-test;⚠️失败提示与头注称 `HUSKY_SKIP_TAGSVIEW_GUARD=1` 可跳,实际脚本与 runner 条目均不读该 env——无真实应急通道。
- **单文件行数守门**(11e): `scripts/check-file-size.mjs`(blocking) —— 拦 --staged 新增文件超 800 行(`FILE_SIZE_LIMIT`/`--limit` 可改阈值),exit 1(`scripts/check-file-size.mjs:216`),全量模式仅报告 exit 0;真跑全量 rc=0(1s);无 --self-test;紧急跳过 `HUSKY_SKIP_FILE_SIZE=1`(脚本自读 :51)。
- **原生弹窗禁用守门**(11f): `scripts/check-no-native-dialog.mjs`(blocking) —— 拦 staged 新增行里调用形态 `alert(`/`confirm(`/`prompt(`(去注释+去字符串后判定,`stripStringsAndComments:79`),exit 1(`:298`),全量仅报告;真跑全量 rc=0(7s);无 --self-test;紧急跳过 `HUSKY_SKIP_NATIVE_DIALOG=1`(脚本自读 :40)。
- **渐变遮罩禁用守门**(11g): `scripts/check-no-mask-image.mjs`(blocking) —— 拦 staged 新增行出现 `mask-image`/`-webkit-mask-image` 字面量(注释行豁免 `isExempt:63`),exit 1(`:244`),全量仅报告;真跑全量 rc=0(3s);无 --self-test;紧急跳过 `HUSKY_SKIP_MASK_IMAGE=1`(脚本自读 :39)。
- **i18n JSON 同层重复键守门**(2e-dupns): `scripts/check-i18n-duplicate-namespaces.mjs`(blocking) —— 字符级深度栈扫 `packages/i18n/messages/**/*.json` 找同一对象层重复键(JSON.parse last-wins 静默遮蔽),发现即 exit 1(`:163`,核心 `findDuplicateKeys:64`),另接 cert `i18n-duplicate-ns`(warnOnly:false)+ CI path 触发;真跑全量 rc=0(0s);无 --self-test;紧急跳过 `HUSKY_SKIP_I18N_DUP_NS=1`(脚本自读 :115);⚠️头注称"调用方 guardian-runner 第 2f 项",实际条目 id 为 `2e-dupns`。
- **陈旧副本/保护区删除守门**(30c): `scripts/check-stale-copy.mjs`(blocking) —— 拦两态:staged D 命中保护区(drizzle .sql/各端 tests/scripts/check-*/scripts/lib)或 staged M 内容 = 该路径基线祖先的历史 blob(`git log --find-object` 判据),exit 1(`:116`);真跑 rc=1(13s)红 3 处(`PROJECT_PLAN.md`、`scripts/check-c-drive-pollution.mjs` 及其测试被判陈旧副本——系本机共享工作树滞后/并行会话现场,存量红待清,非 HEAD 缺陷);无 --self-test;紧急跳过 `HUSKY_SKIP_STALE_COPY=1`(脚本自读 :55)。
- **LLM provider 字典 schema 守门**(33): `scripts/check-llm-provider-schema.mjs`(blocking) —— 对 `apps/ai-service/.env` 的 `LLM_PROVIDERS(_JSON)` 跑 7 条规则(32 provider 白名单/字段类型/未知 provider/空值/重复等),含 error 即 exit 1(`:339`),`.env` 缺失或参数错 exit 2(`:306/:316/:324`);真跑 rc=0(0s);无 --self-test,但镜像测试 node --test 真跑 rc=0(45 例);无应急通道(runner 条目无 skipEnv、脚本不读 env);⚠️头注"集成位置:.husky/pre-commit 第 N+1 项"与实际不符(实际挂 runner id 33 + 专属 CI `llm-provider-schema-test.yml`)。
- **新增 @ts-ignore/@ts-nocheck 守门**(34): `scripts/check-ts-ignore.mjs`(warn) —— 扫 staged .ts/.tsx/.mjs/.js/.cjs 注释形态新增 @ts-ignore/@ts-nocheck(e2e 等白名单 `SKIP_PATTERNS:55`),exit 1(`:160`)但 runner 以 warn 计不阻塞;真跑 rc=0(0s);无 --self-test,但镜像测试 node --test 真跑 rc=0(15 例);无应急通道(warn 级)。
- **web globals.css token 单源守门**(37): `scripts/check-web-tokens-sync.mjs`(blocking) —— 拦两态:globals.css 丢失 `@import tokens.css`(:136-141 REGRESSION)或顶层 `:root/.dark` 重宣 tokens.css @theme 变量,exit 1(`scripts/check-web-tokens-sync.mjs:132` 读失败 / 末尾 exitCode 汇总);真跑全量 rc=0(0s);无 --self-test;无应急通道(runner 无 skipEnv、脚本不读 env);⚠️头注只述 :root/.dark 回归,实际还承载 @import 存在性判据(门 89 台账引用的是后者)。
- **solito 幽灵依赖回潮守门**(38): `scripts/check-solito-residue.mjs`(blocking) —— 五查(package.json 三类依赖/pnpm.patchedDependencies/pnpm-workspace publicHoist/`patches/solito*.patch`/packages/app tsx `from 'solito/...'`)命中即 exit 1(`:353`,检测函数 `:110/:154/:189/:234`);真跑 rc=0(0s);无 --self-test,但镜像测试 node --test 真跑 rc=0(15 例);无应急通道。
- **共享层重复实现守门**(40): `scripts/check-shared-layer-duplication.mjs`(blocking) —— apps/*/src 的 export 名与 packages/shared/src export 名求交集(`collectExports:21`,`scanAppDir:85`),端内重新实现即 exit 1(`:152`);真跑 rc=0(0s);无 --self-test;无应急通道(runner 无 skipEnv;re-export wrapper 豁免逻辑在白名单)。
- **统一返回键私接守门**(46): `scripts/check-inline-back-button.mjs`(blocking) —— 拦 apps/web/src 内 `router.back()`/`history.back()` 出现在 GlobalTopBar.tsx 之外(注释行自动跳过 `isCommentLine:51`,ALLOWLIST/BANNED :317-318),exit 1(`:90`);真跑全量 rc=0(15s);无 --self-test;⚠️头注称 `HUSKY_SKIP_INLINE_BACK_GUARD=1` 可跳,实际脚本与 runner 条目均不读该 env——无真实应急通道。
- **GitHub Actions 步骤顺序守门**(48): `scripts/check-workflow-step-order.mjs`(blocking) —— 同 job 内 `actions/setup-node` 带 `cache: pnpm` 而 `pnpm/action-setup` 未排其前 → exit 1(`scripts/workflow 扫描主体 :79`,立因 2026-09-10 两 workflow 恒败);真跑 rc=0(1s);无 --self-test;紧急跳过 `HUSKY_SKIP_WORKFLOW_ORDER=1`(脚本自读 :30)。
- **迁移记账结构守门**(49): `scripts/check-migration-bookkeeping.mjs`(blocking) —— 离线 B1-B5:journal tag↔drizzle/*.sql 双向双射、tag 唯一、when 严格递增唯一、idx 唯一、journal 结构完整,违反 exit 1(`:258`);`--db` 追加 B6-B9 库内双射(行数/created_at 集合/max 相等/hash 合法唯一);真跑(离线)rc=0(0s),`--db` 模式未跑(连库);无 --self-test;紧急跳过 `HUSKY_SKIP_MIGRATION_BOOKKEEPING=1`(脚本自读 :56);另被 ci.yml run 步引用。
- **next-env.d.ts 构建污染守门**(50): `scripts/check-next-env-dist.mjs`(blocking) —— --staged:暂存的 `apps/web/next-env.d.ts` 引用 `.next-<suffix>` 变体即无条件 exit 1(`:112`);全量:变体目录仍存活(并发会话在用)只提示、目录已消失才判红;真跑全量 rc=0(0s);无 --self-test;⚠️头注称 `HUSKY_SKIP_NEXT_ENV_DIST=1` 可跳,脚本与 runner 条目实际都不读该 env——无真实应急通道。
- **能力目录三方一致守门**(51): `scripts/check-capability-catalog.mjs`(blocking) —— A `capability-catalog.ts`↔`generated/capabilities.json` 产物一致 / B v1 路由逐 handler 被能力闸覆盖 / C scope 语义(platform/thirdPartyEligible=false 不得进 /v1 rules)/ D 反向核对 warn,违规 exit 1(`:1097` 附近聚合,解析核心 `parseCatalogEntries:285`),--staged 把 B/C 收窄到暂存 v1 文件;真跑全量 rc=0(2s);--self-test rc=0 ✅ 18 条断言全绿 + 镜像测试 `scripts/tests/check-capability-catalog.test.mjs` node --test 真跑 rc=0(20 例);紧急跳过 `HUSKY_SKIP_CAPABILITY_CATALOG_GUARD=1`(脚本自读 :893;id 51 注册块 HEAD:1131/1133 已 grep 直验在场)。
- **未提交改动年龄守门**(54): `scripts/check-uncommitted-age.mjs`(warn) —— `git status --porcelain -z` 取已改+未跟踪源码文件(.ts/.tsx/.js/.jsx/.mjs/.cjs/.py/.css/.sql),mtime 年龄>45 分钟(阈值可 `resolveThresholdMin:200` 覆盖)即判风险 exit 1(runner warn 计不阻塞;绝不输出文件内容,.env*/generated/.ihui-agent 豁免);真跑 rc=0(0s);--self-test rc=0 ✅ 28 例全绿 + 镜像测试 node --test 真跑 rc=0(21 例);无应急通道(warn 级)。
- **权限模式词汇对账守门**(68): `scripts/check-permission-mode-vocabulary.mjs`(blocking) —— R1 TS/Py 双注册表成员+别名逐字同(`checkMirror:163`)/R2 别名值域闭合(`checkAliasClosure:195`)/R3 已知消费点字面量落在成员∪别名键、AgentLoopV2 禁 `auto`/自造白名单(`checkConsumers:214`/`checkNoSecondList:258`)/wire 镜像(`checkWireMirrors:312`),违规 exit 1(`:586`);真跑全量 rc=0(0s);--self-test rc=0 ✅ 21 条输出(含"两侧一致放过"反例)+ 镜像测试 node --test 真跑 rc=0(11 例);紧急跳过 `HUSKY_SKIP_PERMISSION_VOCAB=1`(runner skipEnv)。
- **端内绕过 api-client 直连后端守门**(73): `scripts/check-direct-backend-calls.mjs`(blocking) —— 拦端内裸 `fetch(`/`axios.*(`/`{Taro,wx,my,tt,…}.request(`/`new XMLHttpRequest(`+`.open(`/`navigator.sendBeacon(`/`http(s).request(` 且 URL 经污点传播锚定本仓后端(BASE_URL 符号族/自家 host/锚定 `/api/` 段,跨文件回溯深度 5,`findCallSites:410`),违规 exit 1,存量走 `scripts/direct-backend-calls-baseline.json` 棘轮只减不增;未真跑(副作用 grep 命中 :76/:1093 基线回写形态,按规则整门保守不跑);--self-test 存在(3 处)未跑(其镜像测试 node --test 真跑 rc=0,15 例);紧急跳过 `HUSKY_SKIP_DIRECT_BACKEND_CALLS=1`(runner skipEnv,留痕)。
- **词表键五语言可解析守门**(74): `scripts/check-word-table-resolvable.mjs`(blocking) —— W1 对象字面量词表发现(`scanObjectLiterals:133`/`discoverWordTables:171`)/W2 权威语料 ≥2 键且 ≥50% 锚定为 i18n 词表/W3 逐键×逐语言在消费端 shared+端合并视图必须解析出非空且≠键名/W4 miniapp 离线包载荷/W5 未接入端只记 notice 不计退出码,违反 exit 1(`:885` 提示行所在判红聚合)。**W3 的"消费端"按符号粒度认**(2026-09-24 修盲区):同一模块多导出时,只有经模块内引用链**真正读到这张表**的导出符号才算消费端。旧判据是"提到任一导出符号即算",于是 `packages/shared/src/utils/error-messages.ts` 里返回固定中文、根本不查词表的 `toUserFriendlyMessage` 把 mobile-rn 40 个屏算成了 `errors.*` 的消费端,产出 70 枚 blocking 恒红 —— 而那 14 枚键的出口 `getErrorI18nKey` 全仓零调用方。顶层切分失效与 re-export 两种情形一律**退回全量符号面**(宁可多报,绝不让判据失效表现为"没有消费端"而把红洗成绿),`--self-test` 与镜像测试各含变异对照。取证口径:干净 HEAD(`git archive` 隔离检出)真跑 exit 0,55 张表 54 张结论逐字不变、唯一变化的正是上述那一张;--self-test rc=0 ✅ 33 条(含真语料注入验证)+ 镜像测试 node --test 真跑 rc=0(26 例);紧急跳过 `HUSKY_SKIP_WORD_TABLE_RESOLVABLE=1`(runner skipEnv + 脚本自读 :57)。
- **Agent Engine 协议三方 parity 守门**(无 id,hook :582): `scripts/check-agent-engine-parity.mjs`(blocking) —— JSON-RPC 引擎 handler 表(agent_engine.py 事实源)↔ TS `packages/sdk/src/agent-engine.ts` ↔ Python SDK 的方法/notifications/streaming/error code 常量集合静态对齐,漂移 exit 1(`:216`),hook 以 `--quiet` 跑;真跑全量 rc=0(0s);无 --self-test;紧急跳过 `HUSKY_SKIP_AGENT_ENGINE_PARITY=1`(hook :578)。
- **凭据前缀一致性守门**(无 id,hook :538): `scripts/check-api-credential-prefix.mjs`(blocking,--staged) —— 面向用户文案/示例里 `Authorization: Bearer`/`api_key` 必须 `ihui_` 前缀,`sk_` 仅允许在描述 `X-Api-Secret` 语境,暂存违规 exit 1(`scanFile:160`,作用域 `inScope:145`),全量模式仅报告 exit 0;真跑全量 rc=0(2s);--self-test rc=0 ✅ 20 条规则;无镜像测试;紧急跳过 `HUSKY_SKIP_CREDENTIAL_PREFIX_GUARD=1`(hook :534)。
- **auth refresh 单例守门**(无 id,hook :273): `scripts/check-auth-refresh-singleton.mjs`(blocking) —— 客户端业务代码禁绕过 `refreshAccessTokenOnce` 单例裸发 `/auth/refresh`(fetchApi/fetch/裸 `refreshAccessToken(` 三形态,`scanFile:131`;定义处/后端/测试/token-provider 注入 :98/extension 合法刷新域 :115 白名单),违规 exit 1(`:211`);未真跑(网络 grep 命中 `fetch(` —— 命中行是门自身判据模式串,脚本实为只读,仍按规则保守不跑);无 --self-test;紧急跳过 `HUSKY_SKIP_AUTH_REFRESH=1`(hook :269)。
- **桌面端事件链路接线守门**(无 id,hook :606): `scripts/check-desktop-event-wiring.mjs`(blocking) —— Tauri `.emit("事件名")` → `use-desktop.ts` listen+case+`new CustomEvent` → 前端 `addEventListener`/事件注册表三层对账,任一层断链 exit 1(立因托盘主题/设置点击无反应);**它在 `guardian-runner` 与 `check:all` 的清单之外**(钩子直调、无 id)—— 所以"全量审计 0 红"不覆盖它,反过来它红了也只会表现为"每次提交都要跳门";紧急跳过 `HUSKY_SKIP_DESKTOP_EVENT_WIRING=1`(hook :602);另在 `pnpm check:all` 链内。
  - **G4 的"就绪点亮"按可达性判,不按字面量所在函数体判(2026-09-26 修,登记 G-205)**:该判据原先只取 `take_pending_deep_links` 的函数体找 `target_ready = true`,而 `15573c449` 把状态迁移抽成了纯函数 `apply_take_ready`(为了 Rust 侧单测能直接断言)—— 赋值换了地方,门就红了,于是**全队每次提交被逼 `--no-verify`、约 158 道门对每次提交作废**。现判据 = 命令体内没有时,**沿命令体真的调用到了的函数再找一跳**(不追传递闭包、不做全文件搜索)。正反自检两条:赋值提交给同文件 helper ⇒ 不得判红(门必须认自己产出的形态)/ 命令体与 helper 都不置就绪 ⇒ **必红**(允许一跳不等于放过)。**同格判据由两条会话同日独立改到,落地的是走得更远的那一版**(它同时新增 **G8** 整页导航复位对账,并含"导航面找不到任何发起点 ⇒ 判失明、不记通过");本会话那版作废 —— 记这一句是为了让后来人知道**最终生效的是哪一份实现**,两条登记并存就会有人按没落地的改法去推下一步。**通用规矩:凡"某处必须出现 X"的判断,先问"X 被抽进被调函数还算不算";按文本位置判 red 的代价从来不是误报一次,而是大家学会跳过钩子。**
- **miniapp ICU .replace 反模式守门**(无 id,hook :403 条件 blocking): `scripts/check-miniapp-replace-antipattern.mjs`(blocking,--staged) —— 拦 `t/tt('key', '...{{n}}...').replace('{{n}}',v)` 三命中规则(5 白名单:`t(key,{vars})` 等),staged 新增行命中 exit 1(`main:302`,异常 :305/:384 exit 2),仅 staged 触及 apps/miniapp-taro/src/ 时执行;真跑全量 rc=0(1s);无 --self-test;紧急跳过 `HUSKY_SKIP_MINIAPP_ICU_CHECK=1`(hook :387)。
- **createPortal 定位守门**(无 id,hook :495): `scripts/check-portal-fixed.mjs`(blocking,--staged) —— `createPortal(<div …>)` 定位容器必须显式 `position:'fixed'/'absolute'` style 或 `fixed/` 类(`checkPortalElement:65`;style 引外部变量降级 WARN 留人工 review),违规 exit 1(`:134`);真跑全量 rc=1(0s)红 1 处(`apps/web/src/components/feedback/portal-panel.tsx:147` 缺显式 fixed——全量口径存量待清;hook 实际以 --staged 只咬新增);无 --self-test;紧急跳过 `HUSKY_SKIP_PORTAL_GUARD=1`(hook :491)。
- **mobile-rn global.css token 同步守门**(无 id,由 `scripts/lib/pre-commit-hook.js` 的 16c 步条件 blocking 调用): `scripts/check-rn-global-css-sync.mjs`(blocking) —— `apps/mobile-rn/global.css` 的 `--color-*` 值与 `packages/design-tokens/src/styles/tokens.css` 逐变量对账(NativeWind 手抄副本,漂移即视觉 bug),不一致 exit 1,staged 触及两文件之一才触发。**取材面 2026-09-26 收口**(与 36/124/93 同口径):默认判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 只作人工逃生舱、两面旗同给 exit 2、任一面取不到**不回落**另一个面 ⇒ exit 2。旧形态两处缺陷叠在一起:① 默认判磁盘;② `--staged` 索引取不到时悄悄回落 HEAD —— **回落就是把"没判"写成"判过了"**,而自洽的假绿比可见的红难查。**同批给钩子调用点补上 `--staged`**(这条不是顺手改:本门收口后默认档判 HEAD,钩子若不传面旗就等于在审上一提交态,生成器刚写回、已 `git add` 的新值它结构上看不见 —— 而它是 `TOKEN_SYNC_TARGETS` mobile-rn 那一行写回之后的**唯一**复核闸)。取证:镜像测试 22 例(含 F1–F5:纯函数四态锁、临时仓三面互异、CLI 三面成对、无提交判死、F5 形状锁禁止 `gitShow` / `readFileSync(join(root` / `node:fs`),变异自证 `def:'worktree'` 后 4 例红 ⇒ "默认判 HEAD"不是恒真);真仓 HEAD 与索引两面 exit 0(187 受管档逐位同值、26 个端内自立 `--color-*` 档只报数)。仍无应急通道(hook 未设 skip env)。
- **SiteFooter 防回退守门**(无 id,hook :471): `scripts/check-site-footer.mjs`(blocking) —— 6 项定稿断言(关键 className 集合/ECOSYSTEM_GROUPS=5 分组/INTERNATIONAL+CHINESE_MODELS import+export/单一 `useTranslations('footer')`/5 语言 footer 键齐),失败 exit 1(`:174`,判定 `check:39`);真跑 rc=0(0s);无 --self-test;紧急跳过 `HUSKY_SKIP_FOOTER_GUARD=1`(hook :467);另有 `pnpm footer:guard`。
- **staged 文件数预检守门**(无 id,hook :44): `scripts/check-staged-files-count.mjs`(warn) —— staged 文件数 > 阈值(默认 10,`--max` 可调)打警告,仅 `--strict` 才 exit 1(`:66`);hook 用 execSync+catch 吞失败,恒不阻塞;真跑 rc=0(1s);无 --self-test;紧急跳过 `HUSKY_SKIP_STAGED_COUNT=1`(脚本自读 :36);⚠️头注"集成位置:.husky/pre-commit 第 0 项"与实际不符(实挂 `scripts/lib/pre-commit-hook.js:44`,且为 warn)。
- **样式改动 Verified-DOM trailer 守门**(无 id,.husky/commit-msg:10): `scripts/check-style-verification.mjs`(warn——wiring `|| true` 吞退出码) —— staged 含 apps/web `.css` 时 commit message 必须带 `Verified-DOM:` trailer,缺失 exit 1(`:79`),缺参数亦 exit 1(`:33`);未真跑(需 commit-msg 文件参数);无 --self-test,但镜像测试 node --test 真跑 rc=0(15 例);紧急跳过 `HUSKY_SKIP_STYLE_VERIFY=1`(脚本自读 :25);⚠️AGENTS.md §17 称"commit-msg hook 自动守门"暗示阻塞,实际接线 `|| true`(hook 注释自述"失败不阻断 commit,仅警告")。
- **Agent SSE 事件契约 parity 守门**(无 id,cert `sse-event-parity` warnOnly:false + `8end-consistency-cert.yml` path 触发 + check:all): `scripts/check-agent-event-parity.mjs`(仅CI) —— 全仓 SSE 生产面(api 路由 `event:` 模板/broadcastSSEEvent/@ihui/types 契约声明/ai-service agents·agent_runtime·langgraph·llm 生产者)提取事件名与消费面监听集合双向对账,漂移 exit 1(`:716`);未真跑(副作用 grep 命中 :77-79 —— 命中为 tee 结果文件回写);无 --self-test;无应急通道(CI 门)。
- **mobile-rn 语言包 key parity 守门**(无 id,cert `i18n-parity-mobile-rn` warnOnly:false + CI path 触发): `scripts/check-i18n-parity.mjs`(仅CI) —— `packages/i18n/messages/mobile-rn` 各语言键集以 `en.json` 为基准做缺失/多余 diff(`getAllKeys:11`),报告写 `tmp/i18n-parity-report.txt`(:62-63);未真跑(写 `tmp/` 报告命中副作用 grep);无 --self-test;⚠️脚本源码无任何 process.exit——退出码恒 0,cert 虽标 warnOnly:false 该判据是否真生效待持有人核;无应急通道。
- **死代码棘轮守门**(无 id,`knip.yml:74` run 步): `scripts/check-knip-ratchet.mjs`(仅CI) —— `knip --reporter json` 分类计数 vs `scripts/knip-baseline.json`(可 `KNIP_RATCHET_TOLERANCE` 加容差),任一项超基线 exit 1(`:182`),knip 不可用/解析失败 exit 2(`:74-99`),`--update` 收紧基线(`:140`);未真跑(基线回写 + 全量 knip 重);无 --self-test;无应急通道(CI 门)。
- **空库全链迁移重放守门**(无 id,`db-from-zero-migrate.yml:93` run 步 + path 触发): `scripts/check-migration-from-zero.mjs`(仅CI) —— 一次性空库按 journal 逐迁移 psql 重放 + `drizzle-kit migrate` 全链 + 迁移后库↔drizzle 元数据表/列双向 diff(`KNOWN_SCHEMA_HOLES` 基线扣减),不 100% 应用或 diff 非空即非零退出(`:719` 为脚本异常态),临时库必定 DROP;未真跑(在生产 PG :8810 建删临时库,§5 测试隔离铁律);无 --self-test,但镜像测试 node --test 真跑 rc=0(16 例);无应急通道(CI 门)。
- **NativeWind 升级就绪监控(反向判据)**(无 id,`nativewind-monitor.yml:45` 定时 + `pnpm nativewind:status`): `scripts/check-nativewind-status.mjs`(仅CI) —— `npm view nativewind dist-tags`,latest 为 5.x stable → exit 1(=升级窗口已到、提醒移除 metro monkey-patch,红≠违规,`:73`);registry 不可达 exit 2 不误报(`:48/:56`);真跑 rc=0(2s,latest 仍 4.x/preview);无 --self-test,但镜像测试 node --test 真跑 rc=0(15 例);无应急通道(监控语义)。
- **!important 禁用守门(CSS 感知版)**(无 id,`style-spec.yml:57` run 步): `scripts/check-no-important.mjs`(仅CI) —— 解析式扫 apps/web 样式(剥注释后),非豁免 `!important` 判违规 exit 1(`:141`,分析 `analyze:62`),豁免仅两类:`prefers-reduced-motion` 块 + 行内 `-- ihui-allow-important: 理由`;真跑 rc=0(1s);无 --self-test;无应急通道(CI 门)。
- **跨端色值 token 漂移对账**(93): `scripts/check-cross-end-tokens.mjs`(blocking,2026-09-24 由 id 90 改号而来 —— 与 SSE 门撞号，守门 89 的 R5 实测抓到) —— RN `rn-tokens.ts` hex ↔ miniapp/web `tokens.css` 按“确定语义相同”映射表逐位比对(hsl→hex 归一、rgba 空格归一、.dark 未覆盖按 cascade 回退),不一致 exit 1(:226/:263);另由 cert `cross-end-tokens`(warnOnly:false)+ CI path 触发 + check:all 承载;真跑 rc=0(0s);无 --self-test;无应急通道(runner 未声明 skipEnv)。
- **button 文本换行浏览器实测守门**(无 id,仅 `pnpm check:button-wrap:browser`): `scripts/check-button-text-wrap.mjs`(手动) —— Playwright 实测各 button 内最深 span 高/单行基线 ratio>1.4 判换行成 2 行,`--strict` 下命中或 server 不可达场景 exit 1(`:318/:393/:461/:468`,算法 `buildDetectFn:188`,探活 `probeServer:255`);未真跑(需 Playwright+dev server,且写临时目录 :351 命中副作用 grep);无 --self-test;无应急通道(不在提交链);⚠️头注称可用于 pre-commit/nightly,当前未接任何提交链。
- **全端杀手锏常量二次写死守门**(无 id,check:all): `scripts/check-killer-parity-ends.mjs`(blocking 语义,仅 check:all 内) —— 扫全部 TS 端源码中绕过 `tunables.py`→`constants.ts` 单源直接硬编码同值的"二次写死"(白名单显式豁免),违规 exit 1(`:156`,`--warn-only` 降级);真跑 rc=0(0s);无 --self-test;无应急通道。
- **mobile-rn 硬编码 hex 基线棘轮守门**(无 id,check:all + `pnpm check:rn-parity`): `scripts/check-mobile-rn-style-parity.mjs`(blocking 语义,仅 check:all 内) —— RULE-1 基线外新增 `#hex` 字面量即 BLOCK(exit 1,`scanFile:98`,基线 `scripts/rn-style-parity-baseline.json` 只减不增,`--update-baseline` 收紧 :130),RULE-2 基线失条目 WARN;未真跑(--update-baseline 回写命中副作用 grep);无 --self-test;无应急通道。
- **侧边栏导航死链守门**(无 id,check:all + `pnpm check:nav-dead-links`): `scripts/check-nav-dead-links.mjs`(blocking 语义,仅 check:all 内) —— `nav-data.ts` 全部 href 必须能在 apps/web/app 推出 page 路由((group)/_private 不占段、动态段通配 `toRoutePattern:59`),死链 exit 1(`:103`);真跑 rc=0(1s);无 --self-test;无应急通道。
- **workspace 包可安装性自检**(无 id,仅 `pnpm check:pkg-installable <pkg>`): `scripts/check-pkg-installable.mjs`(手动,发布前硬闸) —— 真 `npm pack` + 解包 8 项判据(dist 双产物在包内/不含 src/无凭据噪音/无 `workspace:` 残留/LICENSE+NOTICE/ESM 相对 import 带扩展名/manifest 名版一致),blocker exit 1(`:190/:201`),脚本异常 exit 2(`:167`),private 只提示不计红;未真跑(npm pack+mkdtemp 解包 :174 命中副作用 grep);无 --self-test,但镜像测试 node --test 真跑 rc=0(14 例);无应急通道。
- **自写 popover data-state 守门**(无 id,check:all + `pnpm check:popover-trigger-data-state`): `scripts/check-popover-trigger-data-state.mjs`(warn/手动,--staged 才 blocking) —— 含 `createPortal(` 且不 import Radix 的文件必须手挂 `data-state` 属性(`classifyFile:111`),全量模式 WARN exit 0,`--staged` 新增违规 exit 1(`:209`);真跑(全量)rc=0(0s);无 --self-test;紧急跳过 `HUSKY_SKIP_POPOVER_DATA_STATE=1`(脚本自读 :47)。
- **web 死 i18n key 审计别名 wrapper**(无 id,wiring 仅 template 层引用): `scripts/scan-web-dead-i18n-keys.mjs`(手动别名) —— 自身零判据:转发全部参数到 `scan-dead-i18n-keys.mjs --target=web` 并透传退出码(`:11-15` spawnSync,status 透传),实际判据/报告在统一入口(check:all 走 `--target all --exit 1`,CI 审计 workflow 跑其镜像测试);真跑 rc=0(3s,经委托链);无 --self-test,但镜像测试 node --test 真跑 rc=0(7 例)(其本体);无应急通道;⚠️门 89 判其接线为 template 弱接线,登记时勿当作独立闸。
- **守门接线层对账**(89): `scripts/check-gate-wiring.mjs`(blocking) —— 本门是其余全部守门的"装车检",查接线层四类结构缺陷:**R1/R2** 脚本头部或 AGENTS.md 声称"已接 pre-commit/pre-push/CI 必跑/第 N 项"而五处权威点(`guardian-runner` 的 `script:` ∪ `scripts/lib/pre-commit-hook.js` ∪ `.husky/*` ∪ 根 `package.json` ∪ `.github/workflows`+cert)全部零命中 → 拦;**R4**(2026-09-24 由"仅报数"升档,前置=真仓缺口 48→0 已清零)门已接线但 AGENTS.md/README.md 通篇未点名 → 拦(判据刻意宽松:出现去后缀同名即算,故只会漏报不会误拦);**R5** 同一 `id` 登记两道门 → 拦(串 skipEnv 与失败归属);**R7** 台账 `type=dispatcher` 的依据文件不存在或文件里没提该脚本 → 拦。R3(五处零命中且无肯定式声称)/R6(共用 skipEnv)/弱接线(仅 CI)只如实报数不判红。**取材口径分两面**:接线面(哪些脚本被登记为已接)一律按 **HEAD** 判 —— 共享工作区的工作区文件会滞后,产出相反结论;而**文档面**(AGENTS.md / README.md 有没有点名)按 **HEAD ∪ 索引**判(2026-09-24 改),因为"同一枚提交里既注册新门又补点名行"是正确姿势,只读 HEAD 会让这枚提交被自己判红 ⇒ 逼人 `--no-verify` ⇒ 全部守门作废;索引取不到退回 HEAD,口径在结论行如实报出,且**绝不读工作区**(只躺在工作区没 `git add` 的文档行不算点名,已由自检 M9d/M9e 一红一绿的可逆对照钉死)。;台账 `scripts/gate-wiring-allowlist.json` 只能救 R3,凡声称已接线者台账不救。⚠️ 本门对自身 `SELF_EXEMPT`(创建当期 HEAD 里还没有它),所以"它自己被摘线"由镜像测试 T13 钉死。真跑 `node scripts/check-gate-wiring.mjs` exit 0(已接线 143 / 台账豁免 13 / R4 0 枚,数字以命令末行现测为准);自检 `--self-test`(例数以末行为准,含 M8a/M8b 的 R4 双向端到端证明 + M9a-M9e 的文档取材口径五形态对照)+ `node --test scripts/tests/check-gate-wiring.test.mjs`(例数以末行为准,含 T13 装车证明与 T14/T15 取材对照);紧急跳过 `HUSKY_SKIP_GATE_WIRING=1`。
- **跨端主题透线对账**(91): `scripts/check-theme-prop-wiring.mjs`(blocking) —— packages/app 的主题驱动组件(形参解构含 `colorScheme` 且一律带 `= 'light'` 默认值)必须在每个 JSX 渲染点被**真的传参**:默认值就是"静默失效开关"，写死或漏传都不报错、typecheck 也不红。拦两类真事故形态 —— L1 调用方写死 `colorScheme="light"`(实测导航栏深色而页面体浅色，同一屏两套档案)、L2 漏传(当前 0 处，本门把它钉死在 0；刻意不把 213 处形参改成必填:无收益且会与并行会话在 213 个文件上对撞)。组件集**不靠手工清单**(清单过期正是上一道门漏判的原因)，改为扫 `packages/app/src` 导出组件名 + **import 解析**(含 alias / 默认导入 / 命名空间成员)，只有解析到 `@ihui/rn-app` 或包内相对路径才算共享主题组件 —— 端内同名自绘版(NavBar/TabBar/Carousel/UserInfoCard)因此不会误判；`{...spread}` 转发的渲染点单列「不确定」如实报数(不静默放过、也不误判为红)。实测真仓 `--staged` exit 0；应急跳过 `HUSKY_SKIP_THEME_PROP_WIRING=1`。

- **顶部状态栏避让单一源头**(97):`scripts/check-statusbar-single-source.mjs`(blocking,2026-09-24 立)—— 真机实测(Redmi 720x1640 / density 320)状态栏 inset = 68px = **34dp**;而 399 个 `*Screen.tsx` 里真读 inset 的只有 3 个:83 处在页头/根容器写死 `paddingTop: 48` 硬蒙量级(换机型即失配)、113 处完全没有顶距 ⇒ 页头与系统时钟/电量**叠字**。**唯一注入点 = `apps/mobile-rn/App.tsx` 的 `<SafeAreaView edges={['top']}>`**;状态栏是平台概念,所以 `packages/app`(跨端共享屏)一律不碰顶距,共享层 NavBar 那个默认 0、零调用方的 `statusBarHeight` prop 属第二个真相源,**已删**,不得再加回。三判据:**S1** 单点在位(该文件必须真有 `SafeAreaView` 且 `edges` 含 `'top'` —— 防"装好被摘线",机制不在而门仍报绿等于没有)/ **S2** 第二取值口(代码里出现 `StatusBar.currentHeight` 或 `statusBarHeight` 即红;前者 iOS 恒 `undefined` ⇒ 0)/ **S3** 魔法顶距(`container|page|root|wrapper|header|headerRow|screen|body` 的对象内 `paddingTop: 24..60` 字面量即红,**零容忍** —— 83 处已随本票清零,所以不需要基线)。口径同守门 77/83:全量判 HEAD blob、`--staged` 判索引 blob,不判滞后的共享工作树。行内豁免 `statusbar-exempt: <一句话原因>`(**M2:逐行生效且必须带原因**,一行标记救不了同文件另一处)。**M1 泄压阀**:含 JSX `<Modal` 的文件(Drawer/SideMenu/BottomPops/HandPlatePops 等)渲染在导航树外、不继承单点,其顶距命中只报数不判红(`--all` 逐条列出);注释里提 `<Modal` **不配豁免**。S2 的 `statusBarHeight` 只拦"布局取值"(`paddingTop/top/marginTop/= …`),共享层"调用方注入、默认 0"的 prop 声明不判红。扫描面为整包 `packages/app` + `apps/mobile-rn`(App.tsx 自身也在 S2/S3 射程内);全量经一次 `git grep` 预筛(627→87,38s→5s,模式串为判据字面量严格超集,筛不动退回全量)。取证 `--self-test` **40 例**(含 Modal 豁免、逐行豁免、单点被摘等 E2E 阳性对照)+ §22c 镜像测试 12 例(装车证明 + `--json` 可 parse + AGENTS/README 点名)。**两条写门过程中的教训值得留**:① 第一版"沉浸式豁免名单"是拿"出现 `position:'absolute'` + `top:0`"探的,8 个候选全是假阳(命中的是眼睛按钮和下拉框)—— 判据探不到就别硬编,默认值本身就是正确答案;② `readFileSync` 漏 import 被外层 `catch` 吞成"取不到内容",一个编码错误伪装成业务结论,故**工作树面不得套 try**。紧急跳过 `HUSKY_SKIP_STATUSBAR_SINGLE_SOURCE=1`。
- **暂存删除存续性对账**(99):`scripts/check-staged-deletions.mjs`(blocking,2026-09-24 立,Agent A 交付)—— 堵守门 65(整树删除拦截,只看规模 ≥1000/≥20%)与 `heal-worktree-tracked`(对暂存删除按设计"只报数、不代裁")之间的**空档**:删除规模不大、但仓库仍在引用它的暂存删除(`D `),任何人跑一次不带 pathspec 的普通 commit 就把已入库的功能与测试从版本树里删掉 —— 一次静默回滚,而 `git status`/diff/守门 65 全都看不见。判"误删"依据两条同时成立才判红:**E1 引用仍在**(索引 blob 里的相对 import / require / 动态 import / barrel `export *` / 别名路径含父目录名 / 整仓库路径字面量,覆盖代码+配置,`.md` 叙述与夹具/产物/记账 JSON/生成物不算)∧ **E2 无替代路径**(同名同后缀文件不在索引其他目录,有 = 已迁移放行)。只成立一条 → 不计红但**如实报数**(绝不静默成"看起来全绿");引用方(barrel)自己也一起删 = 正当删除形态,放行。护栏:名字 <4 字符 / 候选引用方 >2000 / 删除面 >400(那是 65 的地盘)一律 `undetermined` 只报数;豁免清单 `scripts/staged-deletions-allowlist.json`(坏 JSON 显式报错,不静默当空清单)。**不加 stagedTriggers —— 暂存删除可触及任意路径,任何提交都不得跳过**。口径:判索引 blob(与 70/77/83 同取向),全程只读。取证 `--self-test` 33 例(正反成对:barrel 仍 export 必红 / 引用方一起删必绿 / 歧义、注释、夹具、生成物各一反例)+ §22c 镜像测试 `scripts/tests/check-staged-deletions.test.mjs` 12 例(含"未注册时绿、注册后必须 blocking + skipEnv"的装车前置断言)。紧急跳过 `HUSKY_SKIP_STAGED_DELETIONS=1`。
- **合并新增文件存续性对账**(100):`scripts/check-merge-addition-loss.mjs`(blocking,2026-09-24 立)—— 堵的是**合并提交在文件面上的静默吞并**,与守门 71(只管 PROJECT_PLAN 登记行)、84(只管「暂存内容 == 祖先 blob」)都不重叠:2026-09-24 实测一枚写着「台账按 union 归并,双方每一行均存活」的合并 `9a0f7610e9`,把对侧**独有的 35 个新增路径整批抹掉**、连带 72 个文件回退成旧基线(相对共同祖先净 **−12014 行**),而它**不产生冲突、也不进 diff 报告** —— 「一侧新增、另一侧从未有过」的路径在「取某一侧整棵树」的合并里会直接消失。判据一条,且结构上排除了误伤正常合并:**A1** 路径 P ∈ 某父提交树 ∧ P ∉ 本次合并的共同基底(`git merge-base --all <parents>`)⇒ P 必须 ∈ 合并结果;「∉ 基底」正是把「对侧曾删除它」这一唯一正当解释排除掉。真要在合并里删除,必须**合并之后**单独 `git rm`(那时所有父都不含它,自动放过)。**口径是本门的生命线**:默认只判 `origin/main..HEAD` 里的合并 —— 已入库的历史事故若每轮重判,会让之后每一次提交恒红,唯一结局是人人绕过钩子、连带废掉全部守门;别人推来的合并由 `--all-new` 增量台账(`.workbuddy/merge-addition-loss-audited.json`)判到**一次**,`--limit N` 留给人工回看。取证 `--self-test` 9 例(真临时仓:正常合并必绿 / 整树回写必红并点名 / 未推必拦与已推必放用同一条判据 / 台账不重复判红)+ §22c 镜像测试 5 例。含两条**写门时自己踩到**的缺陷:① `rev-list --parents` 的第一个 token 是提交**自己**,`slice(2)` 会把合并误判成单父 ⇒ 整门恒绿;② 树缓存键必须先剥到 tree oid,按 `'HEAD'` 这种符号名缓存会在 ref 移动后读到旧树(自检第一轮就是把真事故判成了绿)。紧急跳过 `HUSKY_SKIP_MERGE_ADDITION_LOSS=1`。
- **本轮四道门的口径/落点收口(2026-09-24 立,一批内完成)**:① **守门 94 `check-error-code-coverage.mjs`** 此前**以 uncaught 异常 exit 1 冒充判据失败**——成因不在判据,而在它按磁盘读输入,而共享工作树的 `packages/i18n/messages/web/zh-CN.json` 副本滞后 HEAD(HEAD 有 `ai.pane.errorCatalog`)。现统一为 `makeFaceReader` 单一取材层(默认 **HEAD blob** / `--staged` 判索引 / `--worktree` 仅逃生舱,两个面旗同给判死),清单与内容同面同轮;取不到输入 ⇒ `UndeterminedError` ⇒ **exit 2 显式"无法判定"**,绝不冒烟成判据红也绝不记绿。② **守门 84 `check-stale-revert.mjs` 的 `MAX_FILES=300` 护栏原先一超限就整门跳过**,而共享工作区常年滞后数百文件 ⇒ 恰在最需要它的时候失明;现把**乘数级路径**(`guardian-runner.mjs` / 各道 `check-*.mjs` / `scripts/lib/` / `.husky/` 钩子 / 根 `package.json` / `pnpm-*.yaml` / `.github/workflows/` / 门的测试)从护栏里摘出来恒照判——它们被写回旧版不表现为"少一个功能",而是**一批守门静默失效**(当天 `guardian-runner.mjs` 落后 61 行、离一次 `git add` 只差一步)。③ **`union-converge` 的"对侧自己动过"原以文件为粒度**,两侧同改同一文件会整文件取对侧而丢本侧改动;现走 `git merge-file` 真三方,**冲突/二进制/非普通 mode 一律折进落地闸并点名文件**(不猜、不选边),另加第四条断言 `lostAddedLines` 保住两侧相对基底的新增行。④ **`git-guardian` 有了真实的到人出口**(见 §5e:它此前在"现役生产者清单"里挂着名字却零命中):判红经 `notify-deploy-failure.ts` 派发,按 alert 身份 + 内容指纹 4h 去重、未送达 30min 退避、**无每日总量封顶**,发信失败不改自愈与退出码。取证:门 94 镜像 6 例 / 门 84 镜像 4 例(含"顶过上限仍判红"与"真新编辑不判红"成对)/ union 自检 20 例 + 镜像 9 例 / 守护通知镜像 19 例;两门新入守门 80 的 HOT 清单(它们现在派生 git)。
- **合并吞并的修复出口 `scripts/union-converge.mjs`**(2026-09-24 立,守门 100 的另一半)—— 判据只说"红了",不修就等于把红留给下一个人(§12e 同型)。构造是**集合运算不是启发式**:合并树 = 本侧整棵树 ∪ 对侧「相对共同基底自己动过的路径」逐个取对侧版本 ∪ 活文档(PROJECT_PLAN / AGENTS / README)按「每行重数 = 本侧 + max(0, 对侧相对基底的新增)」union(2026-09-29 起:带「重复登记副本」指针的行只许从无到有带 1 份,少带的份数当着落地点名并写进合并提交信息);**对侧的删除不随合并传播**(确要删必须在合并之后显式 `git rm`,那才是 A1 认得的合法形态)。落地前自证 `丢本侧路径 = 0 ∧ 丢对侧路径 = 0 ∧ 三份文档未存活行 = 0`,落地后**用守门 100 本人的 A1 复核这枚合并**(用它的判据验它的产物);写盘一律临时索引 + commit-tree + CAS update-ref,从不 checkout、不碰共享工作区(§12d)。触发点两处:① `git-sync-converge` 的 merge-tree **冲突分支**先交它归并,收不住才退回"需人工"(人工最容易犯的错就是选边,而本次事故正是这么来的);② `git-guardian` 每轮的 `auditMergeAdditionLoss()` 调 `check-merge-addition-loss.mjs --all-new` 增量台账 —— 提交链上那道门按设计只判未推的合并,而 converge / 手工 commit-tree **不跑钩子**,别人造好推来的合并只能靠这一层看到一次(**只判不修**:自动重做合并风险远大于收益)。取证 `--self-test` 8 例 + §22c 镜像测试 4 例(含"选边式合并必须判失败"反向对照与两条挂点装车证明);手动:`node scripts/union-converge.mjs [--apply|--theirs <sha>|--self-test]`。

- **prod-bundle 影子副本对账**:`scripts/check-prod-bundle-shadow.mjs`(2026-09-24 立,挂在 `pnpm check:all` 与 `node scripts/check-prod-bundle-shadow.mjs`)—— §5e 说"落进 `deploy/prod-bundle/` 的运维脚本必须同时落一份入库源,否则等于写进盲区",但这句话过去**只有散文没有尺子**。本门登记「入库源 ↔ 生产实际执行的那份」逐字节必须等值,三条判据:S1 入库源必须真被 git 跟踪(不被跟踪就没有对账对象)、S2 运行副本必须真被忽略(否则登记表过期,同样判红)、S3 两侧 sha1 全等,不等即红并给出差异行数。任一侧取不到 ⇒ **exit 2 显式"无法判定"**,绝不记绿。首跑抓到并入库的是数据库备份那一对:`deploy/prod-bundle/pg-backup{,-scheduler}.ps1`(整目录被 `.gitignore` 忽略、全仓零入库源,而它每天 03:00 产出 94MB 生产库 dump)。本门**刻意没有改成转发壳**——把 runner 换成转发壳后重启服务,`pg_dump` 出现 3.5 分钟零输出挂死(旧 runner 同库同日 34 秒跑完),在解释清之前不动生产备份;因此两侧都是全文,靠本门钉住等值。自检 `--self-test`(5 例,含"入库源被摘线必红"与"缺一侧必计无法判定")。紧急跳过 `HUSKY_SKIP_PROD_BUNDLE_SHADOW=1`。
  - 同批登记两条实测事实,免得下一个人重新猜:(a) 备份脚本靠什么凭据认证**至今没答上** —— 脚本显式 `$dbPw = ""`、注释写"pg_hba 本地 trust 免密",而实测机器级与用户级都没有 `PGPASSWORD`、`systemprofile` 与 Administrator 下都没有 `pgpass.conf`、`postgres` 对错误口令回 `password authentication failed`、空口令被拒;任何**带控制台上下文**的 `pg_dump`/`psql` 会阻塞在口令提示(`stdin` 重定向也拦不住,libpq 走 ReadConsoleW)。兜底不是没有:`apps/api/src/services/alert-check-service.ts` 巡检两条备份链目录,判"备份缺失 / 0 字节"并走邮件。(b) 调度器 `Run-Backup` 用 `& script.ps1` 调子脚本,而 PowerShell 的 `&` **不因被调脚本 `exit 1` 抛异常** ⇒ `catch` 永不触发;实测一次真实失败(日志先打 `[ERROR] 备份失败`)之后仍打"备份完成"。修它要改生产 runner 并重启服务(该服务"启动即备份"),与凭据问题一并处理。
- **按凭据存在性豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **157**,blocking,`stagedTriggers=apps/api/src/plugins/` + `apps/api/src/routes/` + `apps/api/src/utils/`,紧急跳过 `HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS`,问责档 `pnpm check:credential-presence`):`scripts/check-credential-presence-bypass.mjs` 拦的是**"看见凭据头名在场就跳过安全判定"** —— `if (request.headers['x-anything']) return` 把防线换成了客户端可以自填的一个字符串。它没有编译期症状,typecheck / lint / 其余门全绿,只有"别人机器上少了一次 401"才看得见(与 §5「认证不等于授权」同族,那一族在 TS 侧此前只有 Python 那把尺子 117)。**判据一条**:红 = 安全面内 `if (条件) return` 的条件**整体就是**一个 `request.headers[...]` / `request.cookies.…` 引用(既无比较也无验真调用)**且** 该 if 之后同一函数体内仍有执行动作(throw / 4xx / verify)。**两条放过通道各由自检钉住**:① 条件带 `===` / `!==` / `secretsEqual` / `timingSafeEqual` / `isVerified…(` / `verify…(` —— 判据必须认下 #23 那轮真实修法产出的形态,否则按规矩写就红,唯一结局是逼人 `--no-verify` 连带全部守门作废(§12e);② if 之后**没有** enforcement 的是控制流短路(实测 `mapGeminiAuth` 正是这一形),把它判红就是假阳 —— **假阳比漏报更贵**,它指使人去修没坏的东西,还会把口径说歪成"问题很多"。**出路只有两条**:行内 `credential-presence-exempt: <原因>`(须带原因、只救本行或紧邻上行,**已同笔登记进守门 108 的 30 天存活期档**,跨文件锁在镜像 T10 —— 不登记就会被邻居 E4 判成"新引入的未登记豁免族",即这道门自己的合法出口被钉红),或台账 `scripts/data/credential-presence-exemptions.json` 的 `file + anchor + reason + reviewBy` 四字段(**锚点用内容不用行号**,§1 第 3 条);缺字段 / 复核日过期 / 登记了而被审面没命中(清单腐烂)三种都判红 —— 登记表必然腐烂,所以腐烂必须是红而不是安静。立项现读:候选 2 处全在 `plugins/csrf.ts`,其中 `request.cookies.auth_token` 是 #23 那轮**从没任何人登记过**的豁免、由本门第一次点出来(靠 sameSite=lax 三处实测 + 路由侧仍验 JWT),另 3 处括号配平 / 取不出名字如实报未判定。**口径同 70/77/83/98/101/103/118**:全量判 HEAD blob、`--staged` 判索引 blob 且**收窄到本次暂存集**(刻意不学守门 150 判整张索引面 —— 那会让任何触及射程的提交拿到一道自己清不掉的红,已登记 G-458)、`--worktree` 仅人工且仍走 `readWorktreeFile`(不在门里留磁盘读)、两面旗同给 exit 2、取不到不回落、全量枚举到 0 判死、`--strict` 下有未判定即 exit 2 拒绝出合格证;遮罩只引 `scripts/lib/code-mask.mjs` 那一份实现且判据面**只遮注释、保留字符串**(头名住在字符串字面量里,连字符串一起抹会让门对自己立项那一型失明 —— 第一版就错在这里,由"真仓必须看得见存量"那条自检抓出;同批还修了 `readCondition` 把外层括号留在条件文本里、导致所有命中被自己放行的失明)。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像 T1/T2 的**装车证明读 HEAD 面的 runner 而非磁盘** —— 磁盘那份常年滞后,会把刚经对象空间落地的注册判成"未装";另有摘线方向锁、台账缺席端到端必红并点名站点、取材面形状锁、暂存档收窄锁、遮罩单份锁、豁免族存活期锁)。

### 守门手动触发 / 紧急跳过抽查

- **手动触发全量守门**:`node scripts/guardian-runner.mjs --staged`(pre-commit 模式,传给所有脚本);不带 `--staged` 为全量扫描。
- **执行语义(2026-09-22 由 fail-fast 改为跑完再汇总)**:任一 blocking 门失败不再中断本轮 —— 全部 96 项跑完后末尾列「失败门清单 + 单独复现命令」再 exit(1)。原 fail-fast 会遮蔽其后所有门(实测同一工作区真实存在 5 道红,旧行为只显示 1 道,且让人误判"新注册的门没生效")。**两条不变量**:① 子门 `exit 75`(中断)仍**立即**以 75 向上传播,不收敛成 1(§5b ⑦ 的 push guard 重试链依赖它);② 逃生舱 `GUARDIAN_STOP_ON_FIRST=1` 完整恢复旧的"首个失败立即 exit(1)"。绿的路径耗时不变(原本就要跑完),仅失败轮次变长。
- **commit 污染防护(scope 一致性)**:`.husky/commit-msg` 的 `check-commit-scope-consistency.mjs`(AGENTS.md §16 配套);紧急跳过 `HUSKY_SKIP_SCOPE_CHECK=1 git commit ...`。
- **commit 丢失防护**:guardian 第 30a 项 `check-commit-loss-guard.mjs`(AGENTS.md §22 配套);紧急跳过 `HUSKY_SKIP_COMMIT_LOSS_CHECK=1 git commit ...`。

> post-commit 钩子:`git-push-guard.mjs` 自动 push + 验证 local == remote(见 §20)。

---

## 26. C 盘防护强制规则(强制)

### 触发背景(2026-07-27 立,真实事故)

C 盘 120 GB 频繁告急,根因排查发现:

- **第三方 AI IDE 自身缓存 12.88 GB**(国内版 CN 7.68 + 旧版 3.46 + 国内旧版 1.74)
- **Chrome OptGuideOnDeviceModel 4 GB**(Chrome 内置 AI 模型,用户不用)
- **Local\Temp 累积 1.6 GB**(第三方 AI IDE 旧版安装包 + pip 安装临时)
- **项目历史违规写入 `C:\temp\ihui-*` 0.33 GB**

已通过环境变量迁移 + 符号链接 + 自动维护计划任务根治。

### 开发工具缓存路径强制规则(强制)

**所有开发工具的全局缓存/存储/临时目录必须指向 D 盘**(2026-09-23 全量实测校正 —— 下表旧版写的
`D:\caches\*` 是**死路径**,本机不存在 `D:\caches`,且 `CARGO_HOME`/`RUSTUP_HOME`/`OLLAMA_MODELS`
当时**根本没设**,即"文档说已迁、实际还在 C 盘";真实外置根是 `D:\DevEnv\`)。

| 工具                | 环境变量 / 配置                             | 实测路径                                                                            |
| ------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------- |
| Temp/TMP            | `TEMP` / `TMP` / `TMPDIR`                   | `D:\DevEnv\Temp`(2026-09-23 才真正写入 HKCU)                                        |
| pnpm                | `PNPM_HOME` + `pnpm store path`             | `D:\DevEnv\tools\pnpm`,store=`...\pnpm\store\v11`                                   |
| npm                 | `npm config`                                | `D:\DevEnv\cache\npm`                                                               |
| pip                 | `PIP_CACHE_DIR`                             | `D:\DevEnv\cache\pip`                                                               |
| uv                  | `UV_CACHE_DIR`                              | `D:\DevEnv\cache\uv`                                                                |
| Cargo               | junction(`%USERPROFILE%\.cargo`)            | `D:\DevEnv\cache\userhome\.cargo`                                                   |
| Rustup              | junction(`%USERPROFILE%\.rustup`)           | `D:\DevEnv\cache\userhome\.rustup`                                                  |
| Maven/.m2           | junction(`%USERPROFILE%\.m2`)               | `D:\DevEnv\cache\userhome\.m2`                                                      |
| Codex/.cache/.codex | junction                                    | `D:\DevEnv\cache\userhome\{.cache,.codex,.codex-session-delete}`                    |
| Trae 全家           | junction(`.trae`/`.trae-cn`/`.trae-aicc`)   | `D:\DevEnv\cache\userhome\`                                                         |
| DeepSeek CLI        | junction(`%USERPROFILE%\.deepseek`)         | `D:\DevEnv\cache\userhome\.deepseek`                                                |
| Ollama(含服务)      | junction(`%USERPROFILE%\.ollama`)           | `D:\DevEnv\cache\ollama`(服务 `OLLAMA_MODELS` 路径经 junction 解析)                 |
| IHUI CLI 状态       | junction(`%USERPROFILE%\.ihui`)             | `D:\DevEnv\cache\userhome\.ihui`                                                    |
| 桌面端 Local 态     | junction(`%LOCALAPPDATA%\com.ihui.desktop`) | `D:\DevEnv\cache\userhome\appdata-local-com.ihui.desktop`(515 文件/39.25MB)         |
| 桌面端 Roaming 态   | junction(`%APPDATA%\com.ihui.desktop`)      | `D:\DevEnv\cache\userhome\appdata-roaming-com.ihui.desktop`(auth/tray/window-state) |
| **服务身份 TEMP**   | **没有 env 可改**(HKCU 迁移对它无效)        | 必须显式选目录 + 用完删除,详见下方"junction 管不了身份"                             |
| Go                  | `GOPATH` / `GOMODCACHE` / `GOCACHE`         | `D:\DevEnv\cache\go{,\pkg\mod}` / `...\go-build`                                    |
| Playwright          | `PLAYWRIGHT_BROWSERS_PATH`                  | `D:\DevEnv\cache\playwright`                                                        |

| 工具                | 环境变量 / 配置                           | 实测路径                                                            |
| ------------------- | ----------------------------------------- | ------------------------------------------------------------------- |
| Temp/TMP            | `TEMP` / `TMP` / `TMPDIR`                 | `D:\DevEnv\Temp`(2026-09-23 才真正写入 HKCU)                        |
| pnpm                | `PNPM_HOME` + `pnpm store path`           | `D:\DevEnv\tools\pnpm`,store=`...\pnpm\store\v11`                   |
| npm                 | `npm config`                              | `D:\DevEnv\cache\npm`                                               |
| pip                 | `PIP_CACHE_DIR`                           | `D:\DevEnv\cache\pip`                                               |
| uv                  | `UV_CACHE_DIR`                            | `D:\DevEnv\cache\uv`                                                |
| Cargo               | junction(`%USERPROFILE%\.cargo`)          | `D:\DevEnv\cache\userhome\.cargo`                                   |
| Rustup              | junction(`%USERPROFILE%\.rustup`)         | `D:\DevEnv\cache\userhome\.rustup`                                  |
| Maven/.m2           | junction(`%USERPROFILE%\.m2`)             | `D:\DevEnv\cache\userhome\.m2`                                      |
| Codex/.cache/.codex | junction                                  | `D:\DevEnv\cache\userhome\{.cache,.codex,.codex-session-delete}`    |
| Trae 全家           | junction(`.trae`/`.trae-cn`/`.trae-aicc`) | `D:\DevEnv\cache\userhome\`                                         |
| DeepSeek CLI        | junction(`%USERPROFILE%\.deepseek`)       | `D:\DevEnv\cache\userhome\.deepseek`                                |
| Ollama(含服务)      | junction(`%USERPROFILE%\.ollama`)         | `D:\DevEnv\cache\ollama`(服务 `OLLAMA_MODELS` 路径经 junction 解析) |
| IHUI CLI 状态       | junction(`%USERPROFILE%\.ihui`)           | `D:\DevEnv\cache\userhome\.ihui`                                    |
| Go                  | `GOPATH` / `GOMODCACHE` / `GOCACHE`       | `D:\DevEnv\cache\go{,\pkg\mod}` / `...\go-build`                    |
| Playwright          | `PLAYWRIGHT_BROWSERS_PATH`                | `D:\DevEnv\cache\playwright`                                        |

**改道机制定为 junction,不 env 优先**:家目录工具态一律 `robocopy <src> <dst> /E /MOVE` →
`mklink /J <旧路径> <新路径>`。理由:工具态常有**硬编码**读取方(如 `~/.cargo\bin` 在
`HKCU\Environment\Path` 首位、守门脚本按 `%USERPROFILE%` 拼路径),junction 让旧路径继续可解析,
因此**不需要也不允许**去改 `Path` 或逐个改码;改环境变量反而会造出"双根分裂"。
校验方法:`rustup show home`、`reg query HKCU\Environment`、`(Get-Item ~\.cargo).Attributes -match ReparsePoint`。
新增工具的缓存落点一律走 `D:\DevEnv\cache\userhome\<名称>` + junction,不得再在家目录留实体目录。
**盘根"写歪项"一律封口改道,不许只删(2026-09-24 立)**:第三方程序(剪映、微信输入法、MSYS 侧工具、
各类安装器)用**相对路径**写自己的状态,而进程工作目录恰好是 `C:\` ⇒ `common_attachment`、
`persistent_data`、`tmp`、`tools` 直接长在盘根。**这类残骸删掉必然复发,因为成因改不了(闭源)**。
唯一正解是把那个名字换成 junction 指向 §15b 落点 —— 程序按原路径读写不变,内容落到 D 盘。
清单与执行的唯一入口是 `scripts/seal-c-root-stray.mjs`(`--check` 零副作用 / `--apply` 幂等,
**换机或重装后需重跑一次**),守门与 `c-drive-auto-maintain.ps1` 第 4 段都 import 同一份清单,
**禁止在别处再抄一份名字**。发现某新残骸也属这一型:加进 `SEALED_DIRS`(必须带 `owner` + `evidence`
两项取证)而不是写进删除名单。
**封口必须同时挂进 `git-guardian` 的自愈轮(2026-09-24 实测)**:`--apply` 做的 junction **会被重启清掉**
(当天 4 个里死 3 个,D 侧内容完好)。只靠每日 03:00 体检 = 最长 23 小时空窗,够第三方自建真目录回到
复发原点。正解是第三层 `healRootSeal()` 挂在 `git-guardian` 的 `!CHECK_ONLY` 分支(与
`healWorktreeTracked` 同位,2 分钟一轮),`seal --check` 判红才 `--apply`;挂点写成 CHECK_ONLY
路径等于永不执行(工作区自愈层踩过同一坑),三条装车证明由 `scripts/tests/seal-c-root-stray.test.mjs` 钉死。
**⚠️ junction 的头号危险是"被递归穿透"(同日实测)**:PowerShell 7 的 `Get-ChildItem -Recurse`
**会穿过 junction** 枚举到目标里的文件,于是"按名字删 `C:\tmp\ihui-*`"会顺着链接清空 D 盘真实目标,
把改道机制变成自毁机制。三条要求:① 任何递归删除/枚举前必须判 `ReparsePoint`,重解析点只能
`[System.IO.Directory]::Delete($path, $false)` 断链;② 量体积的工具遇 junction **不得跟随**
(否则把 D 盘的量报成 C 盘的债);③ Node 侧 `lstatSync(p).isSymbolicLink()` 对 junction 报 `true`,
`rmSync(link)` 只断链不穿透(均已实测)。判据由 `seal-c-root-stray` 与 C 盘污染守门的镜像测试钉死。
**要藏住 junction 的名字,只能用 PowerShell 提供器 + 父目录枚举复核(2026-09-24 实测)**:用户选择
"设隐藏,保留改道"后,`attrib +h <junction>` 是**陷阱** —— 它把 Hidden 设到**目标**那一侧,链接本体
纹丝不动,而 `attrib` 回显时又顺着链接读目标,于是打印出 `H` 让调用者以为成功了(本仓第一版就这样
"隐藏了 4 次",C 盘那 4 个名字照旧可见,反倒把 D 盘的 4 个数据目录藏掉了)。正确做法:
`(Get-Item -LiteralPath <链接> -Force).Attributes = $i.Attributes -bor [System.IO.FileAttributes]::Hidden`,
并且**唯一可信的 oracle 是父目录枚举** `Get-ChildItem <父目录> -Force`(那正是 Explorer 读的那份目录项
属性)—— 设完必须自己回读,不许把"没抛错"当成成功。隐藏只影响浏览,穿透读写与
`isSymbolicLink()` 判定均不受影响(已实测)。策略固化在 `seal-c-root-stray.mjs` 的 `setLinkHidden`,
每次 `--apply` 都确保在位(封口被重建也不会露回来),镜像测试断言源码里**不得再出现 `attrib`**。
**改页面文件必须留"待重启生效"哨兵(2026-09-24 立)**:本机 C/D 两个 pagefile 都是**手设固定值**
(C 32768MB / D 98304MB)而非系统管理。要缩 C 的占用,改的是
`HKLM\...\Session Manager\Memory Management\PagingFiles`(用 `Set-CimInstance Win32_PageFileSetting`
写入,回读该注册表值才算落盘)—— 但**内存管理器运行期锁住 pagefile.sys,磁盘上的旧大小只有重启才收缩**,
而本机是生产机(20+ 个 IHUI-* 服务在跑),重启时机归用户。所以任何这类改动都必须同时留一条
会自我清空的哨兵:比对「配置上限 vs WMI `Win32_PageFileUsage.AllocatedBaseSize`」,落差 >512MB 且 >25%
就报「待重启生效」,缩到位后不再报。**量这个大小有三连坑,都不报错、只给假绿**:`fs.statSync` 对
`pagefile.sys` 必报 `EINVAL`(打不开句柄);`cmd /c for %A in (...) do %~zA` 会被 Node 的加引号 +
cmd 剥首尾引号的双层规则打掉;属性名写成 MSDN 文档的 `AllocBaseSize`(本机真名是
**`AllocatedBaseSize`**)会被 PowerShell 静默渲染成空串。三条已由守门 `--self-test` 与镜像测试钉死,
且"一条都没量到"必须打印**未判定**、绝不记为通过。
**⚠️ junction 只管"路径",管不了"身份"(2026-09-24 实测的第四类真因)**:同一个 `$env:TEMP` /
`os.tmpdir()` 在**不同身份下指向不同目录** —— HKCU 把交互账户 TEMP 迁到 `D:\DevEnv\Temp` 之后,
nssm 服务(IHUI-API / IHUI-DEPLOYLOOP 以 LocalSystem 运行)拿到的仍是 `C:\Windows\Temp`。
**任何"改成 `$env:TEMP`"式修复对它只是往 C 盘内部挪坑。**所以三条硬要求:
① 服务/守护类脚本若要落临时物,必须**显式指定目录**,不能信任 `$env:TEMP`;
② **用完必须删** —— 实测反例:`deploy/win/ihui-deploy.ps1` 把构建 stdout/stderr 重定向到
`$env:TEMP` 的 `ihui-next-build-<PID>-try<N>-{out,err}.log`,Tail 进 deploy-loop.log 之后**从不删除**
⇒ 部署环每 30 分钟漏 2 个,`C:\Windows\Temp` 攒到 **526 项 / 6.86MB**(同文件里的
`ihui-align-$PID.log` 反而有删,所以泄漏面精确到构建这一处;2026-09-24 已改为用完即删,
并现场观察到一轮构建结束后该 2 个文件自动消失);
③ **守门必须扫"服务身份的 TEMP"** —— 守门 `check-c-drive-pollution.mjs`(编号同日漂移过
同日三次撞号)此前只扫 `tmpdir()`,即"看门人自己的 TEMP",于是它一路报"本项目产物 0 项"
而真凶全在 `C:\Windows\Temp`。现 `tempScanDirs()` 显式并入 `SystemRoot\Temp` 并由 `--self-test`
钉死(扫描面缩回去即红)。**同批修掉这条门自身的两处假绿灯**:同一目录因大小写被计两次
(`C:\windows\Temp` 与 `C:\Windows\Temp` ⇒ 实测 1056 项 vs 真实 526,按小写键去重)、文件一律
记 `sizeMB: 0`(只有目录量体积 ⇒ 526 个小文件把 6.86MB 报成"合计约 0 MB")。
它的修复提示原写 `pnpm c-drive:clean-ours`,而根 package.json **从来没有这个脚本**
(实测取该键得 undefined)⇒ 门给出的是跑不通的出路,已改为真实入口并要求先 `-DryRun`。
**该方法唯一的真实危险**:`robocopy` 非零返回码(如 `.codex` 持锁 rc=9)会让内容已搬走但**不建 junction**,
路径直接消失 —— 必须回读"旧路径是否存在 + 新路径文件数/字节"再补 junction(本次即手工补建后凭据零丢失)。
**校验必须逐文件比对相对路径 + 字节数**(`robocopy /E` 不带 `/MOVE` 先做镜像副本),通过后才删源。

**禁止 agent 在代码或脚本中硬编码 C 盘路径**作为写入目标:

- ❌ `C:\temp\*` / `C:\Users\荣耀\AppData\Local\Temp\*`(用 `os.tmpdir()` / `$env:TEMP` 替代,会自动走 D 盘)
- ❌ `C:\Users\荣耀\AppData\Local\*\cache`(用工具自带配置或环境变量)
- ❌ `C:\Users\荣耀\AppData\Roaming\AICodingIDE*\*`(第三方 AI IDE 自身管理,agent 不触碰)

**唯一例外**:系统日志(`debug.log` / `next-server.log`)可走 `$env:TEMP`(已指向 D 盘)。

### 自动维护计划任务

| `IHUI C-Drive AutoMaintain` | 每天 3am | `wscript.exe scripts/c-drive-maintain-hidden.vbs` → `pwsh -File c-drive-auto-maintain.ps1` | **六段**:①Chrome 缓存 ②Temp 旧目录 ③本项目 C 盘产物 ④盘根封口体检 ⑤系统自产残骸(卡死打印队列/内核转储/孤儿浏览器构建) ⑥回潮源封禁(Chrome 政策,写前先验政策名真在 chrome.dll 里) |

| 任务名                      | 触发     | 实际执行体                                                                                 | 功能                                  |
| --------------------------- | -------- | ------------------------------------------------------------------------------------------ | ------------------------------------- |
| `IHUI C-Drive AutoMaintain` | 每天 3am | `wscript.exe scripts/c-drive-maintain-hidden.vbs` → `pwsh -File c-drive-auto-maintain.ps1` | 清理 Chrome/Temp 缓存 + 报告 C 盘状态 |

**⚠️ 注册名含空格,不是连字符**(`schtasks /query /fo CSV` 实读回 = `\IHUI C-Drive AutoMaintain`)。按文档里旧写的 `IHUI-C-Drive-AutoMaintain` 去查,`schtasks` 回「系统找不到指定的文件」—— 2026-09-23 我就是因此**误判成"任务从未注册"**,而它当时确实还没注册(23:55 才注册),但**名字陷阱会让人在任何一天都得出同样的错结论**。Git Bash 下查还要加 `MSYS_NO_PATHCONV=1`,否则 `/query` 被改写成路径。判存在与否只认 `schtasks /query /fo CSV | grep -i c-drive` 这种全量列名法,不要拿文档里的名字去点名查。

**2026-09-23 曾实测:该任务在本机并不存在**(当时代号未注册,`D:\DevEnv\logs\c-drive-maintain.log` 也从未生成)。当时的表是**设计意图**而非现状,曾据此以为"每天在清"⇒ 实际零执行,这是 C 盘能攒下 13.2GB `.next` 构建备份的直接原因之一。

**同日 23:59 经用户授权后已真正注册**(状态=现状,不再是设计意图):

> **该"现状"于 2026-09-24 再次失真,已三路取证终判为「当前不存在」**(本节下方表格里的"每天 3am"因此也是设计意图而非实况):
> ① `schtasks /query /fo CSV | grep -i c-drive` **零命中**(这正是本节规定的权威查法);
> ② `Get-ScheduledTask | Where TaskName -match 'C-Drive|Maintain'` 返回**空**;
> ③ 递归枚举 `C:\Windows\System32\Tasks\*.XML`,**没有**任何 C-Drive/Maintain 定义文件 —— 而同目录其余 **14 个 `IHUI*` 任务全部在位可列**
> ⇒ 排除"查法失效"这一假阴性解释,任务确实不在了。
> `D:\DevEnv\logs\c-drive-maintain.log` 今天(09-24 10:59)那条记录是**人工 `-DryRun` 预演**,不是 03:00 自动执行(全文 `[DRY]` 无 `[DEL]`,合计释放 0 MB),
> 所以"每天在清"在今天并没有发生。**注册动作 = 影响全机的每日自动删除,仍须用户授权,agent 不得自行 `schtasks /create` 恢复**;
> 上一条"回读 `schtasks /Query /XML` 实证 `LogonType=S4U`"当时为真,但那份定义现已不在 —— 名字陷阱的解释**不成立**(权威全量列表法连空格名一起扫,零命中)。
>
> **同日 17:1x 已按用户授权重新注册,并留配对证明(本条从"设计意图"重新变回现状)**:
> ① **注册前实跑预检**(§26 硬约束):在 `scripts/` 放一份与 `c-drive-maintain-hidden.vbs` 同体、仅把参数换成
> `-DryRun` 的副本,用 `cscript //nologo` 真跑一遍 —— `cscript` 退出 0、日志新增 76 行、
> 首行 `DRY RUN(全脚本不删任何东西)`、**`[DEL]` 计数 = 0**(证明拉起链通且零删除),预检副本当场删除;
> ② 注册命令 `schtasks /create /tn "IHUI C-Drive AutoMaintain" /tr "wscript.exe \"<工作树实测绝对路径>\c-drive-maintain-hidden.vbs\"" /sc daily /st 03:00 /f`,
> 再 `cscript //nologo scripts/task-set-s4u.vbs "IHUI C-Drive AutoMaintain"` 升 S4U(`/RU <user> /NP` 会**弹密码提示**、非交互跑不通,
> 而本机 pwsh 无 ScheduledTasks 模块 ⇒ 只有 Schedule.Service COM 这条活路,该工具头注已记);
> ③ **XML 回读独立取证**:`<LogonType>S4U</LogonType>`、`<StartBoundary>…T03:00:00</StartBoundary>`、
> `<Command>wscript.exe</Command>` ⇒ 三项齐备才算注册落地(不得把 `schtasks` 打印的"成功"当成功);
> ④ 权威存在法同一时刻由 0 命中翻成 **1 命中**(`\IHUI C-Drive AutoMaintain`)。
> **盘符按当次实测取**:本机工作树是 `G:\IHUI-AI`,注册的 `/tr` 就指 `G:\…`,不得照抄本节正文里的 `D:/…`(§5b/§15b 已各记过一次盘符失真)。
>
> **同批根掉了一句会再骗一次的东西**:守门 92 `check-c-drive-pollution.mjs` 原先在结论后**无条件打印**
> "计划任务 IHUI C-Drive AutoMaintain 每天 03:00 已注册(S4U,wscript 包装)",而脚本内**一次 `schtasks` 都没调过**
> —— 一道只读污染门正在替一个当时并不存在的防护背书。现改为**实测三态判据** `registered / unregistered / undetermined`
> (查法仍是全量 `/FO CSV /NH` + 名字不区分大小写匹配,**禁止**点名查),非 win32 / ENOENT / 超时 / 空输出 /
> 输出形态解不出任务路径一律 `未判定` 并写原因,**绝不计为通过**;本门仍只读、未注册不改退出码(warn-only 语义由断言钉死)。
> 配对证明:注册前该判据实测 `unregistered`(346 项零命中),注册后 `registered`(347 项命中 1 条),两次退出码均 0。

- **动作链按本节下方「计划任务禁止直接执行控制台程序」硬约束走**:`wscript.exe` → 纯 ASCII 的 `scripts/c-drive-maintain-hidden.vbs` → `pwsh -NoProfile -ExecutionPolicy Bypass -File …ps1`。注册前用 `cscript //nologo` 实跑过一份**只带 `-DryRun` 的同体副本**做语法+拉起链证明(实测写出 `[WARN] … DRY RUN(全脚本不删任何东西)`),因此注册过程零删除。
- **登录类型已升 S4U**(与凭据巡检同一套 `scripts/task-set-s4u.vbs`),否则 3am 无人登录时不会跑。回读 `schtasks /Query /XML` 实证:`LogonType=S4U`、`Command=wscript.exe`、`StartBoundary=03:00`、下次运行 `2026-09-24 03:00`。
- **它的删除面是"按名字"的,不是"按目录整片"**(2026-09-24 把行为收拢到与这句话一致,之前并非如此):盘根只认 `IHUI-*` / `.empty-tmp*` / `.pnpm-store`;`C:\tmp` 与 `C:\temp` 内只删 `ihui-*` / `IHUI-*` / `next-backup-*` / `probe-*` / `wb-ext-debug.log`;`%LOCALAPPDATA%\Temp` **默认也只删这四类名字**,原来是对"所有 mtime>3 天的目录"整片删(09-23 一次误跑就是因此清掉约 29.8MB 无主旧目录)—— 宽口径整片清扫现在必须显式设 `IHUI_TEMP_WIDE_SWEEP=1` 才恢复,未开启时把"被跳过多少个、合计多少 MB"如实打印,不静默。唯一剩下的非按名字删除面是 `C:\Windows\Temp` 里 mtime>3 天的**文件**(系统临时文件,保留原语义),日志里以 `[NOTE]` + 逐条留痕明示。
- **Chrome 缓存那 6 条路径是死路径,但故意不"修好"**:它们硬编码 `C:\Users\荣耀\AppData\Local\...`,本机用户是 `Administrator` ⇒ 天天空转。改成指向真实用户配置等于**突然开始删一个在用浏览器的缓存**,风险远大于收益;现在改为检测到路径不存在就显式 `[WARN]`(6/6 不存在 + 当前用户名),不再静默。要清这台机器的 Chrome 缓存,请人工确认后再动。
- **全脚本的删除出口只有 `ForceDelete` 一个**,且每次过 `Test-Protected`(`secrets` / `密钥` / `credentials` / `backups` / `BaiduSyncdisk` 等整目录不碰);`-DryRun` 拦在该出口上,预演模式**一条 `[DEL]` 都不会出现**(实测 179 行日志 `[DEL]` 计数 = 0)。
- **TEMP 漂移(2026-09-24 已根治写入侧,残余只是"活进程不刷新")**:HKCU `TEMP` 已指 `D:\DevEnv\Temp`,但活着的宿主/终端进程仍持 `C:\Users\Administrator\AppData\Local\Temp`(实测 `node -p os.tmpdir()` 即旧值),新开终端/重启宿主后自愈。**更严重的那一半是"服务身份根本不读 HKCU"** —— LocalSystem 的 TEMP 是 `C:\Windows\Temp`,已按上文③把守门扫描面补齐,并把 `deploy/win/ihui-deploy.ps1` 的构建重定向日志改为**用完即删**(实测存量 526 项 / 6.86MB 清完、下一轮构建结束后 2 个文件自行消失)。判据与污染可见性由守门 `check-c-drive-pollution.mjs`(只读、永不删;编号见 runner)承担。
- **家目录实体目录再少两处**(2026-09-24):桌面端 `%LOCALAPPDATA%\com.ihui.desktop`(515 文件/39.25MB)与 `%APPDATA%\com.ihui.desktop`(auth/tray/window-state)已按本节 junction 机制改道到 `D:\DevEnv\cache\userhome\appdata-{local,roaming}-com.ihui.desktop`;流程是"镜像复制 ⇒ 逐文件(相对路径+字节)校验 ⇒ 源改名为 `.pre-junction-<ts>` ⇒ `mklink /J` ⇒ 经 junction 回读数量一致 ⇒ 才删源",任一步不符即改名回退。改道前已确认桌面端不在运行(该目录最后写入 09-06,机上 13 个 `msedgewebview2.exe` 全属他应用)。同批删除 `AppData\Local\智汇AI` 与 `AppData\Local\ihui-node-hooks` 两个**空**孤儿目录(后者说明机器级 windowsHide 钩子当前未装:`HKCU\Environment\NODE_OPTIONS` 实测未设)。
- **仍未闭环的一条**:TEMP 漂移 —— HKCU `TEMP` 已指 `D:\DevEnv\Temp`,但活着的宿主/终端进程仍持 `C:\Users\Administrator\AppData\Local\Temp`(实测 `node -p os.tmpdir()` 即旧值),所以走 `os.tmpdir()` 的脚本会继续落 C 盘;新开终端/重启宿主后自愈。判据与污染可见性由守门 **93** `check-c-drive-pollution.mjs`(只读、永不删)承担。

**同日修 `c-drive-auto-maintain.ps1` 的三处失效**(全部实测取证):

1. 原"清理本项目产物"段扫的是 `C:\temp\ihui-*`,而我们实际写到 `C:\tmp\*`(构建备份)、`%LOCALAPPDATA%\Temp\ihui-*`(测试夹具)、以及盘根本身 —— **扫错目录 = 每天跑也零效果**。现覆盖这三处 + 盘根 `IHUI-*`/`.empty-tmp*`/`.pnpm-store`。
2. `ForceDelete` 只有 `DeleteDirectory` 分支,对**文件**路径必然抛后被 catch 吞掉 ⇒ 静默"清理成功但什么都没删"(盘根 `IHUI-probe-*.ps1` 就属于这一类)。现按 Leaf/Directory 分流。
3. 新增 `-DryRun`,**拦在 `ForceDelete` 这个唯一删除出口上**而非某一段里。教训实测:最初只把 DryRun 写在第三段,预演模式下第一段(Chrome 缓存)和第二段(Temp >3 天目录)**照样被真删了**(DeletePermanently 不进回收站);当时释放约 29.8MB,均为 `Temp` 下 mtime>3 天的目录,项目文件/备份/凭据全在 D 盘未受影响。现每段逐条留痕(`[DEL]`/`[DRY]`),不再静默删除。

**手动触发**:`pwsh -File scripts/c-drive-auto-maintain.ps1`
**查看日志**:`D:\DevEnv\logs\c-drive-maintain.log`(旧文档写 `D:\caches\...`,该目录本机不存在,日志从未写出)
**查看任务状态**:`Get-ScheduledTask -TaskName "IHUI-*"` / `schtasks /Query /TN "IHUI-C-Drive-AutoMaintain"`

### 2026-09-23 已办与剩余阻碍

**已办**:① **10.3GB 游离备份收口进 `D:\DevEnv\backups\`**(根目录散落的 `IHUI-AI-backup-*.tar` 8.3G、
`IHUI-AI-backup-20260911.git` 727M、`IHUI-AI_workbak_20260910` 537M、两个根 `.sql`、`IHUI AI Desktop` 94M
等,同卷 `mv` + 每步回读"源已无 + 目标体积一致");② **C 盘 20G → 31G 可用**;③ **12 项家目录改道**
(`.rustup .cargo .m2 .cache .codex .trae .trae-cn .trae-aicc .deepseek .codex-session-delete .ihui` →
`D:\DevEnv\cache\userhome\`,`.ollama` → `D:\DevEnv\cache\ollama`);④ **Ollama 2.9GB** 先镜像复制 +
逐文件(路径+字节)校验 + sha256 寻址核对,13/13 一致后才 `nssm stop` → 删 C 原件 → `mklink /J` →
`nssm start`;验收=服务 RUNNING **且真跑一次 `qwen2.5-coder:1.5b` 生成成功**(1.53s,日志无 error);
顺带修掉该服务把 `TEMP`/`TMP` 钉死在 `C:\Users\...\AppData\Local\Temp` 的配置(nssm LocalSystem
自带环境块,HKCU 的 TEMP 迁移对它无效,它一直在往 C 写)。原环境块备份在
`D:\DevEnv\backups\env\ollama-service-env-original.txt`。

**剩余客观阻碍(不是遗漏)**:`~/.workbuddy` 3.1GB 内含 `binaries\PortableGit`
(被 `scripts/lib/gitdir.mjs` 的 `resolvePortableGitCandidates()` 当 git 二进制首选解析)不得搬,其 `logs`/`traces` 约 2GB 属该 IDE
自管;`.qoder-cn`/`.qoder` 是本会话宿主状态,改道即丢记忆。`scripts/kill-git-selector-hidden.vbs`
已改为随自身目录定位目标脚本,但其包装的 `kill-git-selector.ps1` **在仓库里并不存在**(死代码);
`scripts/release-desktop-local.mjs` 需要 `%USERPROFILE%\.tauri\ihui-updater.key`,而**旧句"本机无 `.tauri` ⇒ 桌面发布在此机必 `exit 1`"已于 2026-10-01 实测作废**:`.tauri\ihui-updater.key` 348 B、`ihui-updater-password.txt` 33 B **都在位**,mtime 2026-09-07(早于该句登记日 09-23 三周)⇒ 登记当时就不成立;那句还写着 `:67,74` 的行号指针,而 §1 明令证据指针禁写行号(任何一次 append 后都会挪位)。桌面端能否发布在本机的真判据是"跑一次取签名密钥的那条路径并看它报什么",不是这条散文
→ 桌面端发布在此机必 `exit 1`(需发布机或补生成密钥)。

### 守门(已实现,guardian-runner 第 45 项)

- `scripts/check-c-drive-paths.mjs`(guardian-runner 第 45 项,warn-only,2026-08-13 立):扫描 staged 文件中硬编码的 C 盘写入路径(`C:\temp\` / `C:\Users\*\AppData\Local\Temp\` 等,排除 `os.tmpdir()` / `$env:TEMP` / 注释 / 文档)。
- **`scripts/check-c-drive-pollution.mjs`(guardian-runner 已注册项,warn-only,2026-09-23 立。**编号同日重排 5 次(85→90→91→92→93→96),文档一律不写死,以 runner 里该 `script:` 所在条目的 `id` 为准**)—— 补上第 45 项看不见的那一半**。成因:第 45 项只扫**源码字面量**,而 C 盘残骸恰恰是从 `os.tmpdir()` / `$env:TEMP` 这类"源码里没写 C"的路径流出去的;`check-parent-pollution`(只扫项目父目录 `D:\`)和 `check-root-dir-clean`(只扫项目根)同样不看 C 盘文件系统 —— 全链 90+ 道门没有一道实地扫过 C,于是 13.2GB `.next` 备份和单日 45 个 git 夹具可以在全量审计恒绿的情况下一直堆在 C 盘。本门实地扫 `C:\` 根 + `C:\tmp` + `C:\temp` + 活 TEMP,按名字白名单只认**本项目产物**(他人条目进"未识别清单",只登记不定性、不清理);并单独判 **TEMP 漂移**(注册表 `HKCU\Environment\TEMP` 已指 `D:\DevEnv\Temp` 而活进程仍持 `C:\Users\...\AppData\Local\Temp`)—— 这就是"改了指针但残骸天天还在长"的机制,新建终端/重启宿主后自愈。
- **`scripts/check-c-drive-pollution.mjs`(guardian-runner 第 93 项,warn-only,2026-09-23 立)—— 补上第 45 项看不见的那一半**。成因:第 45 项只扫**源码字面量**,而 C 盘残骸恰恰是从 `os.tmpdir()` / `$env:TEMP` 这类"源码里没写 C"的路径流出去的;`check-parent-pollution`(只扫项目父目录 `D:\`)和 `check-root-dir-clean`(只扫项目根)同样不看 C 盘文件系统 —— 全链 90+ 道门没有一道实地扫过 C,于是 13.2GB `.next` 备份和单日 45 个 git 夹具可以在全量审计恒绿的情况下一直堆在 C 盘。本门实地扫 `C:\` 根 + `C:\tmp` + `C:\temp` + 活 TEMP,按名字白名单只认**本项目产物**(他人条目进"未识别清单",只登记不定性、不清理);并单独判 **TEMP 漂移**(注册表 `HKCU\Environment\TEMP` 已指 `D:\DevEnv\Temp` 而活进程仍持 `C:\Users\...\AppData\Local\Temp`)—— 这就是"改了指针但残骸天天还在长"的机制,新建终端/重启宿主后自愈。
  - **定级 warn 而非 blocking**:盘根多数条目不属本仓,拦提交只会逼人 `--no-verify`,连带废掉其余守门(与守门 77/52 同取向)。`--strict` 供 CI/巡检改判红。
  - 本门**只读,永不删文件**;清理动作一律走 `c-drive-auto-maintain.ps1`(带 `-DryRun` 与逐条留痕)。
  - 取证:`--self-test` 12 例 + §22c 镜像测试 `node --test scripts/tests/check-c-drive-pollution.test.mjs`(7 例,含"他人工具态不得被判为我们的"、"TEMP 漂移判据"与**"本门编号在 runner 中必须唯一(反查 id,不硬写编号)+ 邻门注册块不得缺失)"**)。
  - 取证:`--self-test` 11 例 + §22c 镜像测试 `node --test scripts/tests/check-c-drive-pollution.test.mjs`(7 例,含"他人工具态不得被判为我们的"、"TEMP 漂移判据"与**"本门编号在 runner 中必须唯一(反查 id,不硬写编号)+ 邻门注册块不得缺失)"**)。
  - **自有产物特征含一条"盘根单字母目录"**:`C:\c` 这类是 MSYS/Git-Bash 把 `/c/...` 当**相对路径**用的错位指纹。实测 2026-08-06 一次就这样在 C 盘里套出 515MB(4 份 origin 浅克隆 + 一份错位的 npm 全局前缀),`git status` 与其余守门全都不知道。只认目录、同名文件不判(宁漏不误报);本门仍**只报不删**,该形态是否清理由人定。
  - **编号事故实录(一天撞四次,且"先查占用"被证明不够)**:85(与 `check-test-paths` 撞)→ 90(与 `ce261e1a8` 的 `check-sse-dispatch-parity` 撞)→ 91(与 `check-error-code-coverage` 撞)→ 92 又撞一次 —— **这次不是我没查**:我取 92 时它确实在 91,是别的会话随后把 `errorCode` 从 91 重排到 92,把重复号**带进了 origin/main**。⇒ 在高并发同日仓里,"提交前查一次占用"挡不住别人事后挪号,**唯一可靠的是让 runner 自己说话**:本门镜像测试现在断言"全 runner 任何 id 不得出现两次"(反查法,不硬写编号),它正是这次红掉的成因;本门编号此后仍在漂移,现值一律以 runner 为准。其中改 90 那次最严重:按整文件提交 `guardian-runner.mjs`(提交 `5db08f26e`)曾把别人刚装上的门**注册块直接覆盖掉**(diff 里就是一行 `script:` 被替换)—— 撞号只是重名,覆盖却是替别人卸闸;已按原文回插,并把断言写成"邻门注册块必须存在"。⇒ 两条硬规矩:① 改共享注册类文件(runner / package.json / CI)必须 `git show <commit> -- <f> | grep '^[-+].*(id:|script:|label:)'` 逐块核对;② **判据要能让机器自己发现撞号**,不要依赖人记得去查。紧急跳过 `HUSKY_SKIP_C_DRIVE_POLLUTION=1`。
  - **编号事故实录(一天撞四次,且"先查占用"被证明不够)**:85(与 `check-test-paths` 撞)→ 90(与 `ce261e1a8` 的 `check-sse-dispatch-parity` 撞)→ 91(与 `check-error-code-coverage` 撞)→ 92 又撞一次 —— **这次不是我没查**:我取 92 时它确实在 91,是别的会话随后把 `errorCode` 从 91 重排到 92,把重复号**带进了 origin/main**。⇒ 在高并发同日仓里,"提交前查一次占用"挡不住别人事后挪号,**唯一可靠的是让 runner 自己说话**:本门镜像测试现在断言"全 runner 任何 id 不得出现两次"(反查法,不硬写编号),它正是这次红掉的成因;终落 **93**。其中改 90 那次最严重:按整文件提交 `guardian-runner.mjs`(提交 `5db08f26e`)曾把别人刚装上的门**注册块直接覆盖掉**(diff 里就是一行 `script:` 被替换)—— 撞号只是重名,覆盖却是替别人卸闸;已按原文回插,并把断言写成"邻门注册块必须存在"。⇒ 两条硬规矩:① 改共享注册类文件(runner / package.json / CI)必须 `git show <commit> -- <f> | grep '^[-+].*(id:|script:|label:)'` 逐块核对;② **判据要能让机器自己发现撞号**,不要依赖人记得去查。紧急跳过 `HUSKY_SKIP_C_DRIVE_POLLUTION=1`。
- **临时夹具唯一落点:`scripts/lib/scratch-dir.mjs`(`mkScratch` / `rmScratch`,2026-09-23 立)**。两条选址硬约束都由实测踩坑固化:① 不得用 `os.tmpdir()`(活进程 TEMP 可能仍钉在 C 盘);② 不得落在仓库树内 —— git 夹具要模拟"非 git 目录",放在 `.ihui-agent/tmp/` 里时 `git rev-parse --show-toplevel` 会向上逃逸到真仓库,使该用例恒红(已用 HEAD 副本 A/B 实证,是当初先试后撤的方案)。现锚定**工作树同盘的 `DevEnv/Temp/ihui-scratch`**(§15b 批准的临时物落点),与 `gitArchiveDir()` 同一套盘符推导;`IHUI_SCRATCH_DIR` 为换机/CI 逃生舱,指向仓库内时按硬约束直接拒建而非静默产出会逃逸的夹具。回归:`node --test scripts/tests/scratch-dir.test.mjs`(4 例)。
- **行为(与 guardian-runner.mjs 实际一致)**:本脚本违规时 exit 1(供统计),guardian-runner 以 warn 模式捕获后计为"警告"、**不阻塞 commit**;仅 blocking 项失败才 exit 1 阻塞 commit。
- 紧急跳过(应急,默认不推荐):`HUSKY_SKIP_C_DRIVE_PATHS=1 git commit ...`

### 历史案例

- 2026-07-27:C 盘 28 GB → 42 GB,释放 13.85 GB(第三方 AI IDE 旧版残留 + Chrome OptGuideOnDeviceModel + Temp 旧文件)
- 后续配置 11 个环境变量永久指向 D 盘,杜绝开发工具缓存再写 C 盘
- 第三方 AI IDE 自身缓存由其自管理,项目维护脚本不再代清理

---

## 27. PowerShell 7 强制规则(强制,2026-08-13 立)

### 触发背景

Windows PowerShell 5.1(`powershell.exe`)已 EOL(微软停止维护),且存在已知 bug:

- `Out-File -Encoding UTF8` 实际写 UTF-16 LE(BOM 处理 bug)
- ANSI 代码页读 UTF-8 写入的 `.ps1` 文件时,`if/else` 块解析截断(`Missing closing '}'` 误报)
- 中文路径/文件名处理在 5.1 上不稳定
- 跨平台兼容性差(Linux/macOS 跑不通)
- 真实事故:2026-08-13 C 盘 130MB 修复任务,D 盘诊断脚本二次报 `Missing closing '}'`,根因是 5.1 读 pwsh 7 写入的 UTF-8 文件的 BOM bug

### 强制规则(违反视为交付事故)

1. **agent 跑 PowerShell 必须用 `pwsh.exe`**(PowerShell 7+):
   - ✅ `pwsh -File scripts/foo.ps1`
   - ✅ `& "C:/Program Files/PowerShell/7/pwsh.exe" -File ...`
   - ❌ `powershell -File scripts/foo.ps1`
   - ❌ `powershell.exe -Command ...`
   - 唯一例外:Windows 系统 5.1 专属 cmdlet 需在脚本里 `Set-Alias` 显式标注
2. **所有项目内 `.ps1` 文件第一行必须 `#requires -Version 7`**:在 5.1 上跑会**直接报错退出**,这正是强制效果
- **适用面例外(2026-09-28 G-225 出路②落地)**:`deploy/**` 的 `.ps1` **不吃该 pragma**,守门 `scripts/check-pwsh-version.mjs` 对该面**不判红只报名**(报告行 `[EXEMPT]` 给出该面总数与缺 pragma 数,现读一律看该行,不引用文档数字)。原因不是遗漏而是可用性决定:`#requires` 在 5.1 上是**直接拒绝执行**,而这些脚本由 nssm 服务与计划任务拉起 —— 别机/服务身份若仍是 5.1,补 pragma 等于让部署/备份/监控服务停摆。**因此不得按本节那句"所有项目内 .ps1"推断运维脚本已受版本强制**;那句话现在只覆盖非豁免面。这一族的真判据在**调用侧**:代码面凡调用 `deploy/**.ps1` 必须显式指名 `pwsh`/`pwsh.exe` 或经 `*.vbs` 包装(§26),裸 `powershell -File deploy/…` 即 `[CALLER]` 命中;默认档与提交链只报数,问责跑 `--strict`,升 blocking 的前置是真仓 HEAD 面现读为 0。门内逐字打印的盲区:调用侧只判 JS/TS 系,`.ps1/.sh/.bat/.vbs` 的调用面因 `code-mask` 不认其注释语法而**不判** —— 那是"没判",不是"已确认没有"。
3. **CI / 守门脚本必须用 `pwsh`** 跑
4. **路径统一用正斜杠 `/`**:`C:/Program Files/PowerShell/7/`,避免 5.1 反斜杠转义 bug
5. **`.ps1` 文件用 UTF-8 with BOM 写**:PowerShell 5.1 默认以 ANSI 代码页读 `.ps1`,`Out-File -Encoding UTF8` 在 5.1 上写的是 UTF-16 LE,必须用 `[System.IO.File]::WriteAllText($path, $content, [System.Text.UTF8Encoding]::new($true))`

### 守门(blocking)

- `scripts/check-pwsh-version.mjs`:检查 `.ps1` 前 5 行是否含 `#requires -Version 7`,缺失则 exit 1。ROOT 由脚本自身位置推导(不写死盘符)
  - `--staged`:仅检查 **git index 中已暂存的** `.ps1`(`.husky/pre-commit` 用此模式)
  - 缺省:全树扫描(人工 / CI 全量审计用)
- 集成位置:`.husky/pre-commit` 直接调用 `node scripts/check-pwsh-version.mjs --staged`(blocking);跳过 `HUSKY_SKIP_PWSH_VERSION_GUARD=1`(应急,默认不推荐)
- 检查范围:项目内 `.ps1`(`scripts/`、`apps/*/scripts/`、`.ihui-agent/scripts/` 等)
- 白名单:`*.venv/*`、`venv/*`、`node_modules/*`、`.git/*`、`tmp/*`、`deploy/*`、`.ihui-agent/*`、`site-packages/*`(playwright 驱动)、`.workbuddy/quarantine/*`(污染治理隔离归档)
- **2026-09-15 修复(本守门自身 P0 回归)**:`--staged` 自挂载起只在用法注释里声明、**从未实现**,实现中只有无条件的全树 `scan(ROOT)`,导致工作区里未跟踪且被 gitignore 的遗留 `.ps1`(实测 `.android-toolchain/*.ps1`、`.tmp-wechat-test/watch.ps1`)**阻断每一次提交**——而它们在干净 checkout / CI 里根本不存在。已真正实现 `--staged`,并补齐 `deploy/*` 等白名单口径。

### 安装指引(机器上没装 PowerShell 7)

```powershell
winget install --id Microsoft.PowerShell -e --source winget
# 或一键脚本
iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI"
```

### 历史教训

- 2026-08-13:CredentialHelperSelector 弹窗修复任务全程用 `powershell`(5.1),导致 D 盘诊断脚本二次报 `Missing closing '}'`(5.1 对 BOM 处理 bug)。改用 `pwsh` + `#requires -Version 7` 后立即通过。教训:RunCommand 默认 PowerShell 解释器**不是 7 也不是 5 的中性选择**——必须显式指定 `pwsh`。

---

## 28. 根目录整洁铁律(强制,2026-08-15 立)

### 触发背景

根目录曾散落 10+ 临时产物:`debug.log` / `aha_electron_2026.0810.log` / `cookies.txt` /
`page_eval/prompts/response/usage.html` / 过时 `start-dev.ps1`(D 盘旧路径 + 8810 旧端口) /
`browser_test_output/` 等。2026-08-15 一次性清理后,立此铁律防回潮。

### 强制规则(违反视为交付事故)

1. **一级目录只允许白名单内条目**,白名单封闭维护于 `scripts/check-root-dir-clean.mjs`(4 组 Set):
   - `ALLOWED_FILES`:配置(`package.json`/`pnpm-*.yaml`/`turbo.json`/`eslint.config.mjs`/`tsconfig.base.json`/`knip.jsonc`/`docker-compose.yml`/`railway.json`/`render.yaml`/`app.json`)+ 标准文档(`README*.md`/`LICENSE`/`SECURITY.md`/`CONTRIBUTING.md`/`CODE_OF_CONDUCT.md`)+ 项目强制(`AGENTS.md`/`PROJECT_PLAN.md`)
   - `ALLOWED_DIRS`:`apps`/`packages`/`scripts`/`docs`/`deploy`/`monitoring`/`reports`/`sdks`/`cert`/`products`/`logs`/`node_modules`/`tmp`/`test-results`
   - `ALLOWED_HIDDEN_FILES` / `ALLOWED_HIDDEN_DIRS`:`.env*`/`.gitignore`/`.git`/`.github`/`.husky`/IDE/工具目录等
2. **禁止在一级目录生成 `.log` / `.html` / `cookies*` / 截图 / ad-hoc 脚本**:临时产物一律进 `tmp/` 或 `logs/`,测试产物进 `test-results/` 或对应子目录。
3. **新增合法根目录文件/目录必须显式审批**:把条目加进 `scripts/check-root-dir-clean.mjs` 白名单并随 commit 提交,禁止绕过白名单。
4. **任务收尾必查**:交付前跑 `node scripts/check-root-dir-clean.mjs --staged`,0 违规才算完成。
5. **忽略产物也纳入视野(2026-09-23 补)**:守门原对"被 git 忽略的一级目录条目"整体跳过(该豁免必要 —— 忽略条目本由 `.gitignore` 承接,若一并拦截则各类构建产物与本地目录会让每次提交皆红;注意 `tmp/`、`logs/` 等已在 `ALLOWED_DIRS`,驱动这条豁免的是"未进白名单却被忽略"的产物),致本规则第 2 条的禁令对被忽略文件零覆盖 —— 实测一次性翻出 14 个静默残留(`_knip.log`/`.tmp-tsc.log`/`build-oidc.log` 等),`git status` 与守门都看不见。现新增**只告警层**(不计失败、不改退出语义:全量恒 0,`--staged` 仍只对非忽略违规 exit 1)。看到该告警即删除或移入 `tmp/`、`logs/`。

### 守门(blocking)

- `scripts/check-root-dir-clean.mjs`:扫描一级目录,白名单外条目 `--staged` 模式 exit 1
- 集成位置:`scripts/guardian-runner.mjs` pre-commit 第 44 项(2026-08-15 立)
- 逃生舱(应急,默认不推荐):`HUSKY_SKIP_ROOT_DIR_GUARD=1 git commit ...`

### 配套

- `.gitignore` 已补 `browser_test_output/`、`cookies.txt` 防回潮(未跟踪垃圾不污染 `git status`)

---

## 29. dangling commit 备份策略(2026-08-19 立)

### 由来

`commit-loss-check` 守门会拦截 dangling commits,要求先备份为 tag 再继续操作。
本仓库实践:所有 dangling commits 备份为 `lost-commit/wip-*` tag(以 commit 短 hash 命名,
例如 `lost-commit/0141d221`、`lost-commit/wip-batch-00f2429`)。

截至 2026-08-19,本地 `lost-commit/*` tag 数量 = **4188** 个,远超 §22「已被 reset 丢失的 commit
永久记录」清单的 3 个手工备份 tag — 绝大多数来自 `check-commit-loss-guard.mjs` 自动 fsck
悬空检测后批量打 tag 的产物(典型为 merge commit / stash pop 失败的中间 commit)。

抽样 `git show --stat lost-commit/{0141d221,02000bfd,043af88d}` 均显示为 Merge commit,
作者 `AI智汇社 <lizong@aizhs.top>`,日期集中在 2026-08-17 — 与 §22 守门日志一致。

### 保留周期

建议 **30 天**。超出后一次性 GC(由仓库维护者人工触发,不在守门脚本里自动跑):

```bash
# 1. 先确认无重要未提交工作(红线!)
git status               # 应 clean
git stash list           # 应为空
git diff --stat          # 应无改动

# 2. 列出所有 lost-commit tag(预演,确认数量级合理)
git tag -l 'lost-commit/*' | wc -l   # 当前 4188

# 3. 本地一次性删除
git tag -l 'lost-commit/*' | xargs git tag -d

# 4. 同步删除远程 tag(仅当你确认远端也允许清理时;默认保留远端)
#    警告:此操作会真正影响 origin ref 列表,需在 PR 中明确说明
git tag -l 'lost-commit/*' | xargs -I{} git push origin :refs/tags/{}

# 5. 验证 (强校验,任何 missing/unreachable 都应人工复核)
git fsck --unreachable --no-reflogs
git tag -l 'lost-commit/*' | wc -l   # 应为 0
```

### 为什么必须人工触发而不是脚本自动化

- **守门不擅自删 tag**: `check-commit-loss-guard.mjs` 的职责是"检出 + 备份",
  不应承担"GC 清理"职责 — 后者一旦误删,会真正丢失 dangling commit 的可达路径,
  即使远端有备份也增加了恢复成本。
- **数量级跃升需要人审**: 从 0 → 4188 是日积月累的结果,任何"一键 GC"脚本都应要求
  人类在 PR 里显式 ack,而不是 nightly cron 自动跑。
- **§22 守门只挡新增**: pre-commit 第 30a 项(`--blocking --filter-stash`)只关心
  "这一次操作是否会产生 dangling",不关心历史积累。

### 红线

- ❌ **不要把 lost-commit tag push 到 origin 之外的 fork / mirror**(会污染外部 ref 列表)。
  §22「自动化 tag 同步」明确禁止了向非 origin 的推送。
- ❌ **GC 前未确认 `git status clean` + `git stash list` 为空**(会丢失未提交改动)。
- ❌ **一次性删除超过 1000 个 tag 后不验证 `git fsck` 就 push**(可能误删有意义的 commit,
  因为 fsck 检出的是"创建时刻"的悬挂,不代表当下已被 merge 进 main)。
- ❌ **amend 含 dangling 备份说明的 commit**(本节一旦 commit,内容视为定稿;
  后续 GC 时间窗的调整应新加 §29a / §29b,不要回头改这一节)。

### 配套

- §22「防止 commit / push / merge 提交丢失硬性规则」— 提供守门 + 备份机制
- §22「自动化 tag 同步」(`scripts/sync-lost-commit-tags.mjs`)— 保证远端有副本
- `docs/lost-commit-archive.md` — 丢失 commit 的永久档案(人工可读清单)
- `.ihui-agent/archive/AGENTS_history.md` — 历史 GC 案例(本节首次落地后应补一条案例)

---

<!-- AGENTS.md 瘦身 slice-A(2026-09-30):以下原 1952–2473 行(522 行 / 约 497KB)整区逐字归档于 .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md(HEAD d990bfa6bc);
     非条目行(标题/表格/代码块)逐字保留,顶层条目压成指针行:守门号、门脚本名、blocking/warn、HUSKY_SKIP_* 全部保留,原文按行号在归档件逐字找回。 -->
## 42. React SyntheticEvent 闭包陷阱(强制)

### 触发背景(2026-08-12 立,真实 bug)

`apps/web/src/components/chat/model-selector.tsx` 原 `onMouseLeave` 在 `setTimeout` 回调闭包内访问 `e.currentTarget`:

```tsx
setTimeout(() => {
  setPopoverAnchor((prev) => (prev?.el === e.currentTarget ? null : prev))
}, 100)
```

React 17+ 的 SyntheticEvent 在事件处理函数返回后 `currentTarget` 会被置 `null`,异步回调触发时 `prev?.el === null` 永远为 `false`,关闭分支永远不进 → model picker 常驻显示。

### 陷阱机制

- React 事件处理函数(`onClick={e => ...}` 等)**同步作用域内** `e.target` / `e.currentTarget` 有效。……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L32–L32
- 一旦进入异步回调(`setTimeout` / `setInterval` / `requestAnimationFrame` / `requestIdleCallback` / `queueMicrotask` / 未捕获 `e` 的 promise)或 memoized / 延迟执行的闭包场景,……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L33–L33

### 强制做法(违反视为 defect 事故,强制)

1. **同步缓存**:在事件处理函数**同步作用域**先缓存所需字段,再在异步中使用。例:`const id = e.currentTarget.dataset.id` → 异步闭包内读 `id`,严禁直接读 `e.currentTarget.dataset.id`。
2. **用 ref 管理 DOM**:`anchorRef.current` 替代 `e.currentTarget`,适合需在多个回调 / 清理函数中引用同一 DOM。
3. **异步闭包内禁止访问 `e.` 任何属性/方法**(`currentTarget` / `target` / `preventDefault` / `stopPropagation`)。

### 守门

`scripts/check-event-closure-leak.mjs`(guardian-runner 第 42 项,blocking):用括号配对算法提取异步回调第一个参数(箭头函数体),在其内搜 `e.X` 模式,命中 → exit 1 阻塞 commit。
参考修复:`apps/web/src/components/chat/model-selector.tsx` MemberDiscountSection。
## 30. 代理与会话面的状态词汇与撤销语义(强制,2026-09-27 立,G-104 规格补强的单一落点)

- **为什么住在本文而不是任务行**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L47–L50
- **两套撤销语义必须分离**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L51–L53
- **子智能体 / 后台进程的状态词汇是一等契约**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L54–L56
- **钩子无终态不得渲染成"完成"**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L57–L59
- **自动档写活文档必须按被审面取底稿,并自带"状态不得倒退"的零损失断言(强制,2026-09-28 立,近 24h 实测 18 行翻勾被退回逼出)**〔scripts/archive-completed-tasks.mjs · scripts/lib/face-reader.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L60–L62

---

## 关键参考文档

| 文档                      | 说明                                                          |
| ------------------------- | ------------------------------------------------------------- |
| `PROJECT_PLAN.md`         | 唯一任务计划文档(必读)                                        |
| `.ihui-agent/archive/`    | 历史归档(audit/交接/迁移报告,只读)                            |
| `docs/architecture.md`    | 系统架构文档                                                  |
| `docs/port-management.md` | 端口注册表(88xx 段)                                           |
| `docs/learning-assets.md` | 学习资产登记(34 个工作流反馈来源,新增/删除工作流必须同步更新) |

<!-- ⁠​‌​​‌​​‌‍‍​‌​​‌​​​‍‍​‌​‌​‌​‌‍‍​‌​​‌​​‌‍‍​​‌​‌‌​‌‍‍​‌​​​​​‌‍‍​‌​​‌​​‌‍‍‌​‌‌​‌‌‌‍‍‌‌​​‌‌​​‌‌‌‌​‌​‍‍‌‌​‌‌​​​‌​​​‌‌‌‍‍​‌​​​​​‌‍‍​‌​​‌​​‌‍‍‌​‌‌​‌‌‌‍‍‌‌​​‌‌‌​‌​​‌‌‌​‍‍‌‌​​‌‌​​​‌​​‌​‌‍‍‌​‌‌‌​‌‌‌​‌‌‌​‌‍‍‌​‌‌​‌‌‌‍‍​‌​​‌‌​​‍‍​‌​​​​‌‌‍‍‌​‌‌​‌‌‌‍‍​‌‌​​​​‌‍‍​‌‌​‌​​‌‍‍​‌‌‌‌​‌​‍‍​‌‌​‌​​​‍‍​‌‌‌​​‌‌‍‍​​‌​‌‌‌​‍‍​‌‌‌​‌​​‍‍​‌‌​‌‌‌‌‍‍​‌‌‌​​​​‍‍‌​‌‌​‌‌‌‍‍​‌​‌​​​​‍‍​‌​‌​​‌​‍‍​‌​​‌‌‌‌‍‍​‌​‌​‌‌​‍‍​‌​​​‌​‌‍‍​‌​​‌‌‌​‍‍​‌​​​​​‌‍‍​‌​​‌‌‌​‍‍​‌​​​​‌‌‍‍​‌​​​‌​‌‍‍​​‌​‌‌​‌‍‍​​‌‌​​‌​‍‍​​‌‌​​​​‍‍​​‌‌​​‌​‍‍​​‌‌​‌‌​⁠ -->

| 路径                             | 角色                                                        |
| -------------------------------- | ----------------------------------------------------------- |
| `D:/IHUI-AI/.git`                | **28 字节指针文件**(`gitdir: D:/IHUI-AI-git-repo`),不是目录 |
| `D:/IHUI-AI-git-repo`            | 真实 gitdir(544MB),在工作区之外                             |
| `D:/IHUI-AI.git-backup-20260912` | gitdir 完整备份,守护的本地恢复源                            |

- **唯一写法**〔scripts/guardian-runner.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L84–L84
- **唯一写法**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L85–L85

> ⚠️ 本行已于 2026-09-24 被上面「唯一写法(2026-09-24 改档)」就地改写取代(旧档 brand.DEFAULT/foreground 已废);保留原文只为不丢行,勿照它执行。

- 悬空引用由该门的"品牌键"判据直接拦(编译不一定红,运行时是 `undefined` 颜色)。并行会话的暂存区里若还残留 `tokens.brand.ctaFill/ctaText`,改法只有一行:`ctaFill` → `DEFAULT`、`ctaText` → `foreground`。……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L89–L89
- **清单↔锁 specifier 对账**〔scripts/check-lock-manifest-consistency.mjs · blocking · HUSKY_SKIP_LOCK_MANIFEST_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L90–L90

  - **`--take-ours <path>` 是"声明式例外",不是选边后门**(2026-09-24 加):两侧同改且真三方报冲突时,默认一律 `needHuman` 交人工 —— 但**报冲突不等于必须交人工**:若合并树的内容已让对侧那一版判据**必红**,取本侧就是被内容强制的唯一解。当天实例:`scripts/tests/check-cross-end-tokens.test.mjs` 两侧在同一段各写各的,对侧留着回归锁"brand 里不得再有 CTA 档",而 `packages/design-tokens` **只有本侧改过**(对侧动过数实测 0)⇒ 合并树必含 `brand.cta` ⇒ 那把锁 100% 失败;取证是**跑两版**:本侧版 `pass 11 / fail 0`,对侧版 `pass 7 / fail 1`(红的正是那把锁)。使用该例外必须**逐条附这样的取证**,且工具会把声明过的项目打进输出与合并提交信息(`;取本侧(已声明+可复核): <path>`)⇒ 无从静默。判据底线不变:落地前 `丢本侧路径 = 0 ∧ 丢对侧路径 = 0 ∧ 活文档未存活行 = 0`,落地后由守门 100 的 A1 复核。镜像测试钉住三件事:默认必须 `needHuman`、声明后 `keptOurs` 必须点名、且**一条例外不得连带丢掉对侧其它独有新增**。

- **mobile-rn 深色前景/容器对账**〔scripts/brand-foreground-baseline.js · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L94–L94
- **mobile-rn 深色前景/容器对账**〔scripts/brand-foreground-baseline.js · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L95–L95
- **mobile-rn 深色前景/容器对账**〔scripts/brand-foreground-baseline.js · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L96–L96
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**〔scripts/tests/check-brand-foreground.test.mjs · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L97–L97
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**〔scripts/tests/check-brand-foreground.test.mjs · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L98–L98
- ⚠️ 本行原为守门 83 的**第三条重复登记**,且把取证量写死成"`--self-test` 11 例"(实为 94 条断言)。2026-09-24 就地改写为指向条:该门的现行条目见**上方两条**(编号 83,判据 R1–R5);保留本行只为不丢行,勿照它执行。……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L99–L99
- **守门 83 追加 R7(2026-09-25,JSX 渲染嵌套级跨档配对)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L100–L100
- ⚠️ 上面那条的"判定文件 >300 跳过"护栏已被 **2026-09-24 的乘数级例外**收窄(普通文件仍受护栏约束,但门的注册表/门自身/`scripts/lib/`/钩子/根清单/工作流恒照判)——现行口径见本节「本轮四道门的口径/落点收口」第 ② 条与 `scripts/tests/ch〔scripts/tests/check-stale-revert.test.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L101–L101
- **UI/样式**〔warn · HUSKY_SKIP_Z_INDEX_GUARD HUSKY_SKIP_OVERLAY_ZINDEX〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L102–L102
- **桌面端本地缓存明文巡检**〔scripts/check-desktop-cache-plaintext.mjs · scripts/tests/check-desktop-cache-plaintext.test.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L103–L103
- **上面"真实库在 `F:`、`D:/BaiduSyncdisk` 根本不存在"是另一台机(G: 那份 checkout)的实测,在这一台机上正好相反**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L104–L104
- **非 node 调用方(PowerShell / 批处理 / 计划任务)取凭据路径的唯一出口 = `node scripts/secret-path.mjs <子目录> <文件名>`**〔scripts/secret-path.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L105–L105
- **口令落点按用途分子目录,规矩同一套**〔scripts/merge-live-doc.mjs · scripts/tests/merge-live-doc-anchor.test.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L106–L109
- **反回退对账**〔scripts/guardian-runner.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_STALE_REVERT_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L110–L110
- **架构契约对账**〔scripts/check-architecture-policy.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L111–L111
- **Tailwind v3 消费端的 `/alpha` 修饰符由共享 preset 负责**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L112–L112
- **硬编码中文基线棘轮**〔scripts/hardcoded-zh-baseline.js · scripts/scan-hardcoded-zh.mjs · blocking · HUSKY_SKIP_HARDCODED_ZH_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L113–L113
- **架构契约对账**〔scripts/check-architecture-policy.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L114–L114
- **架构契约对账**〔scripts/check-architecture-policy.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L115–L115

> ⚠️ 本行为守门 103 的**立项原文**,其中"策略表自身按 HEAD→索引→工作树降级"已被 2026-09-25 同日的批量归并留成旧副本 —— 现行判据是**按档定向取材**(`policyFaceOrder`:全量 HEAD 优先 / `--staged` 索引优先),原文照抄会重新引入"改表那枚提交不被审"的盲视;现行条目见本节内"镜像测试 13 例"那一条,勿照本行执行。保留本行只为不丢行(§12)。

- **`merge-live-doc` 的第三种定性:同锚点 ⇒ 就地改写(2026-09-25 补,`--self-test` 13→16 例)**〔scripts/merge-live-doc.mjs · scripts/tests/merge-live-doc-anchor.test.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L119–L119

> ⚠️ 本行是守门 103 的**中间态副本**(2026-09-25 同日两次改写之间的一次),其中"取材面双向条件不变量"这条断言**已被删除** —— 它两条前提都不成立(改注释/requires 会让表不同而 managed 集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等),留着会教人把它当判据复原。现行是"镜像测试 13 例"那一条(T12 改用纯函数+构造面、新增 T12b 尺子反例、T8 改为自带构造的 false 侧)。勿照本行执行;保留只为不丢行(§12)。

- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {}……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L123–L123

- **prod-bundle 影子副本对账**〔scripts/check-prod-bundle-shadow.mjs · scripts/guardian-runner.mjs · blocking · HUSKY_SKIP_PROD_BUNDLE_SHADOW〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L125–L125
- **架构契约对账**〔scripts/check-architecture-policy.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L126–L126
- **`merge-live-doc` 的第三种定性:同锚点 ⇒ 就地改写(2026-09-25 补,`--self-test` 13→16 例)**〔scripts/merge-live-doc.mjs · scripts/tests/merge-live-doc-anchor.test.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L127–L127
- **架构契约对账**〔scripts/check-architecture-policy.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L128–L128

<!-- ⚠️ 本行与上方同名条目(第 1339 行)同体,是同日两次改写被并集留下的旧副本;现行以第 1339 行为准。其中"10 例/12 例"之类的镜像测试例数、以及任何具体收口块数,都按当次实测取,勿照本行派单。保留本行只为不丢行(AGENTS §12)。 -->- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个)(⚠️ 这几个数字是**立项状态、不是现值**;现值一律跑 `node scripts/check-architecture-policy.mjs` 看 `managed:true` 行),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 13 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、T12 的**纯函数+构造面**取材面证明、以及 T12b 的尺子反例;变异回旧顺序只有 T12 红)。**本门自身踩过的判据教训(勿再犯)**:这里曾写过一条"索引表≠HEAD 表 ⇒ 两档收口集合必异形"的条件断言,两条前提都不成立(改注释/requires 都会让表不同而集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等)⇒ 一支误红、一支无牙,已删。**证明取材面这类行为只能用纯函数+构造面**,不得依赖仓库瞬时状态。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。

- ⚠️ **中文出参会被控制台码页吃掉(2026-09-25 实测,和上一条同为"码页"但方向相反)**:上面说中文当 **argv** 传出去无损;但 node 往 stdout 打的**路径**含中文(`密钥/...`)时,PowerShell 按 **GBK 码页**解码 ⇒ 拿回的是乱码字符串……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L132–L132
- **凭据目录现状**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L133–L133
- **"服务身份能不能跑到这一步"要用它那份环境验,不得用我的终端验(2026-09-25 实测)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L134–L140

  - **认领升级为租约(2026-09-25 立,守门 109 `check-task-claims.mjs --check-gate`)**:裸 `（进行中）` 只是文本 —— 没有持有者、没有时间戳、不判年龄,于是 agent 一死标记就永久留在 HEAD,下一个派单会话按上一条扫描**要么看不见那件活、要么把它当"别人正在做"而按住不做**(本仓记录里已反复出现,不需要构造)。**新格式**:`- [ ]（进行中@YYYY-MM-DD/持有者短名）`,三要素齐备才算一次认领;裸 `（进行中）` 仍被解析但**只计数不判红**(实测存量 36 条全是裸标记,当场判红就是一台恒红门)。年龄 > 72h(`IHUI_CLAIM_LEASE_TTL_HOURS` / `--ttl-hours` 可调)判红并点名行号与持有者;写了 `@日期` 却缺 `/持有者` 同样判红 —— 半个租约比没有租约更危险,它看起来像被管过。同一行既"进行中"又 `[x]` 判红(协议自相矛盾 = 释放失败的现场)。**门绝不自动摘除任何人的认领**(那属 §16 越权),出路只有三条:续租(改日期)/ 完成翻勾并摘牌 / 显式让渡给某持有者。派单前除扫进行中项,还要跑一次三轴基线自检 `node scripts/check-baseline-freshness.mjs --preflight`(见 §守门脚本速查)。
- **单一真相源**〔scripts/sync-design-tokens.mjs · scripts/check-miniapp-taro-design-tokens.mjs · scripts/check-miniapp-tokens-sync.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L143–L143
- **发给 provider 的 function parameters 必须由校验面单向投影**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L144–L144
- **候选序解析器必须同时提供「出处」出口**〔scripts/lib/key-dir.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L145–L145
- **架构契约对账**〔scripts/check-architecture-policy.mjs · scripts/module-context.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L146–L146
- **第三方来源台账对账**〔scripts/provenance-ledger.mjs · scripts/lib/third-party-roots.mjs · blocking · HUSKY_SKIP_PROVENANCE_LEDGER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L147–L148

- **豁免到期账**〔scripts/check-exemption-expiry.mjs · scripts/exemption-expiry-baseline.js · blocking · HUSKY_SKIP_EXEMPTION_EXPIRY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L150–L150
- **任务认领租约对账**〔scripts/check-task-claims.mjs · scripts/plan-tasks-merge.mjs · blocking · HUSKY_SKIP_TASK_CLAIM_LEASE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L151–L151
- **产物预算对账**〔scripts/check-artifact-budget.mjs · blocking · HUSKY_SKIP_ARTIFACT_BUDGET〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L152–L152
- **服务二进制路径存续性对账**〔scripts/check-service-binary-paths.mjs · blocking · HUSKY_SKIP_SERVICE_BINARY_PATHS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L153–L153
- **工具契约声明对账**〔scripts/check-tool-contract-declared.mjs · blocking · HUSKY_SKIP_TOOL_CONTRACT_DECLARED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L154–L154
- **测试收集存续性对账**〔scripts/check-test-collection-runs.mjs · blocking · HUSKY_SKIP_TEST_COLLECTION〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L155–L155
- 三件同批事实:以上五道门都按 §12 的"活文档与注册表单写者"由主会话统一接线(`guardian-runner.mjs` / `package.json` / 本节 / README 守门章节同枚提交),各实现票按任务书**没有**碰过这些共享注册文件 —— 并行改注册表必然互相覆盖注册块(门 ……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L156–L156

- **小程序端「源 ↔ 派生产物」存续性对账**〔scripts/check-miniapp-generated.mjs · blocking · HUSKY_SKIP_MINIAPP_GENERATED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L158–L159
- ⚠️ **中文出参会被控制台码页吃掉(2026-09-25 实测,和上一条同为"码页"但方向相反)**:上面说中文当 **argv** 传出去无损;但 node 往 stdout 打的**路径**含中文(`密钥/...`)时,PowerShell 按 **GBK 码页**解码 ⇒ 拿回的是乱码字符串……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L160–L160
- **凭据目录现状**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L161–L161
- **发给 provider 的 function parameters 必须由校验面单向投影**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L162–L162
- **压缩分母对账**〔scripts/check-compaction-denominator.mjs · scripts/compaction-denominator-baseline.js · blocking · HUSKY_SKIP_COMPACTION_DENOMINATOR〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L163–L163
- **路由身份键对账**〔scripts/check-tool-arg-routing-identity.mjs · scripts/c-disk-breakdown.mjs · scripts/tests/c-disk-breakdown.test.mjs · scripts/plan-union-merge.mjs · blocking · HUSKY_SKIP_TOOL_ARG_ROUTING_IDENTITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L164–L174
- **入参校验装车对账**〔scripts/check-tool-arg-validation-wired.mjs · scripts/c-disk-breakdown.mjs · scripts/tests/c-disk-breakdown.test.mjs · scripts/plan-union-merge.mjs · blocking · HUSKY_SKIP_TOOL_ARG_VALIDATION_WIRED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L175–L183
- **出站事实对账**〔scripts/check-egress-facts.mjs · scripts/egress-facts-baseline.js · blocking · HUSKY_SKIP_EGRESS_FACTS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L184–L184
- **对话流元素覆盖**〔blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L185–L185
- **测试收集存续性对账**〔scripts/check-test-collection-runs.mjs · blocking · HUSKY_SKIP_TEST_COLLECTION〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L186–L186
- **门脚本取材面纪律对账**〔scripts/check-gate-face-discipline.mjs · blocking · HUSKY_SKIP_GATE_FACE_DISCIPLINE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L187–L187
- **子代理权限继承对账**〔scripts/check-subagent-permission-inherited.mjs · blocking · HUSKY_SKIP_SUBAGENT_PERMISSION_INHERITED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L188–L188
- **名单类判据正向证明对账**〔scripts/check-list-predicate-has-positive-proof.mjs · blocking · HUSKY_SKIP_LIST_POSITIVE_PROOF〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L189–L189
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,当前不在提交链上(2026-09-30 曾一度注册,复跑整轮检查后已撤下;要重新挂上,先清台账那条"解阻前置"并同枚声明应急跳过变量) · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **声明策略消费者对账**〔scripts/check-declared-policy-has-consumer.mjs · blocking · HUSKY_SKIP_DECLARED_POLICY_CONSUMER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L191–L191
- **文件写盘安全对账**〔scripts/check-file-write-safety.mjs · blocking · HUSKY_SKIP_FILE_WRITE_SAFETY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L192–L192
- **工具族注册对账**〔scripts/check-tool-family-registered.mjs · blocking · HUSKY_SKIP_TOOL_FAMILY_REGISTERED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L193–L193

- **工具执行预算对账**〔scripts/check-tool-exec-budget.mjs · blocking · HUSKY_SKIP_TOOL_EXEC_BUDGET〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L195–L195
- **记忆端点属主绑定对账**〔scripts/check-memory-owner-binding.mjs · blocking · HUSKY_SKIP_MEMORY_OWNER_BINDING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L196–L196
- **记忆端点属主绑定对账**〔scripts/check-memory-owner-binding.mjs · blocking · HUSKY_SKIP_MEMORY_OWNER_BINDING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L197–L197
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**〔scripts/lib/commit-gate-attribution.mjs · scripts/lib/pre-commit-hook.js〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L198–L198
- **守门接线层对账**〔scripts/check-gate-wiring.mjs · scripts/lib/pre-commit-hook.js · scripts/lib/guardian-triggers.mjs · scripts/gate-wiring-allowlist.js · scripts/tests/check-gate-wiring.test.mjs · blocking · HUSKY_SKIP_GATE_WIRING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L199–L199
- **记忆端点属主绑定对账**〔scripts/check-memory-owner-binding.mjs · blocking · HUSKY_SKIP_MEMORY_OWNER_BINDING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L200–L200

<!-- ⚠️ 本行是守门 102 的**四类判据原文**(2026-09-25 第二轮收口前),已被下方"六类判据"条就地取代:缺 GA5 混写与 GA6 双箭头两条,S0 也只登记 4 个机制。照它派单会以为 RN 共享层那份 BackChevron 有人看守。保留原文只为不丢行(AGENTS §12),勿照它执行。 -->

- **守门**〔scripts/check-glyph-arrow-icon.mjs · blocking · HUSKY_SKIP_GLYPH_ARROW_ICON〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L204–L204
- **GA1 存量已清零(2026-09-25 第三轮;取代上一行末「行尾字符箭头存量 70 处」那个读数)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L205–L217
- **守门 102 的 HEAD 存量已全清 + 覆盖面换成实测(2026-09-26,终读:S0/GA1/GA2/GA4/GA5/GA6 全部 0)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L218–L234
- **miniapp 原生 chrome 两份副本已收口为派生态 + 专属门**〔scripts/sync-miniapp-chrome.mjs · scripts/check-miniapp-chrome.mjs · blocking · HUSKY_SKIP_MINIAPP_CHROME〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L235–L235
- **小程序端「源 ↔ 派生产物」存续性对账**〔scripts/check-miniapp-generated.mjs · blocking · HUSKY_SKIP_MINIAPP_GENERATED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L236–L236
- **自动归档**〔scripts/archive-completed-tasks.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L237–L238
- ⚠️ **但 `apps/miniapp-taro` 实际跑的是 Tailwind v4.3.3,不是端内 `package.json` 声明的 v3**(2026-09-25 实测,详见 PROJECT_PLAN O62附⑤):`weapp-tailwindcss@5.2.9` 引用的是 **`@……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L239–L240
- **豁免到期账**〔scripts/check-exemption-expiry.mjs · scripts/exemption-expiry-baseline.js · blocking · HUSKY_SKIP_EXEMPTION_EXPIRY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L241–L241
- **miniapp 原生 chrome 两份副本已收口为派生态 + 专属门**〔scripts/sync-miniapp-chrome.mjs · scripts/check-miniapp-chrome.mjs · blocking · HUSKY_SKIP_MINIAPP_CHROME〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L242–L242
- **派生面登记表必须自证自洽(守门 `scripts/check-token-sync-registry.mjs`,编号以 runner 现值为准,紧急跳过 `HUSKY_SKIP_TOKEN_SYNC_REGISTRY=1`)**〔scripts/check-token-sync-registry.mjs · scripts/lib/pre-commit-hook.js · warn · HUSKY_SKIP_TOKEN_SYNC_REGISTRY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L243–L243
- **小程序端有第 6 个受管区 = `app.css` 的 `@theme` 色档注册区**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L244–L250
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/mi……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L251–L251
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {}……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L252–L252
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/mi……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L253–L253
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {}……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L254–L254
- **门脚本取材面纪律对账**〔scripts/check-gate-face-discipline.mjs · scripts/lib/face-reader.mjs · blocking · HUSKY_SKIP_GATE_FACE_DISCIPLINE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L255–L255
- **记忆端点属主绑定对账**〔scripts/check-memory-owner-binding.mjs · blocking · HUSKY_SKIP_MEMORY_OWNER_BINDING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L256–L256
- **提交活文档前必须做"工作树 ⊇ HEAD"行级对账(2026-09-24 立,一夜三次自伤换来的)**〔scripts/merge-live-doc.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L257–L257
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**〔scripts/lib/commit-gate-attribution.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L258–L258
- **mobile-rn global.css token 同步守门**〔scripts/check-rn-global-css-sync.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L259–L259
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**〔scripts/lib/commit-gate-attribution.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L260–L260
- **小程序端有第 6 个受管区 = `app.css` 的 `@theme` 色档注册区**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L261–L261
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/mi……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L262–L262
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {}……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L263–L263
- **门脚本取材面纪律对账**〔scripts/check-gate-face-discipline.mjs · scripts/lib/face-reader.mjs · blocking · HUSKY_SKIP_GATE_FACE_DISCIPLINE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L264–L266
- **宿主级提示块与第三方内容的边界唯一出口**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L267–L267
- **shared 包非 Node 宿主纯度对账**〔scripts/check-shared-nonde-node-purity.mjs · blocking · HUSKY_SKIP_SHARED_NONDE_PURITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L268–L268
- **跨语言出站路由声明对账**〔scripts/check-declared-outbound-routes.mjs · blocking · HUSKY_SKIP_DECLARED_OUTBOUND_ROUTES〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L269–L269
- **工具名本地化**〔scripts/tests/check-tool-display-resolvable.test.mjs · blocking · HUSKY_SKIP_TOOL_NAME_COVERAGE HUSKY_SKIP_TOOL_DISPLAY_RESOLVABLE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L270–L270
- **i18n JSON 同层重复键守门**〔scripts/check-i18n-duplicate-namespaces.mjs · blocking · HUSKY_SKIP_I18N_DUP_NS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L271–L271

>

- **D3(2026-09-26 G-196 新增):别名导入解析不到文件必拦**〔blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L275–L275

> ⚠️ **上面这段里两个数在 2026-09-26 的三路并行审计中被测错,现值以这一格为准**:渲染位「返回」是 **47 处**
> (不是 49 —— 多出的 2 条,一条是 JSDoc 里逐字引用了 JSX,一条是 `ariaLabel=` 驼峰属性不被 `aria-`/`label=` 滤网命中);
> `‹` 字符是 **2 行**(不是 10;`{'‹'}` 在 HEAD 面 0 处,第 3 处只存在于被并发会话污染的工作树里)。
> 并且"GA4 = 0"要带一条限定:门 102 现有 **1 处自报盲区**(`--all` 点名
> `packages/app/src/features/course-screen/CourseScreen.tsx:113`)—— 跨行自闭合标签会让该处之后的遍历栈失配。
> **所以那个 0 读作"没有已判定的违规",不读作"这一族文件都被看过"**;盲区现在由 `backBlind` 探针点名,不再静默。
> 同一轮还量到"第二/三份自绘返回键"6+3 处、以及 web 端 16 处 `router.back()` 落在守门 46 扫描面之外 ——
> 清单、file:line 与处置顺序在 PROJECT_PLAN 第五十波·续末③ 的 A/B/C/D 四格,**派单前先读它**。

- **锁异常处理**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L286–L287
- **跨语言出站路由声明对账**〔scripts/check-declared-outbound-routes.mjs · blocking · HUSKY_SKIP_DECLARED_OUTBOUND_ROUTES〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L288–L289

### 跨端 UI 单一源:小程序端与 App(RN)端必须长成同一张脸(强制,2026-09-26 立,O81)

- **交付标准**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L293–L293
- **自动生效只有一种实现方式**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L294–L294
- **可见真值仍只有一处**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L295–L295
- **对账**〔scripts/check-cross-end-ui-parity.mjs · scripts/cross-end-ui-parity-baseline.js · blocking · HUSKY_SKIP_CROSS_END_UI_PARITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L296–L296

> ⚠️ 本行原文写的是"圆角与色档**不在本门射程**(守门 77 / 93 各管一段,一台尺子只量一段)"——**那句前提是错的**,而它就是 2026-09-27 用户报"为什么还有那么多地方没按统一圆角"的根因:守门 77 判的是「档位值同源 + 端内不得绕档写死数字」,两端各自规矩引用 token 时它一路报绿(实测扫 6373 文件 0 违规);而"同一个界面元素在小程序取 `rounded-lg`(8px)、在 App 取 `rnRadius.xl`(12px)"这一型**两边都不判** —— 两台尺子互相指认,这一格无人看守。现由 128 独占判 RD 维;`radiusCount` 与 `diffCount` **必须分家立锚点**,否则"修一处几何、换一处圆角"会净零逃逸(守门 134 扩布尔档键时同一课)。

- **差异必须显式登记**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L300–L300
- **不得做的事**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L301–L301

- **可见真值仍只有一处**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L303–L303
- **守门**〔scripts/check-prompt-injection-registry.mjs · scripts/guardian-runner.mjs · blocking · HUSKY_SKIP_PROMPT_INJECTION_REGISTRY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L304–L304

- **parity 绿不等于内容语种对(2026-09-26 立,由 D20 实测逼出)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L306–L306
- **RN 函数形态 style 被 cssInterop 吃掉**〔scripts/check-rn-interop-fn-style.mjs · blocking · HUSKY_SKIP_RN_INTEROP_FN_STYLE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L307–L307
- **RN 函数形态 style 被 cssInterop 吃掉**〔scripts/check-rn-interop-fn-style.mjs · scripts/plan-tasks.mjs · scripts/plan-tasks-merge.mjs · scripts/plan-task-state-baseline.js · blocking · HUSKY_SKIP_RN_INTEROP_FN_STYLE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L308–L311
- **对象空间落地与活文档编辑的常驻出口 = `scripts/object-space-land.mjs` / `scripts/live-doc-edit.mjs` / `scripts/gate-registry-insert.mjs`(+ 共用 plumbing `scripts/lib/bypass-git.mjs`),不是守门**〔scripts/object-space-land.mjs · scripts/live-doc-edit.mjs · scripts/gate-registry-insert.mjs · scripts/lib/bypass-git.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L312–L312
- **共享层重复**〔scripts/check-shared-layer-duplication.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L313–L313
- **品牌邮件通道对账**〔scripts/brand-email-channel-baseline.js · scripts/check-credential-health.mjs · scripts/notify-deploy-failure.ts · blocking · HUSKY_SKIP_BRAND_MAIL_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L314–L314
- **主题接线对账**〔scripts/theme-prop-wiring-baseline.js · scripts/tests/check-theme-prop-wiring.test.mjs · scripts/check-theme-prop-wiring.mjs · blocking · HUSKY_SKIP_THEME_PROP_WIRING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L315–L315
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**〔scripts/tests/check-brand-foreground.test.mjs · scripts/brand-foreground-baseline.js · blocking · HUSKY_SKIP_BRAND_FOREGROUND〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L316–L316
- **反回退对账**〔scripts/guardian-runner.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_STALE_REVERT_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L317–L317
- **硬编码中文基线棘轮**〔scripts/hardcoded-zh-baseline.js · scripts/scan-hardcoded-zh.mjs · blocking · HUSKY_SKIP_HARDCODED_ZH_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L318–L318
- **架构契约对账**〔scripts/check-architecture-policy.mjs · scripts/module-context.mjs · scripts/check-miniapp-generated.mjs · blocking · HUSKY_SKIP_ARCH_POLICY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L319–L321
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {}……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L322–L322
- **记忆端点属主绑定对账**〔scripts/check-memory-owner-binding.mjs · scripts/lib/face-reader.mjs · scripts/check-gate-face-discipline.mjs · blocking · HUSKY_SKIP_MEMORY_OWNER_BINDING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L323–L324
- **对象空间落地与活文档编辑的常驻出口 = `scripts/object-space-land.mjs` / `scripts/live-doc-edit.mjs` / `scripts/gate-registry-insert.mjs`(+ 共用 plumbing `scripts/lib/bypass-git.mjs`),不是守门**〔scripts/object-space-land.mjs · scripts/live-doc-edit.mjs · scripts/gate-registry-insert.mjs · scripts/lib/bypass-git.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L325–L325
- **批量写计数诚实性对账**〔scripts/guardian-runner.mjs · scripts/check-batch-write-count-honesty.mjs · scripts/tests/check-batch-write-count-honesty.test.mjs · blocking · HUSKY_SKIP_BATCH_WRITE_COUNT_HONESTY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L326–L326

- **同一把尺子的另外三维(2026-09-27 补,读数一律现读)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L328–L333
- **ApiResult 失败分支身份对账**〔scripts/check-api-failure-throw.mjs · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_API_FAILURE_THROW〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L334–L335

- **@ 多维提及引擎接线与单一源对账**〔scripts/check-mention-engine-wired.mjs · blocking · HUSKY_SKIP_MENTION_ENGINE_WIRED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L337–L337

- **模式×权限档×审批三轴矩阵对账**〔scripts/check-mode-permission-matrix.mjs · blocking · HUSKY_SKIP_MODE_PERMISSION_MATRIX〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L339–L339
- **状态词汇跨语言与端内抄本对账**〔scripts/check-agent-status-vocabulary-parity.mjs · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_STATUS_VOCABULARY_PARITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L340–L340
- **本轮新增的两维与一条通路规矩(2026-09-29 立,D145 / D142 / D159 的交付)**〔scripts/tests/d159-approval-env-wiring.test.mjs · scripts/data/sse-dispatch-coverage.js〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L341–L341

- **RN 包装器双层页头对账**〔scripts/guardian-runner.mjs · scripts/check-rn-double-header.mjs · scripts/gate-registry-insert.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_RN_DOUBLE_HEADER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L343–L343

- **README 表格完整性对账**〔scripts/guardian-runner.mjs · scripts/check-readme-table-integrity.mjs · scripts/readme-table-unwrap.mjs · scripts/readme-table-integrity-baseline.js · blocking · HUSKY_SKIP_README_TABLE_INTEGRITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L345–L345
- **ApiResult 失败分支身份对账**〔scripts/check-api-failure-throw.mjs · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_API_FAILURE_THROW〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L346–L346
- **硬编码中文基线棘轮**〔scripts/hardcoded-zh-baseline.js · scripts/scan-hardcoded-zh.mjs · blocking · HUSKY_SKIP_HARDCODED_ZH_GUARD〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L347–L347
- **"已登录"不等于"可以动这条数据"(强制,2026-09-27 立,第三十二批实证)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L348–L356
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L357–L357
- **整数主键序列落后对账**〔scripts/check-sequence-lag.mjs · scripts/tests/check-sequence-lag.test.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L358–L358
- **对账**〔scripts/check-cross-end-ui-parity.mjs · scripts/cross-end-ui-parity-baseline.js · blocking · HUSKY_SKIP_CROSS_END_UI_PARITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L359–L359
- **对话流挂载与契约登记对账**〔scripts/check-v3-62-conversation-mount.mjs · scripts/tests/check-v3-62-conversation-mount.test.mjs · blocking · HUSKY_SKIP_V362_CONV_MOUNT〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L360–L360
- **本轮三道新守门（V3 #54 / #80 / #83 的常驻尺子，guardian id 以 runner 现值为准：落地时取到 141 / 142 / 143，并发会话可能挪号；一律 blocking，紧急跳过变量见各条）**〔scripts/check-doom-loop-parity.mjs · scripts/check-guardian-review-wired.mjs · scripts/check-lsp-language-table.mjs · blocking · HUSKY_SKIP_DOOM_LOOP_PARITY HUSKY_SKIP_GUARDIAN_REVIEW_WIRED HUSKY_SKIP_LSP_LANGUAGE_TABLE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L361–L364
- **本轮一并登记的两条口径更正**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L365–L365
- **数字计数徽章强制规范(2026-09-15 立,复发根治)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L366–L366
- **崩溃上报出口脱敏对账**〔scripts/check-crash-report-redaction.mjs · scripts/tests/check-crash-report-redaction.test.mjs · blocking · HUSKY_SKIP_CRASH_REDACTION〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L367–L367
- ⚠️ 本条已被取代(该门现读 guardian id 见 `scripts/guardian-runner.mjs`;现行条是本节内同门最后一条),保留只为不丢行,**勿照本条派单**。〔scripts/guardian-runner.mjs · scripts/check-rn-double-header.mjs · scripts/gate-registry-insert.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_RN_DOUBLE_HEADER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L368–L368
- ⚠️ 本条已被取代(该门现读 guardian id 见 `scripts/guardian-runner.mjs`;现行条是本节内同门最后一条),保留只为不丢行,**勿照本条派单**。〔scripts/guardian-runner.mjs · scripts/check-rn-double-header.mjs · scripts/gate-registry-insert.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_RN_DOUBLE_HEADER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L369–L369
- **README 表格完整性对账**〔scripts/guardian-runner.mjs · scripts/check-readme-table-integrity.mjs · scripts/readme-table-unwrap.mjs · scripts/readme-table-integrity-baseline.js · blocking · HUSKY_SKIP_README_TABLE_INTEGRITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L370–L370
- **README 表格完整性对账**〔scripts/readme-table-unwrap.mjs · scripts/gate-registry-insert.mjs · scripts/check-readme-table-integrity.mjs · scripts/readme-table-integrity-baseline.js · HUSKY_SKIP_README_TABLE_INTEGRITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L371–L371
- **README 表格完整性对账**〔scripts/guardian-runner.mjs · scripts/check-readme-table-integrity.mjs · scripts/readme-table-unwrap.mjs · scripts/readme-table-integrity-baseline.js · blocking · HUSKY_SKIP_README_TABLE_INTEGRITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L372–L373
- **模型容量/推理档位跨语言对账**〔scripts/guardian-runner.mjs · scripts/check-model-capacity-parity.mjs · scripts/tests/check-model-capacity-parity.test.mjs · blocking · HUSKY_SKIP_MODEL_CAPACITY_PARITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L374–L374
- **入参校验装车对账**〔scripts/check-tool-arg-validation-wired.mjs · blocking · HUSKY_SKIP_TOOL_ARG_VALIDATION_WIRED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L375–L375
- **包入口 barrel 漏 re-export 对账**〔scripts/guardian-runner.mjs · scripts/check-package-barrel-export.mjs · scripts/gate-registry-insert.mjs · blocking · HUSKY_SKIP_PACKAGE_BARREL_EXPORT〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L376–L376
- **会话失效出口注册对账**〔scripts/guardian-runner.mjs · scripts/check-auth-handler-registration-parity.mjs · scripts/auth-handler-registration-exemptions.js · scripts/tests/check-auth-handler-registration-parity.test.mjs · blocking · HUSKY_SKIP_AUTH_HANDLER_PARITY〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L377–L377
- **角色档合规对账**〔scripts/check-radius-role-conformance.mjs · scripts/radius-role-conformance-baseline.js · scripts/lib/face-reader.mjs · scripts/lib/radius-tokens.mjs · scripts/lib/code-mask.mjs · scripts/tests/check-radius-role-conformance.test.mjs · blocking · HUSKY_SKIP_RADIUS_ROLE_CONFORMANCE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L378–L378
- **斜杠命令注册到达性对账**〔scripts/guardian-runner.mjs · scripts/check-slash-command-wired.mjs · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_SLASH_COMMAND_WIRED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L379–L379
- **入站契约 .strict() 到达性对账**〔scripts/guardian-runner.mjs · scripts/check-inbound-schema-strict.mjs · blocking · HUSKY_SKIP_INBOUND_SCHEMA_STRICT〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L380–L381
- **ai-service 身份消费对账**〔scripts/guardian-runner.mjs · scripts/check-principal-consumed.mjs · scripts/lib/face-reader.mjs · scripts/tests/check-principal-consumed.test.mjs · blocking · HUSKY_SKIP_PRINCIPAL_CONSUMED〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L382–L382
- **RD 与 RE 是两把不同的尺子,读数不得互相顶结论(2026-09-27 O81 票⑮)**〔scripts/git-lock.mjs · scripts/lib/proc-identity.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L383–L385
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**〔scripts/deploy-lock.mjs · scripts/tests/proc-identity.test.mjs · scripts/git-lock.mjs · scripts/visible-window-probe.mjs · scripts/tests/visible-window-probe.test.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L386–L387
- **引擎 JSON-RPC 通道的同一族规矩(2026-09-27 第五十八批落地)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L388–L389
- **取证包装器 `scripts/run-evidence.mjs`(常驻出口,不是守门、不在提交链)**〔scripts/run-evidence.mjs · scripts/git-push-converge.mjs · scripts/c-disk-breakdown.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L390–L390
- **计划文档 commit 引用可兑现性对账**〔scripts/check-plan-sha-resolvable.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L391–L391
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L392–L392
- **同词不同义的两张名单不得并置在同一个 barrel(强制,2026-09-27 D113 票⑲ 立)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L393–L393
- **经对象空间通道"往上落"时,声明路径的底稿必须取自被审面,不得取自磁盘副本(2026-09-28 立,主会话自己撞上的一型)**〔scripts/object-space-land.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L394–L394

- **临时夹具落点两把只读尺子**〔scripts/check-scratch-root-no-nesting.mjs · scripts/check-fixture-tmpdir.mjs · scripts/lib/scratch-dir.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L396–L396
> **口径(本条的真正约束):凡「盘符 / gitdir 形态 / 远端 URL / 代理」四类,每次使用前按当次实测取值**,不得把另一台机的现状当本机事实写进判据,也不得据此推断"通道坏了"。 —— **本行 2026-09-24 被并发旧基线回写带走过一次,同日原地补注使其重新成为"本侧修改"**
- **禁止创建乱七八糟分支,所有改动统一往 main 合并**〔scripts/check-single-branch.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L398–L401


> ⚠️ 本行是「归档机制」一节里那条现行登记的**重复副本**,且原文数字已被 2026-09-28 实测推翻:默认阈值是 **0 = 完成即归档**(不是"≥7 天"),大批量阀现值是 `MASS_MAX_ENTRIES` / `MASS_MAX_BYTES`(现读 500 段 / 256 KB,且已改为"最旧优先贪心取前缀"而非整批拒绝),而 `--dry-run` 在阀**之前**返回 ⇒ 不判字节。现行文本见本节上方带"(2026-09-28 改)"的那条;凡数字一律跑脚本现读,勿照本行执行(保留只为不丢行,§12)。
- **静默失效双探测(2026-09-28 立;warn / 手动问责,刻意不进提交链,因此**〔scripts/check-service-binary-paths.mjs · scripts/check-public-path-probe.mjs · scripts/git-guardian.mjs · scripts/run-evidence.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L405–L405
- **主机时区漂移对账**〔scripts/check-host-timezone.mjs · warn〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L406–L406
- **教育欠费口径单一出口对账**〔scripts/guardian-runner.mjs · scripts/check-edu-arrears-single-source.mjs · scripts/lib/jsx-scope.mjs · scripts/check-radius-role-conformance.mjs · blocking · HUSKY_SKIP_EDU_ARREARS_SINGLE_SOURCE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L407–L408
- **自动归档**〔scripts/archive-completed-tasks.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L409–L410
> ⚠️ 本行是「归档机制」一节里那条现行登记的**重复副本**,且原文数字已被 2026-09-28 实测推翻:默认阈值是 **0 = 完成即归档**(不是"≥7 天"),大批量阀现值是 `MASS_MAX_ENTRIES` / `MASS_MAX_BYTES`(现读 500 段 / 256 KB,且已改为"最旧优先贪心取前缀"而非整批拒绝),而 `--dry-run` 在阀**之前**返回 ⇒ 不判字节。现行文本见本节上方带"(2026-09-28 改)"的那条;凡数字一律跑脚本现读,勿照本行执行(保留只为不丢行,§12)。
> ⚠️ 本行是「归档机制」一节里那条现行登记的**重复副本**,且原文数字已被 2026-09-28 实测推翻:默认阈值是 **0 = 完成即归档**(不是"≥7 天"),大批量阀现值是 `MASS_MAX_ENTRIES` / `MASS_MAX_BYTES`(现读 500 段 / 256 KB,且已改为"最旧优先贪心取前缀"而非整批拒绝),而 `--dry-run` 在阀**之前**返回 ⇒ 不判字节。现行文本见本节上方带"(2026-09-28 改)"的那条;凡数字一律跑脚本现读,勿照本行执行(保留只为不丢行,§12)。
  - **C5 组件名档(2026-09-28 票㉗)**:根容器按**它自己的**组件名判类别,取证次序 = 元素自身名字/样式键 > 组件名 > 颜色实用类(`bg-card`/`text-*` 只是背景档,不是身份)。两条防假阳的规矩必须同时成立:① **只在 JSX 根启用**,子元素不吃(按钮装在面板里仍是 control);② 归属按 `declarationRanges()` + `ownerOfLine()` 取**最内层声明** —— 不加这一层,同文件内联的小组件会被外层组件名顶判(实测一次造出 79 处假阳,`ModelConfigDialog.tsx` 里的 `RatioSelector` 就是那一型)。名字给不出唯一角色 ⇒ **不启用并逐条报名**(清单在 `--json` 的 componentUndetermined 字段,`--all` 逐行打印),绝不猜一档;该档摘线时镜像 **T19** 必读红,不得被"合规 3211 处"冒充。
    同批修掉两处**取证越界**,它们的症状都不是"多算",而是"把能判的格写成判不出":`classStringsInLine` 原按"距 class 锚点 ≤60 字符连采 3 段字符串"收类名,会把**兄弟属性**的值(`data-testid="plan-review-panel"`)当类名收,凭空造出一条类别证据去撞 `bg-card` ⇒ HEAD 唯一那处 role-conflict 就是这么来的;现要求两段字面量之间只可能是 `cn(` / `,` / `||` / `?` / `+` 这类表达式连接符,出现 `=` / `>` / `}` 即停(镜像 **T21** 成对:兄弟属性不得采、同属性第二段不得漏)。`jsx-scope` 词法器原把 `return <X/>`(不套括号)的前一词元当标识符 ⇒ 判成泛型/比较式,整棵子树不建元素而只计 corrupt;现按"可接表达式的关键字"(return/yield/await/else/do/throw/in/of)放行,同时 **`a < b` 必须仍不建元素**(镜像 **T20** 成对,幽灵祖先比漏判更贵)。修后 HEAD 面 corrupt **443→344**(99 个文件的容器维回到可判),并同批清偿 C5 新露出的 6 处 panel 根容器(md/lg→xl)。
    问责入口 `pnpm check:radius-role`(走 `--strict`);`--strict` 现 exit 0 —— 未判定归零之后,这道门不再拒绝出合格证。**还有一格刻意不做**:`弱证据待裁` 那一族(类别信号只有颜色实用类、元素无名也非组件根)不得批量改档 —— 那是拿 20–70% 判错率的证据重新设计界面,正解是给它补真实身份,门只逐条报名。



- **迁移正文不可变对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚注册取到 **185**,并发会话可能挪号;blocking,`stagedTriggers=packages/database/ + apps/api/src/`,紧急跳过 `HUSKY_SKIP_MIGRATION_IMMUTABLE=1`):`scripts/check-migration-immutable.mjs` 拦的是"**一条已进 journal、已在别人库上跑过的 .sql 被就地改正文**"—— 同一版本号于是有两份 schema,旧库不会重放、新库拿到另一份,而账面仍然"迁移都跑成功"(drizzle 只按 journal 的 `when` 与库内 hash 记账,**从不回看 .sql 正文**)。此前本仓只有散文约定;而且不是假想:`215d4c1e10`(2026-09-26)就改掉过 `20260927100000_tenant_rls_policies_batch1.sql` 首次入库时的正文,那次改动本身有理,但它证明的是"这条路真会走到",走到之后没有任何东西会红。两条判据对照面不同、**不得互相顶账**:**IM1** 被审面正文与该路径历史上第一次以 `A` 出现时的 blob 比,**存量只报数**(`--strict` 才问责 —— 与本次提交无关的恒红门只会逼人跳门)/ **IM2** 索引面 vs HEAD 面,本次把已入库正文改了 ⇒ 判红(这条不需要基线清单,也不会因存量恒红)。**归一化三条口径**是"只改水印/空白必绿、加一行注释必红"的前提:① 先剥水印结构行(唯一实现 `scripts/lib/watermark-lines.mjs` 的 `stripWatermarkStructure`,本门不得再抄一份正则)② 逐行 `trim` ③ 丢掉归一后为空的行。**守门 49 在飞的 B11 与本门是两问**:B11 问"库内 `__drizzle_migrations.hash` 与当前正文配不配"(要连库,本机长期未判定),本门问"当前正文与 git 里首次入库那次提交配不配"(只需对象库,提交链跑得动)—— 两边都判红**不是重复计账**,接线时不得摘掉其中一条。取材同 70/77/83/98/101/118:全量判 HEAD blob、`--staged` 判索引、`--worktree` 仅人工、两面旗同给 exit 2、清单与内容同面同轮、取不到判"未判定"**不回落**、**枚举到 0 判死不记绿**。阳性对照**钉出处不钉 HEAD**:显式 SHA `0f94717464` vs `215d4c1e10` 从对象库现取喂同一判据必红、同 SHA 自比必绿(T4)—— 账还清那天"HEAD 上还能量到存量"这句话当场失效。取证 `--self-test` 21 条 + §22c 镜像 `node --test scripts/tests/check-migration-immutable.test.mjs` 10 例(主会话现跑复验:自检 RC=0 / 镜像 pass 10 fail 0 / `--check`、全量与 `--staged` 三面均 RC=0,两旗同给 RC=2)。

- **受管 `.env` 键值被清空对账**(`scripts/check-env-drift.mjs`,2026-09-26 立 G-223;**定级=守护巡检档,刻意不在提交链**,因此**没有**紧急跳过变量,问责跑 `node scripts/check-env-drift.mjs`;调度器 = `scripts/git-guardian.mjs` 每 2 分钟一轮,必须挂在 `!CHECK_ONLY` 分支、健康轮次早退之前 —— 挂错位置等于永不执行,该文件已踩过两次):量的是"**这台机上的 `.env` 此刻有没有被整体替换清空**"这一维 —— 它判的是**机器状态**而不是提交内容,挂 blocking 就是每台每次被逼 `--no-verify` 连带废掉全部守门(§12e)。立因是实测:`apps/api/.env` 曾被一次按时间戳的整体复制清空 55 个键值,而症状**什么都不响** —— `DATABASE_URL`/`JWT_SECRET` 恰好非空所以应用不崩,唯一到人通道(邮件)静默寄不出去,只在 `deploy-loop.log` 留一行 `MAIL 两条通道均未送达`。取证 `--self-test` + 镜像 `scripts/tests/check-env-drift.test.mjs`。**这一格此前在文档面完全隐形**(守门 89 的 R4 就是为此而存在:已接线而无人点名,门会被重复造或绕)。
- **元运维巡检轮**(`scripts/check-ops-patrol.mjs`,2026-09-29 立;**告警档,刻意不进提交链**,无紧急跳过变量;调度器 = `scripts/git-guardian.mjs` 的巡检 tick;问责/取证 `--self-test`、`--strict`、镜像 `scripts/tests/check-ops-patrol.test.mjs`):回答"**看守者本身还活着吗**"。本仓 190+ 道门全在判仓库内容,而下面这些故障的共同形态是"某件事**没有发生**",账面完全安静:P1 Prometheus 运行副本落后于仓库源且新规则从不热加载(实测线上少一条)/ P2 Alertmanager 生效副本与仓库模板漂移(多出 `max_alerts: 50`、缺整段维护窗口抑制 —— 与 §5e"禁止自设总量上限"直接冲突)/ P3 主机时钟连续约 12 小时同步失败而所有"多久一次/去重窗口"判据都踩着它 / P4 `D:\DevEnv` 的 Temp·logs·archives 无任何上限保护 / P5 部署环·备份·凭据巡检·网盘同步的心跳无人判"多久没响" / P6 告警规则**引用根本不存在的指标序列**(⇒ 那条规则永远不会响而台账读成"这一维已覆盖")。定级理由同上:判机器运行时状态的挂进提交链就是恒红门。
- **孤儿删除的引用对账**(`scripts/check-orphan-deletion-refs.mjs`,2026-09-29 立;**warn/告警档,刻意不进提交链**,无紧急跳过变量;调度器 = `scripts/git-guardian.mjs`;取证 `--self-test`,**尚无 §22c 镜像测试 —— 那一格归本门持有人**):拦的是这一型的**前置条件** —— 索引与磁盘里已没有某文件,而 HEAD 的树里仍有人引用它。立因是 09-29 一晚两次把生产 web 构建打挂(两轮各 4 次失败,线上停在 03:47 那版),第一现场报 `wxt build`/`next build` 失败,真凶是 9–10 个处于 `D `(已暂存删除)态的跟踪文件,而构建取的是**工作树**。为什么既有判据看不见:守门 98 判 HEAD、守门 99 判索引,本型恰在**两面之间**;而旁路落地(commit-tree + CAS)不跑钩子,提交链上那两道门对这批提交从未执行;`heal-worktree-tracked` 一直如实报数,但"报数"在没人看日志时等于安静。

### 内部机器调用的豁免必须"验过才免"(强制,2026-09-28 立)
- **规矩**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L420–L420
- **唯一出口**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L421–L421
- **同一条豁免与它所跳过的校验必须共用同一个判定实现**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L422–L422
- **越权用例必须断言"未发出查询/未产生副作用"**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L423–L423
- **已知无尺子的一格**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L424–L424
### 会话状态:登出标记必须比凭据活得久、且不同层(强制,2026-09-28 立)
- **规矩**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L426–L426
- **判据唯一出口**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L427–L427
- **写序**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L428–L428
- **待收口**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L429–L429
- **待收口**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L430–L430
- **迁移记账结构守门**〔scripts/check-migration-bookkeeping.mjs · scripts/lib/face-reader.mjs · blocking · HUSKY_SKIP_MIGRATION_BOOKKEEPING〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L431–L431
- ⚠️ 本行是「待收口:web 与小程序同型未收」那条规则在**并发并集**下留下的副本(两份只差台账号),规则正文以带 `PROJECT_PLAN G-370` 的那一行为准;此处只留痕不删行(§1 禁止无声删除),而失效的 `G-363` 指针必须消掉 —— 按旧号找到的是别人那一格,比没有指针更……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L432–L432
- **旁路落地(object-space / commit-tree)不跑钩子 ⇒ "HEAD 是否绿"是调用方自己的责任**〔scripts/object-space-land.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L433–L433
- **守门 150 的三条后续(2026-09-28 O81 票㉘/㉙,现行口径以这一格为准)**〔scripts/lib/box-geometry.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L434–L454
- **自动归档**〔scripts/archive-completed-tasks.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L455–L456
- **自动归档**〔scripts/archive-completed-tasks.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L457–L457
- **按凭据存在性豁免对账**〔scripts/guardian-runner.mjs · scripts/check-credential-presence-bypass.mjs · scripts/data/credential-presence-exemptions.js · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L458–L459
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L460–L460
- **凭据在场即豁免对账**〔scripts/guardian-runner.mjs · scripts/check-gate-presence-exemption.mjs · scripts/lib/code-mask.mjs · blocking · HUSKY_SKIP_GATE_PRESENCE_EXEMPTION〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L461–L467
- **SVG 圆角同样受管(B5)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L468–L468
- **真圆/胶囊:靠量出来的形状,不靠标记(2026-09-29 O81 票㊵ 收口)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L469–L469
- **宽度下限不是定值(2026-09-29 O81 票㊼ 立)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L470–L477
- **跳门次数只有一个真值来源:`.workbuddy/safe-commit-attestation.jsonl`**〔scripts/alert-volume-report.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L478–L478
- **图源池与境外图片 URL 对账**〔scripts/check-image-source-domains.mjs · scripts/tests/image-source-pool-parity.test.mjs · blocking · HUSKY_SKIP_IMAGE_SOURCE_POOL〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L479–L479
- **半径上限:一刀切 ≤ 2xl(16px)(2026-09-29 用户定档)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L480–L480
> ⚠️ 本行是 §4「真圆/胶囊:靠量出来的形状,不靠标记」那条现行登记的**重复副本**,且它教的通道已被 2026-09-27 O81 票㊵ **整体废除**(用户定档:不允许任何豁免、不允许胶囊型)—— 照本行去写 `radius-exempt` 会被守门 77 的 B8 直接判红。现行文本见上文那条,勿照本行执行;保留只为不丢行(§12)。
- **任务状态一致性(2026-09-26 立,守门 130 配套)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L482–L482
**三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 独立不可见行(注入时落在文件末尾)。
**判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/**`仅隐写`(有可解码载荷但可见横幅整块不见)**/ `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
- **为什么必须有「仅隐写」这一态**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L485–L485
- **L3 的位置不是不变量,"独立成行且可解码"才是**〔scripts/watermark.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L486–L486
- **切片不得把水印结构行搬进别的文件**〔scripts/lib/watermark-lines.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L487–L487
- **合并吞并的修复出口 `scripts/union-converge.mjs`**〔scripts/union-converge.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L488–L488
- **上限判据必须量得到"跨行书写的盒子"(2026-09-29 定档)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L489–L489
- **测不到的尺寸:上限由构造封顶,不由推断放行(2026-09-29 定档)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L490–L490
- **HEAD 悬空具名导入对账**〔scripts/check-dangling-local-imports.mjs · blocking · HUSKY_SKIP_DANGLING_IMPORTS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L491–L491

- **收敛链的三道"崩溃 ≠ 裁决"口径(2026-09-29 立,G-815406 / G-814402 / G-815922 三票同批落地)**〔scripts/git-sync-converge.mjs · scripts/union-converge.mjs · scripts/lib/plan-task-index.mjs · scripts/tests/union-converge.test.mjs · scripts/tests/git-sync-converge.test.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L493–L493

- **维护性写归属/CAS/回报计数三态对账**〔scripts/guardian-runner.mjs · scripts/check-write-owner-predicate.mjs · blocking · HUSKY_SKIP_WRITE_OWNER_PREDICATE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L495–L495
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L496–L496
- **计划文档 commit 引用可兑现性对账**〔scripts/check-plan-sha-resolvable.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L497–L497
- **工作队列必须有死亡机制(强制,2026-09-29 立)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L498–L498
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L499–L499
- **源码替换符(U+FFFD)对账**〔scripts/guardian-runner.mjs · scripts/check-replacement-chars.mjs · scripts/tests/check-replacement-chars.test.mjs · blocking · HUSKY_SKIP_REPLACEMENT_CHARS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L500–L500
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**〔scripts/deploy-lock.mjs · scripts/tests/proc-identity.test.mjs · scripts/git-lock.mjs · scripts/visible-window-probe.mjs · scripts/tests/visible-window-probe.test.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L501–L501
- **排空步骤已在提交链上(2026-09-28 接)**〔scripts/ledger-strip-open-rows.mjs · scripts/git-sync-converge.mjs · scripts/tests/converge-object-presence-g473.test.mjs · scripts/watermark.mjs · blocking · HUSKY_SKIP_LEDGER_STRIP HUSKY_SKIP_ARCHIVE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L502–L510
**判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ **`仅隐写`(有可解码载荷但可见横幅整块不见)** / `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
- **为什么必须有「仅隐写」这一态**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L512–L512
- **L3 的位置不是不变量,"独立成行且可解码"才是**〔scripts/watermark.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L513–L513
- **切片不得把水印结构行搬进别的文件**〔scripts/lib/watermark-lines.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L514–L514
- **RN 包装器双层页头对账**〔scripts/guardian-runner.mjs · scripts/check-rn-double-header.mjs · scripts/gate-registry-insert.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_RN_DOUBLE_HEADER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L515–L515
- **RN 包装器双层页头对账**〔scripts/guardian-runner.mjs · scripts/check-rn-double-header.mjs · scripts/gate-registry-insert.mjs · scripts/heal-worktree-tracked.mjs · blocking · HUSKY_SKIP_RN_DOUBLE_HEADER〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L516–L516
- **旁路落地/跳门留痕的写入者有四家,合起来才是总量:`.workbuddy/safe-commit-attestation.jsonl`**〔scripts/plan-bypass-ledger-report.mjs · scripts/alert-volume-report.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L517–L517
- **跨端 Button 共享档位值的唯一源(2026-09-29 O92 立)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L518–L518
- **图片占位内容对账**〔scripts/check-image-placeholders.mjs · blocking〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L519–L519
- **活文档「按旧副本提交会写回他人已入库行」对账**〔scripts/guardian-runner.mjs · scripts/check-live-doc-pathspec.mjs · scripts/lib/live-doc-classify.mjs · blocking · HUSKY_SKIP_LIVE_DOC_PATHSPEC〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L520–L520
- **turn 序号分配点上锁对账**〔scripts/guardian-runner.mjs · scripts/check-turn-ordinal-lock.mjs · scripts/lib/code-mask.mjs · scripts/tests/check-turn-ordinal-lock.test.mjs · blocking · HUSKY_SKIP_TURN_ORDINAL_LOCK〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L521–L521
- **唯一例外位:输入框/选择控件的聚焦态描边取墨档(2026-09-30 用户定档)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L522–L522
- **web 侧曾经整族不渲染的 border 颜色工具类已根治(2026-09-30,`globals.css` 那条 `*{border-color}` 已移进 `@layer base`,枚 `dfa31872ca`,G-978058 已收口)**……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L523–L523
- **角色档判据必须能"够到"真实写法,否则账面一路报绿(2026-09-30 实测并修)**〔scripts/lib/radius-roles.mjs · scripts/check-radius-role-conformance.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L524–L524
- **跨端 Button 共享档位值的唯一源(2026-09-29 O92 立;同日按用户拍板改成「基座 + 追加」)**〔scripts/tests/shared-button-tier-base.test.mjs · scripts/run-evidence.mjs · scripts/object-space-land.mjs · scripts/lib/stale-content-analysis.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L525–L526
- **经对象空间通道"往上落"时,声明路径的底稿必须取自被审面,不得取自磁盘副本(2026-09-28 立,主会话自己撞上的一型)**〔scripts/object-space-land.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L527–L527
- **聚焦描边的唯一实现(2026-09-30 加法补记,不改写上一行)**〔scripts/check-cross-end-ui-parity.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L528–L529
- **旁路落地/跳门留痕的写入者有四家,合起来才是总量:`.workbuddy/safe-commit-attestation.jsonl`**〔scripts/plan-bypass-ledger-report.mjs · scripts/alert-volume-report.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L530–L530
- **但 web 侧的 `focus:border-*` / `border-input` 这类 Tailwind 边框颜色工具类今天整体不渲染(2026-09-30 实测;整站收口是待决票 G-978058,须用户拍板)**〔scripts/check-cross-end-ui-parity.mjs〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L531–L532
- **合并复活已删文件对账**〔scripts/check-merge-deletion-resurrection.mjs · scripts/data/deletion-survival-allowlist.js · scripts/lib/face-reader.mjs · blocking · HUSKY_SKIP_MERGE_DELETION_RESURRECTION〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L533–L533
- **AI 面板装载守门**〔scripts/check-ai-panel-mount-guards.mjs · blocking · HUSKY_SKIP_AI_PANEL_MOUNT_GUARDS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L534–L534
- **门脚本取材面纪律对账**〔scripts/check-gate-face-discipline.mjs · blocking · HUSKY_SKIP_GATE_FACE_DISCIPLINE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L535–L535
- **门脚本取材面纪律对账**〔scripts/check-gate-face-discipline.mjs · blocking · HUSKY_SKIP_GATE_FACE_DISCIPLINE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L536–L536
- **旁路落地声明面对账**〔scripts/check-bypass-landing-scope.mjs · scripts/tests/check-bypass-landing-scope.test.mjs · blocking · HUSKY_SKIP_BYPASS_LANDING_SCOPE〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L537–L537
- **跳门变量承诺/兑现对账**(172,blocking,紧急跳过 `HUSKY_SKIP_SKIP_ENV_PROMISE=1`):`scripts/check-skip-env-promise.mjs` —— 文档(AGENTS.md/README.md)承诺的 `HUSKY_SKIP_X=1` 应急出路必须有人真读:全仓零读点 = 假承诺,按了静默无效,唯一出路退化成 `--no-verify`,一次绕过等于全部守门对该提交作废(§12e 同型)。承诺面×兑现面(六目录代码)双面对账,读点语法 7 档(process.env / env[""] / skipEnv 注册块 / sh $NAME / ps $env: 等),JS 读点必在 code-mask 剥注释面找(否则门给假通道发合格证);三态不并桶(wired/fake/undetermined);差值棘轮只拦新增 fake(存量 --strict 问责档才判);0 承诺判死 exit 2;行内出口 `skip-env-promise-exempt: <原因>`(须带原因)。自检与镜像例数以命令末行为准。2026-09-30 由持有人会话接线(44cddee6e3 / 8ea4ecb95b),落账(5fe67b1ab7)未同步点名行 ⇒ 本行由 slice-A 收尾会话补登;补登时以 stem 级口径(`findUndocumentedGates` 去后缀匹配)静态复算 R4 文档面全通过。
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\``式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟`#`像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人`--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged`判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证`--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace`返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒`--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- **i18n**(2/2b/2c/2d/2e/2f/2g-web/2f-mobile-rn/2f-cli):check-i18n-keys(parity+白名单)/ scan-i18n-zh-residue(zh-TW/ko 阻塞,ja warn)/ check-i18n-broken-en(阻塞)/ i18n-diff(翻译流水线,2f-web + 2f-miniapp-taro 阻塞)/ check-i18n-namespace-passing(命名空间传递,warn)/ check-cli-i18n-parity(cli 端 parity,warn,2f-cli)
- React 事件处理函数(`onClick={e => ...}` 等)**同步作用域内** `e.target` / `e.currentTarget` 有效。
- 一旦进入异步回调(`setTimeout` / `setInterval` / `requestAnimationFrame` / `requestIdleCallback` / `queueMicrotask` / 未捕获 `e` 的 promise)或 memoized / 延迟执行的闭包场景,SyntheticEvent 会被事件池复用/清空,`e.target` / `e.currentTarget` 失效或为 `null`(React 17+ 不再 pooled,但 `currentTarget` 一定在 handler 返回后置 null)。
- **为什么住在本文而不是任务行**:这三条补强原登记为"写入既有任务描述(D47/D55/D40/D24/D44/D65)",
  但 2026-09-27 现读那六条目标行**全部已是已完成形态,且同主键存在 4–5 份归并副本** —— 往其中一份
  追加规格 = 只有那一份副本带规格,读另一份的人拿到的是缺规格里,这正是 §1 判据 F1/F4 在防的形态。
  规格必须落**单一权威位置**,而本仓的单一权威位置就是本文;任务行只负责引用它。
- **两套撤销语义必须分离**:代码的 `rollback`(回到上一个检查点)与会话/问答的 `revert`(撤销一次
  回答及其副作用)是两件事,**不得共用一个"撤销"标签或同一个按钮**。代理被机器代批拒绝后,
  必须留**人工放行**入口 —— 拒绝是一次判定,不是永久禁止;没有放行入口就等于把风险决策静默成"不允许"。
- **子智能体 / 后台进程的状态词汇是一等契约**:子智能体六态、后台进程六态、`阶段性回复` 三键,以及
  `输出过长，当前仅保留最新内容。` 这条截断提示,都必须走 i18n 五语言同批补齐(§19 的 parity 口径),
  **不得在端内硬编码中文**。新增一个状态就必须同枚提交补齐五语言词表,否则端上表现是"一半语言显示键名"。
- **钩子无终态不得渲染成"完成"**:`hook_non_blocking_error` 这类"未记录最终结果"的形态,呈现层必须
  落一个明确终态(成功 / 失败 / 取消 三选一)。**没有终态就写"已完成"是本仓最高频的失效型**
  —— 账面绿而实际没人知道那一步发生了什么(与守门 118"把没判写成判过了"同一条禁令)。
- **自动档写活文档必须按被审面取底稿,并自带"状态不得倒退"的零损失断言(强制,2026-09-28 立,近 24h 实测 18 行翻勾被退回逼出)**:§12 那条"提交活文档前做工作树 ⊇ HEAD 行级对账"只管**人写的提交**;而挂在 post-commit 上**每枚提交都跑**的自动档(归档器 `scripts/archive-completed-tasks.mjs`、台账自愈/归并器)取的是**磁盘副本底稿**并整文件写回 —— 活文档的磁盘副本在这台机上常年滞后 HEAD,于是它一次"例行归档"就能把别人**当日已入库并复验过的翻勾**整批退回未勾。实测读数:近 24h 内 473 枚触及 `PROJECT_PLAN.md` 的提交里,**18 行**同一**复合主键**(编号 + 标题前缀逐字等值;只按编号配对会把"归并指针行 + 原件行"误判成回退,那是假账)出现 done→open 回退,其中 **10 行集中在同一枚** `chore(auto): 归档 …`(`b54c79c36`,单枚改动台账 4249 行)。它不产生冲突、`git status` 只显一个 `M`,而门 71/130 各自只看得见自己那一维 —— 分叉再由归并/回捞层成批补提交,即"两层自愈互咬"的上游。
  - **三条不可漂的写法**:① 底稿一律取被审面(`git show HEAD:<文档>` 或 `scripts/lib/face-reader.mjs` 的 `catBatch`,**不得自拼 git 读内容**,见守门 118);② 落盘走对象空间(临时索引 + `commit-tree` + CAS,声明路径只有那一个文档),**工作树副本属于他人在飞时逐字不动磁盘并点名归属**;③ 落地前自证"父提交里每一条 `[x]` 行的复合主键,在本次产出的同一面里仍是 `[x]`"(被归档搬走的按 §1 两步走豁免:原位置留占位 + 占位点名的文件必须真在本次提交面上),不成立就**拒绝落地**,不许"先写再让回捞层补"。
  - **为什么不能只靠"记得先跑 `merge-live-doc`"**:自动档没有人工那一步对账,而且它的产出读起来像正常提交(`chore(auto)`);判据失效的表现永远是安静。同类"造好没人跑/跑了没人判"的盲区见守门 64 / 70 / 81 / 105 / 115 —— **判据必须在真跑它的那一刻才成立,否则等于没有。**
- **唯一写法**:主按钮、选中胶囊、悬浮加号这类"品牌实底 + 其上文字",RN/共享包写 `brand.DEFAULT`(底)+ `brand.foreground`(文字)**成对**;CSS/类名侧用 `--color-primary` + `--color-primary-foreground`(小程序 `CategoryBar`/FAB 同值)。这两档在两主题下逐位同值(亮 `#000/#fff`、暗 `#fff/#000`),对账由「跨端色值同源对账」守门负责(编号以 `scripts/guardian-runner.mjs` 现值为准,勿照抄文档)。
- **唯一写法**:主按钮、选中胶囊、悬浮加号这类"品牌实底 + 其上文字",RN/共享包写 `brand.DEFAULT`(底)+ `brand.foreground`(文字)成对;CSS/类名侧用 `--color-primary` + `--color-primary-foreground`(小程序 `CategoryBar`/FAB 同值)。这两档在两主题下逐位同值(亮 `#000/#fff`、暗 `#fff/#000`),对账由守门 90 负责。
- 悬空引用由该门的"品牌键"判据直接拦(编译不一定红,运行时是 `undefined` 颜色)。并行会话的暂存区里若还残留 `tokens.brand.ctaFill/ctaText`,改法只有一行:`ctaFill` → `DEFAULT`、`ctaText` → `foreground`。
- **清单↔锁 specifier 对账**(101):`scripts/check-lock-manifest-consistency.mjs`(blocking,2026-09-24 立)—— 堵的是**依赖真值**这一整类"本地全绿、生产构建必炸":`package.json` 声明与 `pnpm-lock.yaml` 的 `importers.<pkg>` specifier 不一致时,pnpm **整段跳过该包的链接步骤**(不报错、不改锁、`pnpm install` 与 `pnpm install --force` 都只回 "Already up to date"),本地 node_modules 早就装好 ⇒ typecheck/lint/单测/其余守门全绿,直到 bundler 报 Module not found 打死生产构建。立因实例:当天 `apps/web` 写 `"xlsx": "^0.18.5"` 而锁记 `npm:@e965/xlsx@^0.20.3`,症状是"97 个声明依赖精确缺 3 条"——**只缺某一个 importer 的全部新增项**这个形状本身就是指纹(第一反应容易误判成"stale dist / 服务进程锁住文件",两者当日都被实测证伪:短停 IHUI-WEB 后 `--force` 仍 285ms 回绿)。三条判据 R1 缺记账 / R2 specifier 不等 / R3 反向孤儿**只报数不判红**;两个必须建模的 pnpm 合法行为,否则 22 枚存量误红逼人绕过钩子:**维度 A** 读 `pnpm-workspace.yaml` 的 `overrides`(真仓 50 条,裸名与 `pkg@<=版本` 两形态),lock 等于 override 目标即放过(19 条),但**严格侧保留**——某依赖只有唯一一条裸名 override 而 lock 仍停在 manifest 原值 ⇒ 判 `override-not-applied` 红(否则维度 A 就成了无条件放过);**维度 B** peerDependencies 不比 specifier(pnpm 记进 devDeps 段且写解析后的范围,3 条),但仍要求"任一段有条目",缺条目照红。**口径是本门生命线**:`--staged` 判**索引 blob**、全量判 **HEAD blob**(同 70/77/83/98/99),只允许同一轮读同一个面(含包清单枚举也走同一面,否则"glob 读盘 + 内容读 git"会造出自洽但基准错位的假绿尺子),`--worktree` 仅作人工排查逃生舱、两个面旗标同时给 ⇒ exit 2;取不到(缺路径/unmerged/非 blob/二进制/git 失败/列出 0 路径/toplevel 不匹配)一律 **exit 2 显式"无法判定"并点名路径**,绝不记绿。不挂 `stagedTriggers` —— 前缀语义表达不出"任意 `*/package.json`",而漏挂等于没有这道门(守门 81 教训),实测 282ms/342ms,每轮跑得起。取证 `--self-test` 34 例 + §22c 镜像测试 24 例,含三条端到端**假绿/假红钉死**对照(临时 git 仓:索引坏而盘上好 ⇒ staged 必红;索引好而盘上别人半编辑 ⇒ staged 必绿;该面取不到 ⇒ exit 2)与"改写真 decision/真事故值必红"的阳性对照;写门过程中自检另抓出 `splitOverrideKey` 把 13 条 `>=` 版本选择器误判成父作用域(整类隐身)这一自造缺陷。紧急跳过 `HUSKY_SKIP_LOCK_MANIFEST_GUARD=1`。
- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 四条判据:**R1** 零豁免,同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);**R4** 同一关系被拆到**兄弟 key**(`retryBtn` × `retryText`)时按命名配对判,走基线棘轮;**R2** `surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json` 只减不增;**R3** 数 brand.DEFAULT 填充/描边但**不计 §4 成对 CTA**(同块或兄弟键用 `brand.foreground` 作前景)—— 见本节上方「品牌 CTA / 主按钮色同源」,按规矩写就红的门只会逼人 `--no-verify`;**无配对的白卡片照旧计**(自检里两条阳性对照钉住)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会在恒红/假绿间来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。 ⚠️ 本行为 2026-09-24 就地改写**前**的原文,保留以不丢行(§12);现行文本见下方「品牌实底前景/容器对账(RN + 共享包 + web 类名面)」条,勿照本行执行。
- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— R1 零豁免:同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);R2 基线棘轮:`surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json`(现 13 文件 24 处,均为媒体上的合法浮层或已带 `dark:` 变体)只减不增。`--staged` / `--update-baseline` / `--self-test`(11 例);紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。 ⚠️ 本行为 2026-09-24 就地改写**前**的原文,保留以不丢行(§12);现行文本见下方「品牌实底前景/容器对账(RN + 共享包 + web 类名面)」条,勿照本行执行。
- **mobile-rn 深色前景/容器对账**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 四条判据:**R1** 零豁免,同一 style 块内 `tokens.brand.DEFAULT` 作背景 × `surface.light`/`text.primary` 作前景(深色档案下 brand.DEFAULT=纯白 ⇒ 白底白字);**R4** 同一关系被拆到**兄弟 key**(`retryBtn` × `retryText`)时按命名配对判,走基线棘轮;**R2** `surface.light` 背景 / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white` 作容器底,每文件计数对 `scripts/brand-foreground-baseline.json` 只减不增;**R3** 数 brand.DEFAULT 填充/描边但**不计 §4 成对 CTA**(同块或兄弟键用 `brand.foreground` 作前景)—— 见本节上方「品牌 CTA / 主按钮色同源」,按规矩写就红的门只会逼人 `--no-verify`;**无配对的白卡片照旧计**(自检里两条阳性对照钉住)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会在恒红/假绿间来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。 ⚠️ 本行为 2026-09-24 就地改写**前**的原文,保留以不丢行(§12);现行文本见下方「品牌实底前景/容器对账(RN + 共享包 + web 类名面)」条,勿照本行执行。
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 五条判据,`brand.DEFAULT` 与 `brand.cta` **同形认**(只认 DEFAULT 会让迁移后的主实底整片隐身 —— 判据必须覆盖门自己产出的形态):**R1** 零豁免,同块内 `backgroundColor: (tokens|tk).brand.(DEFAULT|cta)` × `surface.light`/`text.primary` 作前景;**R4** 同一关系拆到**兄弟 key**(`retryBtn` × `retryText`)按命名配对判,走棘轮(现 127 处);**R2** 拦"浅色当容器底"(`surface.light` / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white`),棘轮现 24 处;**R3** 数品牌实底填充/描边,**按档配对**免债:DEFAULT 底 ↔ `brand.foreground`、cta 底 ↔ `brand.ctaForeground`,**跨档配对仍计**(DEFAULT 底 × ctaForeground 深色档案下=白底白字),棘轮现 235 处;**R5** web/ui-react 的 **Tailwind 类名形态**(R1–R4 全解析 RN style 对象,对 `bg-primary`+`text-primary-foreground` 这一 §4 已废配对原本整侧盲视),同行成对才算,变体前缀(`data-[state=checked]:bg-primary`)与染色底(`bg-primary/10`)不算,`r5-cta-exempt: 原因` 豁免,基线 `webClassPairCounts` **现为空 = 零容忍**(立项实测 22 处/14 文件,含 `@ihui/ui-react` Button 的 6 个 variant,当日由并行提交 `4e0b24689a` 迁完)。**R5 两条已知限制如实登记**:跨行配对不计(宁窄不误)、变体前缀不算实底(排除前缀的字符类与排除 `/90` 的是同一个,放宽任一即破坏口径)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会恒红/假绿来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。基线 6 键:`counts`/`ctaCounts`/`r4Counts`/`webClassPairCounts` + 两个文档性注记键(`pairedCtaNotVisibleToRule`/`ctaFillRenameLedger`)—— `--update-baseline` **必须保留注记键**,旧写法整文件重写会把他人审计台账冲掉(本轮并入远端时实测踩过)。取证:`--self-test` **94 条断言**(含 3 条阳性对照 + 变异对照)+ `node --test scripts/tests/check-brand-foreground.test.mjs` **13 例**;`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 五条判据,`brand.DEFAULT` 与 `brand.cta` **同形认**(只认 DEFAULT 会让迁移后的主实底整片隐身 —— 判据必须覆盖门自己产出的形态):**R1** 零豁免,同块内 `backgroundColor: (tokens|tk).brand.(DEFAULT|cta)` × `surface.light`/`text.primary` 作前景;**R4** 同一关系拆到**兄弟 key**(`retryBtn` × `retryText`)按命名配对判,走棘轮(现 127 处);**R2** 拦"浅色当容器底"(`surface.light` / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white`),棘轮现 24 处;**R3** 数品牌实底填充/描边,**按档配对**免债:DEFAULT 底 ↔ `brand.foreground`、cta 底 ↔ `brand.ctaForeground`,**跨档配对仍计**(DEFAULT 底 × ctaForeground 深色档案下=白底白字),棘轮现 235 处;**R5** web/ui-react 的 **Tailwind 类名形态**(R1–R4 全解析 RN style 对象,对 `bg-primary`+`text-primary-foreground` 这一 §4 已废配对原本整侧盲视),同行成对才算,变体前缀(`data-[state=checked]:bg-primary`)与染色底(`bg-primary/10`)不算,`r5-cta-exempt: 原因` 豁免,基线 `webClassPairCounts` **现为空 = 零容忍**(立项实测 22 处/14 文件,含 `@ihui/ui-react` Button 的 6 个 variant,当日由并行提交 `4e0b24689a` 迁完)。**R5 两条已知限制如实登记**:跨行配对不计(宁窄不误)、变体前缀不算实底(排除前缀的字符类与排除 `/90` 的是同一个,放宽任一即破坏口径)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会恒红/假绿来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。基线 6 键:`counts`/`ctaCounts`/`r4Counts`/`webClassPairCounts` + 两个文档性注记键(`pairedCtaNotVisibleToRule`/`ctaFillRenameLedger`)—— `--update-baseline` **必须保留注记键**,旧写法整文件重写会把他人审计台账冲掉(本轮并入远端时实测踩过)。取证:`--self-test` **94 条断言**(含 3 条阳性对照 + 变异对照)+ `node --test scripts/tests/check-brand-foreground.test.mjs` **13 例**;`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。
- ⚠️ 本行原为守门 83 的**第三条重复登记**,且把取证量写死成"`--self-test` 11 例"(实为 94 条断言)。2026-09-24 就地改写为指向条:该门的现行条目见**上方两条**(编号 83,判据 R1–R5);保留本行只为不丢行,勿照它执行。
- **守门 83 追加 R7(2026-09-25,JSX 渲染嵌套级跨档配对)**:R1 只看同一 style 块、R4 只认命名兄弟(`X`/`XText`),但真实代码里"底"与"它的文字"常毫无命名关系 —— `balanceCard: {backgroundColor: tk.brand.cta}` 配 `balanceLabel: {color: tk.surface.light}`,深色档 `surface.light=#262626` 压 `#4A7A96` = **3.25:1,低于 AA 4.5:1**。R7 用递归下降扫 JSX(深度感知、跳字符串/注释,吃三元、`.map()` 体、泛型 `<FlatList<T>>`、多行与数组 `style={[a, b && c]}`),把前景归到**最近一个自带背景的祖先**;该祖先须整族为品牌档且所有候选前景全不匹配才判,命名兄弟让给 R4 不重复计;并覆盖内联 `color=` 的 icon/spinner(如 `<Plus color={tk.surface.card}>` 落在 cta 悬浮钮上 —— R1/R4 一条都看不见)。**HEAD 实测 16 处 / 9 文件**冻进基线键 `nestMismatchCounts`(只减不增),人工出口 `r7-nest-exempt: <原因>`(**须带原因,不得用它清账**)。构建期自查出两类假阳性并已钉成回归:① 嵌套元素 `style=` 并入宿主 key 集(致 `commentEmptyText` 冒充 `commentNickname` 的底)② 与 R4 重叠的命名兄弟重复计债。取证:`--self-test` 133 条断言(含 34 条 R7,含"把真文件 ctaForeground 改回 surface.light 必命中、换回必 0"的判别力证明)+ 镜像测试 17 例。**性能**:R7 在 557 文件上 137ms(R1+R3+R4 合计 174ms);本门 27–45s 的大头是既有的逐文件 `git show` 取材成本,不是新判据。
- ⚠️ 上面那条的"判定文件 >300 跳过"护栏已被 **2026-09-24 的乘数级例外**收窄(普通文件仍受护栏约束,但门的注册表/门自身/`scripts/lib/`/钩子/根清单/工作流恒照判)——现行口径见本节「本轮四道门的口径/落点收口」第 ② 条与 `scripts/tests/check-stale-revert.test.mjs`;保留原文只为不丢行,勿照它执行。
- **UI/样式**(11/11b/17/18/20/24a/24b/27/28/36):圆角 / 圆角溢出(父 rounded + 子 bg 贴边,warn,2026-08-06 立)/ CSS token / title tooltip / Tailwind 冲突 / 侧边栏宽度+端口注册表(warn)/ z-index 层叠+遮罩 z-index(阻塞,id 27/28,2026-09-22 加固:补 skipEnv + onFailHint,并在 id 27 新增第 5 项两组契约——桌面端自绘窗口控制三按钮挂 z-max(10003) 不能降(须高于 resize 抓手 z-loading=10000),`z-modal` 遮罩永远盖不到它,故等效压暗层 `data-window-controls`+`data-window-controls-dim` 与失焦非活动态 `data-window-inactive` 两组标记缺一不可,**两态弱化/瞬时规则必须写在 `apps/web/app/globals.css`**(入口 CSS 变更必然重编译;实测生产构建里组件内 Tailwind 任意变体未进 CSS 产物);判据有效性自查 `--self-test`;紧急跳过 HUSKY_SKIP_Z_INDEX_GUARD / HUSKY_SKIP_OVERLAY_ZINDEX)/ miniapp-taro design-tokens 同步(阻塞,防 app.css 漂移)
- **桌面端本地缓存明文巡检**(warn-only,2026-09-24 立,**不进提交链**):`scripts/check-desktop-cache-plaintext.mjs` —— D48 的加密代码早在 HEAD(`apps/web/src/lib/chat-persist-crypto.ts` / `local-vault.ts` / `desktop-token-vault.ts`),而桌面端 WebView 数据目录整棵 `ihuiVaultV1` **零命中** ⇒ 票面"静态盘 grep 明文会话为 0"这条验收**是虚假通过**:它绿是因为这台机当前没有登录数据,不是因为数据被加密。四判据必须同时成立才算数 —— **判据 0** 含唯一 nonce 中文串的**阳性对照**(先证明"扫描动作本身看得见明文",缺了它的门等于没有);**判据 1** 断言 `ihui-chat` / goal 键的**结构位**恰为 `{ihuiVaultV1}` → `{alg,kid,iv,ct}`,常量自 `local-vault.ts` 源码解析(**禁止抄第二份字面量**,§"两处算同一 key 必须共用一份实现");**判据 2** 目录级 CJK 命中为 0 **且必须打印实扫清单**,扫描面刻意排除 `Cache_Data`/`Code Cache`/`GPUCache`(实测无正文,35MB+ 只会拖慢并引入二进制误报);**判据 3** 密钥不入仓(`git ls-files` 双向断言)+ `ihui-vault.json` 落点字符串单源。路径一律 `realpathSync` 解析 junction 真身,**禁写死盘符**(§26 台账里的 `D:` 在本机已漂到 `G:`);非 win32 / 目录不存在 → 显式判"未判定"并给原因,**绝不记为通过**。全程只读,任何递归枚举前先判重解析点(§26 的 junction 穿透清空事故)。定级 warn 且刻意不挂 guardian-runner:它判的是**机器状态**,提交者结构上无法满足,挂 blocking 只会逼全队 `--no-verify` 连带废掉全部守门(§12e 同型)。入口 `pnpm check:desktop-cache-plaintext`;取证 `--self-test` 10/10 + `node --test scripts/tests/check-desktop-cache-plaintext.test.mjs` 12/12。
- **上面"真实库在 `F:`、`D:/BaiduSyncdisk` 根本不存在"是另一台机(G: 那份 checkout)的实测,在这一台机上正好相反**(2026-09-25 实测:`F:`/`E:` 都 absent,`D:/BaiduSyncdisk/密钥/` 才是真库 —— 里面有 `模型/`、`git仓库/`、`IHUI生产账号/` 等)。与 §5b 四条"机器事实"同族:**盘符每次使用前按当次实测取值**,不得把另一台机的现状当本机事实,更不得据此推断"凭据失效"。
- **非 node 调用方(PowerShell / 批处理 / 计划任务)取凭据路径的唯一出口 = `node scripts/secret-path.mjs <子目录> <文件名>`**(2026-09-25 立)。它只做 `key-dir.mjs` 的出口包装,**stdout 恒为路径、绝不含任何凭据内容**(NSSM 会把服务 stdout 落进日志,打内容等于把口令写进日志),且**区分"凭据目录不存在"(要先 mkdir)与"文件不存在"**(两者处置动作不同,混成一句会让人写好文件仍读不到)。退出码:`0` 解析到 / `1` 目录或文件缺失 / `2` 连根都不可达(`2` 是"无法判定",不得报成"口令错误")。调用方**禁止再自己抄一份 F→D→E→G→C 候选** —— 那正是本节记过的"读不到被下游说成失效"的成因。
- **口令落点按用途分子目录,规矩同一套**:现有 `模型/`(厂商 key)、`git仓库/`(远端凭据),2026-09-25 起新增 **`db-backup/ihui-backup.txt`**(PG 备份专用角色口令,见 `deploy/win/ihui-pg-backup-role.sql` 与 `deploy/win/ihui-pg-backup.ps1` 的凭据段)。新增用途就再加一个子目录,**不要**把口令挪进仓库、`.env`、日志或聊天记录。
  - **跨进程参数选 ASCII**(本例 `db-backup` 而非 `数据库备份`)——但先看清**本机实测中文是无损的**:`.ps1` 保持无 BOM UTF-8,经服务实际使用的 `C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`(**在这个 Windows 11 26200 上产品版本已是 7.6.2**,`Get-Item .VersionInfo.ProductVersion` 实读)传给 node 后 hex 逐字节一致。选 ASCII 只是让"解释器版本 / 控制台代码页"这类未证差异**不可能**影响一条功能参数;真正会发生的现象是**输出面**因 GBK 控制台代码页花屏(实测),那不影响判据,所以中文留在注释与 `Write-Host` 里无妨。
  - ⚠️ **由此推翻 §27 的一个前提**:在**写下这句话的那台机**上 `powershell.exe` 与 `pwsh.exe` 曾是同一引擎(7.6.2);但 2026-09-28 在 `G:\IHUI-AI` 这份 checkout 上量到**相反形态**——`powershell.exe`=5.1.26100.6584、`pwsh.exe`=7.6.4 且**只经绝对路径可达**(裸 `pwsh` 不在会话 PATH)。所以这句"推翻"本身也是一条机器事实,**必须每次现取**才能用:判"能不能按 5.1 省掉防御代码"之前,先跑 `$PSVersionTable.PSVersion` 量当次那份解释器,不要把任何一侧的读数当通用结论。**别再按 5.1 的假设写防御代码** —— 本票就先写了一段 `ErrorActionPreference='Continue'` 包裹(防"Stop + 原生 `2>&1` 把 stderr 升成终止错误"),实测两引擎都不抛、`$LASTEXITCODE` 完好,遂删。凡"某个 PowerShell 版本会怎样"的断言,一律当场跑一次再说。
  - **`merge-live-doc` 的第三种定性:同锚点 ⇒ 就地改写(2026-09-25 补,`--self-test` 13→16 例)**。上面两句判据只分"吃掉 vs 存活":容器短路管"旧行逐字活在新行里",Jaccard 管"两行很像"。**改行中段**时两者同时失效(旧行不逐字存活、长行大改后相似度掉到阈值下),于是正当的就地改写被判成"真丢失"并要人跑 `--apply` —— 而 `--apply` 会把旧行原样插回 ⇒ **同一件事新旧两行并存**,本节上方那对重复的"提交活文档前必须做…"登记行、以及守门 83 的三条同体行,制造路径之一就是这个。现规则:三份活文档都是「一件事一行」且行首有稳定锚点(`- **名称**` / `### 标题`),**同一锚点在"HEAD 缺失集"与"工作树独有集"里各只出现一次** ⇒ 判 `superseded`(不插回)。唯一性是生命线:锚点在任一侧数量 ≠ 1 就不猜,退回原判据(宁可多报交人工,也不能把"整段登记被删"洗成"他改写了")。取证两层:①`node scripts/merge-live-doc.mjs --self-test` 16 例(⑭改写⇒0 / ⑮删除⇒仍 lost / ⑯同锚点两条⇒不猜);②CLI 级装车证明 `scripts/tests/merge-live-doc-anchor.test.mjs`(3 例 A/B/C)—— 本文件顶层就是 CLI 且**没有 §22d `isDirectRun` 守卫**(`lib/live-doc-similarity.mjs` 就是为此被抽出来的),所以测试只能把脚本 spawn 起来验,不能 import 判据函数;C 例自证夹具确实"不像",防止 A 臂退化成容器短路的测试。**误判的代价不是"多一行报告",是逼人跑 `--apply`,而 `--apply` 造出的重复行下一次又被判成需要归并 —— 这是它自己的正反馈环。**
- **反回退对账**(84,原 76):check-stale-revert.mjs(blocking,2026-09-23 立)—— 堵共享工作区**静默回滚**:§12d converge 用 merge-tree/commit-tree 只推进 HEAD+index、**不 checkout**,工作区落后 HEAD 时 `git add <file>` 交的是旧基线(实测 503 文件落后 486 提交),等于把别人该路径的后续改动静默回滚,而 diff 只显"改了几行"。判据 R1 = 暂存 blob != HEAD blob **且字节级等于该路径某祖先提交版本** → 拦并点名回到的 commit;真新编辑不可能恰好等于历史 blob,故误报极低。三条护栏:merge 上下文**不再整轮豁免**(2026-09-25 收窄,见下)、暂存删除只 warn(`git rm` 合法,机器分不清就被删)、判定文件 >300 跳过(性能护栏,防逼人 --no-verify 连带关掉全部守门;**乘数级路径不吃这条护栏**,2026-09-24 立)。**合并期窄判据 R1m**:某路径上 `ours == theirs`(两父逐字节一致)⇒ 合并对它无事可做 ⇒ 索引里与两父都不同的内容**不可能来自本次合并**,再要求它恰等于该路径某历史版本才判红 —— 第三个条件是必须的,因为"人工解冲突写进新内容"同样满足前两条,而那是正当形态。`cherry-pick / revert / rebase` 仍整轮豁免(取历史内容本就是其语义)。立因(第四十二批未闭环④):旧口径那句"merge 上下文整轮豁免"是**本门自己的盲区**,当夜 30c 判红、84 沉默,合并期把旧基线写进索引完全无人看守。取证 `--self-test` **12 例**(新增 5a/5b/5c/5d/6b 五例合并对照:两父不同⇒绿、两父一致而等于历史版本⇒红、等于新写内容⇒绿、cherry-pick⇒仍绿)+ §22c 镜像测试 7 例(含"整轮豁免那句写法不得回来"的**反向回归锁** —— 收窄是判据不是临时关闭,必须能被机器发现)+ 临时 index 端到端演练 3/3(2026-09-23 原始取证,保留);**有意回退一律改用 `git revert` 生成前向提交**。**一条本门看不见的时间窗(2026-09-26 实测)**:84 判的是**索引**里的 blob,而"工作树副本 == 祖先版本、但尚未 `git add`"这一格它结构上碰不到 —— 偏偏危险就在这格里:任何人一次 `git add <该文件>` 或一条不带 pathspec 的 `git commit -a` 就会把它交上去。当天实测 `scripts/guardian-runner.mjs` 的工作树副本等于祖先 `4c1610662`(09-25 19:34),**比 HEAD 少 3 道门(124/125/126)**,而 `git status` 只显示一个 ` M`、13c/71/84 全都不响。量这一型的尺子是 `node scripts/heal-worktree-tracked.mjs --align-drift`(判据:索引==HEAD 且工作树==该路径某祖先 ⇒ 才动,可证零独有数据),手工等价动作是 `git checkout-index -f -- <path>`;**先看判据再动手,不满足就不许对齐** —— 它也可能是别人真在写的现场。紧急跳过 `HUSKY_SKIP_STALE_REVERT_GUARD=1`。
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 12 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、以及 T12 的**取材面双向条件不变量** —— 索引表≠HEAD 表则两档结论必不同形、相同则必同形;变异回旧顺序只有 T12 红)。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- **Tailwind v3 消费端的 `/alpha` 修饰符由共享 preset 负责**(2026-09-25 立):v3 的 `bg-<色>/<透明度>` 语法要求把颜色拆成 rgb 分量,而 preset 若只给 `var(--color-x)` 单值,这一整类类名在 miniapp-taro 端**根本不产出 CSS** —— 页面"样式没生效"却零报错,而 web/extension(v4,原生支持)一切正常,即"手机上改了 web 没改"的又一成因。修法唯一:`packages/design-tokens/src/tailwind-alpha-plugin.js` 为每个颜色键补 `<key>-rgb` 三元组并 `addUtilities`,挂在该 preset 的 `plugins` 上,**禁止**在端内自拼 `rgba(...)` 绕过。**新增颜色档必须同时进这个插件**,否则新档又缺 `/alpha`。取证:真 miniapp 源码上选择器 73 → 91、REMOVED 0 / ADDED 18(含任意值形态 `bg-muted/[0.12]`);NativeWind 侧装 release 包 v21→v22 逐指标 A/B 六项读数完全相同(零回归)。
- **硬编码中文基线棘轮**(70):scan-hardcoded-zh.mjs(blocking,2026-09-22 接线)—— 扫 `apps/web/{app,src/components,src/hooks}` + `packages/{ui-react,shared}/src` + 2026-09-24 补的三端(`apps/mobile-rn/src`、`apps/extension/{entrypoints,src,lib}`、`apps/cli/src`),排除注释/metadata/useTranslations 行/admin/测试。**每文件命中数与基线额度比较**(`scripts/hardcoded-zh-baseline.json`):只拦"比基线更多",存量债(886 文件 / 13090 行)冻结、新增即拦;清理后 `--update-baseline` 下调额度(必须人工确认后再跑,禁止为过门而调高)。该脚本 2026-07-20 即存在但从未接入守门链,是"造好没装车"的第二个实例(第一个是同一批的 69)。`--staged` 由 runner 自动下发(runner 传 `--exit 1`,不带该参数时**永远 exit 0** —— 集成测试踩过),暂存集为空时回退全量防"空暂存恒绿"。**两处同日修**:① 14 例镜像测试自 §15「ROOT 由脚本自身位置推导」起 **13/14 恒红** —— 测试靠 `cwd` 定位夹具而脚本按定义忽略 cwd,14 例全在扫真仓(不是判据错,是调用方式失效);加 `--root <dir>` 显式测试通道(生产不带,语义不变)后 15/15 绿。② 命中判定漏剥**行尾 `//`** ⇒ PriceChart / TerminalTab / TerminalStatusIndicators 各多出 2/1/1 处假阳,把它们顶过基线额度、把本门钉成"谁碰这三个文件谁被拦";同时修 `'https://x/*'` 这类串内 `/*` 骗进块注释状态机的假绿。**遗留已消除(2026-09-25 实测复核)**：全量模式的取材口径**已改为判 HEAD blob** —— `scan-hardcoded-zh.mjs` 现有"磁盘/索引/HEAD 三种取材共用一份判据"(约 :204 行注释与实现)，实测 `node scripts/scan-hardcoded-zh.mjs` 全量**越线文件数 = 0**。本段此前写的"全量模式按共享工作树判 ⇒ 同一份 HEAD 代码干净检出报 1 个、本机工作树报 281 个"是**已修状态**，保留原话只为说明那一型为什么会发生；**下一个接手者不必再做这件事**，若要复核判据请跑上面那条命令看末行现值。（另记 `heal-worktree-tracked` 的刷新面仍在扩大，两件事互补不互替。）紧急跳过 `HUSKY_SKIP_HARDCODED_ZH_GUARD=1`
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 13 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、T12 的**纯函数+构造面**取材面证明、以及 T12b 的尺子反例;变异回旧顺序只有 T12 红)。**本门自身踩过的判据教训(勿再犯)**:这里曾写过一条"索引表≠HEAD 表 ⇒ 两档收口集合必异形"的条件断言,两条前提都不成立(改注释/requires 都会让表不同而集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等)⇒ 一支误红、一支无牙,已删。**证明取材面这类行为只能用纯函数+构造面**,不得依赖仓库瞬时状态。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:立项时 24 块一律 `managed: false`(只报数不判红),逐块翻正时才按判红口径问责。⚠️ "**存量一律 false**"描述的是**立项状态,不是现值** —— 现值一律按当次实测取,见下。翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 13 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、T12 的**纯函数+构造面**取材面证明、以及 T12b 的尺子反例;变异回旧顺序只有 T12 红)。**本门自身踩过的判据教训(勿再犯)**:这里曾写过一条"索引表≠HEAD 表 ⇒ 两档收口集合必异形"的条件断言,两条前提都不成立(改注释/requires 都会让表不同而集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等)⇒ 一支误红、一支无牙,已删。**证明取材面这类行为只能用纯函数+构造面**,不得依赖仓库瞬时状态。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- **`merge-live-doc` 的第三种定性:同锚点 ⇒ 就地改写(2026-09-25 补,`--self-test` 13→16 例)**。上面两句判据只分"吃掉 vs 存活":容器短路管"旧行逐字活在新行里",Jaccard 管"两行很像"。**改行中段**时两者同时失效(旧行不逐字存活、长行大改后相似度掉到阈值下),于是正当的就地改写被判成"真丢失"并要人跑 `--apply` —— 而 `--apply` 会把旧行原样插回 ⇒ **同一件事新旧两行并存**,本节上方那对重复的"提交活文档前必须做…"登记行、以及守门 83 的三条同体行,制造路径之一就是这个。现规则:三份活文档都是「一件事一行」且行首有稳定锚点(`- **名称**` / `### 标题`),**同一锚点在"HEAD 缺失集"与"工作树独有集"里各只出现一次** ⇒ 判 `superseded`(不插回)。唯一性是生命线:锚点在任一侧数量 ≠ 1 就不猜,退回原判据(宁可多报交人工,也不能把"整段登记被删"洗成"他改写了")。取证两层:①`node scripts/merge-live-doc.mjs --self-test` 16 例(⑭改写⇒0 / ⑮删除⇒仍 lost / ⑯同锚点两条⇒不猜);②CLI 级装车证明 `scripts/tests/merge-live-doc-anchor.test.mjs`(3 例 A/B/C)—— 本文件顶层就是 CLI 且**没有 §22d `isDirectRun` 守卫**(`lib/live-doc-similarity.mjs` 就是为此被抽出来的),所以测试只能把脚本 spawn 起来验,不能 import 判据函数;C 例自证夹具确实"不像",防止 A 臂退化成容器短路的测试。**误判的代价不是"多一行报告",是逼人跑 `--apply`,而 `--apply` 造出的重复行下一次又被判成需要归并 —— 这是它自己的正反馈环。**
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {} }` 让端内 `tailwind.config.ts`(content globs + `presets:[@ihui/design-tokens/tailwind-preset]` + 本插件)在真实构建中**从未被加载** —— 实测真实 build 后 dist 的 154 个 wxss 里,源码用到的 **687 个 Tailwind utility / 11,105 处用法有 0 个产出规则**,`.flex{` 与 preflight 指纹双双缺失;阳性对照是同一份 dist 里 `app.css` 手写类名 75/98 可查到规则,故不是检索姿势问题。**后果**:改这条配置等于让整端 11,105 处用法同时开始产出 CSS,而现状很可能是手写 CSS 已补偿过(自有类名 2,228 个),开启后好坏只能由微信开发者工具真机渲染判定 ⇒ 属未决决策项,不是已收口项。**同时提醒同源对账门的边界**:门 36/37/93/`check-miniapp-taro-style-parity` 全部只核源码与 token 源头,**没有一道看产物**;它们五道全绿而到端 CSS 为零,是可能且已发生的。新增跨端同源门时,请先问自己判的是"写的同源"还是"生效的同源"。**⚠️ 本条的"0 产出"现值已作废**(同上,`edb225c909` + `f17b5fbdda`):真产物落地率 740/771 = 96.0%,`app.css` 现有一个由 `sync-miniapp-tokens.mjs` 派生的 `@theme` 注册区;但"没有一道同源门看产物"这半句仍然成立 —— 看产物的是 `check-miniapp-css-landing.mjs`(手动/CI,未接提交链)。
- **prod-bundle 影子副本对账**:`scripts/check-prod-bundle-shadow.mjs`(2026-09-24 立;**2026-09-25 接进提交链 = guardian id `104`,blocking,紧急跳过 `HUSKY_SKIP_PROD_BUNDLE_SHADOW=1`**)—— 此前它只挂在 `pnpm check:all`,`grep -c check-prod-bundle-shadow scripts/guardian-runner.mjs` = 0,即"门存在、判据正确、没有任何调度器跑它",正是本仓最高频的**造好没装车**。§5e 那句"落进 `deploy/prod-bundle/` 的运维脚本必须同时落一份入库源,否则等于写进盲区"从此才有尺子。本门登记「入库源 ↔ 生产实际执行的那份」逐字节必须等值,判据:S0 入库源缺失(跟踪文件,任何检出都该在 ⇒ 判红,不是机器态)、S1 入库源必须真被 git 跟踪(不被跟踪就没有对账对象)、S2 运行副本必须真被忽略(否则登记表过期)、S3 两侧 sha1 全等,不等即红并点名两侧路径。**退出码按"机器态 vs 内容态"三态分流**:整目录 `deploy/prod-bundle/` 被 `.gitignore` 忽略 ⇒ 运行副本**只存在于部署机**,若在非部署机/CI/干净检出上判红,结局是逼人 `--no-verify` 连带 130+ 道门全废(§4/§12e 同型),故"缺运行副本"= **未判定,exit 0 但逐条喊出原因**(禁静默绿,也禁静默红);git 问不到 = exit 2 优先于 1。聚合抽成导出纯函数 `decide()`,机器态分流可用构造输入证明、不必真机改名。刻意**不挂 `stagedTriggers`**:会漂移那侧是被忽略的运行副本,结构上永不出现在暂存区,按 staged 收窄等于放过本门立门那一型。首跑抓到并入库的是数据库备份那一对:`deploy/prod-bundle/pg-backup{,-scheduler}.ps1`(整目录被忽略、全仓零入库源,而它每天 03:00 产出 94MB 生产库 dump)。本门**刻意没有改成转发壳**——把 runner 换成转发壳后重启服务,`pg_dump` 出现 3.5 分钟零输出挂死(旧 runner 同库同日 34 秒跑完),在解释清之前不动生产备份;因此两侧都是全文,靠本门钉住等值。自检 `--self-test`(例数以命令末行现测为准,含"入库源被摘线必红""缺运行副本计未判定不判红"成对正反例;**必须连跑两次**——该门曾 `--self-test` 第二次起恒 exit 2,只能跑一次的取证等于没取证)。
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个)(⚠️ 这几个数字是**立项状态、不是现值**;现值一律跑 `node scripts/check-architecture-policy.mjs` 看 `managed:true` 行),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob,策略表自身按 HEAD→索引→工作树降级取且**退到工作树会大声提示**;取不到判"无法判定",不冒红也不记绿。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 10 例(含 runner 装车证明与"同一代码 managed 两侧结论不同"的判据有牙证明)。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- **`merge-live-doc` 的第三种定性:同锚点 ⇒ 就地改写(2026-09-25 补,`--self-test` 13→16 例)**。上面两句判据只分"吃掉 vs 存活":容器短路管"旧行逐字活在新行里",Jaccard 管"两行很像"。**改行中段**时两者同时失效(旧行不逐字存活、长行大改后相似度掉到阈值下),于是正当的就地改写被判成"真丢失"并要人跑 `--apply` —— 而 `--apply` 会把旧行原样插回 ⇒ **同一件事新旧两行并存**,本节上方那对重复的"提交活文档前必须做…"登记行、以及守门 83 的三条同体行,制造路径之一就是这个。现规则:三份活文档都是「一件事一行」且行首有稳定锚点(`- **名称**` / `### 标题`),**同一锚点在"HEAD 缺失集"与"工作树独有集"里各只出现一次** ⇒ 判 `superseded`(不插回)。唯一性是生命线:锚点在任一侧数量 ≠ 1 就不猜,退回原判据(宁可多报交人工,也不能把"整段登记被删"洗成"他改写了")。取证两层:①`node scripts/merge-live-doc.mjs --self-test` 16 例(⑭改写⇒0 / ⑮删除⇒仍 lost / ⑯同锚点两条⇒不猜);②CLI 级装车证明 `scripts/tests/merge-live-doc-anchor.test.mjs`(3 例 A/B/C)—— 本文件顶层就是 CLI 且**没有 §22d `isDirectRun` 守卫**(`lib/live-doc-similarity.mjs` 就是为此被抽出来的),所以测试只能把脚本 spawn 起来验,不能 import 判据函数;C 例自证夹具确实"不像",防止 A 臂退化成容器短路的测试。**误判的代价不是"多一行报告",是逼人跑 `--apply`,而 `--apply` 造出的重复行下一次又被判成需要归并 —— 这是它自己的正反馈环。**
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:存量模块一律 `managed: false` ⇒ 只报数不判红(立项实测全量 exit 0、报数 3 处、`managed:true` 0 个)(⚠️ 这几个数字是**立项状态、不是现值**;现值一律跑 `node scripts/check-architecture-policy.mjs` 看 `managed:true` 行),翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 12 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、以及 T12 的**取材面双向条件不变量** —— 索引表≠HEAD 表则两档结论必不同形、相同则必同形;变异回旧顺序只有 T12 红)。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。
- ⚠️ **中文出参会被控制台码页吃掉(2026-09-25 实测,和上一条同为"码页"但方向相反)**:上面说中文当 **argv** 传出去无损;但 node 往 stdout 打的**路径**含中文(`密钥/...`)时,PowerShell 按 **GBK 码页**解码 ⇒ 拿回的是乱码字符串 ⇒ `Test-Path` 判"不存在" ⇒ 脚本静默退回兜底凭据,表现得像"口令文件没建"。**凡"把一个值读回来继续用"的场合(路径/文件名/URL),调 node 前必须显式 `[Console]::OutputEncoding = UTF8` 并在 finally 还原**;只在控制台显示乱码的是"输出面",这条不成立 —— 值一旦参与判据就不是显示问题。同类修法也适用于 §5e 说的"多行中文正文走 --message-file"。
- **凭据目录现状**(2026-09-25 起):`db-backup/ihui-backup.txt` = 只读备份账号 `beifen` 的口令;`IHUI生产账号/pgsql管理员-postgres-*.txt` = 该库 `postgres` 的口令(此前**从未设过**,详见 `deploy/win/ihui-pg-backup-role.sql` 顶部的执行记录)。两份都在仓库外、都在 §5d 权威目录里,都不进日志/聊天/提交。
- **"服务身份能不能跑到这一步"要用它那份环境验,不得用我的终端验(2026-09-25 实测)**:上面那条讲"哪份文件",这条讲**那份文件跑在谁的身份下**。nssm 服务全是 `LocalSystem`,它读的是**机器级 PATH**,而本机机器级 PATH 里那串 `D:\nodejs\` 是**死路径**(目录根本不存在),node 真身 `D:\DevEnv\runtimes\node\node.exe` 只挂在**用户级 PATH** —— 于是 `Get-Command node.exe` 在人手里命中、在服务手里落空,凡是"取凭据/发通知要先找 node"的分支会**静默跳过并回落兜底档**,现象是"手动跑是对的、自动跑用的是旧账号",而日志只有一行 WARN。处置口径:① 取可执行程序一律走 `Resolve-NodeExe`(`deploy/win/ihui-deploy.ps1:120-131`,绝对路径候选已内建),不得只写 `Get-Command`;② 兜底全落空必须**喊出来**(Write-Host WARN/ERROR + 非零),严禁 `if (Get-Command …) { … }` 无 else 静默降级 —— 那与 §5e"失败必须响"是同一条禁令;③ 验证要剥掉环境再跑**服务真正执行的那份副本**:`$env:Path` 去掉含 `node`/`DevEnv` 的条目后跑 `deploy/prod-bundle/*.ps1`,看输出行报的是哪个角色,而不是在我终端里跑 `deploy/win/*`。实例与取证见 `deploy/win/ihui-pg-backup.ps1` 的凭据段注释与 PLAN 第四十九批·续十。
  - **"点名"必须连结论行的缩进续行一起看(2026-09-25 实战补)**:守门 84/30c 这类门把**结论**与**定位**拆成两行
    (`❌ 检出 1 个文件的暂存内容等于其历史提交版本:` 一行不带路径,下一缩进行才给路径)。`findingLines()` 原为
    逐行匹配 `FINDING_LINE_RE`,于是第二行不算结论行 ⇒ 看不见被点名的文件 ⇒ 判 `not-ours` ⇒ **放行跳门** ——
    方向正好反了:**门说得越具体,铰链越松**,而这条铰链决定的是"能不能绕过 130+ 道门"。现并入结论行下方
    ≤3 行的缩进续行;取证是自检 A2b(两行形态必判 mine)/ A3b(续行点的是别人的文件仍判 not-ours),
    变异证明 = 把续行上限改回 0 即退回旧误判。**判据失效的方向必须是"多要一次定向说明",绝不能是"多放一次跳门"。**
- **单一真相源**:design-tokens 在 `packages/design-tokens/src/styles/tokens.css`(`@theme` + `:root` + `.dark`)。miniapp-taro 的 `app.css :root/.dark` 由 `apps/miniapp-taro/scripts/sync-design-tokens.mjs` 自动同步(**禁止手改 app.css 的 token 块**;改 token 改源头 + 跑同步,该脚本是端内脚本,根 `scripts/` 下没有同名文件,`node scripts/sync-design-tokens.mjs` 会报模块找不到)。另有 `scripts/check-miniapp-taro-design-tokens.mjs` 与 `scripts/check-miniapp-tokens-sync.mjs`(后者为 guardian-runner 第 36 项实际调用项)校验同步一致性。
- **发给 provider 的 function parameters 必须由校验面单向投影**(2026-09-25 立,A13):统一出口是 `@ihui/types` 的 `projectToolInputSchema` / `toolsToProviderSchema` (`packages/types/src/schema-projection.ts`),**禁止端内手搓 `parameters` 对象或自拼第二份 JSON Schema** —— "运行时怎么校验"与"模型被告知怎么填"必须是同一份描述的两个投影,否则两边各自漂移而 typecheck 全绿。等价性由 158/158 个真工具对象的同瞬间 A/B 证明(线字节 + 属性名集合 + required 集合三项全等)。
- **候选序解析器必须同时提供「出处」出口**(2026-09-25 立,A19):`scripts/lib/key-dir.mjs` 的 `resolveKeyDirDetailed()` 返回 `{path, triedCandidates[], winnerIndex}` 三态 —— 一个根都没命中(盘没挂上 / 根缺失 = **无法判定**,不得报成「凭据失效」)/ 根在而子目录不在(先建目录再放口令)/ 命中。`resolveKeyDir()` 的签名与行为**一字未动**(它是 12+ 个脚本的依赖面,detailed 版是它的超集,旧版由新版降格投影而来,等价性由单测钉住)。新增任何候选序解析器都要照此配对,调用方**禁止再自己抄一份 F→D→E→G→C 候选表** —— 那正是本节记过的「读不到被下游报成失效」的成因。**但这条是散文约束、没有门**:判「有没有真的回答出处」结构上判不了,为它立门只会逼人写空调用去应付(如实登记,别误以为这里有尺子)。
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:立项时 24 块一律 `managed: false`(只报数不判红),逐块翻正时才按判红口径问责。⚠️ "**存量一律 false**"描述的是**立项状态,不是现值** —— 现值一律按当次实测取,见下。翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 13 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、T12 的**纯函数+构造面**取材面证明、以及 T12b 的尺子反例;变异回旧顺序只有 T12 红)。**本门自身踩过的判据教训(勿再犯)**:这里曾写过一条"索引表≠HEAD 表 ⇒ 两档收口集合必异形"的条件断言,两条前提都不成立(改注释/requires 都会让表不同而集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等)⇒ 一支误红、一支无牙,已删。**证明取材面这类行为只能用纯函数+构造面**,不得依赖仓库瞬时状态。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。 **E1/E2 两条同日补**:E1 公开入口存在性对账 —— `public_entrypoints` 声明的入口必须在**被审判的同一取材面**里解析得到(HEAD 面实测 75 条声明入口全部在位 ⇒ 本条可即时问责,不像 E2 那样带存量)/ E2 契约工件齐备性 —— 纳管模块必须有策略表字段点名的自述与契约文件(**清单取表里的字段,不在代码里硬编码第二份名字**),默认档**只报数**:HEAD 实测 16 块未齐备,当场判红就是一台恒红门(§12e 同型),要问责跑 `--strict`。退出码口径:判红只算「C1/T1 全仓即时 + 已收口模块的契约违规」,E1/E2 的红仅按 `managed:true` 问责。**同批给策略表配了只读出口** `scripts/module-context.mjs`(pnpm `module:context`):按模块 id 打印「是否纳管 / 声明依赖 / 本模块契约文件 / 依赖模块契约文件 / 公开入口 / 边界条款」,`--files` 直接吐派单用的文件清单 —— §11 那张「受影响文件」格过去全靠派单人手写,而 §11 自己就记着这张清单写错会造成越权/污染。未知模块 id **exit 2 并点名**(实测 `node scripts/module-context.mjs nosuch-module-xyz` → rc 2);`--json` 必须可 `JSON.parse`;E2 未齐备既打印 missing **也进退出码** —— 上游那一版只打印不判,这一格我们补上了。
- **第三方来源台账对账**(guardian id **107**,blocking,紧急跳过 `HUSKY_SKIP_PROVENANCE_LEDGER=1`):`scripts/provenance-ledger.mjs` 把"哪些内容不是我们写的"变成机器可查事实 —— 三本账(嵌入 / 复制 / 覆盖)+ 一本机制来源账,数据在 `config/third-party-provenance/*.json`,许可原文按内容哈希自证(读盘不联网)。四类红:P1 `roots` 指向不存在的路径 / P2 盘上有 vendored 目录或第三方归属头而台账无条目 / P3 已登记条目 hash 漂移 / P4 revision 不可考却没显式 `null` + 说明 + P8 **归属反噬**(2026-09-25 同日补):已登记的第三方内容上不得出现我方水印横幅/版权主张,出现即红 —— 起因是实测到 `apps/web/public/pdfjs/pdf.worker.min.mjs` 第 1 行是我们的横幅(等于把 Mozilla 的作品声明成我们的)。水印层的排除源唯一 = 台账 roots(经 `scripts/lib/third-party-roots.mjs`,`watermark.mjs` 与 `check-watermark-coverage.mjs` 两个消费者共用),**这些文件不是改成看不见,而是改由 P8 审计**。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、取不到判"无法判定")。**刻意不挂 `stagedTriggers`**:新登记一条 vendored 内容可以发生在任何路径。立项两记否证值得记:(a) 规格称 `vendor/` 是唯一 vendored 件 —— 实测另有 PDF.js worker、`gradlew{,.bat}`、lucide svg 三处未登记;(b) §5c 的零宽水印门禁与"许可原文不得改动格式"**结构冲突**,那些文件至今幸免只因 `SKIP_DIRS` 里恰有一条 `'vendor'`,是巧合不是设计 —— 处置写进台账注释,**不得用豁免遮**。
  - **可审计锚点必须受版本控制(P5 的 specFile 由"工作树存在"改成"按被审面存在",2026-09-25 立,G-173 换来的)**:旧判据是裸 `existsSync(join(root, e.specFile))`,而台账 `$schemaNote` 当时明写"specFile 允许是 gitignore 的临时规格"——**"允许临时"与"blocking 判存在"结构互斥**:gitignored 路径不进任何检出,而本仓 post-commit 的 `--auto-clean` 自己会清掉它,结果就是作者机常绿、别的检出上**每次提交都被逼 `--no-verify`**(实测 145 项里它一项红)。**通用规矩:凡被 blocking 门要求"必须存在的文件",其路径要先过面判据(`reader.has`)或至少过 `git check-ignore`;写进 `.ihui-agent/` 的引用不叫证据,叫机器-local 巧合。** 取证是自检 `8e` 两条成对用例 —— 夹具故意让锚点**在工作树上存在但未提交**,新判据必须红;旧写法在这里报绿,所以这条同时是"新判据有牙"的证明,不许用"改文案"把它退化成恒红。数据侧 `specFile` 已重指到受版本控制的 `AGENTS.md` 本条,并用 `specNote` 如实声明"上游研究笔记未幸存、内容无从找回,**不得把该字段读成原文仍在**"——恢复不了的东西不要伪造。
- **豁免到期账**(guardian id **108**,blocking,紧急跳过 `HUSKY_SKIP_EXEMPTION_EXPIRY=1`):`scripts/check-exemption-expiry.mjs` 钉"豁免只有出生、没有死亡"。实测全仓 **12 类**行内豁免 / 252 处 / 107 文件,**带真到期日 0 处**(这三个数是**立项读数、不是现值** —— 现值一律跑 `node scripts/check-exemption-expiry.mjs --json` 看 `totals`)。2026-09-25 补第 13 族 **`back-label-exempt`**(守门 102 GA4 的合法例外通道),在 `FAMILY_LIFETIME_DAYS` 给它 **365 天**而非同门 `glyph-arrow-exempt` 的 30:它是**结构性定性**(该位置的「返回」是按钮文案而非导航箭头),位置性质不随时间改变;短周期到期只会逼人删标记,删了又被 GA4 判红,**两道门互咬**。E1 新增不带到期日 ⇒ 红;E2 已过期仍生效 ⇒ 红;E3 lint 抑制面(`eslint-disable` / `@ts-ignore`)只报数。**三重防恒红**:存量进 `scripts/exemption-expiry-baseline.json` 只报数、E1 锚点 = 该文件该族在 **HEAD 自身的存量数**(棘轮)、`grandfatherUntil` 前不追存量 —— 与改动无关的 blocking 红只会逼人 `--no-verify` 连带废掉全部守门。到期**只判红只点名,绝不自动摘除任何人的豁免**。副产品:第 12 族 `r3-cta-exempt` 3 处是从来没生效过的悬空豁免(守门 83 只认 `r5-cta-exempt`),归属该门持有者。
- **任务认领租约对账**(guardian id **109**,blocking,`stagedTriggers=PROJECT_PLAN.md`,紧急跳过 `HUSKY_SKIP_TASK_CLAIM_LEASE=1`):`scripts/check-task-claims.mjs --check-gate` 把 §1 的认领标记升级成带归属与寿命的锁(见 §1 第二条)。CL1 年龄超阈值(默认 72h)/ CL2 有 `@日期` 无 `/持有者` / CL3 同一行**在勾选框位置**既已 `[x]` 又挂着租约形态 `（进行中@…）`(正文叙述里的 `[x]` 不算 —— 判据把叙述当状态,就会替另一会话持有的行造出恒红)。**CL3 自 2026-09-26 起套「HEAD 自身存量」棘轮**:锚点是矛盾行的**行文本多重集**,不是"前 N 条"计数 —— 计数式切法会让新塞进文档前面的一颗顶掉一颗旧残留的名额,两者都得绿(镜像测试 T17 当场抓到第一版实现就是这样)。归并器"翻勾不摘牌"已在**生产者侧**收口(`scripts/plan-tasks-merge.mjs` 的 `rewriteFork()` 翻勾前先摘牌,配 T6 与变异对照),HEAD 里那批残留(逐条带 `**[归并]**` 出处标记)按存量只报数,**换个文案的新增一律判红**;`--worktree` / `--plan` 两条人工与取证面**不套棘轮**,仍全量判。锚点取不到 ⇒ 显式喊"未判定"并列出条数,**不冒绿**。棘轮锚点取 HEAD 而不是手工清单,是为了让它只会自己收紧:谁把残留清进 HEAD,红线就跟着降。**向后兼容第一**:存量裸 `（进行中）` 一律只计数不判红,否则上线当天即恒红门。门**绝不自动摘除任何人的认领**(§16 越权)。
- **产物预算对账**(guardian id **110**,**warn**,紧急跳过 `HUSKY_SKIP_ARTIFACT_BUDGET=1`):`scripts/check-artifact-budget.mjs` 是全仓第一次去量**交付出去的产物**而非源码(AGENTS 末段自己写过"同源门全部只核源码,没有一道看产物")。为什么 warn:产物在不在本机是**机器状态**,提交者结构上满足不了 ⇒ blocking 就是恒红门;问责放 CI(build 之后必有产物),镜像测试钉"一旦接提交链就必须同时有 CI 调用点"。缺产物不判红也不静默绿:exit 0 + 逐条"未判定:<原因>"。主包口径由构造面正反例钉死(同样字节放分包目录 ⇒ 绿,放主包 ⇒ 红)。
- **服务二进制路径存续性对账**(`scripts/check-service-binary-paths.mjs`,定级 **warn**,紧急跳过 `HUSKY_SKIP_SERVICE_BINARY_PATHS=1`,问责跑 `--strict`):钉的是**"外部自升级把 Windows 服务的二进制路径烂掉"**这一型 —— RSSHub 就是被 `~\.workbuddy\binaries\node\versions\<新版本>` 整目录换掉后**静默停了 3 天,期间没有任何告警**,而全仓没有一处会问"服务声明要跑的那个 exe 今天还在不在"。判据只读三态:**可判存在 / 确认缺失 / 未判定**,枚举服务键后按 nssm 的 `Parameters\Application` 逐条判存在性;"一个 nssm 服务都没枚举到"与"派生失败"都是 `未判定`,**不得被读成通过**(把没判写成判过了是本仓最高频失效型,与守门 94/103/118 同一条禁令)。**定级 warn 是设计前提而不是偷懒**:服务路径属**机器状态**,提交者结构上满足不了,挂 blocking 就是每台每次被逼 `--no-verify`、连带全部守门对该提交作废(§12e 同型)。**两条实测教训留在门体头注**:① nssm 的输出面是 **UTF-16**,按 UTF-8 读会得到乱码结论;② PowerShell `-Command` 里注册表路径的**反斜杠会被吃掉**(`HKLM:` + 带反斜杠的整串塌成 `HKLM:SYSTEMCurrentControlSetServices`)⇒ 整面恒空进而伪装成"未判定",必须用正斜杠,已由注释与测试双向钉住。本机(开发机)现读 **0 个 nssm 托管服务** ⇒ 这一维在该机只能报未判定,这是如实而不是故障。
- **工具契约声明对账**(guardian id **111**,blocking,`stagedTriggers=apps/cli/src/tools/`,紧急跳过 `HUSKY_SKIP_TOOL_CONTRACT_DECLARED=1`):`scripts/check-tool-contract-declared.mjs` 守 A13 第一阶段立下的规矩 —— 一次可被模型调用的能力必须在实现旁边有**契约声明**(副作用范围 / 权限 / 结果预算),因为下游三个面(发给 provider 的 function parameters、运行时入参校验、批准与预算判定)现在都从**同一份描述**投影 (`packages/types/src/{tool-contract,schema-projection}.ts`)。没有声明不是"少一条元数据",而是那一格退回"三个面各写各的" —— 本仓 8 道"声明↔实现对账"同族门存在的理由就是它。**存量 104 个走棘轮**(锚点 = 该文件 HEAD 自身违规数,老文件不会被别人欠的债钉红)。**缺省语义本门故意不管**:翻成"缺省即不可信"是第二阶段,前置是先把 `--flip-audit` 点名的 16 个"连 dangerLevel 都没写"的工具逐个补档(`list_dir`/`grep`/`glob`/`run_command` 等 14 个 builtins/terminal/memory 族 + `mcp-runtime.ts` 两个),否则用户侧表现是"昨天能跑今天全要批准" —— 那不是收紧安全,是制造事故。
- **测试收集存续性对账**(guardian id **114**,**warn**,紧急跳过 `HUSKY_SKIP_TEST_COLLECTION=1`):`scripts/check-test-collection-runs.mjs` 判的是"**收集阶段**就失败的套件数必须为 0"。立因是实测到的结构性失明:`apps/mobile-rn` 一度 `Test Files 15 failed | 37 passed`,其中 14 个是 transform / import 解析期失败,内含 131 条 it 声明(展开 137 枚用例)**一条都没跑** —— 端内约三分之一覆盖被静默削掉,而提交链里 130+ 道门没有一道跑 vitest(`check-staged-typecheck` 走 tsc,结构上看不见解析期失败),CI 会红但本机可以永远看着绿。**定级 warn 而非 blocking 是设计前提**:本门判的是端内测试能否被收集,一次提交完全可能与它无关,与改动无关的恒红门只会逼人 `--no-verify` 并连带废掉全部门(§12e 同型);要问责跑 `--strict`(CI / 巡检)。**三条不可动摇的口径**:① 零测试文件不判绿(空扫就是它要防的同一型故障);② 断言失败与收集失败绝不混计 —— `Test Files 1 failed` 两种故障都会出现,故文件级计数不能作判据,只认 `Failed Suites` 区块与带方括号的套件级形态;③ 只接 vitest,pytest 的收集期故障是另一套语法(`ERROR … - ModuleNotFoundError` + `no tests ran`),照抄判据必然空转,非 vitest 入口一律判"未判定"并写明原因,不冒绿。`stagedTriggers` 取 `apps/mobile-rn/` + `packages/` —— 能打断这份收集的只有 RN 端自身与它吃的共享包。
- 三件同批事实:以上五道门都按 §12 的"活文档与注册表单写者"由主会话统一接线(`guardian-runner.mjs` / `package.json` / 本节 / README 守门章节同枚提交),各实现票按任务书**没有**碰过这些共享注册文件 —— 并行改注册表必然互相覆盖注册块(门 93 记过同型事故)。
- **小程序端「源 ↔ 派生产物」存续性对账**(105):`scripts/check-miniapp-generated.mjs`(**blocking;2026-09-25 由 warn 升档,前置条件实测达成**)—— 管四件"生成器造好了但没人跑"的派生产物:i18n 离线包 `remote-locales.gen.ts`、`static/images/icons/*.svg`、`LineIcon/icons.ts`、`assets/tabbar/*.png`。六条判据(G1–G6;G5 由 G-680 加、G6 由 G-415 A8 加):**G1** 源里有而产物没有 ⇒ 判红(端上会缺词、图片 404 且**不报错**);**G2** 产物里有而源已无 ⇒ 默认只报数,`--strict` 才判红(删产物属 §7 删除安全);**G3** `icons.ts` 有键而 `icons/` 无同名 svg ⇒ **永不判红**(图标源在"图片全量外置 CDN"那轮被清空,判红等于造一道恒红门);**G4** 模板字符串拼出的动态路径结构上判不了 ⇒ 如实计数,绝不静默成"看起来全绿";**G5** 离线包自述钉不等 ⇒ 判陈旧;**G6** 调用点 <LineIcon name=字面量> 必须在 icons.ts 键集(取不到素材时组件 return null,界面只是空白且不报错),动态拼接与标签配不平计未判定并逐条点名 —— 真仓 HEAD 现读字面量 272 处 / 动态 35 处,故这一维接线不产生恒红面。取材面同 70/77/83/98/101:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 只作人工与 dev 链逃生舱;取不到 ⇒ **exit 2「无法判定」**。定级 warn 的实证理由:立项当天全量面就有 8 处 G1 + 9 处孤儿在红,挂 blocking 会让每次提交都被逼 `--no-verify`(§12e 那型);**升 blocking 的前置条件是 G1 存量清零 —— 该前置已于 2026-09-25 实测达成并升档**(全量面 `未发现漏生成`、exit 0;R2 孤儿与 G3 图标按设计**不是** blocking 判据,默认只报数,所以升档不新增任何恒红面)。升档的直接起因是本会话自己改了 i18n 键名(`policyForbidsExecution` → `policySkipsApproval`)却没重生成离线包,门判出 4 处 B2 却因为是 warn 而**没拦住任何提交**,于是静默上线一版"小程序取不到该词"的产物 —— **warn 的代价不是"少一道闸",是"门判对了也没人被打断"**。修法单命令、不需要人工裁:`pnpm --filter @ihui/miniapp-taro gen:i18n`。取证 `--self-test`(15 例,成对正反例、零副作用);紧急跳过 `HUSKY_SKIP_MINIAPP_GENERATED=1`;它同时被 `dev-weapp.mjs` 消费 —— 判据要在有人跑它的那一刻才成立。
  - **为什么"产物存在"不等于"能力在线"(本门立论,值得推广到别的生成器)**:这四个生成器里此前**只有 i18n 那个进了构建链**,其余三个既无构建入口也无门禁 ⇒ "造好没装车"只能靠人记得手动跑;而 dev 链不跑 i18n 生成器,意味着**开发时离线语言包常年是旧的**,只有 release 才被发现。同类盲区见守门 64(适配器接线)/ 70(硬编码中文)/ 81(邮件通道):**判据必须在真跑它的那一刻才成立,否则等于没有。**
- ⚠️ **中文出参会被控制台码页吃掉(2026-09-25 实测,和上一条同为"码页"但方向相反)**:上面说中文当 **argv** 传出去无损;但 node 往 stdout 打的**路径**含中文(`密钥/...`)时,PowerShell 按 **GBK 码页**解码 ⇒ 拿回的是乱码字符串 ⇒ `Test-Path` 判"不存在" ⇒ 脚本静默退回兜底凭据,表现得像"口令文件没建"。**凡"把一个值读回来继续用"的场合(路径/文件名/URL),调 node 前必须显式 `[Console]::OutputEncoding = UTF8` 并在 finally 还原**;只在控制台显示乱码的是"输出面",这条不成立 —— 值一旦参与判据就不是显示问题。同类修法也适用于 §5e 说的"多行中文正文走 --message-file"。
- **凭据目录现状**(2026-09-25 起):`db-backup/ihui-backup.txt` = 只读备份账号 `beifen` 的口令;`IHUI生产账号/pgsql管理员-postgres-*.txt` = 该库 `postgres` 的口令(此前**从未设过**,详见 `deploy/win/ihui-pg-backup-role.sql` 顶部的执行记录)。两份都在仓库外、都在 §5d 权威目录里,都不进日志/聊天/提交。
- **发给 provider 的 function parameters 必须由校验面单向投影**(2026-09-25 立,A13):统一出口是 `@ihui/types` 的 `projectToolInputSchema` / `toolsToProviderSchema` (`packages/types/src/schema-projection.ts`),**禁止端内手搓 `parameters` 对象或自拼第二份 JSON Schema** —— "运行时怎么校验"与"模型被告知怎么填"必须是同一份描述的两个投影,否则两边各自漂移而 typecheck 全绿。等价性由 158/158 个真工具对象的同瞬间 A/B 证明(线字节 + 属性名集合 + required 集合三项全等)。 **⚠️ 那句"158/158"当场不可复核,现已换成可重跑判据(2026-09-25)**:158 出自提交 `dd5142f2255` 的一次性 A/B 脚本,而那个脚本**没入库**、**口径也不同** —— 它按 `文件#name` 计数,把 re-export 的同一工具重复计了 56 次,所以 158 ≠ 门 111 的 104。更糟的是那条 A/B **今天再跑仍报 158/158,而它是恒真式**:它比的"旧侧" `toolsToProviderSchema` 在 `dd5142f2255` 之后自己就改成调投影器了,等于拿新实现和它自己比 ⇒ "已证明"三个字当时成立、事后失效,而账面什么都看不出来。现在这句话的唯一入库载体是 `apps/cli/tests/a13-projection-equivalence.test.ts`:旧侧取 `git show dd5142f2255^` 的**历史源码逐字快照**(`tests/fixtures/toJsonProperty.legacy.ts`,头注禁止演进),断言①门数到的工具集合与运行时枚举到的**逐名相等**、②逐工具三项全等(线字节 / properties 键集 / required 集)、③两条变异对照各红在对应判据、④枚举到 0 个工具判死不判绿。**登记一条已知局限**:运行时侧读磁盘而门侧判 HEAD ⇒ 他人在飞新增的真工具会让"逐名相等"点名它,红可能来自别人未提交的文件(§77/§83 同型)。
- **压缩分母对账**(112):`scripts/check-compaction-denominator.mjs`(blocking,2026-09-25 立)—— 钉的是 A30 那条实测缺口:provider 的 context window 是 **input 与 output 共享的同一个窗口**,所以"什么时候该压缩"的分母必须**先扣掉本轮留给输出的预留**;不扣就是拿"占满整个共享窗口的 88%"当阈值,而同一窗口里模型还要生成回复 —— 故障形态是**越接近上限越容易炸,现象却写成"压缩判过了还是 400/overflow",极难归因到分母**。立门前先确认"没有等价实现"再确认"没有这个名字"(两步都跑:本仓实测 `grep -rniE "outputReserve|effectiveContextWindow" apps/cli/src packages/context-compaction/src` = 0)。唯一出口 `effectiveContextWindow({contextWindow, maxOutputTokens, buffer, enabled})`,预留封顶 `MAX_OUTPUT_RESERVE_TOKENS`;凡「token 数 ÷ `contextLimit`/`contextWindow`」而分母未经该出口 ⇒ 红。**只从包主入口导出,不得新建子路径** —— 架构契约表没登记 `packages/context-compaction` 的子入口,深导入会被守门 103 判 D3 红(本仓踩过)。**行为后果如实登记**:分母只会变小 ⇒ **压缩更早触发**,这是用户可感知的变化,回退有两条路(构造参数 `outputReserveEnabled:false` / 环境变量 `IHUI_COMPACTION_OUTPUT_RESERVE=0`);存量 8 文件/12 处进 `scripts/compaction-denominator-baseline.json` 只减不增,其中 **api 端 `/chat/stream` 的溢出面尚未收口** —— 那是留下的账,不得读成"已修完"。口径同 77/83/98/103:全量判 HEAD blob、`--staged` 判索引 blob、取不到 ⇒ **exit 2 无法判定**、枚举到 0 个文件判死不记绿。取证 `--self-test` 15 条(成对正反例 + 空枚举反向对照)+ §22c 镜像测试;紧急跳过 `HUSKY_SKIP_COMPACTION_DENOMINATOR=1`。
- **路由身份键对账**(113):`scripts/check-tool-arg-routing-identity.mjs`(blocking,`stagedTriggers=apps/cli/src/tools/`,2026-09-25 立) —— 钉的是**"谁的数据"不得由模型决定**:路由身份一律由宿主在 closure / ctx 绑定,绝不得出现在模型可见参数集合里。键清单的**唯一源**是 `packages/types/src/tool-contract.ts` 的 `ROUTING_IDENTITY_KEYS`(门与类型层共用一份实现,禁止第二份;该常量被摘线本门即 exit 2 并点名,而不是"清单空 ⇒ 全绿")。三类落点都判:直接 `parameters` 字面量、具名 helper 单跳回溯、契约 `shape.input.properties` 跨文件解析;键名归一化(小写去 `_`/`-`)故 `sessionId`/`session_id`/`SESSION_ID` 同视。**刻意只收 16 个"填错会把结果送到别人那里"的键** —— `messageId`/`toolCallId`/`taskId`/`fileId`/`path` 属内容引用或业务句柄,纳进来会误拦正当用法(加键先问那一句,镜像测试反向钉死)。投影出口侧不是"未判到":静态验 `schema-projection.ts` 里 `properties[name]` 的键名逐字取自 `Object.entries(parameters)`、无字面量下标/改名 ⇒ 判 `name-preserving`;解析不出即计未判定并 exit 2。口径同 77/83/98/103/112(全量判 HEAD blob、`--staged` 判索引、暂存集空退全量并如实点名、枚举 0 个判死)。**首跑 16 处已冻棘轮基线,但那不是清偿**:`memory.ts` 把 `user_id` 标成 `required`,而服务端 `routes/memory.ts:118` 只认 `request.userId!` ⇒ 真是"模型不可能满足的必填参数"(功能面 bug);`debug.ts`/`terminal.ts` 的 11 处是工具自句柄,须把排除理由正式登记进键清单注释,**不得用行内豁免遮掉**。取证 `--self-test` 23 例 + §22c 镜像 7 例;紧急跳过 `HUSKY_SKIP_TOOL_ARG_ROUTING_IDENTITY=1`。
  - ⚠️ **中文出参会被控制台码页吃掉(2026-09-25 实测,和上一条同为"码页"但方向相反)**:上面说中文当 **argv** 传出去无损;但 node 往 stdout 打的**路径**含中文(`密钥/...`)时,PowerShell 按 **GBK 码页**解码 ⇒ 拿回的是乱码字符串 ⇒ `Test-Path` 判"不存在" ⇒ 脚本静默退回兜底凭据,表现得像"口令文件没建"。**凡"把一个值读回来继续用"的场合(路径/文件名/URL),调 node 前必须显式 `[Console]::OutputEncoding = UTF8` 并在 finally 还原**;只在控制台显示乱码的是"输出面",这条不成立 —— 值一旦参与判据就不是显示问题。同类修法也适用于 §5e 说的"多行中文正文走 --message-file"。
  - **凭据目录现状**(2026-09-25 起):`db-backup/ihui-backup.txt` = 只读备份账号 `beifen` 的口令;`IHUI生产账号/pgsql管理员-postgres-*.txt` = 该库 `postgres` 的口令(此前**从未设过**,详见 `deploy/win/ihui-pg-backup-role.sql` 顶部的执行记录)。两份都在仓库外、都在 §5d 权威目录里,都不进日志/聊天/提交。
    **本票新建的两把常驻工具(量算/归并出口,不是守门 —— 勿因"没接 guardian-runner"就当作没装车)**:
    `scripts/c-disk-breakdown.mjs` —— 全盘**只读**量算仪,junction 安全(刻意不跟随重解析点,否则把 D 盘的量算成
    C 盘的债),强制对账「遍历 + 特殊文件 + 未解释 = 已用」,未解释 >2GB 或为负都当场喊话。报磁盘状态先跑它,
    不要跑 `df`(§26 已记过一次它给出相反百分比)。用法 `node scripts/c-disk-breakdown.mjs [--root C:/] [--depth N] [--json]`。
    它的文件名不以 `check|scan|guard` 开头 ⇒ 守门 89 结构上看不见它,所以它的不变量由自己那把尺子钉:
    `scripts/tests/c-disk-breakdown.test.mjs`(7 例:滚到全部祖先 / junction 不跟随 / ROOTKEY 尾斜杠 / 量不到不得伪装成结论)。
    `scripts/plan-union-merge.mjs` —— 活文档台账被撞号或整段回写时的人工归并出口(临时索引 + commit-tree + CAS,
    落地前自证"本侧/对侧路径零丢失"),镜像测试 4 例在位。两者**都不做任何删除**。
- **入参校验装车对账**(115):`scripts/check-tool-arg-validation-wired.mjs`(blocking,`stagedTriggers=apps/cli/src/tools/` + `packages/types/src/`,2026-09-25 立) —— 堵的是 A36 查出来的那一格:`apps/cli/src/tools/argument-validator.ts` 校验器写得挺全(field/expected/actual、嵌套 `items[0]` 路径都有),**但生产面零调用方** ⇒ 工具入参从来没被校验过,`required` 只拿去生成提示文案和 provider schema,模型少传/传错类型时由 104 枚 handler 各自兜 ⇒ 同一类错误 104 种表现。**只判两条**:① `validateToolArguments(` 必须有非测试调用方(**注释里的提及不算** —— 本仓最高频失效型就是"看起来有、其实没装车",全仓三处 `argument-validator` 的出现位置全是注释);② 模式开关必须在位、**默认 off**、`shadow` 档存在 —— `enforce` 未实现前默认绝不能是 enforce,因为那些 `parameters` 描述**从未被执行过、准确度未知**,直接打开拒绝就是运行时版恒红事故(与 §12e/守门 77/83 同一条理由)。影子档只记账:不改 `call.arguments`、不改返回值、不拦调用,遥测**不落任何入参值**(`ValidationError.actual` 在 `enum_mismatch` 分支装的就是用户原值,`expected` 装枚举表 ⇒ 双双排除)。已知覆盖面缺口如实登记:`hubEnabled && hubResolver` 分支在拿到 Tool 对象之前就 return,那条路径影子不生效。三步顺序(①影子 → ②用影子数据修描述 → ③才谈默认 enforce + 逐条回灌 `{字段路径,期望,实得}`)登记在 PROJECT_PLAN 第八波。口径同 77/83/98/103/112/113(全量判 HEAD blob、`--staged` 判索引、取不到 exit 2、枚举 0 判死);取证 `--self-test` 14 条 + §22c 镜像 5 例;紧急跳过 `HUSKY_SKIP_TOOL_ARG_VALIDATION_WIRED=1`。
  **本票新建的两把常驻工具(量算/归并出口,不是守门 —— 勿因"没接 guardian-runner"就当作没装车)**:
  `scripts/c-disk-breakdown.mjs` —— 全盘**只读**量算仪,junction 安全(刻意不跟随重解析点,否则把 D 盘的量算成
  C 盘的债),强制对账「遍历 + 特殊文件 + 未解释 = 已用」,未解释 >2GB 或为负都当场喊话。报磁盘状态先跑它,
  不要跑 `df`(§26 已记过一次它给出相反百分比)。用法 `node scripts/c-disk-breakdown.mjs [--root C:/] [--depth N] [--json]`。
  它的文件名不以 `check|scan|guard` 开头 ⇒ 守门 89 结构上看不见它,所以它的不变量由自己那把尺子钉:
  `scripts/tests/c-disk-breakdown.test.mjs`(7 例:滚到全部祖先 / junction 不跟随 / ROOTKEY 尾斜杠 / 量不到不得伪装成结论)。
  `scripts/plan-union-merge.mjs` —— 活文档台账被撞号或整段回写时的人工归并出口(临时索引 + commit-tree + CAS,
  落地前自证"本侧/对侧路径零丢失"),镜像测试 4 例在位。两者**都不做任何删除**。
- **出站事实对账**(116):`scripts/check-egress-facts.mjs`(blocking,2026-09-25 立) —— 钉 A34:一趟请求**实际**用的代理/CA/NO_PROXY 配置必须是**响应上的返回值**,不是日志行。成因不是假想:AGENTS §5b 连着记过"git 网络时通时不通""钩子进程不继承 shell env""服务身份与交互账户的 `safe.directory` 互不相通",每次都是人肉现读 `git config --local`,因为没有任何一次调用把"我这趟用了哪份配置"带回来。唯一出口 `apps/api/src/utils/proxy-dispatcher.ts` 的 `proxiedFetch`,响应挂**不可枚举** `egress` 字段(不可枚举是为防它被 `JSON.stringify(res)` 意外带进日志/响应体 —— 凭据面同族),用 `readEgressFacts(res)` 取;`isProxiedUrl` 已改成该事实的投影 ⇒ **决策与事实共用一份判据**(两处算同一件事必漂移,本仓记过多次)。厂商域名**不硬编码**,由 `VENDORS.baseUrl` + `DEFAULT_PROXY_DOMAINS` 两张真表推导(实测 100 个;推不出 ⇒ exit 2 不记绿)。覆盖面如实登记 = `apps/api/src` + `apps/cli/src/provider`;`packages/api-client` 打自家后端不入面,Python 侧只登记。**两条"没做"同样在案**:① 错误分流(被策略拦 vs 网络错两个码)—— 本仓 TS 侧没有任何出口会在传输前拒发,造两个码就是永远不响的门;② `NO_PROXY` 只读回成事实、未让它生效(改路由波及全部厂商调用,另票)。存量 6 处/5 文件进 `scripts/egress-facts-baseline.json` 每文件棘轮,只减不增;口径同 77/83/98/103/112/113/115。取证 `--self-test` 28 条 + §22c 镜像 5 例(含真表真挂装车证明);紧急跳过 `HUSKY_SKIP_EGRESS_FACTS=1`。
- **对话流元素覆盖**(57):**五类**判据(blocking)—— ① 锚点漂移(`anchor-missing-file`/`anchor-missing-marker`)② SSE 契约双端不齐 ③ 清单条目倒退(`entryCountBaseline`)④ **锚点存续性倒退**(`anchorCountBaseline` 的 `perElement` 逐条目 + `total`,含"该字段整块不见 = 台账被旧基线回写"的指纹;搬到同条目另一端是合法重构、总数不变即绿)⑤ **注释式摘线**(匹配面是**剥注释后的代码面**,字符串感知状态机;存量出口 `commentOnlyAnchorBaseline` 只报数不判红、只减不增,首轮 3 处存量已于 2026-09-25 按「mustMatch 重指向宿主代码面真实标识」清偿归零 ⇒ 新增该型直接判红,且**禁止**反向删宿主注释或补登记来消账);**判仓库内容而非共享工作区快照** —— 已暂存取索引 blob、仅工作树脏取 HEAD blob,别人未提交的重写不得钉红无关提交(2026-09-24,与守门 77 同取向)
- **测试收集存续性对账**(114,`scripts/check-test-collection-runs.mjs`,**warn 级**,紧急跳过 `HUSKY_SKIP_TEST_COLLECTION=1`)—— 堵"测试到底有没有在跑"这一维:mobile-rn 实测曾有 14 个套件在**收集阶段**整体失败、内含 131 条 it 声明(展开 137 枚用例)一条都没跑,而头条写成"2 failed"看着近乎全绿;更根本的是提交链百余道门**无一跑测试**(`check-staged-typecheck` 走 tsc,结构上看不见 transform / 解析期失败),CI 会红而提交链不拦。判据只认 `Failed Suites` 区块头与 `FAIL <file> [ <file> ]` 方括号形态两信号取大值(文件级计数在两种故障下长得一样,不作判据),断言失败另计并明写不计入本门;`No test files found` 或 `Test Files …(0)` 判红"空扫 ⇒ 覆盖归零";取不到结论(无 test script / 入口非 vitest / 入口不可解析 / 超时 / 无汇总行 / 空输出)一律**未判定**并点名原因,绝不记为通过。**刻意 warn 级**:该数字随他人工作树在 2→0→43 间跳,挂 blocking 等于每台每次逼人 `--no-verify`,一次绕过约 140 道门全废(§12e 同型);问责出口是 `--strict` / `pnpm check:test-collection`(CI 侧可换 `--require-idle` 型判红)。取证 `--self-test` 18 例(mkScratch 真造临时端、真派生)+ 镜像 17 例(含 T1 装车 / T2 摘线必判未接线 / T3 定级被改成 blocking 必读得出 / T4 全 runner id 不得重复 / T12 反向钉死不得 shell:true)。**已知边界**:只接 JS 系(vitest);pytest 侧是另一套版式、单列一票;`Failed Suites` 版式在 vitest 3.x(miniapp)是否逐字相同**未取证**,故扩端顺序是先 `--end apps/web` 连测零红再接提交链;被吞 it 的精确条数只能估算。
- **门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25 立) —— **门数与分档读数一律现读,本文不钉数字**(跑 `node scripts/check-gate-face-discipline.mjs --json` 看 `counts`;2026-09-26 那次实测是 face 28 / loose 71(其中 half-wired 1)/ unknown 38 / no-content 49 —— 写死它下次收紧就变成假账。**那 1 道半接线同日收口了**:`check-rn-global-css-sync` 换成走层的 `catBatch` 三面取材,收口后现读 `half-wired` 应为 0 —— 这条判据落地当天就产出了第一次真实修复,不是只多了一批红)。按磁盘判的门的结构性缺陷:共享工作树常年滞后 HEAD ⇒ 同一份 HEAD 代码在"恒红"与"假绿"之间来回跳,并把错数写回棘轮基线(守门 83 的 R3 登记一天内被整文件回退三次即此型)。**判据锚在"本次改动动过的门"上**(结构棘轮,不留会腐烂的白名单),全量档只报数 —— 一次性把 74 道判红就是逼各会话合法 `--no-verify`、连带废掉全部守门。**2026-09-26 收紧:"引了 `face-reader`" 不再构成合规** —— 旧判序第一刀是 `FACE_IMPORT_RE` 命中即判 `face`,于是"import 了层、内容却仍由自己 `git show` / 磁盘 `readFileSync` 取"这种**半接线**被系统性放过。实证不是假想:门 36 与门 124 都 import 了 `face-reader`(只用 `gitRaw`),却都**默认按磁盘判**;把两者改成 `catBatch` 判 HEAD 之后,本门分类读数一字未变 —— 一道自称守取材面纪律的门,对它立项要防的那一型完全失明。现判序:① 真调用层的**读取入口**(`catBatch` / `readWorktreeFile`,含 `as` 别名与 `import * as ns` 命名空间形态)⇒ `face`;② 引了层却没用它读内容、而内容来自自派生 git 或磁盘 ⇒ **`half-wired`(提交档判红)**;③ 其余照旧 `loose-git` / `loose-fs`。**只有非读取导出不算**:`selectFace` / `gitBinary` / `gitErrText` / `catBatchOids` / `catBatchSizes` 都拿不到正文,把它们当读取凭证就等于没收紧。反向护栏同等重要:别名与多行导入必须认(否则新判据对合法写法产假阳,`T10` 钉死);`usesLayerRead` 导出给镜像用,**禁止**在测试里再抄一份判据(§22c)。**同日两处精度修正(都是量出来的假阳,不是审美)**:① "读内容"与"枚举"必须分开 —— `cat-file -e`(存在性)、`ls-tree --name-only`(路径清单)、`rev-list`/`merge-base` 不产生 blob 正文,把它们算成"散写读内容"会把只做枚举的门钉成半接线(它一处 blob 都没读);**假阳比漏报更贵** —— 它指使人去"修"没坏的东西,还把判据口径说歪成"数字很多"。② 仓库锚点必须落在**路径函数实参**里:旧写法的 `|\bROOT\s*,` 一支会把 `function audit(limit = 400, cwd = ROOT)` 读成"以仓库根拼路径"。③ 借层的 transport 自己读内容(`gitRaw(['show', spec])`)同样算半接线 —— 管子共用不等于面共用。**已知空档如实登记**:锚点只认大写 `ROOT`,故 `join(root, …)` 这类小写模块根写法落进 `unknown`(不判红)。试过放宽(74→93),其中两格是假阳(一格读 `.workbuddy` 运行台账、一格读按设计只存在于部署机的 gitignore 副本),**已收回**:判据分不清"读被审内容"与"读运行态",就不该假装分得清并据此判红;补它要认识 gitignore 语义(参照 `check-prod-bundle-shadow` 的 S2),不是一行正则的事。两层遮噪方向不同,别混用:认 face-reader 导入与 git 动词必须"只遮注释、保留字符串"(模块说明符和 `cat-file` 本身就是字符串),认 `readFileSync` 调用必须"注释与字符串都遮"(`markHidden` 那一档连字符串一起抹,直接套它会让 F1/F2 双双假绿)。取不到 ⇒ exit 2,全量面枚举到 0 道门判死。自检与镜像例数以命令末行为准(本枚落地时 24 例 / 镜像 11 例,含 F16b 与 T9–T11 的收紧锁);紧急跳过 `HUSKY_SKIP_GATE_FACE_DISCIPLINE=1`。
- **子代理权限继承对账**(119):`scripts/check-subagent-permission-inherited.mjs`(blocking,`stagedTriggers=apps/cli/src/`,2026-09-25 立) —— 立门前先否证了票面前提:`spawn-subagent.ts` / `subagent-registry.ts` / `permission-engine.ts` 三路径在工作树+索引+HEAD **三件套均不存在**,全仓把 bypassPermissions 写成默认档 **0 处** ⇒ "派生默认绕过权限闸"不成立。真缺陷是**反方向那一格**:HEAD 面实测 **7 处 / 4 文件**的派生点**整个不下传** permissionMode 与 permissions,子代理落兜底档后父会话 `--disallowed-tools` 的显式 deny 在子代理里查不到(实测不传规则返回 ask、传规则返回 deny,已由 vitest 钉住)。三判据:**P1** 字面量 bypassPermissions 作默认档(零容忍、不吃豁免)/ **P2** 构造调用必须下传两键(**棘轮锚点 = 该文件 HEAD 自身计数**,存量 7 处只报数,绝不造恒红门)/ **P3** 调用之外读到权限键却没喂进去(半个继承)。**继承落点的代码改动因全在实现票清单外而未做**,补丁方案与越权点写在交付报告里 —— 登记事实,不代裁。自检 39 例(含"把 import 语句里的子串误判成读到键"的反向锁)+ 镜像 12 例;紧急跳过 `HUSKY_SKIP_SUBAGENT_PERMISSION_INHERITED=1`。
- **名单类判据正向证明对账**(120):`scripts/check-list-predicate-has-positive-proof.mjs`(blocking,**不挂** `stagedTriggers` —— 名单漂移可发生在任意路径,2026-09-25 立) —— 第八批三份独立取证收敛到同一个形态缺陷:本仓大量门是"名单驱动"(拦名单里的标识符、或放过白名单成员),而它们的自测只有**反向证明**(拦到坏值才红),从没证明**名单里某一条真能命中**;名单于是可以是张死表而门一路报绿。本门要求每条登记名单在其自测/镜像测试里至少有一条断言的输入**取自名单本身**;判不出 ⇒ 记「未判定」并点名,**不记为通过**。真仓 HEAD 面实测 3 条有真证(门 52 的 43 成员 / 门 80 的 22 / 门 107 的 6),尚未入库的名单判"未判定"而非红 ⇒ 不出生即红。自检 15 条 + 镜像 6 例;紧急跳过 `HUSKY_SKIP_LIST_POSITIVE_PROOF=1`。
- **错误码文本判分支对账**(**尚未接提交链** —— 本轮一度按 判红档 接进 注册表,现读整轮守门复跑后已摘出;重新接线由台账那条"解阻前置"管):`scripts/check-error-code-not-text-matching.mjs` 拦的是**"把文本当判据决定失败分支"**这一型 —— `if (err.message.includes('429')) retry()` 看着能跑,实际把重试/降级/拒绝的走向绑在第三方的一句英文上;上游改了措辞、或同一个码在另一条链路上换了文案,分支就静默换人,而 typecheck/lint/单测全都不会红(本仓"失效形态是安静"那一族)。四条算子同形认(`.includes/.indexOf/.lastIndexOf/.startsWith/.endsWith/.search/.match/.test` + 正则宿主 `/re/.test(x)`):参数必须是**字面量**才判(标识符实参一律放过,`RETRYABLE_CODES.includes(code)` 是正当形态);**不要求**必须摆在 if/三元里 —— "先算 severity 再按它分流"的两步式是本仓实际写法,要求分支上下文会让整型隐身,误伤面由"文本来源表达式 + 字面量实参"两道窄口径兜住。**取材遮噪方向与本仓多数门刻意相反**:只遮注释、**保留字符串**(本门要判的就是那个字面量,连字符串一起抹 = 对该型全盲而账面报"零违规" —— 最贵的假绿);两档共用 `scripts/lib/code-mask.mjs` 那一份分词器,任何一门不得留第二份遮噪。跨行拼接(算子在一行、字面量在下一行)**只数不判红但必须打印**("单行判据的已知盲区"不报名就会被读成"仓库里没有这种形态")。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"不回落、**枚举到 0 个候选判死不记绿**;棘轮锚点 = **该文件 HEAD 自身违规数**(HEAD 现读扫描 1036 文件 · 文本判分支 156 处:literal-in-text-op=132 / regex-receiver-on-text=18 / status-as-text=4 / regex-in-text-op=2 —— 存量当场判红就是恒红门)。行内出口 `error-code-exempt: <原因>`(**须带原因**,已登记进守门 108 的 **30 天**存活期档 —— 它豁免的是"待偿的补码债":出路只有给抛出方补 `ToolError(code,…)` 或把判定挪到结构化字段,不给 365 天结构性定性、也不走 90 天默认档)。**唯一出口是数据驱动的需求表** `OUTLET_REQUIREMENTS`(两处文件:`apps/cli/src/tools/index.ts` 的再导出 + `apps/cli/src/tools/failure-classification.ts` 的原声明),出口被摘线时本门判"没有出路"并**参与退出码**;本门自己还没入库那一格报 `self-not-landed`,不冒充 ok 也不冒充 stripped。**为什么摘出来(现读事实,不是预防措施)**:`git show HEAD:apps/cli/src/tools/failure-classification.ts` 取不到该文件 ⇒ 出口在位性判据在**干净 HEAD** 上判红,而它一红每台每次都被逼 `--no-verify`、一次绕过等于该提交上约 194 道守门全部作废(§12e/§12f 同型,这次是我自己接早了造的)。**判据与出口表都没有放宽** —— 判据要的出口是对的,只是还没入库;门体、镜像测试(11/11 RC=0)、`--self-test`(26 条 RC=0)、守门 108 那一档豁免全部原样留在面上。**一条落地过程本身值得抄的教训**:这台尺子由上一轮代理写完就停在轮次上限,自报"自检 26 条全绿 / 已注册 runner 第 162 项",主会话现读发现它**连解析都没过**(第 491 行 `ok('…', () => {` 用了普通箭头函数、闭合却按兄弟块的 IIFE 形态写成 `})())` ⇒ 整个文件 SyntaxError ⇒ 任何"跑过"的记录都是叙述),另有一处自检夹具**绕过权威入口**(`detectOutlet(text)` 单参调用,缺省回落到需求表第一行的名单 ⇒ 对第二行需求恒判 stripped;生产路径一直传 `req.exports`,所以是夹具错不是判据错)与一处镜像测试自身的 Windows 缺陷(`await import(绝对路径)` 未走 `pathToFileURL` ⇒ 恒抛,而抛错会被读成"这一格判过了";同文件第 27 行对门体已经走对了,唯独漏这一处)。三处都改完才有资格说判据存在。应急跳过变量在重新接线时随条目声明为 `当前无应急跳过变量(它不在钩子链上)`。
- **声明策略消费者对账**(121):`scripts/check-declared-policy-has-consumer.mjs`(blocking,`stagedTriggers=apps/,packages/`,2026-09-25 立) —— 判五类"声明"必须有生产面消费者(带链 import 且取用):MAX_AGE/TTL/RETENTION 策略常量、`*BudgetContract*`、packages/types 的 `*Contract` 导出类型与情态谓词、`(prune|cleanup|purge|expire|sweep)` 导出清理函数。注释/字符串/纯 re-export/测试面/scripts 层都不算消费者。立票实测:`apps/cli/src/sessions/state-store.ts` 的 `pruneOldSessions`(7 天保留)全仓零调用方 ⇒ 已挂到 `saveSession` 写前(60s 节流 + `IHUI_SESSION_PRUNE_DISABLED` 出口 + 裁剪异常不冒泡,"宁可不删");`ToolResultBudgetContract` 的唯一合理消费点在 `tools/index.ts` 的 executor 边界,该文件在飞 ⇒ 未接线登记,摘除会砸坏守门 111 的 CONTRACT_GROUPS(§7 有承接功能不得删)。全量现测:候选 131 / 未接线 32 处 29 文件只报数(subagents/state-store.ts 与 apps/api cleanup 族 9 处同型,清偿另计批)。自检 22 例 + 镜像 8 例;紧急跳过 `HUSKY_SKIP_DECLARED_POLICY_CONSUMER=1`。
- **文件写盘安全对账**(122):`scripts/check-file-write-safety.mjs`(blocking,`stagedTriggers=apps/cli/src/tools/`,2026-09-25 立) —— 工具层写文件必须走原子写出口(同目录临时文件 + `rename` 替换 + Windows rename 目标已存在会 EPERM/ENOENT 带重试 + **不跟随符号链接/junction**(§26 递归穿透事故同族)+ **读后写 stale 校验**:编辑前读到的内容与落盘前磁盘不一致时拒绝而非覆盖 —— 多会话共享工作区的真实风险)。裸 `writeFileSync` 存量走棘轮(实测 `file-edit.ts` 2 处只报数),新增即红。自检 24 例 + 镜像 13 例;紧急跳过 `HUSKY_SKIP_FILE_WRITE_SAFETY=1`。
- **工具族注册对账**(guardian id **131**,**warn**,紧急跳过 `HUSKY_SKIP_TOOL_FAMILY_REGISTERED=1`):`scripts/check-tool-family-registered.mjs` 问的是另一句话 —— tools/** 里 export 的每一族在生产面有没有把它挂进注册表的调用点。它补的是 `check-tool-registry-projection`(131 号那条近邻门)结构性看不见的那一格:**没注册 ⇒ 两侧都没有 ⇒ 那道门一致而绿**。三条到达路径(跨文件具名导入且落注册参数位 / 同文件一跳委托但须证明 owner 在生产面被调 / 随父族组合传播);注释与字符串先抹掉再判(否则门给自己发合格证);动态拼接、`export *`、命名空间转发、同名本地符号一律「未判定」—— 不记绿也绝不判红。定级 warn + **HEAD 存量棘轮**(只拦新出现的零注册族与"本次把注册点摘掉"),问责 `pnpm check:tool-family`(`--strict`),**升 blocking 前置 = 未注册存量归零**;现值一律跑该命令看末行(立项读数:族 18 / 已注册 17 / 未注册 1 = `DEBUG_TOOLS`,即 10 枚 DAP 工具与那句"30 分钟自动清理"对模型不可达 —— 是否注册它属 §24 产品决策,不是清账动作)。
- **工具执行预算对账**(123):`scripts/check-tool-exec-budget.mjs`(blocking,`stagedTriggers=apps/cli/src/tools/`,2026-09-25 立) —— 钉"工具执行无界"三源:`ToolContext` 无 `signal` 字段(取消无通道)、执行点无超时、`util/exec.ts` 无 `maxBuffer`(node 默认 1MB,超限抛错并丢已缓冲输出)。S1 系判**机制在位**(常量三件套 `TOOL_EXEC_BUDGET_{DEFAULT,MAX,SETTLE_GRACE}_MS` / 唯一解析出口 `resolveToolExecBudgetMs` 且内含 `Math.min` 封顶 / `linkAbortSignal` / `executeWithinExecBudget` 在代码位出现"自身定义+至少一次调用");S2 判**注入点**(未传 signal 的 ctx 构造逐处点名)。取材面=HEAD/索引,取不到 exit 2。自检 26 例 + 镜像 9 例;紧急跳过 `HUSKY_SKIP_TOOL_EXEC_BUDGET=1`。
- **记忆端点属主绑定对账**(117,**blocking**,2026-09-25 升档;紧急跳过 `HUSKY_SKIP_MEMORY_OWNER_BINDING=1`):`scripts/check-memory-owner-binding.mjs` 钉的是**"认证不等于授权"**那一层 —— `apps/ai-service/app/api/memory.py` 把 `user_id` 当请求体/Query 参数收,全文件**没有一处**与令牌主体对齐;`/api/memory` 不在 `PUBLIC_PATHS` ⇒ 匿名进不来,但**任何已登录用户填别人的 UUID 就能读/改/删别人的记忆**。判洞要问"有没有把请求里的 id 与令牌里的 id 比对",不是"有没有全局中间件"。两条 Python 语法锚点必须同时认:`user_id: str = Query(...)` 与 Pydantic `Field(...)` —— **只认 Query 会让整类 POST 端点隐身**;对齐出口只写在注释里不算(自称已拆、实际没拆那一型)。**定级史(照抄旧档会判错)**:立项时 warn —— 当时未对齐存量 2 个文件;现值一律 `node scripts/check-memory-owner-binding.mjs` 自取 —— `app/api/memory.py` 已于同日 `3b0170fa176` 九端点统一 `require_request_user_id` 归零,剩下的是**此前无人枚举过的** `app/routers/rules.py`,而"怎么对齐"取决于 **v1 对外 API 能不能代表他人访问记忆** —— 实测 `apps/api/src/routes/v1-knowledge-tools.ts` 有 2 条转发是 `jsonInit(parsed.data)` **原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。收紧成 403 与"忽略并覆盖"两种改法**都会让本门归零**,故存量未清时判红就是恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。**2026-09-25 前置已满足并升 blocking**:`752c6eb110b` 收掉最后一个未对齐面(`app/routers/rules.py`)后 HEAD 面实测"未对齐 0 个",runner 侧同时带 `args:['--strict']`(本门默认档只报数,不带它升档是假的)。**降回 warn 等于重新放开新增敞口**;若 HEAD 面再报未对齐(例:有人跳过钩子塞进去),先清偿再提别的票,不得削判据。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、枚举到 0 个文件判"无法判定"而非记绿 —— 判据失明不是通过)。仓里现成出口 = `app/core/jwt_auth.py` 的 `require_request_user_id`(带 `DEV_ANONYMOUS_PRINCIPAL` 开发降级,以免 ASGI in-process 的既有测试整片 401 —— 那不构成安全结论)。取证 `--self-test` 17 例(正反成对) + §22c 镜像 14 例(含"未注册时不得被判定为已装车"的方向性对照);手动问责档 `pnpm check:memory-owner`(走 `--strict`)。
- **记忆端点属主绑定对账**(117,**warn**,紧急跳过 `HUSKY_SKIP_MEMORY_OWNER_BINDING=1`):`scripts/check-memory-owner-binding.mjs` 钉的是**"认证不等于授权"**那一层 —— `apps/ai-service/app/api/memory.py` 把 `user_id` 当请求体/Query 参数收,全文件**没有一处**与令牌主体对齐;`/api/memory` 不在 `PUBLIC_PATHS` ⇒ 匿名进不来,但**任何已登录用户填别人的 UUID 就能读/改/删别人的记忆**。判洞要问"有没有把请求里的 id 与令牌里的 id 比对",不是"有没有全局中间件"。两条 Python 语法锚点必须同时认:`user_id: str = Query(...)` 与 Pydantic `Field(...)` —— **只认 Query 会让整类 POST 端点隐身**;对齐出口只写在注释里不算(自称已拆、实际没拆那一型)。**定级 warn 是设计前提而非偷懒**:HEAD 实测未对齐存量 **2 个文件**(`app/api/memory.py` 与**此前无人枚举过的** `app/routers/rules.py`),而"怎么对齐"取决于 **v1 对外 API 能不能代表他人访问记忆** —— 实测 `apps/api/src/routes/v1-knowledge-tools.ts` 有 2 条转发是 `jsonInit(parsed.data)` **原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。收紧成 403 与"忽略并覆盖"两种改法**都会让本门归零**,故在选型落地前它是"存量报数、新增判红"的棘轮;当场 blocking = 与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。**升 blocking 的前置条件 = 未对齐存量归零**。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、枚举到 0 个文件判"无法判定"而非记绿 —— 判据失明不是通过)。仓里现成出口 = `app/core/jwt_auth.py` 的 `require_request_user_id`(带 `DEV_ANONYMOUS_PRINCIPAL` 开发降级,以免 ASGI in-process 的既有测试整片 401 —— 那不构成安全结论)。取证 `--self-test` 17 例(正反成对) + §22c 镜像 14 例(含"未注册时不得被判定为已装车"的方向性对照);手动问责档 `pnpm check:memory-owner`(走 `--strict`)。
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**:旧版首次 commit 一失败就打印「按用户规则"hook 失败因其他 agent 代码 → --no-verify 重试"」并整批跳门 —— 它**从未计算过归因**。实测一轮 134 道门 111 通过 / 1 失败,红的是 `check-push-sync`(判远端态、与提交内容无关),措辞仍是"他人代码"。现流程:解析 runner 的失败门清单 → **逐道复跑** → 该门这次的输出**点名本次声明的文件 ⇒ 判"本任务自己的红",拒绝 `--no-verify` 并 exit 1**(必须修);一个都没点名 ⇒ 允许跳,但只说量到的话;批没跑完 / 解析不到 / 复跑全不可用 ⇒ 明写「未归因」,按应急路径落地并留痕于 `.workbuddy/safe-commit-attestation.jsonl`。**"批一道都没跑"从 2026-09-26 起不再是中性事实,而是一个必须先补证据的分支**:`scripts/lib/pre-commit-hook.js` 里 lint-staged 跑在守门批**之前**,于是一次 lint/prettier 失败 = 156 道门全不跑 = 归因永远 `unattributed` = 照样跳门,而账面读起来像"跑过了、只是与本次无关"(实测 `acaf8407cc` 与 `d379215126` 前后各一次;这正是"判据失效的表现永远是安静"那一型,守门 70/76/81 同族)。现由 `safe-commit` 在判到 `unattributed ∨ ranFullBatch===false` 时**自己把 `guardian-runner --staged` 跑一遍**,再把那一轮的输出喂回**同一条铰链**(`classifyHookFailure`)分流:点名本次文件 ⇒ `mine` ⇒ 拒绝跳门;一个都没点名 ⇒ 可跳但措辞必须写明"取证来自 safe-commit 自跑的那一轮,不是钩子内那一轮";自跑也没成功 ⇒ 照旧落地,而 `unattributed` 的措辞必须带上具体原因(runner 不在位 / 派生失败码 / 超时)。**刻意不把 `unattributed` 改成拒绝跳门** —— 那会让"别人把我挡在 lint 外"的应急路径消失,逼人手工 `git commit` 跳得更彻底且不留痕。**一条如实登记的覆盖面上限**:自跑发生在首次 commit 失败之后,而 lint-staged 失败时会回滚它自己动过的暂存区 ⇒ 新增文件此刻已退回未跟踪,自跑那一轮看到的暂存集可能少于声明集(与既有"逐道复跑"同一刻同一索引态,两份证据因此可互相比较)。应急提速:`IHUI_SAFE_COMMIT_BATCH_TIMEOUT_MS`(极小值 ⇒ 自跑判"未成功"并带原因入库,**不是**静默开关)。取证:镜像 13 例(含"汇总块在位时绝不自跑"的反向锁与装车证明)。**stdout 没汇总而要去钩子日志取第二输入源时,该汇总块必须先过"轮次绑定"这道锁(2026-09-25 补,G-179)**:钩子日志是多轮追加的,而旧锁只要求"该汇总之前的窗口里出现过本次声明的**任一**文件名"—— 那段窗口实测 48KB–202KB,里头**必然**提到 `PROJECT_PLAN.md` / `AGENTS.md`(守门 71/13c 的结论行就点名它们),于是"我自己的红"可以借别人那一轮的汇总被洗成 `not-ours`(实测提交 `48ac2c03e`:红在 eslint `no-unused-vars`,lint-staged 失败使守门批根本没跑 ⇒ 152 道门被一次误归因跳掉,HEAD 里真留下那枚 lint 错误)。现规则:**该汇总之前那次 `staged 文件清单` 回显必须完整包含本次声明集**才允许用于归因 —— 回显由钩子入口 `auditStagingFiles()` 全量打印,是把一段日志绑到具体某枚提交的唯一内容证据(日志没有时间戳、也没有轮次号,实测按 `^YYYY-MM-DD` 抓零命中)。取"包含"不取"等值":回显反映磁盘实际暂存,并发会话同窗口 staged 的东西会造成超集,而它们结构上进不了本枚提交(commit 带 pathspec)。**绑定不成 ⇒ 落 `unattributed`,照旧能提交,只是措辞不得声称"因他人代码"—— 失效方向必须是"多要一次定向说明",绝不能是"多放一次跳门"**(与本节末那条 `findingLines` 教训同一条禁令)。真仓双向证明(不是夹具):合法那一轮绑定成功并取到 `总检查数: 152(已执行 152)` 的汇总,事故那一轮被拒且点名缺的 2 个文件。**stdout 没汇总时,第二输入源必须先过"轮次绑定"这道锁**(2026-09-25 补,G-179):钩子日志是多轮追加的,而旧锁只要求"该汇总块之前的窗口里出现过本次声明的**任一**文件名"—— 那段窗口实测 48KB–202KB,里头**必然**提到 `PROJECT_PLAN.md` / `AGENTS.md`(守门 71/13c 的结论行就点名它们),于是"我的红"可以借别人那一轮的汇总被洗成 `not-ours`(实测提交 `48ac2c03e`:红在 eslint `no-unused-vars`,而 lint-staged 失败使守门批根本没跑 ⇒ 152 道门被一次误归因跳掉)。现规则:该汇总块之前那次 `staged 文件清单` 回显必须**完整包含**本次声明集(回显由钩子入口 `auditStagingFiles()` 全量打印,是唯一能把一段日志绑定到具体某枚提交的信号 —— 日志没有时间戳、也没有轮次号);缺任何一项 ⇒ 判"不是本轮" ⇒ 落到 `unattributed`。**失效方向刻意是"多要一次定向说明",绝不是"多放一次跳门"**。真仓双向证明:合法那一轮 `绑定成功` 并取到 `总检查数: 152(已执行 152)` 的汇总块,事故那一轮 `拒绝` 且点名缺的 2 个文件(4 项声明 vs 4 项回显,重名 2 项 —— 正是旧锁放过的形态)。**刻意不建"机器态门 id 清单"** —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。取证:镜像测试 5/5(含装车证明 + 真实 `hook-logs/pre-commit.log` 取样校验夹具)+ 三条变异各自变红(ANSI 剥色那条登记为**等价变异**,不造假具硬凑红)。**因此:agent 不得再假设"钩子红了就能跳"** —— 被拒时那句红点名了哪个文件,就去修哪个文件;确属误判才可按 §16 显式说明后手工提交。
- **守门接线层对账**(89): `scripts/check-gate-wiring.mjs`(blocking) —— 本门是其余全部守门的"装车检",查接线层四类结构缺陷:**R1/R2** 脚本头部或 AGENTS.md 声称"已接 pre-commit/pre-push/CI 必跑/第 N 项"而五处权威点(`guardian-runner` 的 `script:` ∪ `scripts/lib/pre-commit-hook.js` ∪ `.husky/*` ∪ 根 `package.json` ∪ `.github/workflows`+cert)全部零命中 → 拦;**R4**(2026-09-24 由"仅报数"升档,前置=真仓缺口 48→0 已清零)门已接线但 AGENTS.md/README.md 通篇未点名 → 拦(判据刻意宽松:出现去后缀同名即算,故只会漏报不会误拦);**R5** 同一 `id` 登记两道门 → 拦(串 skipEnv 与失败归属);**R7** 台账 `type=dispatcher` 的依据文件不存在或文件里没提该脚本 → 拦。**R8**(2026-09-25 补)某道门的 `stagedTriggers` 只要会让归一层 `scripts/lib/guardian-triggers.mjs` **当场抛错**即拦 —— runner 的 `for (const check of effectiveChecks)` 没有 catch,一次抛错=整条 pre-commit 中止(同型崩点实测让 3 枚提交被迫 `--no-verify`,152 道门对那 3 枚全废)。**红条件委托那道 lib 的契约而不是本门自写形状规则**:自己写必然与 lib 漂移,而漂移的代价固定是"一道对合法注册恒红的 blocking 门 ⇒ 逼人跳门 ⇒ 全部守门作废"。据此三类**只分类报数不判红**:裸字符串=「已救回」(lib 有意归一,再判红就是两道门互相矛盾)、falsy 字面量=「死注册」(runner 的守卫走不到归一层,它结构上崩不了)、值不是字面量(变量/模板串)=「判不出」(不猜,也绝不静默算通过)。取材面**与 R1/R2 刻意不同**:全量档 HEAD、`--staged` 档优先索引 —— 注册表自身就是被审对象,"同一枚提交里既修注册形态又新立判据"若被自己的新判据判红,就等于本门亲手制造跳门(与 R2/R4 的 HEAD∪索引例外同一理由,两面一绿一红时才算证明过判据有牙)。R3(五处零命中且无肯定式声称)/R6(共用 skipEnv)/弱接线(仅 CI)只如实报数不判红。**取材口径分两面**:接线面(哪些脚本被登记为已接)一律按 **HEAD** 判 —— 共享工作区的工作区文件会滞后,产出相反结论;而**文档面**(AGENTS.md / README.md 有没有点名)按 **HEAD ∪ 索引**判(2026-09-24 改),因为"同一枚提交里既注册新门又补点名行"是正确姿势,只读 HEAD 会让这枚提交被自己判红 ⇒ 逼人 `--no-verify` ⇒ 全部守门作废;索引取不到退回 HEAD,口径在结论行如实报出,且**绝不读工作区**(只躺在工作区没 `git add` 的文档行不算点名,已由自检 M9d/M9e 一红一绿的可逆对照钉死)。;台账 `scripts/gate-wiring-allowlist.json` 只能救 R3,凡声称已接线者台账不救。⚠️ 本门对自身 `SELF_EXEMPT`(创建当期 HEAD 里还没有它),所以"它自己被摘线"由镜像测试 T13 钉死。真跑 `node scripts/check-gate-wiring.mjs` exit 0(已接线 143 / 台账豁免 13 / R4 0 枚,数字以命令末行现测为准);自检 `--self-test`(例数以末行为准,含 M8a/M8b 的 R4 双向端到端证明 + M9a-M9e 的文档取材口径五形态对照)+ `node --test scripts/tests/check-gate-wiring.test.mjs`(例数以末行为准,含 T13 装车证明与 T14/T15 取材对照);紧急跳过 `HUSKY_SKIP_GATE_WIRING=1`。
- **记忆端点属主绑定对账**(117,**warn**,紧急跳过 `HUSKY_SKIP_MEMORY_OWNER_BINDING=1`):`scripts/check-memory-owner-binding.mjs` 钉的是**"认证不等于授权"**那一层 —— `apps/ai-service/app/api/memory.py` 把 `user_id` 当请求体/Query 参数收,全文件**没有一处**与令牌主体对齐;`/api/memory` 不在 `PUBLIC_PATHS` ⇒ 匿名进不来,但**任何已登录用户填别人的 UUID 就能读/改/删别人的记忆**。判洞要问"有没有把请求里的 id 与令牌里的 id 比对",不是"有没有全局中间件"。两条 Python 语法锚点必须同时认:`user_id: str = Query(...)` 与 Pydantic `Field(...)` —— **只认 Query 会让整类 POST 端点隐身**;对齐出口只写在注释里不算(自称已拆、实际没拆那一型)。**定级 warn 是设计前提而非偷懒**:未对齐存量:立项读数 2 个文件;现值一律 `node scripts/check-memory-owner-binding.mjs` 自取 —— `app/api/memory.py` 已于同日 `3b0170fa176` 九端点统一 `require_request_user_id` 归零,剩下的是**此前无人枚举过的** `app/routers/rules.py`,而"怎么对齐"取决于 **v1 对外 API 能不能代表他人访问记忆** —— 实测 `apps/api/src/routes/v1-knowledge-tools.ts` 有 2 条转发是 `jsonInit(parsed.data)` **原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。收紧成 403 与"忽略并覆盖"两种改法**都会让本门归零**,故在选型落地前它是"存量报数、新增判红"的棘轮;当场 blocking = 与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。**升 blocking 的前置条件 = 未对齐存量归零**。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、枚举到 0 个文件判"无法判定"而非记绿 —— 判据失明不是通过)。仓里现成出口 = `app/core/jwt_auth.py` 的 `require_request_user_id`(带 `DEV_ANONYMOUS_PRINCIPAL` 开发降级,以免 ASGI in-process 的既有测试整片 401 —— 那不构成安全结论)。取证 `--self-test` 17 例(正反成对) + §22c 镜像 14 例(含"未注册时不得被判定为已装车"的方向性对照);手动问责档 `pnpm check:memory-owner`(走 `--strict`)。
- **守门**:`scripts/check-glyph-arrow-icon.mjs`(guardian 第 **102** 项,blocking)六类判据:**GA1** 整格字符箭头 `›»→》‹←`(或 `{'>'}`)在可证 affordance 语境(自身或 ≤8 层祖先带 `on*Press/Click/Tap` 或 `role="button"`,或祖先为 `Link/Pressable/Touchable*/Button`)当图标 —— **左向 `‹`/`←` 于 2026-09-25 补入**(立项时只有右向四字,于是小程序 NavBar 与 7 个页面头拿 `‹` 当返回图标长期零判据);**GA2** 「更多」类标签与**同词干** `*Arrow*` 样式配对后箭头字号 > 标签字号(单位不同不判红,计入 undetermined);**GA4**(同日新增)整格子内容是「返回」类文案(`t('common.back')` 这类键名末段为 `back`/`back<数字>` 的 i18n 调用,或字面量 `返回`)且处在可证 affordance 语境 ⇒ 即"把返回两个字当返回箭头";**带宾语的标签刻意不纳**(`backHome`/`backLogin`/`prevMonth` —— 那些是按钮文案,换成裸箭头反而不表意);**GA5**(同日新增)「返回」与字形的**混写**两型:`← 返回` 整格(GA1 要整格只有字形、GA4 要整格只有文案,混排两条都不纳),以及字形与返回调用**分居同一元素的两个子节点**(`← {t('common.back')}` —— `walkAffordanceChildren` 走 `loneChildText`,多子元素的元素根本不入选 ⇒ 前三条结构上全盲);**GA6**(同日新增,跨文件判据)小程序页面渲染页内返回键(`BackChevron` 或 `<NavBar/>`)却是**原生导航栏** ⇒ 同屏两个箭头 + 双层 chrome,读 Taro 页面 config、页内未写则继承 `app.config.ts` 的全局 window,两处都取不到 ⇒ 计入「未判定」点名(不记绿也不冒红);**S0** 共享唯一实现被摘线或无人 import(防"造好没装车"),现登记 5 条:三端「更多」箭头 + 页头返回键的**两份同名实现**(小程序端内与 RN 共享层 `packages/app` —— 只登记一份时另一份被摘线无人喊红)。GA1 与 GA4 **共用同一遍遍历**(`walkAffordanceChildren`),两条对"可证 affordance"的定义必须同形 —— 各写一遍必然在栈深/祖先窗口上漂移。口径同 77/83:全量判 HEAD blob、`--staged` 判索引、**棘轮锚点 = 该文件 HEAD 自身违规数**(行尾字符箭头存量 70 处只报数不拦,免得恒红逼人绕过钩子连带废掉全部门);豁免**三条互不串门的通道**:`glyph-arrow-exempt: <原因>`(GA1/GA2,只认命中同行)、 `back-label-exempt: <原因>`(GA4/GA5,认命中行、可点元素起始行、及其紧邻上行)、**文件级** `nav-chrome-exempt: <原因>`(GA6 —— 它的"错"是页面配置 × 页面渲染的组合,不落在某一行上,逐行豁免无意义)。三个族都已登记进守门 108 的存活期表(`back-label`/`nav-chrome` 取 365 天:结构性定性而非待偿债务,短周期到期逼人删标记、删了又被判红,两道门互咬)。前两条**宽严刻意不对称**且各由自检钉住:GA1 若也按块放行,一个标记就能救整棵子树,"一行救不了别处"那条反向锁就没了;GA4 若只认同行,人按直觉把标记写在可点块上就永远不生效(初版即如此,四处真实站点全部落空 ⇒ 恒红)。**原因不得由注释闭合符冒充**(裸标记 + `*/` 曾被读成"带了原因"⇒ 整行免检;该洞自 §22c 立项起就在 GA1 里,由 GA4 的自检反手抓出,现两条通道同锁,全仓零裸标记存量故改严无债务)。取证 `--self-test` 81 例(含阳性对照、棘轮四向、预筛必须是判据字面量**超集**的对账 —— GA5 的 `«` 与 GA6 的 `BackChevron`/`NavBar`/`navigationStyle` 三个标识符少一个,门就在自己立项那一型上失明;GA6 的继承链三态、GA5-B 的"去掉判据即翻绿"存在性证明)+ 镜像测试 16 例(含"GA1/GA4 豁免宽严不得悄悄对齐"、"预筛漏字形=门对该形态全盲"、**"GA5/GA6 必须真挂在 `scan()` 上"**(守门 70/76/81 同型:函数在、自检过,但 `scan()` 没调它 = 提交链上一路绿灯)、"GA6 不得按磁盘读 config"、"新增豁免族必须进 108 存活期表")。紧急跳过 `HUSKY_SKIP_GLYPH_ARROW_ICON=1`。
- **GA1 存量已清零(2026-09-25 第三轮;取代上一行末「行尾字符箭头存量 70 处」那个读数)**:
  70 处 / 31 文件的行尾 `› » → 》` 字符箭头全部换成各端矢量 —— 小程序 53 处 → `LineIcon chevron-right`
  (尺寸统一 24rpx = §4 的小程序光学档,色一律沿用该元素原 token),web 8 处 → lucide-react 的
  `ChevronLeft`/`ChevronRight`/`ArrowRight`(跟随原字号),共享屏层 9 处 → lucide-react-native 的
  `ChevronRight`(**沿用原 fontSize,只换载体,不顺手改观感**)。随之失去使用者的样式规则一并删
  (`member-arrow`/`pf-arrow`/`vs-share-arrow`/`cd-aigc-arrow`/`card-entry-arrow`/`ask-create-picker-arrow`
  与 `itemArrow`、`modelBarArrow` 的 `fontSize`),仍被别处引用的(`py-coupon-arrow`/`income-stat-value`)保留。
  **棘轮锚点仍是「该文件 HEAD 自身违规数」** —— 存量清零不等于判据变严,判据一个字没改。
  **路上踩到两条,记为规矩:**① 补 import 不得用 `/^import /` 找「最后一行」——多行 import 的首行也匹配,
  于是新 import 被插进别人 import 的中间,当场写坏文件(eslint 报 `Parsing error: Identifier expected`);
  要按「以 `from …` 收尾的完整 import 行」定位。② 样式工厂里的局部色变量不得直接搬到组件 JSX 作用域
  (`arrowColor` 定义在 makeStyles 内、JSX 在组件内 ⇒ 改用组件作用域的 `tk.text.tertiary`),
  否则 lint 判 unused、运行时取到 undefined。
- **守门 102 的 HEAD 存量已全清 + 覆盖面换成实测(2026-09-26,终读:S0/GA1/GA2/GA4/GA5/GA6 全部 0)**。三件事值得单独留成规矩:
  ① **"全端已覆盖"不能建立在"恰好没有这种文件"上**:原 `TSX_RE = /\.tsx$/` 让 GA1/GA2/GA4/GA5 只在 `.tsx` 上判。
  实测受管 12 目录里 `.js/.html` 共 13 个、喂门自己的 `auditFile` 违规 0(命中全是注释与 config),全仓 `.jsx` 存量 0,
  CSS `content:` 用字符箭头 0 处 —— 数字上"不扩今天不漏",但仍把 `TSX_RE` 扩到 `\.(tsx|jsx)$`(`SRC_RE` 同步),
  并加自检锁"同一条违规在 `.jsx` 与 `.tsx` 上必须同判"。**为什么**:本门立项的理由就是"一条门只管自己立项那一型"
  (左向 `‹` 不在字符集里 ⇒ 9 个真站点长期零判据);一枚新 `.jsx` 就能让这句话静默作废而账面仍全绿。
  锁的有牙证明靠变异:`TSX_RE` 改回 `/\.tsx$/` ⇒ 自检 82/83 且红的正是这一条,还原后 83/83 并与备份 `diff` 逐字一致。
  ② **留在工作树里等 owner 提交的回退,必须当场证明"提交它会被拦"**:最后 8 处所在四文件被并发会话持有
  (工作树各有 19–613 行在飞改动),本票按「`git show HEAD:<path>` ⊕ 锚点精确替换(计数断言)→ 私有索引 →
  commit-tree + CAS」落地,不交工作树版本。其中 `SettingsScreen.tsx` 的工作树副本把 HEAD 的 `<BackChevron/>`
  换回「返回」两个字 —— 把那份 blob 只放进 `GIT_INDEX_FILE` 指的探针索引、按 `--staged` 喂本门 ⇒
  **exit 1 并点名 GA4**。"延后"与"放开一条回退通道"的差别,就是这条证明。
  **另:commit-tree 之后必须逐路径把主索引对齐到新 blob** —— 否则那四个路径留在"索引 == 旧 HEAD"的 `M ` 形态
  (§12d 第三层描述的形态),此后一次不带 pathspec 的普通提交就把修法写回旧版。
  ③ **GA5 那 1 处的正确处置是带理由豁免,不是矢量化**:它在 `ModelConfigDialog.tsx` 的 `RatioSelector` 内,
  实测该组件作用域里没有 `t`、也没有 `tokens`(换矢量要动组件签名)—— **不得为消红去改别人组件的 API**;
  与 `packages/app/src/components/Selecter.tsx:403` 同一处置(同为步骤回退标签,`back-label-exempt` 带原因与到期日)。
- **miniapp 原生 chrome 两份副本已收口为派生态 + 专属门**(2026-09-25 立):`apps/miniapp-taro/src/theme.json`(微信 darkmode 配置,`app.config.ts` 以 `@变量` 读取的编译期源)与 `apps/miniapp-taro/src/lib/theme.ts` 的 `THEME_CHROME`(运行期 `setNavigationBarColor`/`setTabBarStyle`)此前是手抄且**零生成器、零守门**(`grep -rl theme.json scripts/*.mjs` 立项时零命中)。现由 `scripts/sync-miniapp-chrome.mjs` 派生(原位写回、幂等;同色不同字节如 `#A3A3A3` 保留文件原字节,首跑不产伪 diff),挂进 `TOKEN_SYNC_TARGETS`(trigger `tokens`,failMode `block`),由守门 `scripts/check-miniapp-chrome.mjs`(guardian-runner id **122**,blocking,紧急跳过 `HUSKY_SKIP_MINIAPP_CHROME=1`)复核。**登记分流是记录在案的决策,不是漂移**:dark `navBg`/`windowBg` = `#262626` 出自 commit `d2d80c1b23`(2026-09-06)明文「对齐 rn gray.800」,而源头 `.dark --color-background` = `hsl(0 0% 14%)` = `#242424`;该分歧登记在生成器的 `CHROME_DECLARED_DIVERGENCE` 且带 `nearToken` 机判 —— 源头一旦重新同值即判"登记表腐烂"红。**不得**把 #262626「同步」回 #242424,那是回滚那枚决策;`tabSelected` 的 #8fb8cc/#a3c4d6 即 `--color-brand-accent` 两档(2026-09-14 定稿的跨端强调色)。
- **小程序端「源 ↔ 派生产物」存续性对账**(105):`scripts/check-miniapp-generated.mjs`(**warn,刻意不 blocking**)—— 管四件"生成器造好了但没人跑"的派生产物:i18n 离线包 `remote-locales.gen.ts`、`static/images/icons/*.svg`、`LineIcon/icons.ts`、`assets/tabbar/*.png`。六条判据(G1–G6;G5 由 G-680 加、G6 由 G-415 A8 加):**G1** 源里有而产物没有 ⇒ 判红(端上会缺词、图片 404 且**不报错**);**G2** 产物里有而源已无 ⇒ 默认只报数,`--strict` 才判红(删产物属 §7 删除安全);**G3** `icons.ts` 有键而 `icons/` 无同名 svg ⇒ **永不判红**(图标源在"图片全量外置 CDN"那轮被清空,判红等于造一道恒红门);**G4** 模板字符串拼出的动态路径结构上判不了 ⇒ 如实计数,绝不静默成"看起来全绿";**G5** 离线包自述钉不等 ⇒ 判陈旧;**G6** 调用点 <LineIcon name=字面量> 必须在 icons.ts 键集(取不到素材时组件 return null,界面只是空白且不报错),动态拼接与标签配不平计未判定并逐条点名 —— 真仓 HEAD 现读字面量 272 处 / 动态 35 处,故这一维接线不产生恒红面。取材面同 70/77/83/98/101:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 只作人工与 dev 链逃生舱;取不到 ⇒ **exit 2「无法判定」**。定级 warn 的实证理由:立项当天全量面就有 8 处 G1 + 9 处孤儿在红,挂 blocking 会让每次提交都被逼 `--no-verify`(§12e 那型);**升 blocking 的前置条件是 G1 存量清零**。取证 `--self-test`(15 例,成对正反例、零副作用);紧急跳过 `HUSKY_SKIP_MINIAPP_GENERATED=1`;它同时被 `dev-weapp.mjs` 消费 —— 判据要在有人跑它的那一刻才成立。
- **自动归档**:`scripts/archive-completed-tasks.mjs` 扫描**已完成**条目(阈值以脚本缺省为准:2026-09-28 起缺省 0 = '完成即归档',这是用户当天的决定,不是缺陷),post-commit 钩子自动 `--auto-commit`,归档 commit 设 `IHUI_ARCHIVE_COMMIT=1` 防递归。
  - ⚠️ **本条的前半天读数已被同日晚间的实测成反向,现值以这一格为准(2026-09-25 深夜)**:链已由 `edb225c909` 打开,而**起效的载体是 `apps/miniapp-taro/src/app.css` 里的 `@source "./**/*.{ts,tsx}"`**,不是 `config/index.ts` 那行 `tailwindcss: {…}`(附⑧的归因更正)。落地后果实测:命名档 utility 已进产物、C1 类名覆盖 0.79% → **32.15%**、主包**净省 61,654 B**(旧"余量 42,401 vs utilities 42,392,开链会撑爆"这组数**方向是反的**,作废)、余量 114,379 B;`text-card` 同名双义已因改名 `.aigc-text-card` 消除。**仍然成立只剩一点**:项目**色**档(`bg-*`/`text-*`/`border-*` 含整个 `/alpha` 族)依旧 0 落地 —— v4 只认 `@theme`,而副本色值写在普通 `(:root/.dark)`,所以"R6 绿灯 ≠ 到端生效"这句对本条**不变**,色档接线是 O62 **附⑧补** 的在账项,不得读成已收口。
- ⚠️ **但 `apps/miniapp-taro` 实际跑的是 Tailwind v4.3.3,不是端内 `package.json` 声明的 v3**(2026-09-25 实测,详见 PROJECT_PLAN O62附⑤):`weapp-tailwindcss@5.2.9` 引用的是 **`@tailwindcss/postcss`**(v4 专属入口 —— v4 的 main 导出会**直接报错拒绝**被当 postcss 插件用),产物 base 层也带 v4 指纹(`border:0 solid` 合并写法、`--tw-gradient-position`,且无 v3 独有的 `--tw-bg-opacity`)。**取版本别只读 `package.json`** —— 那样会得到相反结论(本会话就错过一次:用 `resolve('tailwindcss/package.json')` 探到 `MODULE_NOT_FOUND` 就否掉了正确说法,真实依赖名是 `@tailwindcss/postcss`)。
  - ⚠️ **同日晚间顺着解析图复核,这条结论仍成立、但当时用的依据是错的,不得照它推论**:"weapp-tailwindcss 引用 `@tailwindcss/postcss`"在依赖图里**不成立** —— miniapp importer 的 weapp-tw peer 链的是 `tailwindcss@3.4.19`,而 `@tailwindcss/postcss@4.3.3` 是 **apps/web importer** 的依赖(从 miniapp 锚 resolve 得 MODULE_NOT_FOUND)。v4 的真身是 weapp-tailwindcss **自带 vendored 引擎**(其 dist 导出 `loadTailwindV4DesignSystem`)。判"这端到底跑哪个引擎"的两条可靠证据是:**产物指纹**(`--tw-leading`/`--tw-gradient-position` 在、`--tw-bg-opacity`/`--tw-text-opacity` 0)与**包内 vendored 路径**,不是"谁的 package.json 里出现过后者" —— 后者会把别的 importer 的依赖读成本端的(本仓 §5b/§15b 记过同型:"凡四类机器事实,每次按当次实测取值")。两条后果:① 上面 R7 拦的裸 rpx 错属性在 **v3 与 v4 上行为一致**(已用 v4 + 真实源码逐字复测),所以判据与那 1,341 处改写**不因版本换人而作废**;② 但"miniapp 的 `/alpha` 靠 `tailwind-alpha-plugin`"这个前提**尚未证实** —— v4 走 `color-mix` 是另一条路,插件的确定价值在 **mobile-rn(NativeWind v3)**。带真 theme 复测前,**不要把 R6 对 miniapp 那一侧的登记要求当成已证事实扩严**。
- **豁免到期账**(guardian id **108**,blocking,紧急跳过 `HUSKY_SKIP_EXEMPTION_EXPIRY=1`):`scripts/check-exemption-expiry.mjs` 钉"豁免只有出生、没有死亡"。实测全仓 **12 类**行内豁免 / 252 处 / 107 文件,**带真到期日 0 处**(这三个数是**立项读数、不是现值** —— 现值一律跑 `node scripts/check-exemption-expiry.mjs --json` 看 `totals`)。2026-09-25 补第 13 族 **`back-label-exempt`**(守门 102 GA4 的合法例外通道),在 `FAMILY_LIFETIME_DAYS` 给它 **365 天**而非同门 `glyph-arrow-exempt` 的 30:它是**结构性定性**(该位置的「返回」是按钮文案而非导航箭头),位置性质不随时间改变;短周期到期只会逼人删标记,删了又被 GA4 判红,**两道门互咬**。E1 新增不带到期日 ⇒ 红;E2 已过期仍生效 ⇒ 红;E3 lint 抑制面(`eslint-disable` / `@ts-ignore`)只报数。**三重防恒红**:存量进 `scripts/exemption-expiry-baseline.json` 只报数、E1 锚点 = 该文件该族在 **HEAD 自身的存量数**(棘轮)、`grandfatherUntil` 前不追存量 —— 与改动无关的 blocking 红只会逼人 `--no-verify` 连带废掉全部守门。到期**只判红只点名,绝不自动摘除任何人的豁免**。**记账面 = 被豁免的那一侧**:`scripts/**` 里的标记是"门在描述自己的出口"(判据正则 / 头注 / 镜像测试夹具),只报数不入账(实测 HEAD 面 372 条里 125 条在那一面);不收窄的话,**任何新增带豁免出口的门都会在自己立项那枚提交上被 E1 判红**——锚点是该文件 HEAD 自身的 0,而标记不写出来门就不工作,等于没有合法出口。这条规矩写在 `mergeBaseline` 而不是手工清一次:旧基线被一次回退带回来时,下一次 `--update-baseline` 自行收干净。副产品:第 12 族 `r3-cta-exempt` 3 处是从来没生效过的悬空豁免(守门 83 只认 `r5-cta-exempt`),归属该门持有者。
- **miniapp 原生 chrome 两份副本已收口为派生态 + 专属门**(2026-09-25 立):`apps/miniapp-taro/src/theme.json`(微信 darkmode 配置,`app.config.ts` 以 `@变量` 读取的编译期源)与 `apps/miniapp-taro/src/lib/theme.ts` 的 `THEME_CHROME`(运行期 `setNavigationBarColor`/`setTabBarStyle`)此前是手抄且**零生成器、零守门**(`grep -rl theme.json scripts/*.mjs` 立项时零命中)。现由 `scripts/sync-miniapp-chrome.mjs` 派生(原位写回、幂等;同色不同字节如 `#A3A3A3` 保留文件原字节,首跑不产伪 diff),挂进 `TOKEN_SYNC_TARGETS`(trigger `tokens`,failMode `block`),由守门 `scripts/check-miniapp-chrome.mjs`(guardian-runner **id 以 runner 现值为准** —— 本条初登 122,落地前该号被另一路的 `check-file-write-safety` 占用,按"后来者改号"顺延为 **124**;blocking,紧急跳过 `HUSKY_SKIP_MINIAPP_CHROME=1`)复核。取材面与 36/77/83/93/98/103 同:全量判 **HEAD blob**、`--staged` 判索引 blob、`--worktree` 只是人工/验生成器写回的逃生舱,两面旗同给 ⇒ exit 2;取不到判"无法判定",**不回落**另一个面。**登记分流是记录在案的决策,不是漂移**:dark `navBg`/`windowBg` = `#262626` 出自 commit `d2d80c1b23`(2026-09-06)明文「对齐 rn gray.800」,而源头 `.dark --color-background` = `hsl(0 0% 14%)` = `#242424`;该分歧登记在生成器的 `CHROME_DECLARED_DIVERGENCE` 且带 `nearToken` 机判 —— 源头一旦重新同值即判"登记表腐烂"红。**不得**把 #262626「同步」回 #242424,那是回滚那枚决策;`tabSelected` 的 #8fb8cc/#a3c4d6 即 `--color-brand-accent` 两档(2026-09-14 定稿的跨端强调色)。
- **派生面登记表必须自证自洽(守门 `scripts/check-token-sync-registry.mjs`,编号以 runner 现值为准,紧急跳过 `HUSKY_SKIP_TOKEN_SYNC_REGISTRY=1`)**:`scripts/lib/pre-commit-hook.js` 的 `TOKEN_SYNC_TARGETS` 是"改 `tokens.css` ⇒ 各端副本自动写回"的唯一登记表,每行三段 **触发文件 / 写回命令 / 复核门(`check:` 字段)**。四种腐烂过去无人看守:触发文件改名 ⇒ 该行**永不触发**(副本从此不跟随);写回脚本被删或改名 ⇒ 提交链跑到那步才崩;复核门被摘线或降级成 warn ⇒ 照样写回却没人判对错;**新加一行忘了配门 ⇒ 新派生面零覆盖** —— 最后一种最贵,表里挂着一行没人配门的行,比根本没有这行更糟,它会替人做出"已经收口了"的判断。本门按被审判的面逐行核这四条,取不到判"无法判定"。**新增一端派生副本的正确顺序**:先在 `tokens.css` 落源头 → 加生成器 → 在表里加一行(含 `check:`)→ 配那道复核门并注册进 runner ⇒ 四段同笔提交,门当场就能问出缺哪一段。修复口径是**改表与配门**,不得为消红放宽判据、更不得删表行(删行等于关掉那一端的自动同步)。
- **小程序端有第 6 个受管区 = `app.css` 的 `@theme` 色档注册区**(2026-09-25 立,`f17b5fbdda`):它不是"又一份色值表",而是 Tailwind v4 的**注册动作** —— v4 只把 `@theme` 里的 `--color-*` 认作主题档,色值若只落在普通 `:root/.dark` 上,`bg-muted`/`text-foreground` 这一整族 utility **一条都不产出**(实测真构建 0 落地)。档位仍**唯一派生自** `deriveMiniappManaged(tokens.css, 'light')` 再剔掉 `-rgb` 三元组 —— `-rgb` 进 `@theme` 会长出 `.bg-muted-rgb` 这类脏档(v4 见 `--color-*` 就当颜色档)。三条不得照抄的形态选择:**只用普通 `@theme { }`**;`@theme inline` 会把亮档值烘进每条规则 ⇒ 深色档整族失效;`@theme reference` 会给每条规则附一份亮档 fallback(更贵,且把"没定义"洗成"默默用亮档")。守门 36 现同时核 `:root` / `.dark` / `@theme` 三面(无缺档 + 逐位同值 + 反向拦 `-rgb` 脏档)。**同批判死一条判据缺陷**:`check-miniapp-css-landing` 的 C1 原拿源类名直比产物类名,而 weapp-tailwindcss 会把 **wxss 选择器**里的标点转写(`[→_b ]→_B /→_f :→_c (→_p )→_P ,→_m .→_d +→_u !→_e`,10 条全部逐字取自同一次真构建),于是 505 条"缺项"里 474 条是比对噪声。⚠️ **但"按转写表把名字认回来"这一步本身同日被我改错、已推翻(附⑩)**:转写只发生在 CSS 侧,产物 JS/WXML 侧仍是**源名**,DOM 上实测连方括号都被当空白吃掉 ⇒ 那些转写规则没有任何元素挂得上,是**死规则**不是命中。现判据是 **C4 可达性**:一个档要算"到端可用",必须**同时在 wxss 里有对应选择器、且在产物运行时 class 语料(js/wxml/wxs)里挂得上**;只在 CSS 出现的进 `deadRuleKinds` 且**不进 pct**。**但"运行时挂不上"也不是一句端常态** —— 2026-09-26 同一份 `config/index.ts` 连测三档构建,得到三种完全不同的产物形态:
  ① CSS 腿在、JS 腿整轮空转 ⇒ `hit 269/771 = 34.89% / 死规则 485 / C5=idle`;
  ② 两腿都在(补 `cssEntries` 显式入口后)⇒ `93.77% / 死规则 31 / C5=in`;
  ③ **CSS 腿整个没跑** ⇒ 产物用 Tailwind 自己的转义选择器 `.z-\[1001\]`,与运行时源名**字面相同**,
  于是 `99.35% / 死规则 0 / C5=in` —— **一门看产物的门把最坏的一档读成了最好的一档**。
  ⇒ 现判据补齐 C5(JS 腿活性)与 **C6(CSS 腿整条未跑:源码有含标点档而产物 0 个转写形态类名)**;
  任何"可达率"读数都必须**先认档再说数**,脱离 C5/C6 的单个百分比没有意义。**三条教训叠在一起**:① 判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;② 但在把两侧名字"对上"之前,必须先问**挂载侧到底写了什么** —— "CSS 里有这条规则"与"这个样式生效了"之间隔着一维,只读声明侧的绿灯结构上排除不了"规则存在、没人挂得上"。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/miniapp-taro build`,exit 0)的 154 个 wxss 里 **0 个产出规则**(`.flex{` `.items-center{` `.rounded-xl{` `.bg-muted{` `.text-sm{` 逐条为 0);而 **base/preflight 层是在的**(`border:0 solid;box-sizing:border-box;margin:0;padding:0`)⇒ 症状精确到"**只有 utilities 层为空**"。阳性对照:同一份 dist 里 `app.css` 手写类名 75/98 可查到规则。**机制不是** `config/index.ts` 那行 `tailwindcss: { enable: true, config: {} }` —— 实测把它改成真实配置路径后 A/B/C 三次构建产物**逐字节相同**(该键不是开关);真因指向 weapp-tailwindcss 5.2.9 **自带一份 vendored tailwind**(`dist/tailwindcss-*.js`)并由它自己的 generator 出 CSS,项目 `tailwind.config.ts` 的 content globs 没有喂进那条链。**后果**:本条 R6 判据守的是"源码用量 ↔ 登记表 ↔ 插件产出"三者一致,这三者都在**项目侧**;小程序实际产物由第三方 generator 决定,R6 的绿灯**不等于**到端生效。改这行配置的爆炸半径见 O62附③(主包余量 42,401 B 而 utilities 约 42,392 B,以及 `text-card` 同名双义会被注入白底白字)。**⚠️ 本条"0 个产出规则"的前提已于同日作废**(2026-09-25 深夜,`edb225c909` + `f17b5fbdda`):命名档与项目色档**都已产出且运行时可达**(`.bg-muted{background-color:var(--color-muted)}` 这类),主包余量 **113,504 B**。但可达性只到命名档为止 —— **含标点的那一整族(arbitrary value / `!` important / 分数)是死规则**:weapp 只转写了 wxss 选择器(`z-[1001]` → `.z-_b1001_B`),运行时 className 仍是源名、DOM 上实测连方括号都被当成空白吃掉 ⇒ 那是**三档构建模式中的第 ① 档**(JS 转写腿整轮空转),不是端常态:同一配置另有两档分别读到 93.77% 与 99.35%。旧稿此处还误写过 96.0%(把死规则计成了命中)、后又误把 34.89% 当端常态 —— 两个方向都错过,现值一律"先认档(C5/C6)再说数"(`附⑩`/`附⑪`)。现行事实见本节末"v4 色档注册区"条与 PROJECT_PLAN O62 附⑧/附⑨/**附⑩**,勿照本行执行。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {} }` 让端内 `tailwind.config.ts`(content globs + `presets:[@ihui/design-tokens/tailwind-preset]` + 本插件)在真实构建中**从未被加载** —— 实测真实 build 后 dist 的 154 个 wxss 里,源码用到的 **687 个 Tailwind utility / 11,105 处用法有 0 个产出规则**,`.flex{` 与 preflight 指纹双双缺失;阳性对照是同一份 dist 里 `app.css` 手写类名 75/98 可查到规则,故不是检索姿势问题。**后果**:改这条配置等于让整端 11,105 处用法同时开始产出 CSS,而现状很可能是手写 CSS 已补偿过(自有类名 2,228 个),开启后好坏只能由微信开发者工具真机渲染判定 ⇒ 属未决决策项,不是已收口项。**同时提醒同源对账门的边界**:门 36/37/93/`check-miniapp-taro-style-parity` 全部只核源码与 token 源头,**没有一道看产物**;它们五道全绿而到端 CSS 为零,是可能且已发生的。新增跨端同源门时,请先问自己判的是"写的同源"还是"生效的同源"。**⚠️ 本条的"0 产出"现值已作废**(同上,`edb225c909` + `f17b5fbdda`):`app.css` 现有一个由 `sync-miniapp-tokens.mjs` 派生的 `@theme` 注册区,命名档与项目色档已产出**且运行时可达**;但含标点那一族(arbitrary value 等)是"CSS 有规则、运行时挂不上"的死规则,真实可达率**必须先认构建档位**再读(同一配置实测三档:34.89% / 93.77% / 99.35%,见下下条与 PROJECT_PLAN 附⑩/附⑪)。"没有一道同源门看产物"这半句仍然成立 —— 看产物的是 `check-miniapp-css-landing.mjs`(手动/CI,未接提交链),它现在连**运行时挂不挂得上**也一起看。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/miniapp-taro build`,exit 0)的 154 个 wxss 里 **0 个产出规则**(`.flex{` `.items-center{` `.rounded-xl{` `.bg-muted{` `.text-sm{` 逐条为 0);而 **base/preflight 层是在的**(`border:0 solid;box-sizing:border-box;margin:0;padding:0`)⇒ 症状精确到"**只有 utilities 层为空**"。阳性对照:同一份 dist 里 `app.css` 手写类名 75/98 可查到规则。**机制不是** `config/index.ts` 那行 `tailwindcss: { enable: true, config: {} }` —— 实测把它改成真实配置路径后 A/B/C 三次构建产物**逐字节相同**(该键不是开关);真因指向 weapp-tailwindcss 5.2.9 **自带一份 vendored tailwind**(`dist/tailwindcss-*.js`)并由它自己的 generator 出 CSS,项目 `tailwind.config.ts` 的 content globs 没有喂进那条链。**后果**:本条 R6 判据守的是"源码用量 ↔ 登记表 ↔ 插件产出"三者一致,这三者都在**项目侧**;小程序实际产物由第三方 generator 决定,R6 的绿灯**不等于**到端生效。改这行配置的爆炸半径见 O62附③(主包余量 42,401 B 而 utilities 约 42,392 B,以及 `text-card` 同名双义会被注入白底白字)。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {} }` 让端内 `tailwind.config.ts`(content globs + `presets:[@ihui/design-tokens/tailwind-preset]` + 本插件)在真实构建中**从未被加载** —— 实测真实 build 后 dist 的 154 个 wxss 里,源码用到的 **687 个 Tailwind utility / 11,105 处用法有 0 个产出规则**,`.flex{` 与 preflight 指纹双双缺失;阳性对照是同一份 dist 里 `app.css` 手写类名 75/98 可查到规则,故不是检索姿势问题。**后果**:改这条配置等于让整端 11,105 处用法同时开始产出 CSS,而现状很可能是手写 CSS 已补偿过(自有类名 2,228 个),开启后好坏只能由微信开发者工具真机渲染判定 ⇒ 属未决决策项,不是已收口项。**同时提醒同源对账门的边界**:门 36/37/93/`check-miniapp-taro-style-parity` 全部只核源码与 token 源头,**没有一道看产物**;它们五道全绿而到端 CSS 为零,是可能且已发生的。新增跨端同源门时,请先问自己判的是"写的同源"还是"生效的同源"。
- **门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25 立) —— 本仓 159 道门里只有 **24** 道走 `scripts/lib/face-reader.mjs`,散写 git 读内容或按磁盘 `readFileSync(join(ROOT,…))` 判的有 **74** 道,另 32 道"读文件但找不到仓库锚点"(疑临时夹具,结构上判不了,只报数)。按磁盘判的门的结构性缺陷:共享工作树常年滞后 HEAD ⇒ 同一份 HEAD 代码在"恒红"与"假绿"之间来回跳,并把错数写回棘轮基线(守门 83 的 R3 登记一天内被整文件回退三次即此型)。**判据锚在"本次改动动过的门"上**(结构棘轮,不留会腐烂的白名单),全量档只报数 —— 一次性把 74 道判红就是逼各会话合法 `--no-verify`、连带废掉全部守门。**两层遮噪方向不同,别混用**:认 face-reader 导入与 git 动词必须"只遮注释、保留字符串"(模块说明符和 `cat-file` 本身就是字符串),认 `readFileSync` 调用必须"注释与字符串都遮"(`markHidden` 那一档连字符串一起抹,直接套它会让 F1/F2 双双假绿)。取不到 ⇒ exit 2,全量面枚举到 0 道门判死。自检 16 例 + 镜像测试;紧急跳过 `HUSKY_SKIP_GATE_FACE_DISCIPLINE=1`。
- **记忆端点属主绑定对账**(117,**blocking**,2026-09-25 升档;紧急跳过 `HUSKY_SKIP_MEMORY_OWNER_BINDING=1`):`scripts/check-memory-owner-binding.mjs` 钉的是**"认证不等于授权"**那一层 —— `apps/ai-service/app/api/memory.py` 把 `user_id` 当请求体/Query 参数收,全文件**没有一处**与令牌主体对齐;`/api/memory` 不在 `PUBLIC_PATHS` ⇒ 匿名进不来,但**任何已登录用户填别人的 UUID 就能读/改/删别人的记忆**。判洞要问"有没有把请求里的 id 与令牌里的 id 比对",不是"有没有全局中间件"。两条 Python 语法锚点必须同时认:`user_id: str = Query(...)` 与 Pydantic `Field(...)` —— **只认 Query 会让整类 POST 端点隐身**;对齐出口只写在注释里不算(自称已拆、实际没拆那一型)。**定级史(照抄旧档会判错)**:立项时 warn —— 当时未对齐存量 2 个文件;现值一律 `node scripts/check-memory-owner-binding.mjs` 自取 —— `app/api/memory.py` 已于同日 `3b0170fa176` 九端点统一 `require_request_user_id` 归零,剩下的是**此前无人枚举过的** `app/routers/rules.py`,而"怎么对齐"取决于 **v1 对外 API 能不能代表他人访问记忆** —— 实测 `apps/api/src/routes/v1-knowledge-tools.ts` 有 2 条转发是 `jsonInit(parsed.data)` **原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。收紧成 403 与"忽略并覆盖"两种改法**都会让本门归零**,故存量未清时判红就是恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。**2026-09-25 前置已满足并升 blocking**:`752c6eb110b` 收掉最后一个未对齐面(`app/routers/rules.py`)后 HEAD 面实测"未对齐 0 个",runner 侧同时带 `args:['--strict']`(本门默认档只报数,不带它升档是假的)。**⚠️ 上面那句"HEAD 面实测未对齐 0 个"当次即作废,是本节里一个真实的假绿灯(2026-09-25 同日更正)**:`3b0170fa176` 把参数一律改写成 `user_id: str | None = Query(...)`,而本门两条正则只认 `: str =` ⇒ **一个端点都扫不到** ⇒ 输出"收 user_id 的端点文件 0 个 / 未对齐 0 个"并 exit 0 —— 一道刚被升到 blocking 的安全门变成永远绿灯的尺子,它既看不见当时的敞口,也看不见"明天有人把 `require_request_user_id` 删掉"。补宽正则(可选标注两档都认)后 HEAD 面实测:**收 user_id 的端点文件 2 个 / 已对齐 1 / 未对齐 1**,那一个就是 `app/routers/usage.py` 的 `/stats`+`/quota`+`/record` —— 写法是 `uid = user_id or request.state.user_id`(**自报身份优先于令牌主体**),而参数说明里的"(管理员用)"在该文件内没有任何对应校验 ⇒ 任何已登录用户带 `?user_id=<别人>` 即可读他人用量/配额。已按 `require_request_user_id` + `roleId ≥ 1` 才可代表他人收口(实测 `apps/web` 两处调用方都只传 `days`、`usage/record` 全仓零调用方 ⇒ 不影响在跑链路),回归 `apps/ai-service/tests/test_usage_authz.py` 10 例。**本条的通用教训:改被审代码的写法,必须同时改审它的正则** —— 否则"升级成 blocking"这个动作本身就是在给一台瞎掉的尺子背书。取证 `--self-test` 17→**21 例**(新增 S17–S20:可选标注的 Query/Field 各一对正反例,夹具文本逐字取自真实文件)。**降回 warn 等于重新放开新增敞口**;若 HEAD 面再报未对齐(例:有人跳过钩子塞进去),先清偿再提别的票,不得削判据。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、枚举到 0 个文件判"无法判定"而非记绿 —— 判据失明不是通过)。仓里现成出口 = `app/core/jwt_auth.py` 的 `require_request_user_id`(带 `DEV_ANONYMOUS_PRINCIPAL` 开发降级,以免 ASGI in-process 的既有测试整片 401 —— 那不构成安全结论)。取证 `--self-test` 17 例(正反成对) + §22c 镜像 14 例(含"未注册时不得被判定为已装车"的方向性对照);手动问责档 `pnpm check:memory-owner`(走 `--strict`)。
- **提交活文档前必须做"工作树 ⊇ HEAD"行级对账(2026-09-24 立,一夜三次自伤换来的)**:`README.md` / `AGENTS.md` / `PROJECT_PLAN.md` 这类多会话共写的文件,工作树副本**常年滞后于 HEAD**(实测分别少 57 / 48 / 54 行),而 `safe-commit` 的 Step ④ 是 `git commit -- <pathspec>` —— 按路径取**工作树**版本,于是"只 add 本任务文件"的规范提交照样把别人已入库的行整批写回旧态,`git status`、diff 行数、typecheck、守门 71 全都看不出来。动作:① 提交前 `node scripts/merge-live-doc.mjs --file <该文档>`(exit 1 = 存在真丢失);② 需归并时 `--apply`(它按锚点把 HEAD 缺失块插回工作树,并区分"被吃掉"与"被就地改写",后者不插回以免新老并存);③ 归并后必须报"仍判 lost = 0 且长行重复新增 = 0"。**但提交前的报告有时效边界**(2026-09-24 实测:`merge-live-doc` 报 0 丢失之后、`safe-commit` 落地之前,并发会话又推进了一轮 ⇒ 那次提交仍丢掉 2 条他人登记行)。所以顺序必须是四步:**报告 → 提交 → 立刻 `git show <新提交>^..<新提交>` 做行级对账 → 有丢失就地回补**(发现手段只能是提交后的 diff,`git status` 与提交前那次报告都看不见)。判据是**字符二元组** Jaccard ≥ 0.6 —— 前缀与按空格切词在中文里都会漏(工具自己栽过一次,README 里一度同时留下 `**第 93 项 X**` 与 `**守门 X**` 两行),已由 `--self-test` 8 例钉死。登记条目见 PLAN 的 O46④/O46⑩。**⚠️ 但"`lost=0`"从来不等于"工作树 ⊇ HEAD"(2026-09-25 实测并修工具)**:superseded 这一类的定义恰恰是"HEAD 的那一行文字不在工作树里",所以工具在 `lost=0` 时印的 `✅ 工作树 ⊇ HEAD,可安全提交` 是一句**做不到的承诺**,而本会话就在那句绿灯下把 20 条别人已翻勾/已追加注记的登记行提交成了旧形态。现分三态:`lost`(插回)/ `superseded`(有人改写,不插回)/ **`stale`(工作树停在旧形态:剥掉勾选状态后 WT 是 HEAD 的严格前缀 + 唯一候选 + HEAD 已勾 ⇒ 也不插回,但报告必须喊"这几行会被退回旧态,要人工取 HEAD 形态")**。为什么 `stale` 不自动回退:实测近 60 枚提交里有 **241** 次"合法缩短"(其中 **73** 次根本不带勾选状态变化),它们与"副本滞后"在内容上同形 —— 自动回退等于连人真删的字一起复原,那比原病更响。唯一性是生命线(两条同前缀候选 ⇒ 不猜)。工具报告现在只在**逐字包含全部 HEAD 行**时才声称 ⊇,否则按三态报数并给出出口;取证 `--self-test` 16→20 例 + 镜像 3→4 例(镜像 A 例的断言方向已反过来:有非逐字存活行时**不得**出现"⊇ HEAD"字样)。
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**:旧版首次 commit 一失败就打印「按用户规则"hook 失败因其他 agent 代码 → --no-verify 重试"」并整批跳门 —— 它**从未计算过归因**。实测一轮 134 道门 111 通过 / 1 失败,红的是 `check-push-sync`(判远端态、与提交内容无关),措辞仍是"他人代码"。现流程:解析 runner 的失败门清单 → **逐道复跑** → 该门这次的输出**点名本次声明的文件 ⇒ 判"本任务自己的红",拒绝 `--no-verify` 并 exit 1**(必须修);一个都没点名 ⇒ 允许跳,但只说量到的话;批没跑完 / 解析不到 / 复跑全不可用 ⇒ 明写「未归因」,按应急路径落地并留痕于 `.workbuddy/safe-commit-attestation.jsonl`。**红在守门批「之外」也要点名,不得把已量到的批内结论丢掉(2026-09-25 补,G-185)**:钩子在 runner **之后**还跑若干独立 blocking 步(实测 `i18n 死 key 扫描`),它一红就 `exit 1` —— 那一轮 154 道门**全部跑完、blocking 失败 0**,而旧归因把这种组合写成「汇总里没列出失败门 ⇒ 归因未计算」,随后照样 `--no-verify` 落地(提交 `46a4c18ed` 即此型),等于**把已经拿到的守门结论整块丢弃**,账面读起来像"没跑守门"。现规则:解析到批外步骤名(`outsideBatchStep`)后仍按同一条铰链分流 —— 该步结论点名本次声明的文件 ⇒ 判 `mine`(拒绝跳门,必须修);一个都没点名 ⇒ 判 `not-ours` 但措辞必须是「批内 0 失败,红在批外这一步」并点名是哪一步、提醒随后清偿。**失效方向仍是"多要一次定向说明"**,不是"多放一次跳门",也不是"干脆不落地"。取证:C0/C0b/C1/C2/C3 五例(含"批内确有失败门时不得走这一支"的反向对照,否则批外步骤会替批内那道门洗责)。**刻意不建"机器态门 id 清单"** —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。 —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。取证:镜像测试 5/5(含装车证明 + 真实 `hook-logs/pre-commit.log` 取样校验夹具)+ 三条变异各自变红(ANSI 剥色那条登记为**等价变异**,不造假具硬凑红)。**因此:agent 不得再假设"钩子红了就能跳"** —— 被拒时那句红点名了哪个文件,就去修哪个文件;确属误判才可按 §16 显式说明后手工提交。
- **mobile-rn global.css token 同步守门**(无 id,hook :348 条件 blocking): `scripts/check-rn-global-css-sync.mjs`(blocking) —— `apps/mobile-rn/global.css` 的 `--color-*` 值与 `packages/design-tokens/src/styles/tokens.css` 逐变量对账(NativeWind 4.x 手抄副本,漂移即视觉 bug),不一致 exit 1(`:117`),staged 触及两文件之一才触发;真跑全量 rc=0(1s);无 --self-test,但镜像测试 node --test 真跑 rc=0(15 例);无应急通道(hook 未设 skip env;头注自述 `--staged` 被接受但语义仍是全量扫描)。
- **钩子失败的归因是量出来的,不是抄来的(2026-09-25 立,`scripts/lib/commit-gate-attribution.mjs`)**:旧版首次 commit 一失败就打印「按用户规则"hook 失败因其他 agent 代码 → --no-verify 重试"」并整批跳门 —— 它**从未计算过归因**。实测一轮 134 道门 111 通过 / 1 失败,红的是 `check-push-sync`(判远端态、与提交内容无关),措辞仍是"他人代码"。现流程:解析 runner 的失败门清单 → **逐道复跑** → 该门这次的输出**点名本次声明的文件 ⇒ 判"本任务自己的红",拒绝 `--no-verify` 并 exit 1**(必须修);一个都没点名 ⇒ 允许跳,但只说量到的话;批没跑完 / 解析不到 / 复跑全不可用 ⇒ 明写「未归因」,按应急路径落地并留痕于 `.workbuddy/safe-commit-attestation.jsonl`。**stdout 没汇总而要去钩子日志取第二输入源时,该汇总块必须先过"轮次绑定"这道锁(2026-09-25 补,G-179)**:钩子日志是多轮追加的,而旧锁只要求"该汇总之前的窗口里出现过本次声明的**任一**文件名"—— 那段窗口实测 48KB–202KB,里头**必然**提到 `PROJECT_PLAN.md` / `AGENTS.md`(守门 71/13c 的结论行就点名它们),于是"我自己的红"可以借别人那一轮的汇总被洗成 `not-ours`(实测提交 `48ac2c03e`:红在 eslint `no-unused-vars`,lint-staged 失败使守门批根本没跑 ⇒ 152 道门被一次误归因跳掉,HEAD 里真留下那枚 lint 错误)。现规则:**该汇总之前那次 `staged 文件清单` 回显必须完整包含本次声明集**才允许用于归因 —— 回显由钩子入口 `auditStagingFiles()` 全量打印,是把一段日志绑到具体某枚提交的唯一内容证据(日志没有时间戳、也没有轮次号,实测按 `^YYYY-MM-DD` 抓零命中)。取"包含"不取"等值":回显反映磁盘实际暂存,并发会话同窗口 staged 的东西会造成超集,而它们结构上进不了本枚提交(commit 带 pathspec)。**绑定不成 ⇒ 落 `unattributed`,照旧能提交,只是措辞不得声称"因他人代码"—— 失效方向必须是"多要一次定向说明",绝不能是"多放一次跳门"**(与本节末那条 `findingLines` 教训同一条禁令)。真仓双向证明(不是夹具):合法那一轮绑定成功并取到 `总检查数: 152(已执行 152)` 的汇总,事故那一轮被拒且点名缺的 2 个文件。**stdout 没汇总时,第二输入源必须先过"轮次绑定"这道锁**(2026-09-25 补,G-179):钩子日志是多轮追加的,而旧锁只要求"该汇总块之前的窗口里出现过本次声明的**任一**文件名"—— 那段窗口实测 48KB–202KB,里头**必然**提到 `PROJECT_PLAN.md` / `AGENTS.md`(守门 71/13c 的结论行就点名它们),于是"我的红"可以借别人那一轮的汇总被洗成 `not-ours`(实测提交 `48ac2c03e`:红在 eslint `no-unused-vars`,而 lint-staged 失败使守门批根本没跑 ⇒ 152 道门被一次误归因跳掉)。现规则:该汇总块之前那次 `staged 文件清单` 回显必须**完整包含**本次声明集(回显由钩子入口 `auditStagingFiles()` 全量打印,是唯一能把一段日志绑定到具体某枚提交的信号 —— 日志没有时间戳、也没有轮次号);缺任何一项 ⇒ 判"不是本轮" ⇒ 落到 `unattributed`。**失效方向刻意是"多要一次定向说明",绝不是"多放一次跳门"**。真仓双向证明:合法那一轮 `绑定成功` 并取到 `总检查数: 152(已执行 152)` 的汇总块,事故那一轮 `拒绝` 且点名缺的 2 个文件(4 项声明 vs 4 项回显,重名 2 项 —— 正是旧锁放过的形态)。**刻意不建"机器态门 id 清单"** —— 豁免清单必然腐烂(见 §4 对 `RN_ONLY_BRAND_KEYS` 的教训)。取证:镜像测试 5/5(含装车证明 + 真实 `hook-logs/pre-commit.log` 取样校验夹具)+ 三条变异各自变红(ANSI 剥色那条登记为**等价变异**,不造假具硬凑红)。**因此:agent 不得再假设"钩子红了就能跳"** —— 被拒时那句红点名了哪个文件,就去修哪个文件;确属误判才可按 §16 显式说明后手工提交。
- **小程序端有第 6 个受管区 = `app.css` 的 `@theme` 色档注册区**(2026-09-25 立,`f17b5fbdda`):它不是"又一份色值表",而是 Tailwind v4 的**注册动作** —— v4 只把 `@theme` 里的 `--color-*` 认作主题档,色值若只落在普通 `:root/.dark` 上,`bg-muted`/`text-foreground` 这一整族 utility **一条都不产出**(实测真构建 0 落地)。档位仍**唯一派生自** `deriveMiniappManaged(tokens.css, 'light')` 再剔掉 `-rgb` 三元组 —— `-rgb` 进 `@theme` 会长出 `.bg-muted-rgb` 这类脏档(v4 见 `--color-*` 就当颜色档)。三条不得照抄的形态选择:**只用普通 `@theme { }`**;`@theme inline` 会把亮档值烘进每条规则 ⇒ 深色档整族失效;`@theme reference` 会给每条规则附一份亮档 fallback(更贵,且把"没定义"洗成"默默用亮档")。守门 36 现同时核 `:root` / `.dark` / `@theme` 三面(无缺档 + 逐位同值 + 反向拦 `-rgb` 脏档)。**同批判死一条判据缺陷**:`check-miniapp-css-landing` 的 C1 原拿源类名直比产物类名,而 weapp-tailwindcss 会把 **wxss 选择器**里的标点转写(`[→_b ]→_B /→_f :→_c (→_p )→_P ,→_m .→_d +→_u !→_e`,10 条全部逐字取自同一次真构建),于是 505 条"缺项"里 474 条是比对噪声。⚠️ **但"按转写表把名字认回来"这一步本身同日被我改错、已推翻(附⑩)**:转写只发生在 CSS 侧,产物 JS/WXML 侧仍是**源名**,DOM 上实测连方括号都被当空白吃掉 ⇒ 那些转写规则没有任何元素挂得上,是**死规则**不是命中。现判据是 **C4 可达性**:一个档要算"到端可用",必须**同时在 wxss 里有对应选择器、且在产物运行时 class 语料(js/wxml/wxs)里挂得上**;只在 CSS 出现的进 `deadRuleKinds` 且**不进 pct**。现测 **hit 269/771 = 34.89%(原名直中 266 + 转写可达 0 + 复合首族 3)/ 死规则 485 / 确定缺失 6**。**两条教训叠在一起**:① 判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;② 但在把两侧名字"对上"之前,必须先问**挂载侧到底写了什么** —— "CSS 里有这条规则"与"这个样式生效了"之间隔着一维,只读声明侧的绿灯结构上排除不了"规则存在、没人挂得上"。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②/附③):源码用到的 **687 个 Tailwind utility / 11,105 处用法**,在真实构建(`pnpm --filter @ihui/miniapp-taro build`,exit 0)的 154 个 wxss 里 **0 个产出规则**(`.flex{` `.items-center{` `.rounded-xl{` `.bg-muted{` `.text-sm{` 逐条为 0);而 **base/preflight 层是在的**(`border:0 solid;box-sizing:border-box;margin:0;padding:0`)⇒ 症状精确到"**只有 utilities 层为空**"。阳性对照:同一份 dist 里 `app.css` 手写类名 75/98 可查到规则。**机制不是** `config/index.ts` 那行 `tailwindcss: { enable: true, config: {} }` —— 实测把它改成真实配置路径后 A/B/C 三次构建产物**逐字节相同**(该键不是开关);真因指向 weapp-tailwindcss 5.2.9 **自带一份 vendored tailwind**(`dist/tailwindcss-*.js`)并由它自己的 generator 出 CSS,项目 `tailwind.config.ts` 的 content globs 没有喂进那条链。**后果**:本条 R6 判据守的是"源码用量 ↔ 登记表 ↔ 插件产出"三者一致,这三者都在**项目侧**;小程序实际产物由第三方 generator 决定,R6 的绿灯**不等于**到端生效。改这行配置的爆炸半径见 O62附③(主包余量 42,401 B 而 utilities 约 42,392 B,以及 `text-card` 同名双义会被注入白底白字)。**⚠️ 本条"0 个产出规则"的前提已于同日作废**(2026-09-25 深夜,`edb225c909` + `f17b5fbdda`):命名档与项目色档**都已产出且运行时可达**(`.bg-muted{background-color:var(--color-muted)}` 这类),主包余量 **113,504 B**。但可达性只到命名档为止 —— **含标点的那一整族(arbitrary value / `!` important / 分数)是死规则**:weapp 只转写了 wxss 选择器(`z-[1001]` → `.z-_b1001_B`),运行时 className 仍是源名、DOM 上实测连方括号都被当成空白吃掉 ⇒ 全端真实可达率现测 **269/771 = 34.89%,死规则 485 类**(旧稿此处曾误写 96.0%,那是把死规则计了命中,`附⑩` 推翻)。现行事实见本节末"v4 色档注册区"条与 PROJECT_PLAN O62 附⑧/附⑨/**附⑩**,勿照本行执行。
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {} }` 让端内 `tailwind.config.ts`(content globs + `presets:[@ihui/design-tokens/tailwind-preset]` + 本插件)在真实构建中**从未被加载** —— 实测真实 build 后 dist 的 154 个 wxss 里,源码用到的 **687 个 Tailwind utility / 11,105 处用法有 0 个产出规则**,`.flex{` 与 preflight 指纹双双缺失;阳性对照是同一份 dist 里 `app.css` 手写类名 75/98 可查到规则,故不是检索姿势问题。**后果**:改这条配置等于让整端 11,105 处用法同时开始产出 CSS,而现状很可能是手写 CSS 已补偿过(自有类名 2,228 个),开启后好坏只能由微信开发者工具真机渲染判定 ⇒ 属未决决策项,不是已收口项。**同时提醒同源对账门的边界**:门 36/37/93/`check-miniapp-taro-style-parity` 全部只核源码与 token 源头,**没有一道看产物**;它们五道全绿而到端 CSS 为零,是可能且已发生的。新增跨端同源门时,请先问自己判的是"写的同源"还是"生效的同源"。**⚠️ 本条的"0 产出"现值已作废**(同上,`edb225c909` + `f17b5fbdda`):`app.css` 现有一个由 `sync-miniapp-tokens.mjs` 派生的 `@theme` 注册区,命名档与项目色档已产出**且运行时可达**;但含标点那一族(arbitrary value 等)是"CSS 有规则、运行时挂不上"的死规则,全端真实可达率现测 **269/771 = 34.89%**(见下下条与 PROJECT_PLAN 附⑩)。"没有一道同源门看产物"这半句仍然成立 —— 看产物的是 `check-miniapp-css-landing.mjs`(手动/CI,未接提交链),它现在连**运行时挂不挂得上**也一起看。
- **门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25 立) —— **门数与分档读数一律现读,本文不钉数字**(跑 `node scripts/check-gate-face-discipline.mjs --json` 看 `counts`;2026-09-26 那次实测是 face 28 / loose 74(含半接线 1)/ unknown 35 / no-content 49 —— 写死它下次收紧就变成假账)。按磁盘判的门的结构性缺陷:共享工作树常年滞后 HEAD ⇒ 同一份 HEAD 代码在"恒红"与"假绿"之间来回跳,并把错数写回棘轮基线(守门 83 的 R3 登记一天内被整文件回退三次即此型)。**判据锚在"本次改动动过的门"上**(结构棘轮,不留会腐烂的白名单),全量档只报数 —— 一次性把 74 道判红就是逼各会话合法 `--no-verify`、连带废掉全部守门。**2026-09-26 收紧:"引了 `face-reader`" 不再构成合规** —— 旧判序第一刀是 `FACE_IMPORT_RE` 命中即判 `face`,于是"import 了层、内容却仍由自己 `git show` / 磁盘 `readFileSync` 取"这种**半接线**被系统性放过。实证不是假想:门 36 与门 124 都 import 了 `face-reader`(只用 `gitRaw`),却都**默认按磁盘判**;把两者改成 `catBatch` 判 HEAD 之后,本门分类读数一字未变 —— 一道自称守取材面纪律的门,对它立项要防的那一型完全失明。现判序:① 真调用层的**读取入口**(`catBatch` / `readWorktreeFile`,含 `as` 别名与 `import * as ns` 命名空间形态)⇒ `face`;② 引了层却没用它读内容、而内容来自自派生 git 或磁盘 ⇒ **`half-wired`(提交档判红)**;③ 其余照旧 `loose-git` / `loose-fs`。**只有非读取导出不算**:`selectFace` / `gitBinary` / `gitErrText` / `catBatchOids` / `catBatchSizes` 都拿不到正文,把它们当读取凭证就等于没收紧。反向护栏同等重要:别名与多行导入必须认(否则新判据对合法写法产假阳,`T10` 钉死);`usesLayerRead` 导出给镜像用,**禁止**在测试里再抄一份判据(§22c)。两层遮噪方向不同,别混用:认 face-reader 导入与 git 动词必须"只遮注释、保留字符串"(模块说明符和 `cat-file` 本身就是字符串),认 `readFileSync` 调用必须"注释与字符串都遮"(`markHidden` 那一档连字符串一起抹,直接套它会让 F1/F2 双双假绿)。取不到 ⇒ exit 2,全量面枚举到 0 道门判死。自检与镜像例数以命令末行为准(本枚落地时 24 例 / 镜像 11 例,含 F16b 与 T9–T11 的收紧锁);紧急跳过 `HUSKY_SKIP_GATE_FACE_DISCIPLINE=1`。
  - **归档占位点名的文件必须真在审面里(守门 13c 的 A1/A2,2026-09-25 立)**:`<!-- 已归档(…):X,完整内容在 .ihui-agent/archive/<FILE> -->` 是一句**可核验的指针**,不是修辞。A1 = 盘上有 `PROJECT_PLAN_*.md` 而审面(全量 HEAD 树 / `--staged` 索引)没有 ⇒ 红;A2 = 占位点名的具体文件不在审面里 ⇒ 红(写成 `PROJECT_PLAN_*.md` 通配的占位不点名,不参与判定)。立项即抓到两条实测:`PROJECT_PLAN_dedup-2026-09-23.md` 只躺在本机(已随本票入库),以及 **8 个被 52 条占位点名的归档文件在仓库与本机两侧都不存在、其路径也从未出现在历史里** —— 即 §1 那句承诺对这批条目一直是空的。这 8 项进 `LOST_ANCHOR_LEDGER` **只报数**:它们是已发生的债,当场判红就是一台与任何提交都无关的恒红门(§12e 同型);台账是**待偿清单不是豁免清单** —— 修好了仍挂着 ⇒ 判"清单腐烂"红,删了行却没修好 ⇒ 照常红,两个方向各有一条自检钉死。按条目从计划文档历史版本逐字找回另计一票。
  - **13c 自身的取材口径(同日迁移)**:此前 `ROOT = process.cwd()` + 裸 `git show` 用 `execSync` 拼字符串(无 timeout)+ 占位合规性看 `git diff` 文本,三处都与本仓现行口径相反(工作树滞后即换结论 / 服务账户 PATH 不通 / 别人同时改这文件时合规性跟着盘走)。现全部经 `scripts/lib/face-reader.mjs`,对照面是 `--staged`:HEAD→索引、全量:HEAD^→HEAD、`--worktree` 仅人工;整面取不到 ⇒ **exit 2 无法判定**,不冒红也不记绿。取证 `--self-test` 10 条,含两条本门自己踩出来的:CRLF/LF 同一条标题不得读成"删除";**A2 的归属必须看该目录在审面上的全集**而不是只看 `PROJECT_PLAN_*` 形状 —— 本门第一次自跑就是被这条假红咬到的(已跟踪的 `orphan-capabilities-equivalence-2026-09-24.md` 被点名"落空")。
- **宿主级提示块与第三方内容的边界唯一出口**(强制,2026-09-26 立,第九轮 ZCode 吸收 A9D-1/A9D-4):CLI 侧一切"以宿主名义进提示"的块必须经 `apps/cli/src/utils/prompt-boundary.ts` —— `frameSystemReminder`(带 `kind` 的结构块,kind 是封闭集、空正文拒发)/ `neutralizeBoundaries`(非宿主内容进提示前必须过)/ `buildSkillPromptSection`(技能名单条与总量两道字节预算)。**`[系统提醒]` 这类裸文本前缀不得再被任何产出点复用**:前缀是纯文本,工具输出里出现同样字样时模型无从分辨"宿主在说话"与"第三方内容在冒充",这是结构缺陷不是措辞偏好(反第二真相由 `apps/cli/tests/prompt-boundary.test.ts` 末组钉死:源码面除唯一出口外不得再出现该字样)。第三方内容(技能正文/工具输出/fs 事件)**被省略必须在输出里留计数行** —— 静默变短等于伪造完整性,与 §5e"失败必须响"、守门 77"绝不静默成看起来全绿"是同一条禁令。不得为过测试去放宽中和判据或把预算调到 0。
- **shared 包非 Node 宿主纯度对账**(guardian id **126**,blocking,紧急跳过 `HUSKY_SKIP_SHARED_NONDE_PURITY=1`):`scripts/check-shared-nonde-node-purity.mjs` 判的是"**本地 typecheck 全绿、端上构建才炸**"那一族(与守门 72/78 同型):`packages/shared` 被 web / 小程序 / RN / `packages/app` 这些**非 Node 宿主**共同消费,而入口闭包可达面内一旦出现 `node:fs`/`node:dns` 等内建导入,`tsc` 结构上看不见(它走 tsconfig paths,不看打包器)。判据按**可达性**而非"文件里有没有 `node:`":从 `src/index.ts` + `package.json` 的**非通配** exports 做值边传递闭包(`import type` 不计,内联 `export { type X }` 保守算值边),闭包内出现内建导入即红并打印链路;**宿主直接 import 那条子路径**(绕过 barrel 的第二个入口)同样判红 —— 那正是"barrel 没导出所以巧合安全"会被一次顺手改动打破的路。三态如实:「含内建但闭包不可达」与「动态拼接/解析不到」**只报数且必须打印**(否则读报告的人会以为 shared 是纯的),取不到判"无法判定" exit 2,枚举到 0 个 shared 候选或 0 个宿主候选判死不记绿。修法唯一:把平台特有依赖改成 adapter 注入或移出共享面(§3 工厂模式),**不得**为消红放宽判据、也不得把可达文件改判成不可达;现状实测 HEAD 面不可达 1 处(`packages/shared/src/utils/ssrf-guard.ts` 的 `node:dns`,属待处置源码面)。
- **跨语言出站路由声明对账**(guardian id **127**,blocking,紧急跳过 `HUSKY_SKIP_DECLARED_OUTBOUND_ROUTES=1`):`scripts/check-declared-outbound-routes.mjs` 钉"代码里写死了要打的自家路由,而那条路由两侧都不存在" —— 声明面(Python hub 路径表 + TS `egress-call` 字面量)对注册面(Fastify 沿 `register(prefix)` 图走通 + FastAPI `include_router`/`APIRouter(prefix=)` 拼接)按归一路径段集合比对,参数段 `{x}`/`:x`/模板插值同视。**三态是这门的全部价值**:命中 / 未匹配 / **不透明前缀下的未判定**(子树挂载、同文件 const 插件、`app.mount` 一律承认看不见,不得把"看不见"洗成"确信没有")。默认档只报数、`--strict` 才判红 —— 实测 12 条存量(6 条 hub 死支柱 + 6 条 api 自转发)若当场 blocking 就是一台与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型);问责入口 `pnpm check:outbound-routes`。豁免 `route-declare-exempt: <原因>` 只救本行且须带原因,**禁止**用基线文件消红。口径同 70/77/83/98/101/103/126(全量判 HEAD blob、`--staged` 判索引、两面旗同给 exit 2、注册集为 0 判死不记绿)。取证 `--self-test` 51 例 + §22c 镜像 6 例(含 T1"接线成套性:已接则必须 blocking + skipEnv 齐备"、T4"无提交判死"、T6 取材面形状锁)。
- **工具名本地化**(55/56):check-tool-name-display-coverage(注册表 `_TOOLS` × 词表 × 五语言 taskStatus 三方比对,blocking,拦"新增工具不补功能名");check-tool-display-resolvable(91 个功能名在 shared + 5 端合并视图 + 小程序离线包逐语言解析,拦两类静默失败:端内取词缺键回显 `toolReadFile`、忘跑 `pnpm gen:i18n` 致离线包过期;`node --test scripts/tests/check-tool-display-resolvable.test.mjs` 自检)。紧急跳过 `HUSKY_SKIP_TOOL_NAME_COVERAGE=1` / `HUSKY_SKIP_TOOL_DISPLAY_RESOLVABLE=1` **判定面 2026-09-26 收口(同 36/124/93 口径)**:默认 **HEAD blob** / `--staged` **索引 blob** / `--worktree` 逃生舱 / 两面旗同给 exit 2 / 取不到 ⇒ exit 2 **不回落**;34 个输入在同一次 `cat-file --batch` 里**同面同轮**读满(旧写法把清单来自磁盘、内容来自 `git show` 混着取,并发会话推进的瞬间会产出自洽却错位的尺子)。`--self-test` 也改跑在被审面上。**反向锁的去处**:本门此前用"只报数"冒充"扫过了",现在未判定与通过是两件事 —— 缺语言包等 4 个必需输入取不到即判死。镜像测试新增 `scripts/tests/check-tool-display-resolvable.test.mjs` 的 F1–F4(四态 / 临时仓索引≠磁盘 / 形状锁),并把原"装车证明"从磁盘面重指到 HEAD 面(断言本体一字未改)。
- **i18n JSON 同层重复键守门**(guardian 条目 `2e-dupns`,blocking,紧急跳过 `HUSKY_SKIP_I18N_DUP_NS=1`): `scripts/check-i18n-duplicate-namespaces.mjs` —— 字符级深度栈扫 `packages/i18n/messages/**/*.json` 找同一对象层重复键(JSON.parse last-wins 静默遮蔽),发现即 exit 1。**判定面 2026-09-26 收口**:默认判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 只作人工/夹具逃生舱、两面旗同给 exit 2、任一面取不到 ⇒ **exit 2 无法判定且不回落**;清单(`ls-tree` / `ls-files`)与正文(**一次** `cat-file --batch`)**同面同轮**;枚举到 0 个 `.json` **判死而不记绿**。收口前的真实代价已量化:它按磁盘判而 runner 一律追加 `--staged`(它不认识),于是并行会话在 `packages/i18n/messages/**` 里的半编辑语料**把当天每一次提交都钉红**(实测一轮 4 次 `--no-verify` 归因里都有它)—— 一次绕过约等于全部 156 道门对该提交作废(§12e 同型)。同批修掉 `const ROOT = process.cwd()`(扫哪棵树由调用者站哪决定 ⇒ 守门 70 的镜像测试 13/14 恒红那一型),ROOT 改由脚本自身位置推导;`--root <dir>` 保留为**测试通道**且**只在 `--worktree` 档有效**(换根却仍按 HEAD/索引读 = 双根分裂,直接判死)。镜像 7 例:R1/R2 定根、R3 通道三态、R4 形状锁(收口后方向反转:必须引 `face-reader` 且必须出现 `catBatch(`,否则视为退化)、R5 面四态纯函数、**R6 同一棵临时仓三面三答**(HEAD 干净 / 索引脏 / 磁盘脏,且索引摘除后不得借 HEAD 内容凑数)、R6b 空目录必须给可诊断结论、禁打"✅ 无重复"。
- **D3(2026-09-26 G-196 新增):别名导入解析不到文件必拦**。旧射程只判仓内相对路径,于是 `@/stores/x` 指向一个**从未写下**的模块时本门永远看不见 —— G-195 那处就是这样进了 HEAD、生产构建死、154 道门全绿。判据取 `tsconfig` 的 `compilerOptions.paths`(只认尾随 `*` 的前缀映射;`extends` 与精确键刻意不碰,认它们就要完整 resolver),口径与 D1/D2 同形(全量 HEAD blob / `--staged` 索引 blob / 棘轮锚点=该文件 HEAD 自身)。HEAD 现测那一处进 **`KNOWN_ALIAS_LEDGER` 只报数不判红** —— 存量未清就上 blocking 就是恒红门(§12e),**修好 G-195 必须删台账行**。两条反"假绿"锁由镜像钉住:别名表为空时不得把"0 处"当通过;台账外的任何 D3 一律红。**踩过的两个坑值得抄下**:`stripJsonc` 第一版是**正则**剥块注释,而每个 tsconfig 的 include 里都有 `"**/*.ts"` 这种 glob —— 串里同时含着块注释的开与闭序列,整段 JSON 被吞 ⇒ 别名表静默为空 ⇒ 判据失明而报告一切正常(`node --check` 随后炸在我描述该 bug 的注释里,因为注释把那两个序列原样写了出来 —— **说明性文字也会带执行性字符**);以及 `readHead` 只按"源文件清单"建批量读,拿它读 `tsconfig.json` 恒 null,必须**另开一次只喂 tsconfig 的批量读**。
- **锁异常处理**:超时自动报错;超过 10min 的悬挂锁(持锁进程已死)自动抢占;紧急可删项目根 `.deploy.lock`(先确认无构建进程)。`.deploy.lock/` 已 gitignore。
  ② 两腿都在(补 `cssEntries` 显式入口后)⇒ `93.77% / 死规则 31 / C5=in`;**而热缓存仍会把整包复制进 42 个页面 wxss(主包 +1,033,700 B ⇒ 越过 2MB 硬上限),该构型由关闭 `cache.enable` 收口,见附⑫**;
- **跨语言出站路由声明对账**(guardian id **127**,blocking,紧急跳过 `HUSKY_SKIP_DECLARED_OUTBOUND_ROUTES=1`):`scripts/check-declared-outbound-routes.mjs` 钉"代码里写死了要打的自家路由,而那条路由两侧都不存在" —— 声明面(Python hub 路径表 + TS `egress-call` 字面量)对注册面(Fastify 沿 `register(prefix)` 图走通 + FastAPI `include_router`/`APIRouter(prefix=)` 拼接)按归一路径段集合比对,参数段 `{x}`/`:x`/模板插值同视。**三态是这门的全部价值**:命中 / 未匹配 / **不透明前缀下的未判定**(子树挂载、同文件 const 插件、`app.mount` 一律承认看不见,不得把"看不见"洗成"确信没有")。默认档只报数、`--strict` 才判红 —— 实测 12 条存量(6 条 hub 死支柱 + 6 条 api 自转发)若当场 blocking 就是一台与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型);问责入口 `pnpm check:outbound-routes`。豁免 `route-declare-exempt: <原因>` 只救本行且须带原因,**禁止**用基线文件消红。口径同 70/77/83/98/101/103/126(全量判 HEAD blob、`--staged` 判索引、两面旗同给 exit 2、注册集为 0 判死不记绿)。取证 `--self-test` 51 例 + §22c 镜像 6 例(含 T1"未接线时不得被判定为已装车"的方向性对照、T4"无提交判死"、T6 取材面形状锁)。
  - **D3(2026-09-26 G-196 新增):别名导入解析不到文件必拦**。旧射程只判仓内相对路径,于是 `@/stores/x` 指向一个**从未写下**的模块时本门永远看不见 —— G-195 那处就是这样进了 HEAD、生产构建死、154 道门全绿。判据取 `tsconfig` 的 `compilerOptions.paths`(只认尾随 `*` 的前缀映射;`extends` 与精确键刻意不碰,认它们就要完整 resolver),口径与 D1/D2 同形(全量 HEAD blob / `--staged` 索引 blob / 棘轮锚点=该文件 HEAD 自身)。HEAD 现测那一处进 **`KNOWN_ALIAS_LEDGER` 只报数不判红** —— 存量未清就上 blocking 就是恒红门(§12e),**修好 G-195 必须删台账行**。两条反"假绿"锁由镜像钉住:别名表为空时不得把"0 处"当通过;台账外的任何 D3 一律红。**踩过的两个坑值得抄下**:`stripJsonc` 第一版是**正则**剥块注释,而每个 tsconfig 的 include 里都有 `"**/*.ts"` 这种 glob —— 串里同时含着块注释的开与闭序列,整段 JSON 被吞 ⇒ 别名表静默为空 ⇒ 判据失明而报告一切正常(`node --check` 随后炸在我描述该 bug 的注释里,因为注释把那两个序列原样写了出来 —— **说明性文字也会带执行性字符**);以及 `readHead` 只按"源文件清单"建批量读,拿它读 `tsconfig.json` 恒 null,必须**另开一次只喂 tsconfig 的批量读**。
- **交付标准**:除登录方式与平台机制(原生导航栏/状态栏/输入法)外,`apps/miniapp-taro` 与 `packages/app`+`apps/mobile-rn` 的同一界面元素**必须逐档同值**,且**改一端另一端自动生效**。
- **自动生效只有一种实现方式**:一份与平台无关的组件源(`packages/shared`)+ 每端注入平台 adapter(§3 工厂/DI 同一条规矩)。**禁止**"两端各写一份再靠对账追平"——本仓 2026-09-22 已试过单向包装并因"同名 ≠ 同契约"删掉 4,662 行适配器。
- **可见真值仍只有一处**:色档 `packages/design-tokens/src/styles/tokens.css`、圆角 `packages/design-tokens/src/radius.js`。两端的 `global.css` / `app.css` / `rn-tokens.ts` 一律是**派生态**(见本节"端内 CSS/色值副本一律是派生态"条),**禁止手改、禁止在端内抄第二个数字**。几何档(尺寸/字号/间距)尚无单一源文件 —— 那是 O81 第二票要建的 `packages/design-tokens/src/geometry.js`,**建它必须同一枚提交就有消费方**,不得先建表再等人用。
- **对账**:`scripts/check-cross-end-ui-parity.mjs`(guardian 第 **128** 项,blocking,紧急跳过 `HUSKY_SKIP_CROSS_END_UI_PARITY=1`)。同名配对组件的单位归一后比几何档集合(`72rpx` ≡ `36px` ≡ `36dp`;Tailwind `h-9` = 36、`text-sm` = 14),差档即点名;**棘轮锚点 = `scripts/cross-end-ui-parity-baseline.json`**(立项读数:19 对同名组件 / 18 对有差异 / 174 处差异档),所以它只拦"把两端差异加大",不会把存量债变成人人跳门(§12e)。**圆角自 2026-09-27 起归本门判(判据 RD,锚点单立 `radiusCounts`)**,色档仍归守门 93。
- **差异必须显式登记**:确属平台导致的(原生 chrome、键盘避让、系统字号缩放路径不同)写进台账 `waivers.<组件>.reason` 并由守门 108 管到期;**静默不同形**与**为消红给单端补数字**都算违规。
- **不得做的事**:不得为让本门变绿去改判据或调台账数字;不得把"两份实现"读成"两份真相"。
- **可见真值仍只有一处**:色档 `packages/design-tokens/src/styles/tokens.css`、圆角 `packages/design-tokens/src/radius.js`、**几何档 `packages/design-tokens/src/geometry.js`**(2026-09-26 O81 票② 立:`GEOMETRY_PX` 一张表 → `rnGeometry`(dp) / `taroGeometry`(已折 2 倍 rpx) 两个投影,与 `radius.js` 同形)。两端的 `global.css` / `app.css` / `rn-tokens.ts` 一律是**派生态**(见本节"端内 CSS/色值副本一律是派生态"条),**禁止手改、禁止在端内抄第二个数字**。命中区/图标墨迹这类尺寸**不得**再写 `const *_SIZE = <数字>` 当端内既定档 —— 两份"唯一实现"的返回键就是那样各差 2px 的。
- **守门**:`scripts/check-prompt-injection-registry.mjs`(guardian 第 **129** 项,blocking,紧急跳过 `HUSKY_SKIP_PROMPT_INJECTION_REGISTRY=1`;手动入口 `pnpm check:prompt-injection`)。三条判据,零豁免清单(判据输入全部由被审面自身推导):**R1** 每条登记的 `producer` 文件必须在**被审面**上存在、其代码面出现该 id、且有一处记账出口调用(注释里的提及不算装车);**R2** `renderInjectionNotice(` 在生产面(非测试)至少一个调用点,且登记表与唯一出口在面上解析得到、导出仍在位;**R3** 出现 `[系统提醒]` / `[系统提示]` 这类裸宿主前缀而该文件既不调用也不定义唯一出口 ⇒ 违规,**锚点 = 该文件 HEAD 自身的违规数**(棘轮,只拦新增;把存量当场判红就是一台与任何提交无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门,§12e 同型)。口径同 70/77/83/98/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工逃生舱、两面旗同给 exit 2、取不到判"无法判定"、枚举到 0 个候选判死。**接线史(两型同体,值得记住)**:该门立项时只有 `package.json` 一个脚本、**不在提交链**,而头注自称"guardian 第 128 项"与 runner 现值不符(128 归 `check-cross-end-ui-parity`)—— "造好没装车"与"编号谎报"同时存在,由守门 89 的 R4(已接线而文档通篇不点名)暴露;2026-09-26 按空闲号 129 补装进 `scripts/guardian-runner.mjs`,并在本节点名。**编号一律以 runner 现值为准,不得照文档派单;头注与 runner 不一致时,以 runner 为真值去改头注,而不是让后人按谎报的编号找门。**
- **parity 绿不等于内容语种对(2026-09-26 立,由 D20 实测逼出)**:上面五门判的是**键集**与"**有没有**中文残留",所以"某一块整体落成另一门语言"这一型结构上看不见。实测抓到三种形态,全部门禁当时都是绿的:① `aiChat.org` 的 **ja 装了繁体、zh-TW 装了谚文**;② `chat.exportMenu.exportPdf` 是 **ja←zh-TW←ko←ja 的三向轮转**(判据必须按**码位**量,按字形肉眼判会把谚文 `을 익스포트` 与假名 `をエクスポート` 看成同一种东西);③ `aiChat.toast.orgSaved` 被机械写成 `orgSaved:{orgSaved:"…"}` **自套一层**,`t('toast.orgSaved')` 取到对象 ⇒ 功能成功但提示永远不响。**批量落 i18n 后必须做两件事**:逐语种量"这块内容是不是这门语言"(CJK 三族互判),以及全量扫"同名自套一层"(`{"x":{"x":…}}`)—— 后一型现仓 0 处,新增即应判红。补正式门禁另计一票(现无门判这两型),在此之前不得把 parity 通过当成语种通过。
- **RN 函数形态 style 被 cssInterop 吃掉**(guardian id **131**,blocking,`stagedTriggers=apps/mobile-rn/ + packages/app/`,紧急跳过 `HUSKY_SKIP_RN_INTEROP_FN_STYLE=1`):`scripts/check-rn-interop-fn-style.mjs` 拦"函数形态 `style={({pressed}) => …}` 落在被 `cssInterop` 注册过的 RN 组件上"。真机 A/B 定案(PROJECT_PLAN「O83 登记④」):interop 对非数组声明执行 `{ ...declaration }`,而 `{ ...函数 }` === `{}`,且 `applyStyles` 先把 `state.props` 清成 `{}` 再合并 ⇒ **该元素整份内联 style 静默消失**,退回默认 column + stretch(实测把「更多」折成两行)。两个设计点:① **注册表现读**,不维护手工清单 —— 每次从 `node_modules/react-native-css-interop/…/components.js` 抽 `cssInterop(...)` 组件名,读不到判"未判定"(内置兜底表只用于"包没装时仍能报数",且表里有现读没有的名字 ⇒ 判"清单腐烂"红);反直觉事实:**`Modal` 不在注册表里**,`StatusBar` 在。正则必须认 CJS 转译形态 `(0, api_1.cssInterop)(react_native_1.Pressable, …)` —— 按 ESM 直写形态写的正则在真包上恒空,而恒空表现为"什么都没注册",门对整型缺陷全盲却一路报绿(§22c"判据失效的表现永远是安静"同型)。② 棘轮锚点 = **该文件 HEAD 自身存量数**(与 77/83/98/102/113 同族):立项实测存量 118 处 / 58 文件,当场判红就是恒红门(§12e)。行内出口 `interop-style-exempt: <原因>`(**必须带原因**,裸标记不放行;已进守门 108 存活期表 30 天)。回溯不到 JSX 开标签的命中计"未判定"并报告,**不静默丢弃**。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged` 判索引 blob、两面旗同给 exit 2、取不到 exit 2)。取证 `--self-test` 11 条 + §22c 镜像 9 例(T1 装车 / T2 摘线不得被读成已装车 / T4 取材面形状锁 / T5 真仓阳性对照"看不见存量不算通过" / T6 注册表现读且兜底表是其子集)。
- **RN 函数形态 style 被 cssInterop 吃掉**(guardian id **131**,blocking,`stagedTriggers=apps/mobile-rn/ + packages/app/`,紧急跳过 `HUSKY_SKIP_RN_INTEROP_FN_STYLE=1`):`scripts/check-rn-interop-fn-style.mjs` 拦"函数形态 `style={({pressed}) => …}` 落在被 `cssInterop` 注册过的 RN 组件上"。真机 A/B 定案(PROJECT_PLAN「O83 登记④」):interop 对非数组声明执行 `{ ...declaration }`,而 `{ ...函数 }` === `{}`,且 `applyStyles` 先把 `state.props` 清成 `{}` 再合并 ⇒ **该元素整份内联 style 静默消失**,退回默认 column + stretch(实测把「更多」折成两行)。两个设计点:① **注册表现读**,不维护手工清单 —— 每次从 `node_modules/react-native-css-interop/…/components.js` 抽 `cssInterop(...)` 组件名,读不到判"未判定"(内置兜底表只用于"包没装时仍能报数",且表里有现读没有的名字 ⇒ 判"清单腐烂"红);反直觉事实:**`Modal` 不在注册表里**,`StatusBar` 在。正则必须认 CJS 转译形态 `(0, api_1.cssInterop)(react_native_1.Pressable, …)` —— 按 ESM 直写形态写的正则在真包上恒空,而恒空表现为"什么都没注册",门对整型缺陷全盲却一路报绿(§22c"判据失效的表现永远是安静"同型)。② 棘轮锚点 = **该文件 HEAD 自身存量数**(与 77/83/98/102/113 同族):立项实测存量 118 处 / 58 文件,当场判红就是恒红门(§12e)。三站点已清(见 PROJECT_PLAN「G-211 更正」),现值一律跑本门读末行,不得引用文档数字。行内出口 `interop-style-exempt: <原因>`(**必须带原因**,裸标记不放行;已进守门 108 存活期表 30 天)。回溯不到 JSX 开标签的命中计"未判定"并报告,**不静默丢弃**。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged` 判索引 blob、两面旗同给 exit 2、取不到 exit 2)。**判据面先剥注释与字符串**(等长遮罩,行号不变 —— 本门按行回溯 JSX 开标签,删字符会错位;豁免标记写在注释里,所以判据看遮罩面而豁免看原文):本门第一次自跑就是被自己写的说明咬到的 —— 给 CategoryInlineBar 补一句"早先这里写 style=函数形态"之后,`--worktree` 读数从预期的 115 变成 116,**门把解释自己的散文判成了违规**(守门 70 的 URL 假注释态同型)。取证 `--self-test` 14 条(新增"注释里的该形态不得计入"+"同一形态换成真代码必须命中"成对) + §22c 镜像 10 例 —— 其中 **T10 是端到端双向锁**:往临时索引里注入一处真代码函数形态 ⇒ 必红,把同一形态只写进注释 ⇒ 必绿,两条同时成立才算"遮罩关掉的是误报、不是判据"(只留后者就是替"门瞎了"背书)。它的牙是量出来的:把判据面从遮罩面改回原文面(`RE.test(line)`),T10 的 B 臂当场翻红,红语即"注释形态不得判红"(T1 装车 / T2 摘线不得被读成已装车 / T4 取材面形状锁 / T5 真仓阳性对照"看不见存量不算通过" / T6 注册表现读且兜底表是其子集)。
  1. **派单口径只认一个入口**:`node scripts/plan-tasks.mjs --open`(未勾选 − 同题已完成副本 − 自带作废声明的行 − 同一件事的第二条待办副本;五条判据 F1/F2/F3/F4/F6 都以"编号 + 标题前缀逐字等值"的复合主键为准,不依赖行号顺序也不依赖相似度)。`grep '^- \[ \]'` 不是待办清单(2026-09-26 一次实测的**不重叠**分解:未勾选 205 = 已认领 44 + 其余排除 28 + 真待办 133;同刻 F1/F2/F3/F4 读数均为 0 —— **这些数是当次现读,派单前必须重跑,勿照抄**;报告里其余排除项的明细计数彼此可重叠,只能当诊断看,不能拿来减法核账)。归并出口:`node scripts/plan-tasks-merge.mjs --heal --commit`(F1/F2 翻勾并写明归并到谁;**F4 只加"重复登记副本"指针、绝不动勾选** —— 两条都是没做完的事,把没做的记成做过的比原病更响)。
     - **F6 是"块"级量纲,行级四条判不到它**:一整块多行登记被并发 union 追加两遍时,每一行都"只是又一个孪生行",F1–F4 与门 71 同时全绿(本会话 2026-09-26 真实自伤:探针登记块从 1 份涨到 3 份而账面什么都没喊)。判据 `findDupBlocks` 只认 **≥3 行且每行 ≥40 字符的连续 bullet 块逐字相同**(低于阈值会把台账里天然成对的短行全数成百地报出来,噪声淹掉信号);修复出口 `node scripts/plan-tasks-merge.mjs --dedupe-blocks`(先不带 `--commit` 出报告,确认四条零损失断言后再 `--commit`),它**只删第 2..N 份逐字相同的副本**,漂移副本(首行同而正文不同)一份都不动 —— 机器折半即有损,那型交人工。与 `--heal` 的关键区别是**不进 post-commit 自动档**:删行是活文档上最危险的动作。
     - **F6 是"块"级量纲,行级四条判不到它**:一整块多行登记被并发 union 追加两遍时,每一行都"只是又一个孪生行",F1–F4 与门 71 同时全绿(本会话 2026-09-26 真实自伤:探针登记块从 1 份涨到 3 份而账面什么都没喊)。判据 `findDupBlocks` 只认 **≥3 行且每行 ≥40 字符的连续 bullet 块逐字相同**(低于阈值会把台账里天然成对的短行全数成百地报出来,噪声淹掉信号);修复出口 `node scripts/plan-tasks-merge.mjs --dedupe-blocks`(先不带 `--commit` 出报告,确认四条零损失断言后再 `--commit`),它**只删第 2..N 份逐字相同的副本**,漂移副本(首行同而正文不同)一份都不动 —— 机器折半即有损,那型交人工。与 `--heal` 的关键区别是**不进 post-commit 自动档**:删行是活文档上最危险的动作。**合并落地闸要跑两把尺子**(同日由一次真实的"已付的账原地复活"逼出):`git-sync-converge` 的**无冲突** merge-tree 分支原先零看守 —— 本地把 F6 从 8 清偿到 0、远端一份陈旧计划文档带着那 8 块进来,merge-tree 判"无冲突 ✅ 推进",账就复活了;而 `union-converge` 的判据是"相对 max(两侧)",这一型里 max(两侧) 恰好等于合并结果 ⇒ **第一把尺子结构上看不见它**。现落地闸同时跑 ① 相对两侧上限 ② 相对入库基线 `scripts/plan-task-state-baseline.json`(维度清单仍只有一份 = `plan-tasks.mjs` 导出的 `probe()`,别处不得再抄),两把各有正反对照(PS-1 同形而基线收紧必点名 / PS-2 基线与实态一致不得判红 —— 第二把若恒红就等于把所有合并堵死,与恒红门同罪)。
- **对象空间落地与活文档编辑的常驻出口 = `scripts/object-space-land.mjs` / `scripts/live-doc-edit.mjs` / `scripts/gate-registry-insert.mjs`(+ 共用 plumbing `scripts/lib/bypass-git.mjs`),不是守门**(2026-09-26 立;镜像测试 `node --test scripts/tests/{object-space-land,live-doc-edit,gate-registry-insert,bypass-git}.test.mjs` = 36 例)。为什么这条要进 AGENTS:本仓在共享工作区里做旁路提交是**常态**(活文档与注册表的工作树副本常年滞后 HEAD,按 pathspec 交工作树就是把别人已入库的行整批写回旧态),而 2026-09-26 那一轮同一套 plumbing(临时 `GIT_INDEX_FILE` + `hash-object` + `update-index` + `write-tree` + `commit-tree` + CAS `update-ref`)在同一会话里被手写了 **6 份**、全躺在 `.ihui-agent/tmp/` 不受版本控制,并且已经互相漂开:索引可动性判据两种写法、零损失判据三种写法 —— 其中一份拿"重复行计数"当零损失判据,被 `guardian-runner.mjs` 里天然存在的 `  },` 与空串行顶出"凭空多出 1322 条"而**一次也没落地成功**,换成结构等值判据才通过。不入库的代价是确定的:下次会话要么再手写一遍(带着同一个 bug 的新版本),要么图省事走 pathspec。**三条判据照抄别重新发明**:① CAS 循环内**每次重读 HEAD** 且"任一目标路径被别人改过即停"(不是抢锁,是拒绝覆盖);② 零损失判据 = **结构等值**(`new == 前缀 ⊕ 本块 ⊕ 后缀`),不是重复行计数;③ 落地后 `git show --name-only` 回读声明路径,并把**共享主索引**逐路径对齐到新 blob —— **只在** 索引 blob 等于父提交 blob 或索引里没有这条时动,否则点名"归属他人"不动(§12d 第三层那型:一次不带 pathspec 的普通提交就会把旁路交付写回旧版)。取号类判据(`gate-registry-insert`)必须同认 `id: '134'` 与 `id: "134"` 两种形态 —— 双引号 id 对一切按单引号解析注册表的判据(含守门 89 的撞号 R5)隐身,这是本轮真实踩过并用夹具钉死的坑。
- **共享层重复**(40):端内重新实现 shared hook/util 检测(阻塞,防端内独立实现回升,白名单:web/useChat + web/useAuth + web/useAgentRuntime + web/useClipboard + web/useNotificationStore + mobile-rn/useAuth + mobile-rn/useNotificationStore(后二者均为 re-export wrapper + 平台 adapter,属 §3 允许形态;守门的 re-export 判定只看 export 那一行有无 `from`,函数型 wrapper 必须显式登记))。〔归并补(副本独有说法,现行判据以上文为准):-**共享层重复实现守门**(40):`scripts/check-shared-layer-duplication.mjs`(blocking)——apps/*/src的export名与packages/shared/srcexport名求交集(`collectExports:21`,`scanAppDir:85`),端内重新实现即exit1(`:152`);(原 L1422) / 无应急通道(runner无skipEnv;(原 L1422) / re-exportwrapper豁免逻辑在白名单)。(原 L1422)〕
- **品牌邮件通道对账**(81):check-brand-email-channel.mjs(blocking,2026-09-23 立)—— 拦绕过品牌模板层的 ops 自发纯文本邮件("能发出去、typecheck/lint 全绿,用户收到的邮件没样式",与守门 72/78 同族):R1 `Send-MailMessage` 调用语句缺 `-BodyAsHtml` / R2 `api.resend.com/emails`(含 host+path 分行形态)同一发送上下文无 `html` 字段 / R3 有发信动作(`createTransport`/`sendMail`/`api.resend.com`)却不引用 `email-templates` 也不调用 `notify-deploy-failure`;范围 `deploy/**`+`scripts/**`(不含 tests)+workflows `*.yml`,注释行/裸域名不判(宁漏不误报),行内豁免 `brand-mail-exempt: <原因>`,存量走 `scripts/brand-email-channel-baseline.json` 只减不增(**2026-09-24 实测基线 `counts` 已为 `{}`** —— 建门时揪出的第三条通道 `scripts/check-credential-health.mjs` 已迁到同一派发器,未用 `brand-mail-exempt:` 糊过去)。**范围已两度扩面**(2026-09-24):原只扫 `deploy/**`+`scripts/**`,而告警邮件正文的实际出口在 `monitoring/alertbridge/*.cjs` —— 目录与扩展名**双重不可见**;现含 `monitoring/**` + `.cjs/.mts` + `apps/api/scripts/**`,并新增两条判据:**R3b** 接了唯一出口却自带一份 HTML 版式(三段与门:邮件版式标记 ∧ ±10 行样式指纹 ∧ 本文件确在邮件语境,注释行不计)—— R1/R2/R3a 只看"有没有走模板",对这一型全盲;**R4 告警接收面** `monitoring/alertmanager/**` 的 yml/yaml/tmpl 出现 `email_configs`/`smtp_*`(AM 原生邮件 = 无版式纯文本)、IM 中转 receiver(名字与 host/路径**都**认,`feishu-copy` 改名躲不过)、或 `webhook_configs` 里 url ≠ `BRIDGE_URL` ⇒ 红;注释行不判红但 `amCommentLiterals` 如实计数。**全仓不扫 yml**(根 compose / prometheus / loki 的历史注释里就写着 `dingtalk-webhook`,全仓扫必假红 —— 面外写进断言)。`stagedTriggers` 与扫描面**必须同步扩**,否则"只改 bridge/模板的提交"根本不唤起本门(判据存在而永不调用 = 没有,守门 70/76 同型;该字段今晚被并发整文件回写抹掉**三次**,由镜像测试"装车证明"抓回)。**修法的唯一姿势:ops 邮件一律经 `apps/api/scripts/notify-deploy-failure.ts`(版式由 `email-templates.ts` 单点决定),不得端内自拼传输层;Alertmanager 出口只有 bridge 一条**。取证 `--self-test` 61 例(正反成对+临时仓双取材面)+ 镜像测试 20 例(含装车证明与 `BRIDGE_URL` 与渲染器逐字等值的漂移即红断言);`--staged` 暂存集为空/取不到 → 退化全量(守门 70 教训);紧急跳过 `HUSKY_SKIP_BRAND_MAIL_GUARD=1`。〔归并补(副本独有说法,现行判据以上文为准):范围`deploy/**`+`scripts/**`(不含tests)+workflows`*.yml`,注释行/裸域名不判(宁漏不误报),行内豁免`brand-mail-exempt:<原因>`,存量走`scripts/brand-email-channel-baseline.json`只减不增(建门实测:ps1自拼通道已被并行会话清除,副产品揪出第三条通道`scripts/check-credential-health.mjs`已入基线待迁移)。(原 L1395) / **修法的唯一姿势:ops邮件一律经`apps/api/scripts/notify-deploy-failure.ts`(版式由`email-templates.ts`单点决定),不得端内自拼传输层**。(原 L1395) / 取证`--self-test`30例(正反成对+临时仓双取材面)+镜像测试8例(含装车证明);(原 L1395)〕
- **主题接线对账**(91):check-theme-prop-wiring.mjs(blocking,2026-09-24 立)—— 堵"顶栏深色 + 正文浅色"这类**同屏双色档案**:`packages/app` 213 个 theme-driven 组件的形参默认值是 `colorScheme = 'light'`,调用方漏传就静默脱主题,类型系统完全不红(`colorScheme?:` 可选)。三判据 —— **missing**(渲染点未传该属性)/ **literal**(写死 `'light'` 或 `'dark'`)/ **spread-unknown**(仅 `{...props}` 展开且回溯不出来源,判不出即如实报数,不静默放行)。建门实测:此前一轮用"JSX 元素文本里有没有 `colorScheme` 字样"统计出"漏传 0 处",**该结论是错的** —— 换成花括号深度扫描 + 组件清单自动推导后真实命中 **118 处 / 117 文件**,即 115 个屏一直在静默锁死浅色档案。**盲区已补(比漏修更值得记)**:写死的 `'light'` 有 5 处藏在**对象构造里**而非 JSX 属性上(`const props = { colorScheme: 'light' }; return <SharedX {...props} />`),"字面量"判据只解析 attrs 故从不判红,"未接线"判据见到 spread 就笼统归"判不出" —— 这 5 屏以"待人工核"的名义静默锁死浅色,**门一直是瞎的**。现由 `resolveSpreadThemeValue()` 回溯对象构造再判:对象里有字面量 → 判红;对象里确实没这个键 → 判 missing;props 来自函数形参且本文件无对象字面量 → 仍承认判不出,不猜。**基线 `scripts/theme-prop-wiring-baseline.json` 现须为空 `{counts:{}}`(零容忍)**,`--strict` 口径为"0 未接线 / 0 字面量 / 0 判不出"。行内豁免 `theme-wiring-exempt: <原因>`(须带原因)。取证 `--self-test`(**含阳性对照**:注释里写 `colorScheme: 'light'` 不得误判、不传 `src` 时行为与旧版一致)+ §22c 镜像测试 `scripts/tests/check-theme-prop-wiring.test.mjs`(含**装车证明**:runner 里必须真有 id 91 + blocking + skipEnv,且本门编号在 runner 中恰好出现一次)。**内容口径(2026-09-25 补齐,此前它是全链最后一道按磁盘判的主题门)**:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工逃生舱;枚举与内容同面同轮;取不到 ⇒ **exit 2「无法判定」**(不冒红也不记绿),`--staged --worktree` 同给判死,全量枚举到 0 个 .tsx 判死;git 面必须先 `prefetch`(一次 `cat-file --batch`)再 `read`,未预取即 read 抛错而非静默跳过。立项当场即由此把他人**未提交**的 `ChatScreen.tsx` 摘 prop 判成本仓违规(HEAD 其实合规)。取证 `--self-test` 的 F1–F4 在 `mkScratch` 临时仓里造"索引≠磁盘"现场,并由**三条变异**分别证明 F1/F4 不是恒真;紧急跳过 `HUSKY_SKIP_THEME_PROP_WIRING=1`。〔归并补(副本独有说法,现行判据以上文为准):-**跨端主题透线对账**(91):`scripts/check-theme-prop-wiring.mjs`(blocking)——packages/app的主题驱动组件(形参解构含`colorScheme`且一律带`='light'`默认值)必须在每个JSX渲染点被**真的传参**:默认值就是"静默失效开关"，写死或漏传都不报错、typecheck也不红。(原 L1458) / 拦两类真事故形态——L1调用方写死`colorScheme="light"`(实测导航栏深色而页面体浅色，同一屏两套档案)、L2漏传(当前0处，本门把它钉死在0；(原 L1458) / 刻意不把213处形参改成必填:无收益且会与并行会话在213个文件上对撞)。(原 L1458) / 组件集**不靠手工清单**(清单过期正是上一道门漏判的原因)，改为扫`packages/app/src`导出组件名+**import解析**(含alias/默认导入/命名空间成员)，只有解析到`@ihui/rn-app`或包内相对路径才算共享主题组件——端内同名自绘版(NavBar/TabBar/Carousel/UserInfoCard)因此不会误判；(原 L1458) / `{...spread}`转发的渲染点单列「不确定」如实报数(不静默放过、也不误判为红)。(原 L1458) / 实测真仓`--staged`exit0；(原 L1458) / 应急跳过`HUSKY_SKIP_THEME_PROP_WIRING=1`。(原 L1458)〕
- **品牌实底前景/容器对账(RN + 共享包 + web 类名面)**(83,原 75):check-brand-foreground.mjs(blocking,2026-09-23 立)—— 五条判据,`brand.DEFAULT` 与 `brand.cta` **同形认**(只认 DEFAULT 会让迁移后的主实底整片隐身 —— 判据必须覆盖门自己产出的形态):**R1** 零豁免,同块内 `backgroundColor: (tokens|tk).brand.(DEFAULT|cta)` × `surface.light`/`text.primary` 作前景;**R4** 同一关系拆到**兄弟 key**(`retryBtn` × `retryText`)按命名配对判,走棘轮(现 127 处);**R2** 拦"浅色当容器底"(`surface.light` / α≥0.5 白 rgba / 无 `dark:` 变体的 `bg-white`),棘轮现 24 处;**R3** 数品牌实底填充/描边,**按档配对**免债:DEFAULT 底 ↔ `brand.foreground`、cta 底 ↔ `brand.ctaForeground`,**跨档配对仍计**(DEFAULT 底 × ctaForeground 深色档案下=白底白字),棘轮现 235 处;**R5** web/ui-react 的 **Tailwind 类名形态**(R1–R4 全解析 RN style 对象,对 `bg-primary`+`text-primary-foreground` 这一 §4 已废配对原本整侧盲视),同行成对才算,变体前缀(`data-[state=checked]:bg-primary`)与染色底(`bg-primary/10`)不算,`r5-cta-exempt: 原因` 豁免,基线 `webClassPairCounts` **现为空 = 零容忍**(立项实测 22 处/14 文件,含 `@ihui/ui-react` Button 的 6 个 variant,当日由并行提交 `4e0b24689a` 迁完)。**R5 两条已知限制如实登记**:跨行配对不计(宁窄不误)、变体前缀不算实底(排除前缀的字符类与排除 `/90` 的是同一个,放宽任一即破坏口径)。**内容口径**:全量与 `--update-baseline` 判 **HEAD blob**,`--staged` 判暂存 —— 共享工作树滞后 HEAD,按磁盘算会恒红/假绿来回跳并把错数写回基线(2026-09-24 一天内 R3 登记被整文件回退三次的直接成因)。基线 6 键:`counts`/`ctaCounts`/`r4Counts`/`webClassPairCounts` + 两个文档性注记键(`pairedCtaNotVisibleToRule`/`ctaFillRenameLedger`)—— `--update-baseline` **必须保留注记键**,旧写法整文件重写会把他人审计台账冲掉(本轮并入远端时实测踩过)。取证:`--self-test` **94 条断言**(含 3 条阳性对照 + 变异对照)+ `node --test scripts/tests/check-brand-foreground.test.mjs` **13 例**;`--staged` / `--update-baseline` / `--self-test`;紧急跳过 `HUSKY_SKIP_BRAND_FOREGROUND=1`。〔归并补(副本独有说法,现行判据以上文为准):-**mobile-rn深色前景/容器对账**(83,原75):check-brand-foreground.mjs(blocking,2026-09-23立)——四条判据:**R1**零豁免,同一style块内`tokens.brand.DEFAULT`作背景×`surface.light`/`text.primary`作前景(深色档案下brand.DEFAULT=纯白⇒白底白字);(原 L1382) / **R4**同一关系被拆到**兄弟key**(`retryBtn`×`retryText`)时按命名配对判,走基线棘轮;(原 L1382) / **R2**`surface.light`背景/α≥0.5白rgba/无`dark:`变体的`bg-white`作容器底,每文件计数对`scripts/brand-foreground-baseline.json`只减不增;(原 L1382) / **R3**数brand.DEFAULT填充/描边但**不计§4成对CTA**(同块或兄弟键用`brand.foreground`作前景)——见本节上方「品牌CTA/主按钮色同源」,按规矩写就红的门只会逼人`--no-verify`;(原 L1382) / **无配对的白卡片照旧计**(自检里两条阳性对照钉住)。(原 L1382) / **内容口径**:全量与`--update-baseline`判**HEADblob**,`--staged`判暂存——共享工作树滞后HEAD,按磁盘算会在恒红/假绿间来回跳并把错数写回基线(2026-09-24一天内R3登记被整文件回退三次的直接成因)。(原 L1382) / -**mobile-rn深色前景/容器对账**(83,原75):check-brand-foreground.mjs(blocking,2026-09-23立)——R1零豁免:同一style块内`tokens.brand.DEFAULT`作背景×`surface.light`/`text.primary`作前景(深色档案下brand.DEFAULT=纯白⇒白底白字);(原 L1384) / R2基线棘轮:`surface.light`背景/α≥0.5白rgba/无`dark:`变体的`bg-white`作容器底,每文件计数对`scripts/brand-foreground-baseline.json`(现13文件24处,均为媒体上的合法浮层或已带`dark:`变体)只减不增。(原 L1384) / `--staged`/`--update-baseline`/`--self-test`(11例);(原 L1384)〕
- **反回退对账**(84,原 76):check-stale-revert.mjs(blocking,2026-09-23 立)—— 堵共享工作区**静默回滚**:§12d converge 用 merge-tree/commit-tree 只推进 HEAD+index、**不 checkout**,工作区落后 HEAD 时 `git add <file>` 交的是旧基线(实测 503 文件落后 486 提交),等于把别人该路径的后续改动静默回滚,而 diff 只显"改了几行"。判据 R1 = 暂存 blob != HEAD blob **且字节级等于该路径某祖先提交版本** → 拦并点名回到的 commit;真新编辑不可能恰好等于历史 blob,故误报极低。三条护栏:merge 上下文**不再整轮豁免**(2026-09-25 收窄,见下)、暂存删除只 warn(`git rm` 合法,机器分不清就被删)、判定文件 >300 跳过(性能护栏,防逼人 --no-verify 连带关掉全部守门;**乘数级路径不吃这条护栏**,2026-09-24 立)。**合并期窄判据 R1m**:某路径上 `ours == theirs`(两父逐字节一致)⇒ 合并对它无事可做 ⇒ 索引里与两父都不同的内容**不可能来自本次合并**,再要求它恰等于该路径某历史版本才判红 —— 第三个条件是必须的,因为"人工解冲突写进新内容"同样满足前两条,而那是正当形态。`cherry-pick / revert / rebase` 仍整轮豁免(取历史内容本就是其语义)。立因(第四十二批未闭环④):旧口径那句"merge 上下文整轮豁免"是**本门自己的盲区**,当夜 30c 判红、84 沉默,合并期把旧基线写进索引完全无人看守。取证 `--self-test` **12 例**(新增 5a/5b/5c/5d/6b 五例合并对照:两父不同⇒绿、两父一致而等于历史版本⇒红、等于新写内容⇒绿、cherry-pick⇒仍绿)+ §22c 镜像测试 7 例(含"整轮豁免那句写法不得回来"的**反向回归锁** —— 收窄是判据不是临时关闭,必须能被机器发现)+ 临时 index 端到端演练 3/3(2026-09-23 原始取证,保留);**有意回退一律改用 `git revert` 生成前向提交**。**一条本门看不见的时间窗(2026-09-26 实测)**:84 判的是**索引**里的 blob,而"工作树副本 == 祖先版本、但尚未 `git add`"这一格它结构上碰不到 —— 偏偏危险就在这格里:任何人一次 `git add <该文件>` 或一条不带 pathspec 的 `git commit -a` 就会把它交上去。当天实测 `scripts/guardian-runner.mjs` 的工作树副本等于祖先 `4c1610662`(09-25 19:34),**比 HEAD 少 3 道门(124/125/126)**,而 `git status` 只显示一个 ` M`、13c/71/84 全都不响。量这一型的尺子是 `node scripts/heal-worktree-tracked.mjs --align-drift`(判据:索引==HEAD 且工作树==该路径某祖先 ⇒ 才动,可证零独有数据),手工等价动作是 `git checkout-index -f -- <path>`;**先看判据再动手,不满足就不许对齐** —— 它也可能是别人真在写的现场。紧急跳过 `HUSKY_SKIP_STALE_REVERT_GUARD=1`。〔归并补(副本独有说法,现行判据以上文为准):三条护栏:merge/cherry-pick/revert上下文整轮豁免、暂存删除只warn(`gitrm`合法,机器分不清就被删)、判定文件>300跳过(性能护栏,防逼人--no-verify连带关掉全部守门)。(原 L1385) / 取证`--self-test`8例(含"写回v1必判红"阳性对照)+临时index端到端演练3/3;(原 L1385)〕
- **硬编码中文基线棘轮**(70):scan-hardcoded-zh.mjs(blocking,2026-09-22 接线)—— 扫 `apps/web/{app,src/components,src/hooks}` + `packages/{ui-react,shared}/src` + 2026-09-24 补的三端(`apps/mobile-rn/src`、`apps/extension/{entrypoints,src,lib}`、`apps/cli/src`),排除注释/metadata/useTranslations 行/admin/测试。**每文件命中数与基线额度比较**(`scripts/hardcoded-zh-baseline.json`):只拦"比基线更多",存量债(886 文件 / 13090 行)冻结、新增即拦;清理后 `--update-baseline` 下调额度(必须人工确认后再跑,禁止为过门而调高)。该脚本 2026-07-20 即存在但从未接入守门链,是"造好没装车"的第二个实例(第一个是同一批的 69)。`--staged` 由 runner 自动下发(runner 传 `--exit 1`,不带该参数时**永远 exit 0** —— 集成测试踩过),暂存集为空时回退全量防"空暂存恒绿"。**两处同日修**:① 14 例镜像测试自 §15「ROOT 由脚本自身位置推导」起 **13/14 恒红** —— 测试靠 `cwd` 定位夹具而脚本按定义忽略 cwd,14 例全在扫真仓(不是判据错,是调用方式失效);加 `--root <dir>` 显式测试通道(生产不带,语义不变)后 15/15 绿。② 命中判定漏剥**行尾 `//`** ⇒ PriceChart / TerminalTab / TerminalStatusIndicators 各多出 2/1/1 处假阳,把它们顶过基线额度、把本门钉成"谁碰这三个文件谁被拦";同时修 `'https://x/*'` 这类串内 `/*` 骗进块注释状态机的假绿。**遗留已消除(2026-09-25 实测复核)**：全量模式的取材口径**已改为判 HEAD blob** —— `scan-hardcoded-zh.mjs` 现有"磁盘/索引/HEAD 三种取材共用一份判据"(约 :204 行注释与实现)，实测 `node scripts/scan-hardcoded-zh.mjs` 全量**越线文件数 = 0**。本段此前写的"全量模式按共享工作树判 ⇒ 同一份 HEAD 代码干净检出报 1 个、本机工作树报 281 个"是**已修状态**，保留原话只为说明那一型为什么会发生；**下一个接手者不必再做这件事**，若要复核判据请跑上面那条命令看末行现值。（另记 `heal-worktree-tracked` 的刷新面仍在扩大，两件事互补不互替。）紧急跳过 `HUSKY_SKIP_HARDCODED_ZH_GUARD=1`。〔归并补(副本独有说法,现行判据以上文为准):**遗留(已量化,未修)**:全量模式按共享工作树判,而共享工作树对约2064个路径落后HEAD——同一份HEAD代码,临时worktree干净检出报**1**个文件越线(`packages/shared/src/chat/handoff-package.ts`49处/基线0),本机工作树报**281**个。(原 L1379) / 要么让全量模式改判HEADblob(守门77已验证的口径),要么把`heal-worktree-tracked`的刷新面扩大;(原 L1379) / 前者是判据口径、后者是环境自愈,都不该靠"反正本地红着习惯就好"糊过去。(原 L1379)〕
- **架构契约对账**(103):`scripts/check-architecture-policy.mjs`(blocking,2026-09-25 立)—— 与其余全部守门**方向相反**的一道:其余是"发现一类违规 → 写一条判据"(打地鼠),本门读 `config/architecture-policy.yaml` 这张**声明表**,从声明反查违规。四类红:**T1** 表与现实脱节(模块改名/目录搬走而表没跟上 —— 表一过期,依赖它的所有判断都在对着空气打分)/ **C1** 单文件行上限(阈值 6000,**取自 HEAD 实测最大文件 5258 行之上**,依据写在 yaml 注释里)/ **D1 D2** 依赖方向(包 import 端、或 `requires` 未声明就 import;以及反向按 rank 更深一层)/ **D3** 深导入(绕过 `public_entrypoints` 直接摸别的包内部 —— 它是"端内重复实现"的入口)。**渐进收口是设计前提**:立项时 24 块一律 `managed: false`(只报数不判红),逐块翻正时才按判红口径问责。⚠️ "**存量一律 false**"描述的是**立项状态,不是现值** —— 现值一律按当次实测取,见下。翻 true 前先用 `--managed-trial <id>` 看条数,**不得为消红去改阈值或把不该对外的模块标成 exported**。行内豁免 `arch-exempt: <原因>`(须带原因)。口径同 70/77/83/98/101:全量判 **HEAD blob**、`--staged` 判索引 blob。**策略表自身的取材按档定向**(`policyFaceOrder`):全量档 HEAD 优先、**`--staged` 档索引优先**,两者都再降级到工作树;取不到判"无法判定",不冒红也不记绿。⚠️ 这里 2026-09-25 修掉一处**本门对自身改动的盲视**:原实现两个面都 HEAD 优先,于是"修改策略表自身"的那枚提交完全不进本门审查 —— 实测往索引版 `apps/cli.requires` 注入一条 `apps/api`(端应用 `exported:false`,T1 必判红)后全量与 `--staged` 双双 exit 0,而 `--staged` 输出照旧打印旧表的 `managed:true` 清单。这个洞偏偏落在"规矩 2:翻 `managed:true` 前先试跑"所要求的动作上(提交链是唯一无验的一环),且源码旧注释"表是输入不是被审对象"正是它的设计理由,已就地推翻。取证 `--self-test` 51 例(成对正反 + 两面口径差异 + 解析器坏了必须大声失败)+ 镜像测试 13 例(含 runner 装车证明、"同一代码 managed 两侧结论不同"、T12 的**纯函数+构造面**取材面证明、以及 T12b 的尺子反例;变异回旧顺序只有 T12 红)。**本门自身踩过的判据教训(勿再犯)**:这里曾写过一条"索引表≠HEAD 表 ⇒ 两档收口集合必异形"的条件断言,两条前提都不成立(改注释/requires 都会让表不同而集合不变;尺子若吞到行尾则两档因"扫描 N 文件"天然不等)⇒ 一支误红、一支无牙,已删。**证明取材面这类行为只能用纯函数+构造面**,不得依赖仓库瞬时状态。**收口进度不写进文档**:某模块是否 `managed:true` 一律按当次实测取(`--staged` 输出行即现值),理由与本门刚修的那类缺陷同源 —— 登记过期数字会替人做出"已经收口"的判断。编号说明:任务书原定 102,落地前已被 `check-glyph-arrow-icon` 占用,按"后来者改号"顺延 103。紧急跳过 `HUSKY_SKIP_ARCH_POLICY=1`。 **E1/E2 两条同日补**:E1 公开入口存在性对账 —— `public_entrypoints` 声明的入口必须在**被审判的同一取材面**里解析得到(HEAD 面实测 75 条声明入口全部在位 ⇒ 本条可即时问责,不像 E2 那样带存量)/ E2 契约工件齐备性 —— 纳管模块必须有策略表字段点名的自述与契约文件(**清单取表里的字段,不在代码里硬编码第二份名字**),默认档**只报数**:HEAD 实测 16 块未齐备,当场判红就是一台恒红门(§12e 同型),要问责跑 `--strict`。退出码口径:判红只算「C1/T1 全仓即时 + 已收口模块的契约违规」,E1/E2 的红仅按 `managed:true` 问责。**同批给策略表配了只读出口** `scripts/module-context.mjs`(pnpm `module:context`):按模块 id 打印「是否纳管 / 声明依赖 / 本模块契约文件 / 依赖模块契约文件 / 公开入口 / 边界条款」,`--files` 直接吐派单用的文件清单 —— §11 那张「受影响文件」格过去全靠派单人手写,而 §11 自己就记着这张清单写错会造成越权/污染。未知模块 id **exit 2 并点名**(实测 `node scripts/module-context.mjs nosuch-module-xyz` → rc 2);`--json` 必须可 `JSON.parse`;E2 未齐备既打印 missing **也进退出码** —— 上游那一版只打印不判,这一格我们补上了。〔归并补(副本独有说法,现行判据以上文为准):口径同70/77/83/98/101:全量判**HEADblob**、`--staged`判索引blob,策略表自身按HEAD→索引→工作树降级取且**退到工作树会大声提示**;(原 L1400) / 取证`--self-test`51例(成对正反+两面口径差异+解析器坏了必须大声失败)+镜像测试10例(含runner装车证明与"同一代码managed两侧结论不同"的判据有牙证明)。(原 L1400) / 〔归并补:取证`--self-test`51例(成对正反+两面口径差异+解析器坏了必须大声失败)+镜像测试12例(含runner装车证明、"同一代码managed两侧结论不同"、以及T12的**取材面双向条件不变量**——索引表≠HEAD表则两档结论必不同形、相同则必同形;(原 L1944) / (原L1946)/**渐进收口是设计前提**:存量模块一律`managed:false`⇒只报数不判红(立项实测全量exit0、报数3处、`managed:true`0个)(⚠️这几个数字是**立项状态、不是现值**;(原 L1944) / **渐进收口是设计前提**:存量模块一律`managed:false`⇒只报数不判红(立项实测全量exit0、报数3处、`managed:true`0个)(⚠️这几个数字是**立项状态、不是现值**;(原 L1954)〕。〔归并补(副本独有说法,现行判据以上文为准):-**为什么"产物存在"不等于"能力在线"(本门立论,值得推广到别的生成器)**:这四个生成器里此前**只有i18n那个进了构建链**,其余三个既无构建入口也无门禁⇒"造好没装车"只能靠人记得手动跑;(原 L2137) / 而dev链不跑i18n生成器,意味着**开发时离线语言包常年是旧的**,只有release才被发现。(原 L2137) / 同类盲区见守门64(适配器接线)/70(硬编码中文)/81(邮件通道):**判据必须在真跑它的那一刻才成立,否则等于没有。(原 L2137)〕
  - **为什么"产物存在"不等于"能力在线"(本门立论,值得推广到别的生成器)**:这四个生成器里此前**只有 i18n 那个进了构建链**,其余三个既无构建入口也无门禁 ⇒ "造好没装车"只能靠人记得手动跑;而 dev 链不跑 i18n 生成器,意味着**开发时离线语言包常年是旧的**,只有 release 才被发现。同类盲区见守门 64(适配器接线)/ 70(硬编码中文)/ 81(邮件通道):**判据必须在真跑它的那一刻才成立,否则等于没有。**。〔归并补(副本独有说法,现行判据以上文为准):-**小程序端「源↔派生产物」存续性对账**(105):`scripts/check-miniapp-generated.mjs`(**warn,刻意不blocking**)——管四件"生成器造好了但没人跑"的派生产物:i18n离线包`remote-locales.gen.ts`、`static/images/icons/*.svg`、`LineIcon/icons.ts`、`assets/tabbar/*.png`。(原 L2057) / **升blocking的前置条件是G1存量清零**。(原 L2057)〕
    任何"可达率"读数都必须**先认档再说数**,脱离 C5/C6 的单个百分比没有意义。**三条教训叠在一起**:① 判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;② 但在把两侧名字"对上"之前,必须先问**挂载侧到底写了什么** —— "CSS 里有这条规则"与"这个样式生效了"之间隔着一维,只读声明侧的绿灯结构上排除不了"规则存在、没人挂得上"。〔归并补:现测**hit269/771=34.89%(原名直中266+转写可达0+复合首族3)/死规则485/确定缺失6**。(原 L2094) / **两条教训叠在一起**:①判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;(原 L2094)〕
- ⚠️ **上面那条 `/alpha` 判据目前到不了小程序产物**(2026-09-25 实测,登记于 PROJECT_PLAN O62附②):`apps/miniapp-taro/config/index.ts` 的 `tailwindcss: { enable: true, config: {} }` 让端内 `tailwind.config.ts`(content globs + `presets:[@ihui/design-tokens/tailwind-preset]` + 本插件)在真实构建中**从未被加载** —— 实测真实 build 后 dist 的 154 个 wxss 里,源码用到的 **687 个 Tailwind utility / 11,105 处用法有 0 个产出规则**,`.flex{` 与 preflight 指纹双双缺失;阳性对照是同一份 dist 里 `app.css` 手写类名 75/98 可查到规则,故不是检索姿势问题。**后果**:改这条配置等于让整端 11,105 处用法同时开始产出 CSS,而现状很可能是手写 CSS 已补偿过(自有类名 2,228 个),开启后好坏只能由微信开发者工具真机渲染判定 ⇒ 属未决决策项,不是已收口项。**同时提醒同源对账门的边界**:门 36/37/93/`check-miniapp-taro-style-parity` 全部只核源码与 token 源头,**没有一道看产物**;它们五道全绿而到端 CSS 为零,是可能且已发生的。新增跨端同源门时,请先问自己判的是"写的同源"还是"生效的同源"。**⚠️ 本条的"0 产出"现值已作废**(同上,`edb225c909` + `f17b5fbdda`):`app.css` 现有一个由 `sync-miniapp-tokens.mjs` 派生的 `@theme` 注册区,命名档与项目色档已产出**且运行时可达**;但含标点那一族(arbitrary value 等)是"CSS 有规则、运行时挂不上"的死规则,真实可达率**必须先认构建档位**再读(同一配置实测三档:34.89% / 93.77% / 99.35%,见下下条与 PROJECT_PLAN 附⑩/附⑪)。"没有一道同源门看产物"这半句仍然成立 —— 看产物的是 `check-miniapp-css-landing.mjs`(手动/CI,未接提交链),它现在连**运行时挂不挂得上**也一起看。〔归并补:**⚠️本条的"0产出"现值已作废**(同上,`edb225c909`+`f17b5fbdda`):真产物落地率740/771=96.0%,`app.css`现有一个由`sync-miniapp-tokens.mjs`派生的`@theme`注册区;(原 L1958) / 但"没有一道同源门看产物"这半句仍然成立——看产物的是`check-miniapp-css-landing.mjs`(手动/CI,未接提交链)。(原 L1958) / 但含标点那一族(arbitraryvalue等)是"CSS有规则、运行时挂不上"的死规则,全端真实可达率现测**269/771=34.89%**(见下下条与PROJECT_PLAN附⑩)。(原 L2096)〕
- **记忆端点属主绑定对账**(117,**blocking**,2026-09-25 升档;紧急跳过 `HUSKY_SKIP_MEMORY_OWNER_BINDING=1`):`scripts/check-memory-owner-binding.mjs` 钉的是**"认证不等于授权"**那一层 —— `apps/ai-service/app/api/memory.py` 把 `user_id` 当请求体/Query 参数收,全文件**没有一处**与令牌主体对齐;`/api/memory` 不在 `PUBLIC_PATHS` ⇒ 匿名进不来,但**任何已登录用户填别人的 UUID 就能读/改/删别人的记忆**。判洞要问"有没有把请求里的 id 与令牌里的 id 比对",不是"有没有全局中间件"。两条 Python 语法锚点必须同时认:`user_id: str = Query(...)` 与 Pydantic `Field(...)` —— **只认 Query 会让整类 POST 端点隐身**;对齐出口只写在注释里不算(自称已拆、实际没拆那一型)。**定级史(照抄旧档会判错)**:立项时 warn —— 当时未对齐存量 2 个文件;现值一律 `node scripts/check-memory-owner-binding.mjs` 自取 —— `app/api/memory.py` 已于同日 `3b0170fa176` 九端点统一 `require_request_user_id` 归零,剩下的是**此前无人枚举过的** `app/routers/rules.py`,而"怎么对齐"取决于 **v1 对外 API 能不能代表他人访问记忆** —— 实测 `apps/api/src/routes/v1-knowledge-tools.ts` 有 2 条转发是 `jsonInit(parsed.data)` **原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。收紧成 403 与"忽略并覆盖"两种改法**都会让本门归零**,故存量未清时判红就是恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型)。**2026-09-25 前置已满足并升 blocking**:`752c6eb110b` 收掉最后一个未对齐面(`app/routers/rules.py`)后 HEAD 面实测"未对齐 0 个",runner 侧同时带 `args:['--strict']`(本门默认档只报数,不带它升档是假的)。**⚠️ 上面那句"HEAD 面实测未对齐 0 个"当次即作废,是本节里一个真实的假绿灯(2026-09-25 同日更正)**:`3b0170fa176` 把参数一律改写成 `user_id: str | None = Query(...)`,而本门两条正则只认 `: str =` ⇒ **一个端点都扫不到** ⇒ 输出"收 user_id 的端点文件 0 个 / 未对齐 0 个"并 exit 0 —— 一道刚被升到 blocking 的安全门变成永远绿灯的尺子,它既看不见当时的敞口,也看不见"明天有人把 `require_request_user_id` 删掉"。补宽正则(可选标注两档都认)后 HEAD 面实测:**收 user_id 的端点文件 2 个 / 已对齐 1 / 未对齐 1**,那一个就是 `app/routers/usage.py` 的 `/stats`+`/quota`+`/record` —— 写法是 `uid = user_id or request.state.user_id`(**自报身份优先于令牌主体**),而参数说明里的"(管理员用)"在该文件内没有任何对应校验 ⇒ 任何已登录用户带 `?user_id=<别人>` 即可读他人用量/配额。已按 `require_request_user_id` + `roleId ≥ 1` 才可代表他人收口(实测 `apps/web` 两处调用方都只传 `days`、`usage/record` 全仓零调用方 ⇒ 不影响在跑链路),回归 `apps/ai-service/tests/test_usage_authz.py` 10 例。**本条的通用教训:改被审代码的写法,必须同时改审它的正则** —— 否则"升级成 blocking"这个动作本身就是在给一台瞎掉的尺子背书。取证 `--self-test` 17→**21 例**(新增 S17–S20:可选标注的 Query/Field 各一对正反例,夹具文本逐字取自真实文件)。**降回 warn 等于重新放开新增敞口**;若 HEAD 面再报未对齐(例:有人跳过钩子塞进去),先清偿再提别的票,不得削判据。口径同 70/77/83/98/101/103(全量判 HEAD blob、`--staged` 判索引、枚举到 0 个文件判"无法判定"而非记绿 —— 判据失明不是通过)。仓里现成出口 = `app/core/jwt_auth.py` 的 `require_request_user_id`(带 `DEV_ANONYMOUS_PRINCIPAL` 开发降级,以免 ASGI in-process 的既有测试整片 401 —— 那不构成安全结论)。取证 `--self-test` 17 例(正反成对) + §22c 镜像 14 例(含"未注册时不得被判定为已装车"的方向性对照);手动问责档 `pnpm check:memory-owner`(走 `--strict`)。〔归并补(副本独有说法,现行判据以上文为准):**定级warn是设计前提而非偷懒**:HEAD实测未对齐存量**2个文件**(`app/api/memory.py`与**此前无人枚举过的**`app/routers/rules.py`),而"怎么对齐"取决于**v1对外API能不能代表他人访问记忆**——实测`apps/api/src/routes/v1-knowledge-tools.ts`有2条转发是`jsonInit(parsed.data)`**原样透传客户端自报身份**(本门把这类列成"上游透传清单"只报数,就是给选型看爆炸半径用的)。(原 L2019) / 收紧成403与"忽略并覆盖"两种改法**都会让本门归零**,故在选型落地前它是"存量报数、新增判红"的棘轮;(原 L2019) / 当场blocking=与任何提交都无关的恒红门,唯一结局是逼人`--no-verify`连带废掉全部守门(§12e同型)。(原 L2019) / **升blocking的前置条件=未对齐存量归零**。(原 L2019) / **定级warn是设计前提而非偷懒**:未对齐存量:立项读数2个文件;(原 L2022)〕
  - **13c 自身的取材口径(同日迁移)**:此前 `ROOT = process.cwd()` + 裸 `git show` 用 `execSync` 拼字符串(无 timeout)+ 占位合规性看 `git diff` 文本,三处都与本仓现行口径相反(工作树滞后即换结论 / 服务账户 PATH 不通 / 别人同时改这文件时合规性跟着盘走)。现全部经 `scripts/lib/face-reader.mjs`,对照面是 `--staged`:HEAD→索引、全量:HEAD^→HEAD、`--worktree` 仅人工;整面取不到 ⇒ **exit 2 无法判定**,不冒红也不记绿。取证 `--self-test` 10 条,含两条本门自己踩出来的:CRLF/LF 同一条标题不得读成"删除";**A2 的归属必须看该目录在审面上的全集**而不是只看 `PROJECT_PLAN_*` 形状 —— 本门第一次自跑就是被这条假红咬到的(已跟踪的 `orphan-capabilities-equivalence-2026-09-24.md` 被点名"落空")。〔归并补(副本独有说法,现行判据以上文为准):2026-09-26那次实测是face28/loose71(其中half-wired1)/unknown38/no-content49——写死它下次收紧就变成假账。(原 L2010) / **那1道半接线同日收口了**:`check-rn-global-css-sync`换成走层的`catBatch`三面取材,收口后现读`half-wired`应为0——这条判据落地当天就产出了第一次真实修复,不是只多了一批红)。(原 L2010) / **同日两处精度修正(都是量出来的假阳,不是审美)**:①"读内容"与"枚举"必须分开——`cat-file-e`(存在性)、`ls-tree--name-only`(路径清单)、`rev-list`/`merge-base`不产生blob正文,把它们算成"散写读内容"会把只做枚举的门钉成半接线(它一处blob都没读);(原 L2010) / **假阳比漏报更贵**——它指使人去"修"没坏的东西,还把判据口径说歪成"数字很多"。(原 L2010) / ②仓库锚点必须落在**路径函数实参**里:旧写法的`|\bROOT\s*,`一支会把`functionaudit(limit=400,cwd=ROOT)`读成"以仓库根拼路径"。(原 L2010) / ③借层的transport自己读内容(`gitRaw(['show',spec])`)同样算半接线——管子共用不等于面共用。(原 L2010) / **已知空档如实登记**:锚点只认大写`ROOT`,故`join(root,…)`这类小写模块根写法落进`unknown`(不判红)。(原 L2010) / 试过放宽(74→93),其中两格是假阳(一格读`.workbuddy`运行台账、一格读按设计只存在于部署机的gitignore副本),**已收回**:判据分不清"读被审内容"与"读运行态",就不该假装分得清并据此判红;(原 L2010) / 补它要认识gitignore语义(参照`check-prod-bundle-shadow`的S2),不是一行正则的事。(原 L2010) / -**门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25立)——本仓159道门里只有**24**道走`scripts/lib/face-reader.mjs`,散写git读内容或按磁盘`readFileSync(join(ROOT,…))`判的有**74**道,另32道"读文件但找不到仓库锚点"(疑临时夹具,结构上判不了,只报数)。(原 L2073) / **两层遮噪方向不同,别混用**:认face-reader导入与git动词必须"只遮注释、保留字符串"(模块说明符和`cat-file`本身就是字符串),认`readFileSync`调用必须"注释与字符串都遮"(`markHidden`那一档连字符串一起抹,直接套它会让F1/F2双双假绿)。(原 L2073)〕
- **对象空间落地与活文档编辑的常驻出口 = `scripts/object-space-land.mjs` / `scripts/live-doc-edit.mjs` / `scripts/gate-registry-insert.mjs`(+ 共用 plumbing `scripts/lib/bypass-git.mjs`),不是守门**(2026-09-26 立;镜像测试 `node --test scripts/tests/{object-space-land,live-doc-edit,gate-registry-insert,bypass-git}.test.mjs`,**例数以该命令末行为准,本文不钉数字**)。为什么这条要进 AGENTS:本仓在共享工作区里做旁路提交是**常态**(活文档与注册表的工作树副本常年滞后 HEAD,按 pathspec 交工作树就是把别人已入库的行整批写回旧态),而 2026-09-26 那一轮同一套 plumbing(临时 `GIT_INDEX_FILE` + `hash-object` + `update-index` + `write-tree` + `commit-tree` + CAS `update-ref`)在同一会话里被手写了 **6 份**、全躺在 `.ihui-agent/tmp/` 不受版本控制,并且已经互相漂开:索引可动性判据两种写法、零损失判据三种写法 —— 其中一份拿"重复行计数"当零损失判据,被 `guardian-runner.mjs` 里天然存在的 `  },` 与空串行顶出"凭空多出 1322 条"而**一次也没落地成功**,换成结构等值判据才通过。不入库的代价是确定的:下次会话要么再手写一遍(带着同一个 bug 的新版本),要么图省事走 pathspec。**三条判据照抄别重新发明**:① CAS 循环内**每次重读 HEAD** 且"任一目标路径被别人改过即停"(不是抢锁,是拒绝覆盖);② 零损失判据 = **结构等值**(`new == 前缀 ⊕ 本块 ⊕ 后缀`),不是重复行计数;③ 落地后 `git show --name-only` 回读声明路径,并把**共享主索引**逐路径对齐到新 blob —— **只在** 索引 blob 等于父提交 blob 或索引里没有这条时动,否则点名"归属他人"不动(§12d 第三层那型:一次不带 pathspec 的普通提交就会把旁路交付写回旧版)。取号类判据(`gate-registry-insert`)必须同认 `id: '134'` 与 `id: "134"` 两种形态 —— 双引号 id 对一切按单引号解析注册表的判据(含守门 89 的撞号 R5)隐身,这是本轮真实踩过并用夹具钉死的坑。
- **批量写计数诚实性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 落地时取到 134,并发会话可能挪号;`blocking`,`stagedTriggers=apps/api/src/routes/` + `apps/api/src/db/`,紧急跳过 `HUSKY_SKIP_BATCH_WRITE_COUNT_HONESTY=1`):`scripts/check-batch-write-count-honesty.mjs` —— 拦的是 2026-09-26 一整批同型静默失真的**形状**:`db.delete/update ... where inArray(ids)` 之后把**请求侧数组的 `.length`** 当 `affected` / `deleted` / `restored` 回给前端。那一型的问题是"改了 0 行"与"改成功"在响应上完全同形 —— `where` 带 `eq(roleId)` / `eq(memberId)` 过滤时,传进来的 id 一个都不命中也照样回 `deleted: 3`,后台显示"已删除 3 项"而库里一行没动。当轮人肉清出 8 处(`chat.ts` batch、`admin-sys/role-routes.ts` cancelAll+selectAll、`admin-demand-square.ts`、`admin/_shared.ts` 的 `registerCrud` 批量删〔**19 处 admin 路由复用同一个工厂**〕、`message.ts` 批量删、`workspace.ts` batch-delete/batch-restore;后两者的 db 层原本返回 `void`,**结构上无法回报命中集**),统一出口是 `apps/api/src/utils/batch-outcome.ts`(`dedupeIds` + `batchWriteOutcome(requested, confirmed)` ⇒ `{requestedIds, affected, missedIds}`),db 层一律 `.returning({id})` 回报**库确认集合**,响应新增 `missedIds` 逐条点名(既有键名与 status code 逐字不变)。**四条放过通道,每条都有正反用例**:① 同一写链带 `.returning(`;② 计数根标识符 ≤2 跳可追到归属预查询(`business-card-routes.ts` 那种"先查 owned 再删"的**正确写法**不得判红);③ 该文件 import 了唯一出口;④ 行内 `batch-count-exempt: <原因>`(须带原因,裸标记不计,并已挂进守门 108 的 30 天到期档 —— 豁免不得只出生不死亡)。**刻意不判、但每次现读并报数的两型**(不计红、不改退出码,写进门自身的观测面,免得"放过"与"没看见"在账面上长得一样):布尔 `deleted: true`(全仓惯例、既有用例当契约钉,要不要改成 `found`/404 语义属全 API 决策、交人定 —— **处数一律跑本门看末行现值,不得照任何文档抄数**)与读查询 `count: rows.length`(不是写)。**判不了的形状如实登记**:写链在 db 层而计数在 handler、`send` 在嵌套箭头里、`deleted: (await …).length` 非点号链形态 ⇒ 落 `未判定` 并报数,`--strict` 下有未判定即 exit 2(**拒绝出合格证**)。**口径同 70/77/83/98/103/118**:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"、枚举到 0 个候选判死;棘轮锚点 = 该文件 HEAD 自身违规数,所以存量未清时不会变成人人跳门。**两处遮噪方向不同**(本门自己踩过):判写链要"剥注释 + 保留字符串"(标识符落在字符串里的形态要看得见),判 `import` 说明符则连字符串一起抹会直接失明。**取证例数一律以命令末行现测为准,本文不钉数字**(本门落地当天就从 35 条长到 50 条、镜像从 10 例长到 14 例 —— 钉死的那份立刻变成假账):`node scripts/check-batch-write-count-honesty.mjs --self-test` / `node --test scripts/tests/check-batch-write-count-honesty.test.mjs`;阳性对照用**修复前的 HEAD blob** 喂同一判据,点名 `_shared.ts` 与 `message.ts` 两处真实存量,证明判据真能命中本仓实际产出的形态。
- **同一把尺子的另外三维(2026-09-27 补,读数一律现读)**:① **具名档**——数字一旦收编进 `packages/shared/src/ui/*-spec.ts` 或 `geometry.js`,组件里只剩标识符,**必须仍然读得出数值**(否则"收进单一源"= 从尺子上消失,这是用户实拍"两端还是不一样"而当次门报绿的直接成因)。**一条血的教训**:该判据 2026-09-26 就写进了 `readGeometry` 并配了自检,而 `main()` 的调用点漏传第四个参数,**在提交链上生效次数为 0** —— 自检恒绿是因为它手工喂档表给函数,证明的是"函数会给答案"而不是"有人问它";现由源码级装车锁(㊵)+ 镜像 T10 钉死。② **SL 单侧具名档** —— 一张 spec 档只被一条腿引用 = 另一条腿还没接线,与"同一元素两端不同值"处置动作不同,所以**只列名字、不折进几何集合**,与 IC 同形走 HEAD 棘轮(存量只报数,新增才红)。③ **G 几何表 ↔ 它自己的 `geometry.d.ts`** 两侧对账:有引用而类型不认 ⇒ TS2339(吵),**没人引用则两边都不红**,而本门正是按运行时键集读表的 ⇒ 判据信任了类型层不承认的档;落地前工作树正发生前者(`controlBox`/`controlGlyph` 入表未入类型 ⇒ miniapp 8 条 TS2339),而提交链上无一覆盖这一格。④ **拆对声明 `pairingRejects`** —— 同名不同物(实测 `FloatBox`:小程序 = 右下角三按钮功能盒,RN = 顶部自动消失的 toast)不是"两端分叉",继续计账只会诱导下一个人去收敛两个不同的东西;三条都判红(声明须带 reason + 未到期 `until` / 拆掉的族不得再挂 `counts` 或 `waivers` / 面上找不到该对则声明须了结),且 `emitBaseline` 必须原样带走它(它是判据输入不是存量数字,重写冲掉的后果与守门 83 那条同型)。**该族差异数从账上消失必须由"配对错了"这个证据换来,不得由"数字太大"换来。**
  - **配对键本身也是判据,不是中性工具(2026-09-27 O81 票⑩ 补)**:本门只比"同名成文件"的元素,而**同名**先前由 `normKey`(去扩展名 + 去非字母数字 + 小写)定义 —— Taro 构建器按后缀解析同名实现,`adapters/SectionHeader.taro.tsx` 才是 weapp 上真被打包的那一份,旧键把 `.taro` 吸进键里 ⇒ 它与 RN 侧 `SectionHeader` 成不了对。**两型同体**:① 该族在射程外(零判据);② 更坏的是若该端**另有**一份未被打包的同名文件,旧配对会拿它去审并记成"两端同值"—— 绿灯建立在死副本上,与"腿层"那条剔除判据是同一型,只是发生在键层。现由 `PLATFORM_SUFFIX`(只列明确平台解析词,**不做模糊匹配**:宁可少配,也绝不把两个不同元素并成一对、造出假"同值")+ `pairKey` + `candRank`(同键多候选时带后缀那份优先 = 该端构建期真被解析的那一份)收口;族名走 `baseName`(**后缀必须先随扩展名一起去掉** —— 对 `Foo.taro` 直接跑剥后缀正则得到 `Foo.`,台账键会裂成 `Foo`/`Foo.` 两份,与守门 134「锚点粒度不够细 ⇒ 换个写法就净零逃逸」同型)。换腿桶 `altsBySide` 与 `scan()` **必须同键同序**,否则带后缀那份与不带那份不在同一桶里,腿不可达时找不到替代。
  - **射程边界必须报名、不得只报数**:旧版只印"仅小程序 N / 仅 RN M"两个计数,而拿到数字的人**无从判断**哪些是同一元素的两种命名、哪些确实只存在一端 —— 这个判断恰是"要不要再扩配对判据"的唯一输入,所以只报数等于把这一格永久锁死(本仓"报数不报名"记过多次:守门 70/76/81 同族)。现在人读面逐名列出、`--json` 面给路径数组。**仍未收口的一格**:名单里剩真同名不同文件的对(小程序 `SearchBar` vs RN `SearchInput`、`AgentListPanel` vs `AgentList`、`DrawerComponent` vs `Drawer`)要靠台账里显式登记的**别名对**才能进审(两侧文件在被审面都必须在位、未使用的别名按腐烂判红),那是下一票;在它落地前本门对这一族**零判据**,而名单已报出 —— 不得把"exit 0"读成"两端界面无待判项"。
  - **配对判据的两格盲区与它们的修法(2026-09-27 O81 票⑪)**:① **只能配"两端起了同一个名字"的元素** —— `SearchBar` vs `SearchInput`、`CategoryBar` vs `CategoryInlineBar`、`DrawerComponent` vs `Drawer` 靠任何按文件名的判据都看不见,而只报"仅小程序 N / 仅 RN M"两个计数时这一格**结构上无法清偿**(拿数字的人不知道名单里哪两个是同一个东西)。出口是台账 `aliases`:逐条点名两侧文件 + 理由 + 到期日,配对从"猜名字"变成**登记出来的证据**;三条红 = 声明坏(与拆对**共用** `rejectProblem`,判据不许有第二份)/ 被审面找不到文件(路径写歪或文件搬家 —— 不判红就静默退化成"这一族没配上")/ 该对已能同名配对(多余行必须了结)。`emitBaseline` 必须原样带走 `aliases`(它是配对输入不是存量数字),HEAD 棘轮的**两次基线取样同样要喂别名表**,否则本轮新开的配对在基准侧不存在、别人欠的债算成新增红。**登记的前提是逐字读两端头注**:`CategoryBar` ↔ `category/CategoryInlineBar`(头注互点组件名)、`DrawerComponent` ↔ `Drawer`(互点同一份历史源文件)可登;`AgentListPanel` ↔ `AgentList`(列表面板 vs 底部选择浮层)**拒登** —— 配错了就是造"两端一致"的假账,与 `FloatBox` 拆对是同一判据的反方向。② **门只读 `.tsx`,而小程序把盒档写在同名 `.css` 里**(6 个配对组件如此)⇒ 读成"RN 11 档、小程序 0 档"。这**不是两端不同形,是尺子没跟到那一层**,与 §4"CSS 声明形态整面隐身"同型。现几何判据读**组件源文本 + 该文件自己 import 的本地样式表**(只跟相对路径 `.css/.scss/.less`,不做全局扫描 —— 把别处的档算到这个组件头上比漏读更糟;取不到 ⇒ 并进未判定点名,绝不静默当"该侧无档")。**圆角(RD)刻意不跟样式表**:它的锚点由另一路会话按"组件源文本"口径钉着(票⑧),同笔抬它的读数 = 把别人钉的锚顶成"新增红";两把尺子互相改基准是本仓最贵的一类事故,所以这里只把量到的新分叉(`ModelConfigDialog` 小程序 12 vs RN 16)**数字交接**,不代裁。
  - **跨端对账门的"发现面"必须覆盖两端各自的书写语言,而扩覆盖面必须拿既有锚点当假阳陷阱线**(2026-09-27 O81 票⑫ 实测)。四条同根缺陷:① 配对源只收 `*.tsx`,而 6 个小程序组件把盒档写在同名 `*.css` ⇒ 读成"RN 11 档 / 小程序 0 档";② 简写声明 `padding: 0 24rpx` 只取第一个数 ⇒ 横向内边距整族隐身,RN 的 `paddingHorizontal: 12` 被记成假分叉(**两端本来同值**);③ `width: 100%` 被当 100px 收进档表;④ 修 ② 时我自己造出新假阳 —— 多值循环沿用键名子串筛,`box-shadow: 0 2rpx 8rpx` 的阴影半径因键名含 `box` 命中几何键,凭空多一档并把 `ModelConfigDialog` **顶过它自己的锚点**。**拦住 ④ 的正是既有锚点**:那枚红不是仓库新债,是刚写的提取式过宽。⇒ 三条规矩:**扩覆盖面那一笔必须在"带既有锚点"的面上跑**(清空基线去跑,过宽与正确在账面上长得一模一样);**多值 / 新形态判据用属性白名单而不是子串筛**,并写明邻居为何不入(描边宽度是另一维、`border-radius` 简写归圆角维、`transform`/`filter`/`background`/`*shadow` 不是长度档);**收紧脚本必须拒绝"任一锚点上升或消失"**,只允许下降 —— 本次台账 107 → 97 全是下降,这才叫"不是为过门调数"。净效果:`CategoryBar` 11→1、`ModelList` 9→4、`UserInfoCard` 5→4、`IntelligentAssistant` 8→7,**端上一行代码未改 ⇒ 这 10 档差异从来不存在**。交付时把这句说在前面,否则下一个人会去改本来同值的两端。`*.module.css` / CSS-in-JS 形态**尚未测量**,不得读成"已确认没有"。
  - **一条通用的自检教训(同一枚提交里踩了两次)**:装车锁正则 `/\n    styles,$/` 少 `m` 旗 ⇒ `$` 是"整串末尾",对任何正常代码恒假;`t(name, cond)` 判的是 `cond === true`,把箭头函数当 cond 传进去得到"实得:function"。**永远绿的断言与永远红的断言同样没用**,判据写完必须拿"它应当红的构造面"和"它应当绿的当前面"各喂一次,而不是只看它此刻的颜色。
- **ApiResult 失败分支身份对账**(guardian id **135**,blocking,`stagedTriggers=apps/ + packages/`,紧急跳过 `HUSKY_SKIP_API_FAILURE_THROW=1`):`scripts/check-api-failure-throw.mjs` 拦 `if (!X.success) throw new Error(X.error)` —— `ApiResult` 失败分支本来带 `status`/`errorCode`(`packages/types/src/api.ts`),只取 message 等于把"这个错误的身份"丢掉;而 `toUserFriendlyMessage` 判序是 **errorCode → HTTP status → 文案正则**,前两档一丢,401 的 `Invalid or expired token` 只能落到参数类正则 `/invalid|missing|required|must be|expected/i`,用户看到「提交的信息有误,请检查后重试」。**立因是真机实测**:进 App「广场」tab 一进来就弹这句 —— 它把"去重新登录"引导成"回去改表单",不是措辞偏好而是**修复方向被指错**。唯一出口 `packages/shared/src/utils/error-messages.ts` 的 `apiFailureToError(res)`;出口被摘线时本门判"没有出路"并**参与退出码**(只打印不改退出码 = 下一次没人看)。三条设计点:① **判据面先剥注释与字符串**,遮罩实现只有一份(`scripts/lib/code-mask.mjs`,守门 131 同日改引它 —— 两处实现必漂移是本仓记过最多次的失败型,由镜像 T7 钉死"任一门不得留本地副本");② 棘轮锚点 = **该文件 HEAD 自身存量**(立项实测 958 处 / 576+ 文件,当场判红就是恒红门,§12e);③ 行内出口 `api-error-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。**刻意不判的第二半必须记住**:catch 里写 `showFloat(e.message)` / `setError(String(e))` 的,即使 throw 换了出口仍然丢身份 —— 所以迁移余量必须逐处读 catch 路径,批量替换会产出"看起来改了、实际没好"的提交。取证 `--self-test`(含真仓 HEAD 阳性对照"看不见存量不算通过"、"HEAD 全量档不得因存量判红")+ §22c 镜像 8 例(T1 装车 / T2 摘线不得被读成已装车 / T4 取材面形状锁 / T5 真仓存量 / **T6 用纯函数 + 构造面证明"没 export 的出口等于不存在"** / T7 遮罩不得有第二份 / T8 自检端到端);另有一次**私有索引注入**的现场复跑:往 `ArticleListScreen.tsx` 的索引副本加一处新违规 ⇒ `--staged` exit 1 并点名该文件。
  任何"可达率"读数都必须**先认档再说数**,脱离 C5/C6 的单个百分比没有意义。**三条教训叠在一起**:① 判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;② 但在把两侧名字"对上"之前,必须先问**挂载侧到底写了什么** —— "CSS 里有这条规则"与"这个样式生效了"之间隔着一维,只读声明侧的绿灯结构上排除不了"规则存在、没人挂得上"。〔归并补:现测**hit269/771=34.89%(原名直中266+转写可达0+复合首族3)/死规则485/确定缺失6**。(原 L2094) / **两条教训叠在一起**:①判落地的门比错形态,症状不是差一点,而是把真信号淹在噪声里;(原 L2094)〕
- **@ 多维提及引擎接线与单一源对账**(guardian id 以 runner 现值为准 —— 本枚落地取到 **138**,blocking,`stagedTriggers=apps/web/src/` + `packages/shared/src/`,紧急跳过 `HUSKY_SKIP_MENTION_ENGINE_WIRED=1`):`scripts/check-mention-engine-wired.mjs` 判三件事 —— **W1** `useSearchMentions` / `addMention` / `MentionChips` 三个件必须各有**生产**调用方(注释与测试面不计),零容忍、**不吃基线**:61 票落地时的真实状态就是"组件、hook、渲染点全在,唯独没人调用",于是 chips 读到的 mentions 恒是空数组、"无提及即 null"的早退恒成立,而 typecheck/lint/单测全绿(它自带的用例绿恰恰证明"没人调用"与"能跑"互不矛盾)。同型先例:守门 64 / 70 / 81 / 115。**W2/W3** 维度清单与触发符解析各只许一份(原先在 `message-input.tsx` 的 `@` 裸正则与 `use-context-selector.ts` 的九类目表 + `#` 正则里各有一份),走**该文件 HEAD 自身存量**棘轮 —— 只拦"这次改动把第二份加回来",仓里天然的同类形状(hex 色值、PG 连接串)留在各自额度里,不当新账判红。取材口径同 70/77/83/98/101/103/115(全量判 HEAD blob、`--staged` 判索引、`--worktree` 仅人工、两旗同给判死、取不到 exit 2、**枚举到 0 个候选判死**);取证 `--self-test`(例数按末行现读)+ 镜像测试。
- **模式×权限档×审批三轴矩阵对账**(guardian id 以 runner 现值为准 —— 本枚落地取到 **139**,blocking,`stagedTriggers=apps/ai-service/` + `packages/types/`,紧急跳过 `HUSKY_SKIP_MODE_PERMISSION_MATRIX=1`):`scripts/check-mode-permission-matrix.mjs` 钉的是 V3 #53 立起来的**跨语言契约**:Python 侧 `CHAT_MODE_TOOL_AXIS`(`apps/ai-service/app/core/permission_mode.py`)与 TypeScript 侧 `packages/types/src/permission-mode.ts` 的契约表必须 **5×5 逐格等值**、与三轴推导一致、只读白名单两侧同值。三条设计约束值得照抄:① **只许一处交集实现** —— 第二处手写 filter/白名单副本即红(三轴"看着正交、实则共用同一个执行闸门",所以轴本身是一等公民,不许被 `is_readonly_mode` 这类局部视图派生式取代而丢成员);`READONLY_TOOLS` 出现次数与谓词调用点**只报数不判红**。② 判定住在共享出口 `core/permission_mode.py`,入口(`routers/llm.py`)与执行层(`services/agent_loop_v2.py`)只调出口,**同形分支实现即红**。③ 契约表被摘线或无人调用 ⇒ 判"失明"不判"通过"(与守门 70/76/81 同型)。取证 `--self-test` 13 条 + 镜像 9 例 + 跨语言快照测试 `apps/ai-service/tests/test_chat_mode_tool_gate.py`(任一侧漂移即红)。
- **状态词汇跨语言与端内抄本对账**(guardian id **151**,blocking,`stagedTriggers=packages/types/src/` + `apps/ai-service/app/services/` + `apps/web/src/components/`,紧急跳过 `HUSKY_SKIP_STATUS_VOCABULARY_PARITY=1`):`scripts/check-agent-status-vocabulary-parity.mjs` 立的是 D6-G3 那一格 —— **同一套状态词汇曾在四地各写一份**(canonical `packages/types/src/agent-runtime.ts` 的 `AGENT_TASK_STATUSES`、`apps/ai-service/app/services/dag_scheduler.py` 的 `Literal[...]`、web 的 `KanbanBoard.tsx` 列序、`agent-tasks-panel.tsx` 徽章表),其中 Python 那一座**长期零判据**:改一侧忘另一侧时两侧都不红,而六档值是**落库列 + REST `z.enum` + SSE 载荷三重对外契约**(`packages/database/src/schema/agent-tasks.ts:31`、`apps/api/src/routes/agents-kanban.ts:128,151,386`)。三条判据:**SV1** 四处成员集合逐字等值(按名字比,不做相似度);**SV2** canonical 自洽(状态机表键集、LEGACY 值域必须落在六档内)且**第二域不相交** —— `running/completed/failed/canceled` 是工作空间任务态(生产侧 `workspace-ai-service.ts:335`,同名类型不同值),把它并进六档就是改对外契约,所以判据要求两域交集为空并各自登记;**SV3** 端内不得再抄第二份成员清单(锚点 = 该文件 HEAD 自身存量,只拦新增)。同一把尺子管两种域,是因为只登记六档的那一型正是"一条门只管自己立项那一型"的洞(守门 102 左向箭头同族)。三态不并桶:枚举到 0 个候选判死、解析不到 ⇒ **exit 2 未判定**,不冒红也不记绿;取材面同 70/77/83/98/101/103/118(全量 HEAD blob / `--staged` 索引 blob / `--worktree` 仅人工,两旗同给判死),遮罩只引 `scripts/lib/code-mask.mjs` 那一份实现。**两条如实登记的判据边界**:只按**类型**引用 canonical,运行时另抄 `z.enum([...])` 的形态算合规 ⇒ 那一格无判据;`agent-tasks-panel.tsx` 的徽章仍把第二域状态值原样显示未本地化(动它要新增 4 键 × 5 语言并走 §19 全流程),归下一票。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准。**新增一档必须同枚提交补齐 `agents.kanban.*` 五语言(AGENTS §30),不得写豁免清单消账 —— 登记表必然腐烂。**
- **本轮新增的两维与一条通路规矩(2026-09-29 立,D145 / D142 / D159 的交付)**:① 守门 151 新增 **SV4** —— 子代理词汇除了类型层与常量表,**运行期还有第二个广告面**(拼接给模型的清单),按注册表现读拼接,写死那份不移动读数即红;② 守门 111 新增 **TC4/TC5** —— 工具契约的 `humanApprovalMandated` 必须被真实执行漏斗消费(缺省语义一字未改,只在"契约说必须人批"时**只升不降**权限档),同词不同义的两张名单不得并置进同一个 barrel;③ **帧的字段"生产了"不等于"到端了"** —— D159 的三个审批载荷字段当时两侧契约、服务端生产点、弹窗组件与 15 条用例全在,而两个解析面(`packages/api-client/src/client.ts::tryParseToolApproval`、`packages/shared/src/sse/agent-events.ts::parseToolApprovalEvent`)是显式挑字段的收窄写法,不递 ⇒ 屏幕上永远"字段缺席=整块不渲染",组件用例一路绿。现投影判据只有 `projectToolApprovalEnvFacts` 一处,两条解析面同引它,通路源码锁在 `scripts/tests/d159-approval-env-wiring.test.mjs`(新增/摘掉任一段桥都会红)。同一型另有一格登记在 `scripts/data/sse-dispatch-coverage.json`:某端**结构上没有**对应控件时(如 D130 的推理强度档位只有 web 有输入区轴),必须在该端 `missing` 写明理由与解阻判据,不得留空让门读成"静默丢弃"。
- **RN 包装器双层页头对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **145**(注册时曾占 140,与并发会话撞号后按"后来者改号"挪到现值),blocking,`stagedTriggers=apps/mobile-rn/`,紧急跳过 `HUSKY_SKIP_RN_DOUBLE_HEADER=1`):`scripts/check-rn-double-header.mjs` 钉的是同一屏**两条页头** —— RN 屏包装器自渲染 `<NavBar>`,而它渲染的共享屏(`@ihui/rn-app`,含 `as` 别名)内部又自带一份页头 ⇒ 两个返回箭头 + 两个标题(用户实拍在设置页)。**导航面不背这个锅**:HEAD 面全 `apps/mobile-rn` 只有两处 `headerShown`,均为 `false`,所以判据不看导航配置。三条设计点:① **抑制通道必须从调用点实参识别** —— `hideHeader` / `renderHeader` / `nestedInScrollView` 三族,缺这一层则 8 个候选里 3 个是假阳(实测);② **`as` 别名必须解析**,否则设置页那一站直接漏判;③ **合规族反向对照** —— 同一扫描把 163 处判为合规(共享屏自带页头而 RN 屏不加 NavBar),本门不是"逢 NavBar 即红"。**修复出口只有一条**:给共享屏补抑制通道并在调用点传入;**禁止删任意一层** —— 共享页头是 163 个消费点的唯一页头,包装器 NavBar 在 Settings/Profile 上承载 SideMenu 抽屉唯一入口,而删包装器会让 loading 态失去唯一返回手段(实测 `packages/app/src/features/course-detail` 的 loading 分支只有 spinner)。行内出口 `double-header-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、枚举到 0 个判死。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(含 T7 私有索引注入的端到端双向锁:注入违规必红、只写进注释必绿、豁免出口必须真能用)。**存量按"该文件 HEAD 自身计数"套棘轮**,立项现读 5 处 / 5 文件只报数 —— 当场判红就是恒红门。**两条落地教训**:① 本门由子代理写完判据与测试后**停在轮次上限**,注册块缺失 ⇒ 镜像 T1/T2 恒红而门体自跑 exit 0,即"门存在、判据对、无人调度";② 注册落地用的是 `scripts/gate-registry-insert.mjs`(commit-tree + CAS,**不碰工作树**),而 `scripts/guardian-runner.mjs` 的工作树副本当场就落成了父提交版本(`git status` 只显 ` M`,一次不带 pathspec 的提交就会把新注册块写回旧版),由 `node scripts/heal-worktree-tracked.mjs --align-drift` 对齐 —— 旁路交付注册表之后必须复验工作树面,不能只看索引。
- **README 表格完整性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **146**,blocking,`stagedTriggers=README.md`,紧急跳过 `HUSKY_SKIP_README_TABLE_INTEGRITY=1`):`scripts/check-readme-table-integrity.mjs` 拦的是**长文被塞进 Markdown 表格单元格**这一型 —— 单元格里不能有换行,而 prettier 会把散文里的 `|` 当列分隔符重排,产出的行竖线数与本表基准不等,渲染出来是碎的(本会话一夜之间在同一张表里手工撞见三例:第 135、131 两格与 131 在叠表里的第二份副本)。分三类,**只有 T-A 判红**:**TI1** = 一个表格簇内出现 ≥2 行连续的 2 竖线行而该簇主竖线数 ≥3,它们是上一行最后一格的竖排续行(纯机械可判);**T-B** = 竖线数比基准少 1 的半截行(多为重复了首列 id),**只报数** —— 该列填什么要逐案判语义,当场判红就是一台与任何提交都无关的恒红门;**孤立 2 竖线候选**同样只报数。取材口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、**枚举到 0 个表格簇判死不记绿**;围栏(``` 与 ~~~,缩进围栏也算)与 HTML 注释内的示例表整段跳过 —— 否则会把文档里"教怎么写表"的样例判成违规。**修复出口是门自己给的、当天就在真仓跑通**:`node scripts/readme-table-unwrap.mjs --file README.md --dry-run`(默认零写盘),`--apply` 前自证"剥竖线去空白后字符多重集全等";主会话另取一把尺子独立复量过两次(735456/735456、缺 0 多 0),**工具的自证不等于它对**。行内出口 `<!-- table-cell-exempt: <原因> -->`(须带原因,已挂守门 108 的 30 天到期档)。存量走 `scripts/readme-table-integrity-baseline.json` 的**该文件 HEAD 自身**棘轮,`--update-baseline` 只减不增且必须保留非计数注记键。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像含"台账不得腐烂成第二份真相"与"摘线不得被读成已装车"的反向锁)。**一条本门立项当夜撞到的落地事故值得单独记**:注册块由 `gate-registry-insert.mjs` 落地(它只写 `guardian-runner.mjs` 一个路径),而门体脚本当时仍是**未跟踪文件** —— 于是 HEAD 里出现一条指向不存在脚本的注册,任何干净检出上那枚门都会以"脚本找不到"失败并挡住整批门;而守门 89 的 R1/R2/R4 **看不见**这一格(它的候选集按"脚本在不在被审面"枚举,脚本不在就不成候选)。⇒ **注册与脚本必须同枚入库**,且核验要跑 `git cat-file -e HEAD:<脚本路径>` 而不是只看 runner 里有没有那行。
- **ApiResult 失败分支身份对账**(guardian id **135**,blocking,`stagedTriggers=apps/ + packages/`,紧急跳过 `HUSKY_SKIP_API_FAILURE_THROW=1`):`scripts/check-api-failure-throw.mjs` 拦 `if (!X.success) throw new Error(X.error)` —— `ApiResult` 失败分支本来带 `status`/`errorCode`(`packages/types/src/api.ts`),只取 message 等于把"这个错误的身份"丢掉;而 `toUserFriendlyMessage` 判序是 **errorCode → HTTP status → 文案正则**,前两档一丢,401 的 `Invalid or expired token` 只能落到参数类正则 `/invalid|missing|required|must be|expected/i`,用户看到「提交的信息有误,请检查后重试」。**立因是真机实测**:进 App「广场」tab 一进来就弹这句 —— 它把"去重新登录"引导成"回去改表单",不是措辞偏好而是**修复方向被指错**。唯一出口 `packages/shared/src/utils/error-messages.ts` 的 `apiFailureToError(res)`;出口被摘线时本门判"没有出路"并**参与退出码**(只打印不改退出码 = 下一次没人看)。四条设计点:① **判据面先剥注释与字符串**,遮罩实现只有一份(`scripts/lib/code-mask.mjs`,守门 131 同日改引它 —— 两处实现必漂移是本仓记过最多次的失败型,由镜像 T7 钉死"任一门不得留本地副本");② 棘轮锚点 = **该文件 HEAD 自身存量**(立项实测 958 处 / 576+ 文件,当场判红就是恒红门,§12e);③ 行内出口 `api-error-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。 ④ **暂存档在"本次没有射程内文件"时回退 HEAD 全量并喊出来**,不得判"无法判定"(exit 2) —— 文档/语言包类提交结构上不带 .ts,把它判成空扫就是替**每一次**无关提交挡路(实测 ffdb860744),而恒挡的唯一结局是各会话走应急跳门、连带全部守门作废(§12e);口径同门 70/101。回退判定抽成纯函数 `shouldRetreatToHead`,四种情形(该回退 / 有射程内文件 / 显式 --files / 全量档)各有正反例。**刻意不判的第二半必须记住**:catch 里写 `showFloat(e.message)` / `setError(String(e))` 的,即使 throw 换了出口仍然丢身份 —— 所以迁移余量必须逐处读 catch 路径,批量替换会产出"看起来改了、实际没好"的提交。取证 `--self-test`(含真仓 HEAD 阳性对照"看不见存量不算通过"、"HEAD 全量档不得因存量判红")+ §22c 镜像 9 例(T1 装车 / T2 摘线不得被读成已装车 / T4 取材面形状锁 / T5 真仓存量 / **T6 用纯函数 + 构造面证明"没 export 的出口等于不存在"** / T7 遮罩不得有第二份 / T8 自检端到端 / **T9 回退判定只认"无射程内文件"这一种,不得被顺手改成无条件回退 —— 端到端那一例刻意不进镜像,它取决于共享索引此刻有什么(守门 103 T12 那一课)**);另把自检里原本 `!head.scannedFiles || true` 那条**恒真断言**换成 inScope 的四路真实判定(永远绿的断言比没有断言更糟);另有一次**私有索引注入**的现场复跑:往 `ArticleListScreen.tsx` 的索引副本加一处新违规 ⇒ `--staged` exit 1 并点名该文件。
- **硬编码中文基线棘轮**(70):scan-hardcoded-zh.mjs(blocking,2026-09-22 接线)—— 扫 `apps/web/{app,src/components,src/hooks}` + `packages/{ui-react,shared}/src` + 2026-09-24 补的三端(`apps/mobile-rn/src`、`apps/extension/{entrypoints,src,lib}`、`apps/cli/src`),排除注释/metadata/useTranslations 行/admin/测试。**每文件命中数与基线额度比较**(`scripts/hardcoded-zh-baseline.json`):只拦"比基线更多",存量债(886 文件 / 13090 行)冻结、新增即拦;清理后 `--update-baseline` 下调额度(必须人工确认后再跑,禁止为过门而调高)。该脚本 2026-07-20 即存在但从未接入守门链,是"造好没装车"的第二个实例(第一个是同一批的 69)。`--staged` 由 runner 自动下发(runner 传 `--exit 1`,不带该参数时**永远 exit 0** —— 集成测试踩过),暂存集为空时回退全量防"空暂存恒绿"。**两处同日修**:① 14 例镜像测试自 §15「ROOT 由脚本自身位置推导」起 **13/14 恒红** —— 测试靠 `cwd` 定位夹具而脚本按定义忽略 cwd,14 例全在扫真仓(不是判据错,是调用方式失效);加 `--root <dir>` 显式测试通道(生产不带,语义不变)后 15/15 绿。② 命中判定漏剥**行尾 `//`** ⇒ PriceChart / TerminalTab / TerminalStatusIndicators 各多出 2/1/1 处假阳,把它们顶过基线额度、把本门钉成"谁碰这三个文件谁被拦";同时修 `'https://x/*'` 这类串内 `/*` 骗进块注释状态机的假绿。**口径现值(2026-09-27 拂晓重新量,取代本条此前"已改为判 HEAD blob / 全量越线 0"那句)**:三档取材确实共用一份判据(源码 `scripts/scan-hardcoded-zh.mjs:328-342`),但**缺省全量档判的是工作树磁盘,不是 HEAD**—— 源码 `:244` 写明理由「本门的"新增即拦"要求看得见尚未 git add 的在途中文」,这是设计不是漂移。后果必须如实说:**本机全量档读数红 ≠ 仓库红**。拂晓实测 `node scripts/scan-hardcoded-zh.mjs --exit 1` 红 2 个文件 ——`apps/web/src/hooks/use-user-menu.ts`(7 处,**未跟踪**)与 `apps/mobile-rn/src/components/PayButton.tsx`(5 处,他人 ` M`),两个都不在 HEAD 里;棘轮额度那一维恒取 HEAD blob,所以它们只是"别人的在飞现场"。**问责与"是否已清"一律看提交链的 `--staged`(索引)或显式 HEAD 档**,不得为让全量档变绿去改判据或动他人文件。历史那一型仍然成立(共享工作树对上千个路径落后 HEAD ⇒ 同一份代码在"恒红"与"假绿"之间来回跳),它当时的修法是把**基线额度**钉在 HEAD blob 上;若要复核判据请跑上面那条命令看末行现值。（另记 `heal-worktree-tracked` 的刷新面仍在扩大，两件事互补不互替。）紧急跳过 `HUSKY_SKIP_HARDCODED_ZH_GUARD=1`
- **"已登录"不等于"可以动这条数据"(强制,2026-09-27 立,第三十二批实证)**:凡是 handler 先 `authenticate` / `checkAuth`
  再按**请求里的 id** 去读或写某条归属型记录(oauth 会话、第三方绑定、文件、卡片、成员关系……),归属条件必须落在
  **被发出的那条 SQL 上**(`and(eq(<表>.id, id), eq(<表>.userId, request.userId!))`),不得只写在调用方的 `if` 里 ——
  `if` 判得了"存在",判不了"命中";症状是别人的行被改了而响应照旧回 `deleted/updated: true`,账面一片绿。
  两条写法纪律:① 属主一律取**令牌主体**,不得取请求体/Query 里自报的 `userId`(那是"可认领他人资产"的入口);
  ② 回报集合取库侧确认集(`.returning({id})` / 裸 SQL 的 `RETURNING`),与守门 134 的计数诚实性是同一族的另一半。
  **越权用例必须断言"未发出查询"**,只断言 403/401 会放过"先改了再抛 403"与"授权判定发生在查库之后"两种写法
  (取证:`capture.whereArgs` 为空 / `whereSeen === 0`)。Python 侧同一层由守门 117 管,TS 侧此前无门 —— 本条是散文规矩,
  新增同类端点时按上面两条写并配一条"别人持有的行 ⇒ 0 命中"用例即可,不要为消红去削判据。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- **整数主键序列落后对账**(`scripts/check-sequence-lag.mjs`,**手动问责档,刻意不进提交链**;紧急跳过无需 —— 它不在钩子链上;问责入口 `pnpm check:sequence-lag`、修复 `pnpm check:sequence-lag:fix`):拦的是"**写入方把撞主键降成一条 warning**"这一型。立项实测(2026-09-27):`publish_history` / `publish_notifications` 的 id 序列落后表内 `max(id)` 9 / 14 名 ⇒ 调度器每次 INSERT 撞 `*_pkey`，而两处写入方都是 `except Exception → logger.warning`，结果是**发布审计静默丢失**(任务判 failed 而库里查不到任何历史行)，`git status` / typecheck / 其余守门全都不响。顺这条扫全库:228 张有序列表 **8 张落后**(含 `publish_tasks` 23 vs 6、`ai_model_config_models` 2228 vs 325)，已逐张只向前 `setval` 推平。判据 `next = last_value + (is_called ? 1 : 0)`，`next <= max(id)` 即落后;**修复铁律**只在落后时前推、领先一档绝不产出回拨目标(回拨等于自己制造新的撞键)、空表不判也不 `setval`。定级为手动档是设计前提:一次全库扫描约 40s，且序列属**库/机器状态**，提交者结构上满足不了 —— 挂 blocking 就是每台每次被逼 `--no-verify`、连带废掉全部守门(§12e 同型)。两条自我纠错记录值得抄:① 落地首跑本门自己漏扫(枚举把"有序列"下推到 SQL 前，JS 里先按表去重 ⇒ 第一个 int 列无序列的表整张被跳过)，报的却是"无落后"——靠**覆盖面自证**(权威候选张数 vs 实扫张数不等即喊"不闭合"、`--strict` 下 exit 2)才现形，凡全量枚举型判据都要配这一条;② 门读 gitignored `.env` 取 DSN 被守门 118 判成"按磁盘判被审内容"，处置不是跳门也不是削判据，而是按该门已登记口径把仓库锚点命名为小写并在头注写明"本门仅有的两处磁盘读是 DSN 与其指向的库实时状态"。同批把判定层收进 `apps/ai-service/app/services/publish/db_write_guard.py`(撞主键判 **error** 并给出可复制的修复命令，`scheduler._write_history` 与 `notifications` 两处共用一份判定，禁止各写各的)。取证:`--self-test` 7 条 + §22c 镜像 `scripts/tests/check-sequence-lag.test.mjs` 11 例(含"领先一档不得给 setval 目标""枚举面必须自带序列过滤""无 DSN 必须算无法判定不记通过"三条形状锁)+ Python 侧 `tests/test_db_write_guard_honesty.py` 4 例。
- **对账**:`scripts/check-cross-end-ui-parity.mjs`(guardian 第 **128** 项,blocking,紧急跳过 `HUSKY_SKIP_CROSS_END_UI_PARITY=1`)。同名配对组件的单位归一后比几何档集合(`72rpx` ≡ `36px` ≡ `36dp`;Tailwind `h-9` = 36、`text-sm` = 14),差档即点名;**棘轮锚点 = `scripts/cross-end-ui-parity-baseline.json`**(立项读数:19 对同名组件 / 18 对有差异 / 174 处差异档),所以它只拦"把两端差异加大",不会把存量债变成人人跳门(§12e)。圆角与色档**不在本门射程**(守门 77 / 93 各管一段,一台尺子只量一段)。
- **对话流挂载与契约登记对账**(guardian id 以 runner 现值为准 —— 本枚取到 **140**,blocking,`stagedTriggers=apps/web/src/components/chat/` + `packages/shared/src/sse/`,紧急跳过 `HUSKY_SKIP_V362_CONV_MOUNT=1`):`scripts/check-v3-62-conversation-mount.mjs` 是 V3 #62/#63 的常驻尺子,钉三件 —— `BusinessFormSection` 必须真被 `MessageList` 挂载(61/63 两票立项时的真实状态就是"宿主、store、渲染点全在,唯独没人挂",与守门 64/70/81/115/138 同族)、`form_request` 的契约登记段与消费点必须逐字段同形(wire 是 snake_case `request_id/reject_reason`,camel 侧同名字段由测试的双向可赋值断言锁住)、以及 `sse/contract.ts` 与 `apps/ai-service/app/core/sse_contract.py` 既有那份 parity 门不得被「顺手并进 SSE_EVENTS」绕过 —— 本票的帧**刻意单列一段**(`FORM_FRAME_EVENTS`),因为 ai-service 侧零生产点,并进去会立刻造出一台恒红门(§12e 同型),解阻前置三条已写在该段注释里。取证 `--self-test` + §22c 镜像 `scripts/tests/check-v3-62-conversation-mount.test.mjs`;口径同 70/77/83/98/103/118/138(全量判 HEAD blob、`--staged` 判索引、两旗同给判死、取不到 exit 2、枚举到 0 个候选判死)。
- **本轮三道新守门（V3 #54 / #80 / #83 的常驻尺子，guardian id 以 runner 现值为准：落地时取到 141 / 142 / 143，并发会话可能挪号；一律 blocking，紧急跳过变量见各条）**：
  ① `scripts/check-doom-loop-parity.mjs`（`HUSKY_SKIP_DOOM_LOOP_PARITY=1`，`stagedTriggers=apps/cli/src/` + `apps/ai-service/app/` + `packages/shared/src/`）—— 钉「卡死/连续失败反思」这套策略数字**只许有一份**：P1 十个策略键在 TS 共享层与 `apps/ai-service/app/core/doom_loop.py` 两侧逐项等值，P2 两侧都必须在**生产面**被消费（注释与测试面不计，即守门 64/70/81/115 那一族「造好没装车」），P3 三个动作（换策略/跳过执行/终止）各有真实分支，不得退化成装饰品。行为后果如实登记：CLI 失败反思阈值由 2 改 3、轮次签名改用键序归一的 SHA-256、ai-service 主链新增**默认开**的中断与换策略（票 #57 禁止新增默认关 env，故无开关）。
  ② `scripts/check-guardian-review-wired.mjs`（`HUSKY_SKIP_GUARDIAN_REVIEW_WIRED=1`，`stagedTriggers=apps/ai-service/app/`）—— 用户点 approve 之前由**独立**代理复核「有没有更安全等价路径」（V3 #80）。本门只判两条：复核出口必须在生产面被调用；**未复核不得被折叠成通过**（超时/无凭据/解析失败一律显式三态，与本仓「把没判写成判过了是最高频失效型」同一条禁令）。
  ③ `scripts/check-lsp-language-table.mjs`（`HUSKY_SKIP_LSP_LANGUAGE_TABLE=1`，`stagedTriggers=apps/cli/src/`）—— LSP 语言表五判据 T1–T5：表自洽、扩展名不得两属、族清单与运行时静态对账、binary 名唯一出处（`LSP_BIN` 那类写死字面量回到源码即红）、能力声明必须投影到对应语言族；**空枚举判死**（扫到 0 个候选不记绿）。配套落差引擎 `ihui spec drift` 两方向都能对真仓出结果，接手时先修掉三处失明（`extractArgUsage` 从不遮声明位导致 117/121 落未判定、`declaresReadOnly` 把「字段缺席」当反证恒 false、全局 `--json` 吞掉子命令同名档），并抓到一条真阳性：`browser_screenshot` 声明 `dangerLevel: read` 而 handler 里有 `writeFileSync/mkdirSync`（`apps/cli/src/tools/browser.ts:462/:470`）——**改它的权限档属产品决策，留给持有人，不得为让 `--strict` 变绿去削判据**。
- **本轮一并登记的两条口径更正**：`packages/shared/src/utils/doom-loop-detector.ts` 是一行 re-export **桥**而非第二份真相（`@ihui/shared` 的 exports 表没有 `./agent/*`，而 package.json 属禁改区；vite 直判 Missing specifier、CLI 相对跨包 import 触发 TS6059 rootDir，两条都实测过）。收口出口 = 给 exports 加 `./agent/*` 后删桥、消费端改子路径，并同步改 parity 门 P2 的两处子路径字符串。另：`apps/ai-service/app/services/codebase_indexer.py` 的注释仍在描述已被本轮删掉的 `_LAZY_INDEX_MAX_FILES = 2000`，该文件此刻由他人持有，**指过来即可，不要按旧注释重新引入无依据字面量**。
- **数字计数徽章强制规范(2026-09-15 立,复发根治)**:按钮/标签内嵌数字计数(如待办数、队列数)**禁止**裸 `<span class="px-1">数字</span>` 手搓 — 行高、字形侧空、位数变化都会导致数字在色块内偏移。**必须**使用确定性居中模板:`inline-flex h-4 min-w-4 items-center justify-center rounded bg-*/… px-1 text-[10px] font-semibold leading-none tabular-nums`。要点:`inline-flex + justify-center` 保证任意位数水平居中;`h-4 + leading-none + items-center` 保证垂直居中(不依赖字体行高);`min-w-4` 保证单位数不掉宽;`tabular-nums` 保证多位数字等宽不抖动。
- **崩溃上报出口脱敏对账**(guardian id 以 runner 现值为准 —— 本枚落地取到 **144**,blocking,`stagedTriggers=apps/` + `packages/`,紧急跳过 `HUSKY_SKIP_CRASH_REDACTION=1`):`scripts/check-crash-report-redaction.mjs` 是第三十五批的尺子,守的是**"链路两端必须真的调用唯一脱敏出口"**,而不是"有没有一份脱敏函数"。病灶实测:`apps/web/src/components/common/ErrorBoundary.tsx:126` 原先把 `error.message`/`error.stack` **原样** POST 到 `/api/crash-reports`,而接收端只有 zod 长度上限 + 5min 去重、**零脱敏**,且该端点按设计**匿名可上报** ⇒ 内嵌的 API key / Bearer / JWT / 用户机器绝对路径明文入 `crash_reports`(保留 90 天)并进管理面板。三条判据:**V1** 被识别为发射点(向该端点发请求)或落库点(`insert(crashReports`)的文件,其**代码面**(注释与字符串已遮 —— 注释里的提及不算装车,否则门会给自己发合格证)必须出现 `redactCrashText(`;**V2** 只要有文件引用了 `redactCrashText`,被审面上的 `redact.ts` 就必须真的 export 它(摘线 ⇒ 判"没有出路"并参与退出码);**V3** 全仓 `export function redactCrashText` 的声明处必须 ≤ 1 —— 出口被复制第二份就是"两处算同一件事必漂移"的开端,注释里的声明不计入该计数(成对自检 F14)。另有一条容易被接手者搞错的设计:**V1 的两条判据不在同一集合上判**,且 V1 在"本次没有射程内文件"时回退 HEAD 全量(文档/注册表类提交结构上不带 .ts,判它空扫就是替每一次无关提交挡路),回退只作用于 V1,V2/V3 照判。**权威防线在服务端唯一落库口** `recordCrash`,客户端那一道只是减损 —— 端点匿名可写,"客户端自觉"结构上不构成防线;顺序必须**先脱敏再截断**(反过来会把凭据切成半截、形状不再成立 ⇒ 漏盖)。规则只有一份:`packages/shared/src/utils/redact.ts` 的 `redactCrashText`,新增的是"拿不到注入值也要盖住用户目录"那一档(浏览器不知用户主目录、服务端收到的是**别人机器**的路径 ⇒ `os.homedir()` 注入式匹配在本链路永远空转,所以这一档必须是形状匹配),保守性由成对用例锁(只替换用户名段、堆栈仍可定位;`/src/pages/home/index.tsx:10:5` 不被误判、普通中文消息逐字不改坏)。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 个候选判死。**棘轮锚点 = 该文件 HEAD 自身存量**:立项现读 4 处站点,web 发射点与服务端落库口由本票收口,另两处(`apps/miniapp-taro/src/utils/crash-report.ts`、`apps/mobile-rn/index.js`)在本票文件清单外、按归属纪律**只报数不判红**(客户端那两处只影响"少把原文送出设备",数据面已由服务端兜住)—— 真仓 HEAD 全量因此 exit 0,**没有新增恒红门**。取证 `--self-test`(26 条,含"零引用而出口缺失 ⇒ V2 不判红"与"引用了且出口在位 ⇒ 绿"成对)+ §22c 镜像 `scripts/tests/check-crash-report-redaction.test.mjs`。
- ⚠️ 本条已被取代(该门现读 guardian id 见 `scripts/guardian-runner.mjs`;现行条是本节内同门最后一条),保留只为不丢行,**勿照本条派单**。 **RN 包装器双层页头对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **140**,blocking,`stagedTriggers=apps/mobile-rn/`,紧急跳过 `HUSKY_SKIP_RN_DOUBLE_HEADER=1`):`scripts/check-rn-double-header.mjs` 钉的是同一屏**两条页头** —— RN 屏包装器自渲染 `<NavBar>`,而它渲染的共享屏(`@ihui/rn-app`,含 `as` 别名)内部又自带一份页头 ⇒ 两个返回箭头 + 两个标题(用户实拍在设置页)。**导航面不背这个锅**:HEAD 面全 `apps/mobile-rn` 只有两处 `headerShown`,均为 `false`,所以判据不看导航配置。三条设计点:① **抑制通道必须从调用点实参识别** —— `hideHeader` / `renderHeader` / `nestedInScrollView` 三族,缺这一层则 8 个候选里 3 个是假阳(实测);② **`as` 别名必须解析**,否则设置页那一站直接漏判;③ **合规族反向对照** —— 同一扫描把 163 处判为合规(共享屏自带页头而 RN 屏不加 NavBar),本门不是"逢 NavBar 即红"。**修复出口只有一条**:给共享屏补抑制通道并在调用点传入;**禁止删任意一层** —— 共享页头是 163 个消费点的唯一页头,包装器 NavBar 在 Settings/Profile 上承载 SideMenu 抽屉唯一入口,而删包装器会让 loading 态失去唯一返回手段(实测 `packages/app/src/features/course-detail` 的 loading 分支只有 spinner)。行内出口 `double-header-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、枚举到 0 个判死。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(含 T7 私有索引注入的端到端双向锁:注入违规必红、只写进注释必绿、豁免出口必须真能用)。**存量按"该文件 HEAD 自身计数"套棘轮**,立项现读 5 处 / 5 文件只报数 —— 当场判红就是恒红门。**两条落地教训**:① 本门由子代理写完判据与测试后**停在轮次上限**,注册块缺失 ⇒ 镜像 T1/T2 恒红而门体自跑 exit 0,即"门存在、判据对、无人调度";② 注册落地用的是 `scripts/gate-registry-insert.mjs`(commit-tree + CAS,**不碰工作树**),而 `scripts/guardian-runner.mjs` 的工作树副本当场就落成了父提交版本(`git status` 只显 ` M`,一次不带 pathspec 的提交就会把新注册块写回旧版),由 `node scripts/heal-worktree-tracked.mjs --align-drift` 对齐 —— 旁路交付注册表之后必须复验工作树面,不能只看索引。
- ⚠️ 本条已被取代(该门现读 guardian id 见 `scripts/guardian-runner.mjs`;现行条是本节内同门最后一条),保留只为不丢行,**勿照本条派单**。 **RN 包装器双层页头对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **144**(注册时曾占 140,与并发会话撞号后按"后来者改号"挪到现值),blocking,`stagedTriggers=apps/mobile-rn/`,紧急跳过 `HUSKY_SKIP_RN_DOUBLE_HEADER=1`):`scripts/check-rn-double-header.mjs` 钉的是同一屏**两条页头** —— RN 屏包装器自渲染 `<NavBar>`,而它渲染的共享屏(`@ihui/rn-app`,含 `as` 别名)内部又自带一份页头 ⇒ 两个返回箭头 + 两个标题(用户实拍在设置页)。**导航面不背这个锅**:HEAD 面全 `apps/mobile-rn` 只有两处 `headerShown`,均为 `false`,所以判据不看导航配置。三条设计点:① **抑制通道必须从调用点实参识别** —— `hideHeader` / `renderHeader` / `nestedInScrollView` 三族,缺这一层则 8 个候选里 3 个是假阳(实测);② **`as` 别名必须解析**,否则设置页那一站直接漏判;③ **合规族反向对照** —— 同一扫描把 163 处判为合规(共享屏自带页头而 RN 屏不加 NavBar),本门不是"逢 NavBar 即红"。**修复出口只有一条**:给共享屏补抑制通道并在调用点传入;**禁止删任意一层** —— 共享页头是 163 个消费点的唯一页头,包装器 NavBar 在 Settings/Profile 上承载 SideMenu 抽屉唯一入口,而删包装器会让 loading 态失去唯一返回手段(实测 `packages/app/src/features/course-detail` 的 loading 分支只有 spinner)。行内出口 `double-header-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、枚举到 0 个判死。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(含 T7 私有索引注入的端到端双向锁:注入违规必红、只写进注释必绿、豁免出口必须真能用)。**存量按"该文件 HEAD 自身计数"套棘轮**,立项现读 5 处 / 5 文件只报数 —— 当场判红就是恒红门。**两条落地教训**:① 本门由子代理写完判据与测试后**停在轮次上限**,注册块缺失 ⇒ 镜像 T1/T2 恒红而门体自跑 exit 0,即"门存在、判据对、无人调度";② 注册落地用的是 `scripts/gate-registry-insert.mjs`(commit-tree + CAS,**不碰工作树**),而 `scripts/guardian-runner.mjs` 的工作树副本当场就落成了父提交版本(`git status` 只显 ` M`,一次不带 pathspec 的提交就会把新注册块写回旧版),由 `node scripts/heal-worktree-tracked.mjs --align-drift` 对齐 —— 旁路交付注册表之后必须复验工作树面,不能只看索引。
- **README 表格完整性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **145**,blocking,`stagedTriggers=README.md`,紧急跳过 `HUSKY_SKIP_README_TABLE_INTEGRITY=1`):`scripts/check-readme-table-integrity.mjs` 拦的是**长文被塞进 Markdown 表格单元格**这一型 —— 单元格里不能有换行,而 prettier 会把散文里的 `|` 当列分隔符重排,产出的行竖线数与本表基准不等,渲染出来是碎的(本会话一夜之间在同一张表里手工撞见三例:第 135、131 两格与 131 在叠表里的第二份副本)。分三类,**只有 T-A 判红**:**TI1** = 一个表格簇内出现 ≥2 行连续的 2 竖线行而该簇主竖线数 ≥3,它们是上一行最后一格的竖排续行(纯机械可判);**T-B** = 竖线数比基准少 1 的半截行(多为重复了首列 id),**只报数** —— 该列填什么要逐案判语义,当场判红就是一台与任何提交都无关的恒红门;**孤立 2 竖线候选**同样只报数。取材口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、**枚举到 0 个表格簇判死不记绿**;围栏(``` 与 ~~~,缩进围栏也算)与 HTML 注释内的示例表整段跳过 —— 否则会把文档里"教怎么写表"的样例判成违规。**修复出口是门自己给的、当天就在真仓跑通**:`node scripts/readme-table-unwrap.mjs --file README.md --dry-run`(默认零写盘),`--apply` 前自证"剥竖线去空白后字符多重集全等";主会话另取一把尺子独立复量过两次(735456/735456、缺 0 多 0),**工具的自证不等于它对**。行内出口 `<!-- table-cell-exempt: <原因> -->`(须带原因,已挂守门 108 的 30 天到期档)。存量走 `scripts/readme-table-integrity-baseline.json` 的**该文件 HEAD 自身**棘轮,`--update-baseline` 只减不增且必须保留非计数注记键。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像含"台账不得腐烂成第二份真相"与"摘线不得被读成已装车"的反向锁)。**一条本门立项当夜撞到的落地事故值得单独记**:注册块由 `gate-registry-insert.mjs` 落地(它只写 `guardian-runner.mjs` 一个路径),而门体脚本当时仍是**未跟踪文件** —— 于是 HEAD 里出现一条指向不存在脚本的注册,任何干净检出上那枚门都会以"脚本找不到"失败并挡住整批门;而守门 89 的 R1/R2/R4 **看不见**这一格(它的候选集按"脚本在不在被审面"枚举,脚本不在就不成候选)。⇒ **注册与脚本必须同枚入库**,且核验要跑 `git cat-file -e HEAD:<脚本路径>` 而不是只看 runner 里有没有那行。
- **README 表格完整性对账**(2026-09-27 现读更正:**尚未注册** —— `guardian-runner.mjs` 的 HEAD/索引/工作树三面均零命中,原登记的 145 号注册块已被并发提交回写掉;而现值也不可径行补注册:门体对 HEAD 面 README 判出 **61 行 TI1**(棘轮 cap=0),镜像 T1/T3/T5/T7/T8 五项各红(T1 = 注册块缺失;T3 = `table-cell-exempt` 未进 id 108 那道到期账的存活期表;T5/T7/T8 都被同一件事带崩 —— 门的 HEAD 面全量档 `exit 1`,而它**自身自检**里那条"全量档不得因存量判红"的断言正红着)。把一台判红的尺子接进提交链 = 与任何提交都无关的恒红,唯一出路是各会话跳钩子、连带其余全部对账一起作废,所以顺序只能是 ① `node scripts/readme-table-unwrap.mjs --file README.md --dry-run` 逐处复核后 `--apply`,把 61 行清偿到 0;② 把豁免族登记进 id 108 那道到期账的 30 天档并复跑其余镜像红(不得为变绿削判据);③ 用 `scripts/gate-registry-insert.mjs` 取当次空闲号补注册,并把本行的编号与定级改回实测值,紧急跳过变量名届时随条目声明为 `HUSKY_SKIP_README_TABLE_INTEGRITY`。在 ③ 之前该判据**零调度器**,下面整段是设计原文而非现状):`scripts/check-readme-table-integrity.mjs` 拦的是**长文被塞进 Markdown 表格单元格**这一型 —— 单元格里不能有换行,而 prettier 会把散文里的 `|` 当列分隔符重排,产出的行竖线数与本表基准不等,渲染出来是碎的(本会话一夜之间在同一张表里手工撞见三例:第 135、131 两格与 131 在叠表里的第二份副本)。分三类,**只有 T-A 判红**:**TI1** = 一个表格簇内出现 ≥2 行连续的 2 竖线行而该簇主竖线数 ≥3,它们是上一行最后一格的竖排续行(纯机械可判);**T-B** = 竖线数比基准少 1 的半截行(多为重复了首列 id),**只报数** —— 该列填什么要逐案判语义,当场判红就是一台与任何提交都无关的恒红门;**孤立 2 竖线候选**同样只报数。取材口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、**枚举到 0 个表格簇判死不记绿**;围栏(``` 与 ~~~,缩进围栏也算)与 HTML 注释内的示例表整段跳过 —— 否则会把文档里"教怎么写表"的样例判成违规。**修复出口是门自己给的、当天就在真仓跑通**:`node scripts/readme-table-unwrap.mjs --file README.md --dry-run`(默认零写盘),`--apply` 前自证"剥竖线去空白后字符多重集全等";主会话另取一把尺子独立复量过两次(735456/735456、缺 0 多 0),**工具的自证不等于它对**。行内出口 `<!-- table-cell-exempt: <原因> -->`(须带原因,已挂守门 108 的 30 天到期档)。存量走 `scripts/readme-table-integrity-baseline.json` 的**该文件 HEAD 自身**棘轮,`--update-baseline` 只减不增且必须保留非计数注记键。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像含"台账不得腐烂成第二份真相"与"摘线不得被读成已装车"的反向锁)。**一条本门立项当夜撞到的落地事故值得单独记**:注册块由 `gate-registry-insert.mjs` 落地(它只写 `guardian-runner.mjs` 一个路径),而门体脚本当时仍是**未跟踪文件** —— 于是 HEAD 里出现一条指向不存在脚本的注册,任何干净检出上那枚门都会以"脚本找不到"失败并挡住整批门;而守门 89 的 R1/R2/R4 **看不见**这一格(它的候选集按"脚本在不在被审面"枚举,脚本不在就不成候选)。⇒ **注册与脚本必须同枚入库**,且核验要跑 `git cat-file -e HEAD:<脚本路径>` 而不是只看 runner 里有没有那行。
- **README 表格完整性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **146**,blocking —— 这条注册块曾被一枚 union 合并整块盖掉,而文档面的声称留在原地,于是守门 89 的 R2 对**每一次碰 README 的提交**判红(恒红的唯一出路是各会话跳钩子、连带约 185 道门一起作废,§12e 同型);由 `b4905b158` 接回,判据侧的 61 行 README 存量与 `table-cell-exempt` 的到期档由批次 41 清偿,`stagedTriggers=README.md`,紧急跳过 `HUSKY_SKIP_README_TABLE_INTEGRITY=1`):`scripts/check-readme-table-integrity.mjs` 拦的是**长文被塞进 Markdown 表格单元格**这一型 —— 单元格里不能有换行,而 prettier 会把散文里的 `|` 当列分隔符重排,产出的行竖线数与本表基准不等,渲染出来是碎的(本会话一夜之间在同一张表里手工撞见三例:第 135、131 两格与 131 在叠表里的第二份副本)。分三类,**只有 T-A 判红**:**TI1** = 一个表格簇内出现 ≥2 行连续的 2 竖线行而该簇主竖线数 ≥3,它们是上一行最后一格的竖排续行(纯机械可判);**T-B** = 竖线数比基准少 1 的半截行(多为重复了首列 id),**只报数** —— 该列填什么要逐案判语义,当场判红就是一台与任何提交都无关的恒红门;**孤立 2 竖线候选**同样只报数。取材口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、**枚举到 0 个表格簇判死不记绿**;围栏(``` 与 ~~~,缩进围栏也算)与 HTML 注释内的示例表整段跳过 —— 否则会把文档里"教怎么写表"的样例判成违规。**修复出口是门自己给的、当天就在真仓跑通**:`node scripts/readme-table-unwrap.mjs --file README.md --dry-run`(默认零写盘),`--apply` 前自证"剥竖线去空白后字符多重集全等";主会话另取一把尺子独立复量过两次(735456/735456、缺 0 多 0),**工具的自证不等于它对**。行内出口 `<!-- table-cell-exempt: <原因> -->`(须带原因,已挂守门 108 的 30 天到期档)。存量走 `scripts/readme-table-integrity-baseline.json` 的**该文件 HEAD 自身**棘轮,`--update-baseline` 只减不增且必须保留非计数注记键。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(镜像含"台账不得腐烂成第二份真相"与"摘线不得被读成已装车"的反向锁)。**一条本门立项当夜撞到的落地事故值得单独记**:注册块由 `gate-registry-insert.mjs` 落地(它只写 `guardian-runner.mjs` 一个路径),而门体脚本当时仍是**未跟踪文件** —— 于是 HEAD 里出现一条指向不存在脚本的注册,任何干净检出上那枚门都会以"脚本找不到"失败并挡住整批门;而守门 89 的 R1/R2/R4 **看不见**这一格(它的候选集按"脚本在不在被审面"枚举,脚本不在就不成候选)。⇒ **注册与脚本必须同枚入库**,且核验要跑 `git cat-file -e HEAD:<脚本路径>` 而不是只看 runner 里有没有那行。
  - **重锚台账的正当性不能靠"只降不升"证明,要靠"中和本次读数改动后的旧口径"证明**(2026-09-27 O81 票⑬)。给门扩覆盖面(收样式表 / 收内联引号串 / 收简写多值)之后,读数**必然上升**,而上升里混着两种东西:本来就存在、以前看不见的真值,以及提取式过宽造出的假值。"只允许下降"的收紧脚本因此有两个对称的坑 —— 它会在真值浮出时把门钉红(而红的是别人正在合的提交,唯一结局是逼人跳门),也会在假值出现时因为"那也是下降的补集"而被绕过。正解是给重锚加**第三个量**:同一判据在**中和本次读数改动**后的旧口径值。规则 = `新读 > 锚点` 只在 `旧读 ≤ 锚点` 时允许写入,并在台账旁边写下该族旧读/新读两个数;`旧读` 也已超锚 ⇒ 那不是读数问题,拒绝重锚(本轮据此拒过一次,拒绝的那条正是"别人已推未合的端上改动",它该由对方的提交带着自己的重锚进来)。反过来,"某族因别人改动而下降"也只跟**该族自己**比,不整表替换 —— 整表重写会替别人下调他还没准备好的锚点。另两条同批立:**棘轮只拦"变更差",不解释"为什么两端不同"** —— 所以台账 `waivers` 恒空本身就是违规(AGENTS O81「静默不同形算违规」),每条差异都要写理由,且理由里必须带**跑过的现读命令**而不是转述;而镜像测试里"`waivers` 必须等于 `{}`"这类**建门当时的状态锁**会在第一次合规登记时变成"禁止合规",要改判它真正在乎的东西(每条豁免都带理由),不是退回空表。
- **模型容量/推理档位跨语言对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **147**,blocking,`stagedTriggers=packages/api-client/src/` + `apps/ai-service/app/core/` + `apps/cli/src/subagents/`,紧急跳过 `HUSKY_SKIP_MODEL_CAPACITY_PARITY=1`;问责档 `pnpm check:model-capacity`):`scripts/check-model-capacity-parity.mjs` 钉的是**同一个事实被两种语言各抄一份**这一型 —— TS 侧 `packages/api-client/src/model-context-capacity.ts` 的 `DEFAULT_CONTEXT_CAPACITY` / `EXACT_CAPACITY`(57 条,其中低于兜底的 22 条)与 Python 侧 `apps/ai-service/app/core/model_context_window.py` 的 `DEFAULT_CONTEXT_WINDOW` / `LOW_WINDOW_OVERRIDES` 必须逐项等值,推理档位三处(`reasoning_effort_pin.py` 的已知档、`subagents/precedence.ts` 的 `REASONING_EFFORTS`、`subagents/types.ts` 的 `ReasoningEffort` 联合)也必须同集合。**立项凭据不是猜的**:Python 侧源码第 25-26 行原文自述"暂无自动对账门",而两侧各自注释都写着"勿单方面改动" —— 散文约束在本仓的失效形态永远是安静。三条判据:**C1** 兜底值两侧等值(改一侧一个数即点名两侧值);**C2** 低于兜底档的模型逐条等值(只红漂移那一条,不误伤同值的 35 条);**C3** 推理档位三处同集合。**有牙证明走纯函数+构造面**(`node --test scripts/tests/check-model-capacity-parity.test.mjs` 的 T3/T4 成对):控制测量 `drifts=0` 之后才允许用"0"当结论 —— 我第一次做 A/B 时拿 `sed` 找 `128000` 注入,而源文件写的是 `128_000`(下划线数字面量),**注入根本没命中而两臂都报 0 处**,差点对着一台空转的尺子登记合格证;真注入命中的原话是 `C1 兜底窗口漂移:TS DEFAULT_CONTEXT_CAPACITY=120000 vs Python DEFAULT_CONTEXT_WINDOW=128000`。**口径同 70/77/83/98/101/103/118**:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"不回落;**默认档漂移只报数**(`--strict` 才判红)——立门前若现读有存量,当场 blocking 就是恒红门(§12e)。三态里 `patternRules` 那 31 条模糊层"Python 侧无对应声明"是**按设计走兜底 fail-visible**,只报数不进数值判据射程。行内**无豁免通道**(该对齐就对齐,不给"登记成永久惯例"的口子)。取证 `--self-test` 20 条 + §22c 镜像 8 例(含 T1"测试不得重写解析规则"与 T7"未注册时不得被读成已装车"两条反向锁)。
- **入参校验装车对账**(115):`scripts/check-tool-arg-validation-wired.mjs`(blocking,`stagedTriggers=apps/cli/src/tools/` + `packages/types/src/`,2026-09-25 立) —— 堵的是 A36 查出来的那一格:`apps/cli/src/tools/argument-validator.ts` 校验器写得挺全(field/expected/actual、嵌套 `items[0]` 路径都有),**但生产面零调用方** ⇒ 工具入参从来没被校验过,`required` 只拿去生成提示文案和 provider schema,模型少传/传错类型时由 104 枚 handler 各自兜 ⇒ 同一类错误 104 种表现。**只判两条**:① `validateToolArguments(` 必须有非测试调用方(**注释里的提及不算** —— 本仓最高频失效型就是"看起来有、其实没装车",全仓三处 `argument-validator` 的出现位置全是注释);② 模式开关必须在位、**默认 off**、`shadow` 档存在 —— `enforce` 档已于批次 49 实现(schema-aware 单次容错解析 + 按工具名计的有界 repair 窗 + 单行违规清单),**但默认档仍是 `off`**:那些 `parameters` 描述长期"从未被执行过、准确度未知",要等影子/离线台账证明描述可信才谈把默认翻成 enforce;直接打开拒绝就是运行时版恒红事故(与 §12e/守门 77/83 同一条理由)。影子档只记账:不改 `call.arguments`、不改返回值、不拦调用,遥测**不落任何入参值**(`ValidationError.actual` 在 `enum_mismatch` 分支装的就是用户原值,`expected` 装枚举表 ⇒ 双双排除)。已知覆盖面缺口如实登记:`hubEnabled && hubResolver` 分支在拿到 Tool 对象之前就 return,那条路径影子不生效。三步顺序(①影子 → ②用影子数据修描述 → ③才谈默认 enforce + 逐条回灌 `{字段路径,期望,实得}`)登记在 PROJECT_PLAN 第八波。口径同 77/83/98/103/112/113(全量判 HEAD blob、`--staged` 判索引、取不到 exit 2、枚举 0 判死);取证 `--self-test` 14 条 + §22c 镜像 5 例;紧急跳过 `HUSKY_SKIP_TOOL_ARG_VALIDATION_WIRED=1`。
- **包入口 barrel 漏 re-export 对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚取到 **149**,blocking,`stagedTriggers=packages/` + `apps/`,紧急跳过 `HUSKY_SKIP_PACKAGE_BARREL_EXPORT=1`):`scripts/check-package-barrel-export.mjs` 判的是**与守门 98 相反的那一半** —— 98 管「import 了不存在的符号」,本门管「符号存在,而包入口没把它递出来」。`packages/*/src/index.ts` 若是**显式命名清单**(本仓多数是),漏列一条就等价于端内拿到 `undefined`;而这类缺陷在本地**一路上不红**:`apps/web/next.config.ts` 设了 `typescript.ignoreBuildErrors: true`(TS2724 被吞)且打包器按 `exports → dist/index.js` 解析(实测 `dist/client.js` 命中 4 处、`dist/index.js` 0 处)⇒ 构建成功、部署成功,只有点到那个按钮的人知道。递出名单按**入口可达图**算(两层 `export *` 边、`./x.js`→`x.ts` 的 ESM 后缀回退),**值档与类型档分开**(`export type` 漏出不影响运行时 ⇒ 另档不判红;inline `type X` 含 `as` 形式必须落类型档,由 A22/A22b 成对钉住)。三态是这门的全部价值:三方整表转发(`packages/ui-react` 的 `export * from 'lucide-react'`)结构上不可枚举 ⇒ 记「未判定」并点名,**`--strict` 下有未判定即拒绝出合格证**;子路径导入(现读 798 处)不判。**修复出口只有一个**:把符号补进入口清单 —— 不得改判据、不得加豁免、不得为了「顺手收紧」去动别人的 `ignoreBuildErrors`。口径同 70/77/83/98/103/118(全量判 HEAD blob、`--staged` 判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 个包判死)。取证 `--self-test`(26 例)+ §22c 镜像 9 例(T6 端到端双向锁 / T7 不可枚举包不得被记成"通过" / T8 面旗矛盾判死 / T9 无提交不记绿)。接线经 `scripts/gate-registry-insert.mjs` **以 HEAD 为底**插入 —— 落地前实测工作树那份注册表滞后并缺别人的 148 块,按工作树提交等于替他人卸闸。
- **会话失效出口注册对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **148**,并发会话可能挪号;blocking,`stagedTriggers=apps/` + `packages/api-client/` + `scripts/`,紧急跳过 `HUSKY_SKIP_AUTH_HANDLER_PARITY=1`;问责档 `pnpm check:auth-handler-parity`):`scripts/check-auth-handler-registration-parity.mjs` 钉的是**"会话死了之后,用户有没有一条回登录的路"** —— 全仓 `setUnauthorizedHandler(` 此前**只有 web 一处注册**,而 RN 与小程序都在消费 `@ihui/api-client` 的 `fetchApi`:双 token 过期后每一屏各自显示一句从错误体取的通用文案,既没有入口也没有跳转(实测 logcat 反复打「Invalid or expired token」而屏幕上什么都没有;小程序侧更糟,`bindTokenStoreToApiClient` 没传 `refreshAccessToken` ⇒ 续期第一行就 return null,连"有机会不死"都没有)。三条判据:**AP1** 凡直连消费 `fetchApi` 的端必须有**代码面**的注册调用(注释/字符串里的名字不算装车,遮罩唯一实现 `lib/code-mask.mjs`)或持 `scripts/auth-handler-registration-exemptions.json` 里带非空 reason + 未过期 `reviewBy` 的条目;**AP2** 台账端已注册 / 指向不存在端 / 同端重复登记 ⇒ 判红(登记表腐烂比没有表更糟,它会替人做出"这一端已被想过"的判断);**AP3** 共享注册口的 export 或 `notifyUnauthorized` 调用点被摘线 ⇒ 判"失明"不记绿。**空枚举 exit 2**,不记通过;台账缺席按"零豁免"判并大声报出,只有坏 JSON 才算无法判定。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给 exit 2、内容经 `face-reader` 的 `catBatch`。取证 `--self-test`(末行现读,含真仓 HEAD 阳性对照"看不见消费端不算通过")+ §22c 镜像 `node --test scripts/tests/check-auth-handler-registration-parity.test.mjs` 8 例(含"未注册时不得被读成已装车"的方向锁与两条变异自证)。**落地时序本身就是这门的存在理由**:注册块先入库(`6160573535`)而脚本与点名行留在未跟踪面 ⇒ 干净检出上这条门指向一个不存在的脚本,而守门 89 的 R1/R2/R4 对这一格结构上失明(它的候选集按"脚本在不在被审面"枚举)。
- **角色档合规对账**(guardian **150**,blocking,`stagedTriggers=apps/ + packages/`,紧急跳过 `HUSKY_SKIP_RADIUS_ROLE_CONFORMANCE=1`):`scripts/check-radius-role-conformance.mjs` 问的是守门 77 从来没问过的那一句 —— **这一类元素取了角色表里的哪一档**。77 判"有没有绕档位表写死数字",所以两端各自规矩地引用 token 时它一路报绿(HEAD 实测扫 6389 文件 0 违规),而"卡片取了 chip 档、控件取了 card 档"这一型**两边都不判** —— 与本轮 RD/RE 互相指认是同一条缝的另一半:表立完(`RADIUS_ROLES`:tiny→xs / control→sm / chip→md / card→lg / panel→xl / hero→2xl)只有散文约束,没有尺子。类别证据三条(显式 `rnRadiusFor.<role>` 取用 / 语义类名与容器族 / 样式键名词干);**认不出类别的取用一律不计红也不计通过**:入库现读(HEAD 面)判红存量 538 个「文件×角色」键 / 842 处、已判合规 592 处、**弱证据待裁 746 处**(不判红)、**未判定 10 处**(`--strict` 下有未判定即 exit 2,拒绝出合格证)、无类别证据 7,723 处(不在射程,`--all` 逐条报名)。台账 `scripts/radius-role-conformance-baseline.json` 是**首锚**(`anchorRegression` 只放行"空→有",任何上升或整键消失都拒落盘),所以本门只拦"这次改动把角色档又配错了",不追存量;存量清零与 746 处弱证据的逐条裁决是**下一票**,不得用"放宽判据"或"给单端补数字"去凑。取材走 `scripts/lib/face-reader.mjs`(`catBatch`,全量判 HEAD blob / `--staged` 判索引 / `--worktree` 仅人工,两面旗同给判死),圆角识别与豁免语义复用 `scripts/lib/radius-tokens.mjs` + `scripts/lib/code-mask.mjs` 各那一份实现,**不得留第二份**。取证 `--self-test` 75 条(含真仓 HEAD 阳性对照:把方向形态 `rounded-t-xl` 判成"角色 card / 实际 xl / 应为 lg",以及同一形态写进注释必不命中的反向对照)+ 镜像 `node --test scripts/tests/check-radius-role-conformance.test.mjs` 14 例。**同一条缝在本票被查到两次**:方向与角形态(`rounded-t-2xl` / `rounded-tr-sm`)若不被识别,**漏读一侧的表现不是"少几个数",而是凭空造出跨端分叉** —— 小程序底部弹层写 `rounded-t-2xl`、RN 写整格 `rnRadius['2xl']`,旧尺子只读到 RN 那侧,于是门报"仅 RN 16";补认之后圆角跨端台账由 16 档降到 6 档而其余四键逐字未动(降的是被错计的名额,不是放过真实差异)。
- **斜杠命令注册到达性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **160**,并发会话可能挪号;blocking,`stagedTriggers=apps/cli/src/commands/`,紧急跳过 `HUSKY_SKIP_SLASH_COMMAND_WIRED=1`):`scripts/check-slash-command-wired.mjs` 钉的是"注册表 ↔ 分派"这一族的**到达性**,与守门 131 的"注册到达性"同形:斜杠命令的名字住在字符串里(`name:'commit'` / `case 'commit'`),所以判据面必须**剥注释、保留字符串**(连字符串一起抹 = 一个名字都读不到却一路报绿,守门 131/134 同一课),而数组括号配平与定位键另走"连字符串也抹"那一遍(否则 usage 里的 `|` 与描述里的 `name:` 会凭空多算注册名,由自检 S11/S12 钉)。**四维**:**W1** 注册名(含别名)必须有活的 `case` 或 `cmd === 'x'` 分支 —— 零容忍;**W2** 注册名多重集不得重复(名字重复 = 两个帮助文档、一个真实行为)—— 零容忍;**W4** 分派顶层必须有 `default` 兜底(未知命令要喊出来,上游那条就是不带 name 键、读 `command.name` 必抛)—— 零容忍;**W3** 分支有而注册表无(名字漂移)默认**只报数**、`--strict` 才判红:HEAD 现读 2 条(`sessions@repl.ts:1010`、`plugin@repl.ts:1319`),存量未清就 blocking 就是一台与任何提交都无关的恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e)。**解阻前置**(写进门体头注):逐条定性那两条(补进 `SLASH_COMMANDS` 或删分支)后,把 W3 一并翻成 blocking。三态不并桶:数组字面量解析不到 ⇒ exit 2、枚举到 0 个注册名或 0 个分支 ⇒ **判死不记绿**;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(镜像 T3 反向锁死不得留第二份)。取证 `--self-test` 23 例(含"注释里的 case 不洗白 W1"与"散文里的 case 不造违规"成对)+ §22c 镜像 10 例(含 T5 的 `cmd === 'x'` 档位存在性证明 —— 现读全仓 0 处,该档只能由夹具证明有牙,不得因"现读没命中"就当已覆盖)。
- **入站契约 .strict() 到达性对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **161**,并发会话可能挪号;blocking,`stagedTriggers=apps/api/src/ + apps/cli/src/ + apps/extension/ + apps/desktop/`,紧急跳过 `HUSKY_SKIP_INBOUND_SCHEMA_STRICT=1`):`scripts/check-inbound-schema-strict.mjs` 钉的是"**客户端自报的字段被静默接受或静默剥离**"这一型 —— 全仓 `z.object(` 默认就是"接受未知字段",而契约一旦静默接受,写侧与读侧对"这条请求到底带了什么"各说各话(与守门 134 的"计数要取库确认集"、§5 的"身份不得从请求体自报"是同一条纪律的另一半)。判据**四态**且不得并桶:已表态(`.strict()`/`z.strictObject`)/ 显式剥离(`.passthrough()` 等 = **待偿**,点名不放过)/ 裸入站 / **未判定**(没有校验点、接收者解析不到链 ⇒ 530 处,`--strict` 下有未判定即 **exit 2 拒绝出合格证**)。方向必须由**校验点**给(`X.safeParse(arg)` / 链内 `.parse(arg)`),再沿"包含边 + 同名词引用边"BFS 传给字段级契约;分不清方向的落未判定,**不得把"看不见"写成"确信没有"也不得写成"已表态"**。遮噪两遍方向不同且都有夹具:判据面"剥注释、保留字符串"(契约就住在字符串邻行),定位键与括号配平走"字符串也抹"那一遍 —— 同族教训见守门 131/134/160。**棘轮锚点是两维**(该文件 HEAD 自身的契约份数 + 入站消费点数):只数份数会让"复用同一裸 schema 再加一条路由"净零逃逸(守门 134 扩布尔档键同一课)。默认档**只拦新增**(HEAD 现读裸入站 2342 处只报数),`--strict` 才逐格问责 —— 存量未清就 blocking 就是恒红门,唯一结局是各会话跳门连带全部守门作废(§12e)。行内出口 `inbound-strict-exempt: <原因>`(**须带原因**,已挂守门 108 的 30 天存活期档 —— 它是待偿契约债,不是结构性定性,不得只出生不死亡)。取证 `--self-test` 26 例 + §22c 镜像 10 例(T8 真取 zod 证明"未知字段必拒"、T9 四臂含"注入必红/补 strict 不越锚点/仅加第二消费点仍红";T1 是**方向锁**:未注册进提交链时头注必须自称"尚未接线",自称已接线即红 —— 与守门 89 的 R4 同一格)。
  - **它的豁免族已进守门 108 的存活期表(2026-09-27 补,`936b4f54cd`)**:`radius-role-exempt` 取 **60 天** —— 是**待偿的配档债**(出路只有两条:把该类别补进 `RADIUS_ROLES` 并同笔给出消费方,或把这一处改回角色表那一档),不是 `back-label-exempt` 那种结构性定性(那种取 365);比 `glyph-arrow-exempt` 的 30 天长,因为改档同时动两端观感、需跨端回归。**为什么单独记这一格**:一条没有到期档的豁免出口 = 无人看管的出口,而守门 108 在"未登记族"清单里点名它时账面仍是绿的(只报数),所以"注册族"这个动作必须由代码同笔完成,不能等下一次有人翻到。
- **ai-service 身份消费对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **152**,blocking,`stagedTriggers=apps/ai-service/`,紧急跳过 `HUSKY_SKIP_PRINCIPAL_CONSUMED=1`):`scripts/check-principal-consumed.mjs` 守的是**"端点取到已验证身份却一次都没用它"**(认证≠授权)这一型 —— 它没有编译期症状、typecheck/lint/其余门全绿,只有"别人机器上的一次 401 缺失"才看得见。**判据只有一份**,住在 `apps/ai-service/scripts/audit_principal_consumed.py`(ast 两条:签名带身份依赖默认值而整函数体零引用该参数 ⇒ 未消费;体内读 `X.state.user_id` / getattr 同形 / 把 `request` 一跳委托给"自己读 state.user_id"的仓内 helper ⇒ 判已消费),本门**不重写判据、不建第二份身份依赖名字表**(反向锁由镜像测试钉死),只做三件事:按被审面物化内容 → 派生那把 python 尺子 → 消费其 `--json`。**三条设计前提**:① 机器态不判红(拿不到解释器/尺子没产出结论 ⇒ 未判定 exit 0,末行如实,不出具"全部已判"合格证;被审**内容**取材不到/枚举 0 个 ⇒ exit 2 判死);② 两面取材走 `scripts/lib/face-reader.mjs` 的 `catBatch`(全量判 HEAD blob、`--staged` 判索引、`--worktree` 仅人工,两面旗同给 exit 2),尺子脚本本体也取被审面上的那一份(判据与被判内容同面演进);③ **棘轮锚点 = 该文件在锚点面自身的存量**(HEAD 档比 HEAD^、`--staged` 档比 HEAD),不是手工白名单也不是常量 0,只拦"本次带进来的新账"与"豁免清单腐烂"。**升 blocking 的三条前置(G-261)全部满足后才接线**:存量 18→0(判红 0 / 带理由豁免 7 / 未判定 0 / 清单腐烂 0,现读 `--json` 为准、不照本文数字派单)、第二判据已落地(否则 hooks 那 11 条委托形态全成假阳)、接线与脚本+点名同枚入库(README 表格门那一课)。修复出口只有两条且**禁止为消红削判据**:端点真的消费身份(`require_request_user_id` / `owner_scoped_allows` / `_owner_filter` 一跳委托),或在尺子内 `PRINCIPAL_EXEMPTIONS` 逐条登记 file+func+理由(键不含行号、无理由直接抛错、禁止按目录整片放行)。取证:python 判据测试 `apps/ai-service/tests/test_audit_principal_consumed.py`(19 例,每条正例配反例,含"打日志不算消费仍判红"与"一跳委托不判红"的变异对照)+ 镜像 `node --test scripts/tests/check-principal-consumed.test.mjs`(接线锁/取材面锁/夹具端到端有牙证明)。
- **RD 与 RE 是两把不同的尺子,读数不得互相顶结论(2026-09-27 O81 票⑮)**:门 128 的**RD**(`radiusCounts`)量的是一族两端**文件级**圆角档集合之差,它**不证明**"同一个界面元素取了不同档";**RE**(`elementRadiusCounts`)按**元素名**配对(RN 的 StyleSheet 键 / 小程序的类名,含该组件自己 import 的样式表),只有它才判"同元素两端不同形"。三维锚点 `counts` / `radiusCounts` / `elementRadiusCounts` **各自独立**、RE 恒写含 0(缺键即锚点 0 ⇒ 存量会被判成新增),任何一维的下调不得替另一维的上升顶名额。**两条射程边界必须跟读数一起说**:① "RE 现读同名分叉 0 族"**不得读成"两端已同值"** —— 两端元素命名语言不同(RN camelCase 键 vs 小程序 kebab 类名,且大量取用写在 utility 串里没有元素名可归属),RE 今天在真仓上**没有配对面**;要让它有用,前置是统一两端元素命名或逐条登记 `aliases`。② **配对面本身还缺一格**:`packages/app/src/features/**` 不在配对源里,而 `packages/app/src/index.ts:218` 的 `UserInfoCard` 出口指向的正是 `features/cards/UserInfoCard.tsx` —— 同族第三份活实现改了像素**不移动任何读数**;本轮已按角色档把它对齐(card=lg / control=sm),但**判据缺口另计一票**。同族第四份 `packages/app/src/components/UserInfoCard.tsx` 全仓零深导入(实测 `git grep "app/src/components/UserInfoCard"` 只在注释里出现),属 DOM 死副本,**不删**(§7 删除安全;且"有人 import"弱于"在屏幕上渲染",删它属该包持有者的裁决)。
  - **上面那句"活进程的锁绝不会被抢占"自 2026-09-27 起要带一个例外读,而且它与代码本来就不一致(已登记,未代改)**:① 例外 = 进程身份三元组确证复用(见下一条);② 既有事实 = `acquire` 的真实条件是"名义存活 且 锁龄超 `staleMs`/`hardStaleMs` 也抢",活着只被用来构造"极可能已被复用"这句措辞,真正兜住长流程的是**心跳续 `ts`** —— 这一处文档与代码的分歧由锁的持有人定夺(改严会把活人的长流程暴露在抢占下),`scripts/git-lock.mjs` 头注与 `acquire` 注释两处都已就地登记,镜像测试 ⑤ 把原判据的形状锁着不让顺手改。
  - **判活再加一环:进程身份三元组(pid + pidStart + host)。**裸 pid 回答不了"这个 pid 还是当初那个进程吗",所以 `scripts/lib/proc-identity.mjs` 把锁里记的启动时间与现测值对账,结论只有三态:`match`(同一个人,继续等)/ `mismatch`(确证这个 pid 已被系统复用,不是持锁过程 ⇒ 允许立即抢占,不必等 300s/1800s 的年龄阈值)/ `unverifiable`(锁里没记、量不到、别机持的锁、PowerShell 不可达 ⇒ **维持改动前的判据,不据此抢**)。现测只在"原判据要我等"那一格发生、同一把锁至多一次(带 5s 缓存,单次实测 211ms),`heartbeat` 与 `check` 都不派生(它们是快路径,派生进去等于把 PowerShell 放进每 300ms 的轮询)。失效方向刻意是"少抢一把":抢错的代价是并发写坏 `.git`,少抢只是多等一轮。**旧 `meta` 没有身份两元时整键不写 ⇒ 落盘字节与改动前逐字同形**,老锁照常被等待与清理。同一型接线在部署锁见下方 G-193 条,两处共用这一份实现,不得各写一遍。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧四个 npm 生命周期(`predev`/`dev:clean`/`dev:stable`/`prebuild`)仍无 owner 可声明、因而身份两元对它们无从对账(上面"如实登记未完成的部分"那条继续成立)。
      - **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **引擎 JSON-RPC 通道的同一族规矩(2026-09-27 第五十八批落地)**:调用方身份**只能取承载层绑定的 `params.userId`**(`apps/ai-service/app/routers/engine.py::_bind_principal` 是唯一注入点),**禁止从被点名的那条线程取 `user_id` 当 principal** —— 那是"拿受害者当授权凭据",判据会退化成"受害者 vs 受害者自己"。属主比对只许经由咽喉点 `_require_thread` + 唯一谓词 `_principal_allows`;**结算/带外回传通道不得另建第二份比对**(带外回传"不经过咽喉点"是漏洞而不是绕行理由 —— 本仓门 36 头注曾用同型措辞把这件事写成设计)。三条不可漂的口径:① 越权与"资源不存在"**同码同消息形状**,不给存在性 oracle;② 属主闸排在"已关闭/状态类"判定**之前**,否则状态码差异本身就在泄露"这条 id 是真的";③ **未鉴权通道(principal=None)维持改动前行为,并在代码里写明这是回退、不是授权结论**(否则非 dev 路径跟着静默放行)。存量已归零 ⇒ 这一族不需要基线清单,新引入的同类写法直接红。**越权用例的断言口径同上一条**:`applied:false` 不够,必须同时断言"那条审批的 future 从未被结算、持久授权从未被授予"。
  - **同轮登记的两格未修(附归属,不由实现票自行裁决)**:① `thread/metadata` 处理器以 `merge=False` 整写 metadata,会把持久化的 `userId`/`roleId` 冲掉 ⇒ 进程重启后该线程变无凭据、②式比对空转,反向 patch 还能伪造恢复出的属主;唯一修法(把这两个键并入 merge 白名单)会撞 batch-46 钉死的"持久 metadata 恰等于客户端 KV"精确等值契约 —— **两条契约谁让步属批次决策**,该代理按纪律已把自己那次改动撤回原状,现在这一格无判据。② 只读面(`thread.search` / `thread.items.list` / `thread.turns.list` / `thread.read`)与 `thread.delete` / `thread.archive` 仍按裸 threadId 跨用户工作,HTTP `GET /api/sessions/threads` 也一样(它手里就有 `get_current_user_id`,却不传给已支持的 `owner_user_id`)—— 收窄会改变前端可见列表,属对外语义决策,需产品拍板后另计一票。
- **取证包装器 `scripts/run-evidence.mjs`(常驻出口,不是守门、不在提交链)** —— 把**「跑失败」与「根本没跑到」变成机器可分辨的两件事**。用法 `node scripts/run-evidence.mjs <证据文件> [--timeout=ms] [--label=…] -- <命令 …>`,读侧 `--verify <证据文件>`。它把 stdout/stderr **直接接进同一个文件句柄**(不经 shell、不经管道),跑完在末行写 `#EVIDENCE-RC=<码>`;`--verify` 判三态:`complete`(给真实 RC)/ `truncated`(无 RC 行 ⇒ **exit 3**)/ `killed`(被信号终止且留痕 ⇒ exit 3)。**为什么必须有它(2026-09-27 一天内三次自伤,全是同一结构)**:① `… | tail -3; echo $?` 拿到的是 `tail` 的退出码,我据此说过"两道门已消红",而那两个 0 与门毫无关系;② `timeout 200 node scripts/git-push-converge.mjs | tail -5` 把工具输出**掐成一行**且整链 exit 0,我当场读成"权威核验入口只打一行就成功 ⇒ §22 双备份在静默失效",差一步就把这条假 P0 登记进台账 —— 不带管道重跑就是完整三行 + `BEHIND`;③ 复现某道门时**少传 runner 会传的 `--blocking`**,真红被读成绿,错误归因进了台账(后另起一条明文推翻)。三条的共同点是**证据里没有"我跑完了"的记号**,于是截断/被杀/参数错全都伪装成"跑过且没问题"。**退出码语义**:0=被包装命令成功;1=其真实失败;3=INCOMPLETE(结论无效,必须重跑而不是下判断);**75 原样传播**(本仓 push guard 的"中断重试"链依赖它,不得收敛成 1)。**一条与既有规矩的接合**:本仓早有"取不到判'无法判定'、绝不记绿"(守门 94/103/118 等多处),但那只覆盖**门自己的输出面**;agent 在终端里手跑判据时的那次取证**从来没有载体能区分"没跑完"** —— 本工具补的就是这一格,所以它是**出口不是判据**。**命名纪律**:它不以 `check|scan|guard` 开头 ⇒ 守门 89 结构上看不见它(同 `scripts/c-disk-breakdown.mjs` 那一格),不变量由 `--self-test` **17 条**(含 T14 真把包装器 SIGKILL 掉、证明被杀那份**没有** RC 行、判非 complete)+ §22c 镜像 8 例钉住(T5 反向锁:它被误接进 `guardian-runner` 即红 —— 每次提交都被一台取证包装器判红就是恒红门);镜像 T7/T8 另证"被包装命令的真实 RC 会留在证据里"与"自检跑完不得留残留取证件"。**规矩:此后凡要把判据结论写进台账/交付报告,取证一律经本工具落件并以 `--verify` 读回;拿不到 `complete` 就写"未判定",不得写"通过"也不得写"失败"。**
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\`` 式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟 `#` 像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人 `--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged` 判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证 `--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace` 返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒ `--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)` 也是写;此前判据只把 `.delete()/.update()` 当写链,裸 SQL 只当"证据"(认里面的 `RETURNING` 字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由 `findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- **同词不同义的两张名单不得并置在同一个 barrel(强制,2026-09-27 D113 票⑲ 立)**:共享包里"名字像同一件事、语义是两件事"的两个导出合并进同一 `export *` 面时,tsc **只在两份同时被导出时**报 TS2308,而"消费方拿到哪一份"是运行时问题。实测形态:`packages/shared/src/chat/task-status.ts` 的 `FILE_WRITE_TOOLS` 是"这次调用算不算改了文件"的**识别**白名单(`ReadonlySet`,web `tool-call-card.tsx` 按 `.has()` 消费),与新增的"这句话要不要把写类工具交给模型"的**能力**清单同词不同义 —— 后者若同名并进 `chat/index.ts`,出口就变成数组,而类型层不会在调用点红给你看。三条规矩:① 新策略模块**只走子路径**(`@ihui/shared/<dir>/<file>`),② 名单名带语义限定词(`FILE_*_INTENT_TOOLS`),③ 在 barrel 里留一句"为什么它不在这里" —— 否则下一个接手的人会把它当"漏加的出口"补回去。反向锁已入库:`packages/shared/src/chat/__tests__/file-tool-intent.test.ts` 同时判"barrel 里不得出现该 export"与"task-status 那份必须在"(只判一边等于允许整块被摘线)。
- **经对象空间通道"往上落"时,声明路径的底稿必须取自被审面,不得取自磁盘副本(2026-09-28 立,主会话自己撞上的一型)**:`scripts/object-space-land.mjs` 的 CAS 循环、"零损失"结构等值断言、`git show --name-only` 回读,**保护的都是"别人在索引与树里的行"**;而对我自己声明的那几个路径,它按**工作树字节**取版本。于是磁盘副本滞后时,一次只打算"加 4 行"的提交会顺带把并行会话当天按守门 KR 合法删除的 25 行写回、并删掉 HEAD 里 3 行活内容 —— `git status`、diff 行数、typecheck、链上 181 道门全部报绿(实测 `c99b163ee5`,由另一路会话在收它自己的孤儿键时反手指认,不是任何一道门抓到的)。**"整份 blob 逐字节等于某祖先版本才算陈旧"那一判据(守门 84 / `heal-worktree-tracked` 用的就是它)对这一型结构上失明**,因为落地内容是 `旧副本 ⊕ 我的新行`,不等于任何历史版本;可判的形态是**行级复归** —— 某行 ① base 面没有 ② 要落的内容里有 ③ 该路径某祖先里有(真新增的行第三条不成立,所以这条判据不会把正当编辑判红)。**两条落地纪律**:① 改代码文件先 `git checkout HEAD -- <path>`(动之前把磁盘那份留底到 `.ihui-agent/tmp/`)再在 HEAD 现值上编辑,或改用 `live-doc-edit.mjs` 的整行改写档 —— 它本来就从被审面取底稿,这也是活文档必须走它而不是走 pathspec 的又一个理由;② 落地后一律 `git show --numstat <新提交>^..<新提交>` 逐枚复验,出现意料之外的删除行就当场回补(§12 那句"提交前的报告有时效边界"在这里同样成立)。**修复那枚提交的形态值得照抄**:做成**纯删除、零新增**(`24446960ce`,224 行删除 / 0 行新增)—— 零新增在结构上就不可能再把任何人的行写回旧态,而复验只需要看 numstat 一眼。**同批又踩到两格,都归进这一条**:① **"读的时候干净"不等于"落的时候干净"** —— 我在 04:36 确认五个 web 语言包的磁盘副本与 HEAD 同形并据此删了一行,而 04:38:37 一枚并发提交给同一批包各加了 15 行;若不重取底稿就直接交磁盘字节,那 15 行会被写回旧态,而 `git status` 在我读取的那一刻报的是**干净**。⇒ **底稿必须在落地那一刻现取被审面**(本仓出口:`object-space-land.mjs` 的 `LAND_BLOBS` + `LAND_BLOB_PROOF`,内容 = `git show HEAD:<path>` 逐行 ⊖/⊕ 本票行,不读盘),落地后一律 `git diff <新提交>^..<新提交> --numstat` 复验"净效应是否恰为我声明的那几行"。② **`execFileSync(git show …).trim()` 会吃掉 blob 的末尾换行** —— 一个 `String().trim()` 把六个文件各制造一行伪改动(`1 insert / 2 delete`),而这类扰动在 JSON/Markdown 上完全无声;⇒ **凡"从 git 取 blob 再落盘",全程走 Buffer 原字节**,只在明确判过末字节不是 `0x0A` 时才补那一个字节。
- **临时夹具落点两把只读尺子**(2026-09-28 立,`scripts/check-scratch-root-no-nesting.mjs` / `scripts/check-fixture-tmpdir.mjs`;定级 **warn**,判据输入全部由被审面自身推导,无豁免清单):前者问"scratch 根内是否又长出一个 scratch 根"——`scripts/lib/scratch-dir.mjs` 的仓库根由脚本自身位置推导,被 `copyScriptWithClosure` 拷进演练仓后算出的 driveRoot 成了夹具自己的盘根,于是夹具里长出二阶树(§26 那条"junction 穿透"不同源但同型);枚举**有界**(深度/预算)且**不穿重解析点** —— 递归删一条没判重解析点的路径就等于清空别处真实数据。三态 `ok / drift / undetermined`,目录不存在或量不到一律报名字与原因,**绝不记为通过**;它**只报不删**,清理属 §7 删除安全。后者问"§26 写的『临时夹具唯一落点 = scratch-dir』有没有真被遵守":默认档逐条报名(现读 59 处 / 26 文件),`--strict` 才判红,射程为空如实回退 HEAD。**两条都是 warn 的设计前提**:它们判的是本机目录形态与测试面写法存量,提交者结构上无法当场满足 —— 挂 blocking 就是每台每次被逼 `--no-verify`,一次绕过等于该枚提交上全部守门作废(§12e)。修复动作:落点改走 `mkScratch`/`rmScratch`,不是放宽判据、也不是把报名改成静默。
- **禁止创建乱七八糟分支,所有改动统一往 main 合并**。除 main 之外**不允许**新建任何本地/远程分支(`feat/*` / `fix/*` / `hotfix/*` / `add-*` / `rescue/*` / 自定义前缀全部禁止)。
  - `scripts/check-single-branch.mjs`:检测 `git branch -a` 列表中除 main / upstream 外的分支,发现任意 1 个 → exit 1 阻塞 commit。
  - 豁免:§8 goal 模式临时分支(必须带 `goal/` 前缀,且在 `.ihui-agent/goal-runtime/STATE.md` 标注 `active` 状态才算合法)。
  - ⚠️ **由此推翻 §27 的一个前提**:在这一台机上 `powershell.exe` 与 `pwsh.exe` **是同一个引擎(7.6.2)**,所以"必须用 pwsh 才安全"在这里不是引擎差异而是路径习惯。**别再按 5.1 的假设写防御代码** —— 本票就先写了一段 `ErrorActionPreference='Continue'` 包裹(防"Stop + 原生 `2>&1` 把 stderr 升成终止错误"),实测两引擎都不抛、`$LASTEXITCODE` 完好,遂删。凡"某个 PowerShell 版本会怎样"的断言,一律当场跑一次再说。
- **静默失效双探测(2026-09-28 立;warn / 手动问责,刻意不进提交链,因此**无**紧急跳过变量)**:`scripts/check-service-binary-paths.mjs`(问责入口 `pnpm check:service-paths`)判 Windows 服务三件事,缺一即瞎 —— `sc query` 的 STATE、`nssm get <svc> Application` 指向的文件是否真存在、服务自身配置里声明的端口是否真应答;其中"取不到值"与"值为空"**以 nssm 退出码分岔**(实测其 stdout 是 UTF-16LE 无 BOM,NUL 占比 0.5),绝不并桶。立因是实测事故:`IHUI-RSSHUB` 因 `.workbuddy` 自升级把 `node\versions\22.22.2-2` 换成 `-3` 而**静默停 3 天**,183 道门禁无一看见、Alertmanager 当时 0 条告警。`scripts/check-public-path-probe.mjs`(问责入口 `pnpm check:public-path`,并由 `scripts/git-guardian.mjs` 在 `!CHECK_ONLY` 分支每 30 分钟探测一次)把**换流窗口(outage 口径,锚 `deploy-loop.log` 的 ASCII 签名)**与**常态公网(ratio 口径 + 本地控制组)**分成两条互不顶账的序列 —— 一条掩盖另一条就等于没量;取证一律经 `scripts/run-evidence.mjs` 封缄,一份"0 次失败"而没跑完的证据判 `truncated`/exit 3,**不成为合格证**。两台都判**机器运行时状态**,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带全部守门作废(AGENTS §12e 同型),所以定级 warn/告警,且不新建计划任务。
- **主机时区漂移对账**(2026-09-30 立;warn + 手动问责 `pnpm check:host-timezone`,**刻意不入提交链**,因此无紧急跳过变量):`scripts/check-host-timezone.mjs` 与声明表 `config/host-timezone.json` 对账主机时区,四维三态不并桶 —— H1 区名(注册表 ∪ tzutil ∪ 新起 node 的 IANA,三通道互证)、H2 变更归属(System 日志 Kernel-General/ID1 的时刻与**发起进程**,取结构化 Properties 的 FILETIME 不解析本地化消息)、H3 落地(活日志最新戳 vs **注册表偏移**之差 ⇒ 判"常驻进程仍按旧区算时间",基准绝不能用跑判据进程自己的缓存区,那是循环论证)、H4 钟本身(NTP)。立因不是假想:**2026-09-04 本机时区被一次未登记的改动从东八区改成 UTC,25 天无人发现**(最后靠事件日志才指认出是 `pwsh.exe` 干的),期间 ① `alipay.ts`/`payment-gateway.ts`/`reconciliation-service.ts` 按主机本地或 UTC 算网关要求的 GMT+8 `bill_date`/签名 ⇒ 支付宝对账单在"北京 00:00–08:00 发起"的调用请求到前天的账单;② `anomaly-detector.ts` 的"凌晨 0–5 点"把**北京 08:00–13:00 高峰正常活动**打 70 分,真凌晨行为看不见;③ `ihui-pg-backup-scheduler.ps1:44` 用 `Get-Date` 算"下一次 03:00" ⇒ 114MB 全库导出落在上午 11:00;④ `agent-automation-scheduler.ts` 无视 `user_automations.timezone` 列按主机区算 next-run。**通用规矩:凡被代码当前提的机器事实(时区/盘符/端口/解释器身份/服务承载体),必须有一把尺子现问它是什么并与一张声明表对账;散文里写"本机是 UTC,读时间戳请换算"不是防护,是替坏状态背书**(§5b 六类现取、守门 92 替不存在的计划任务背书同族)。**改主机时区的配套义务**:结论只能从戳里量,不得从"哪个运行时缓存时区"的先验推 —— 本机实测两种形态同时存在:Node/ICU 进程(IHUI-API / IHUI-AI-SERVICE)改完区后日志仍打旧时刻 ⇒ **必须重启**;而 09-26 起未重启的 IHUI-DEPLOYLOOP(PowerShell 7 / .NET)当日日志内层戳自行写成 `[02:51:33 +08:00]` ⇒ .NET 会随系统变更刷新,**重启是多余动作**。所以处置顺序是"跑 H3 逐条量 → 红了才重启那一个服务",**不是整片重启常驻进程**(PG 自钉 Asia/Shanghai、Prometheus/Grafana/Loki 内部按 UTC 算,重启只制造监控空窗);改完不复核就等于留一半进程旧区,比原状更糟 —— H3 就是量这一格。无偏移日志戳的归属区只能取自声明表(`logStampConsumers.naiveStampDefault`),`alert-volume-report.mjs` 旧写法把 tz-less 默认成 `+00:00` 已改;取不到声明 ⇒ 落 null 不猜。取证 `--self-test` 36 条 + §22c 镜像 13 例(T2 反向锁"被接进提交链必读红"、T5 指认发起进程、T6 空事件不得读成没人动过、T7 基准不得取自跑判据的进程、T11 偏移取负、**T12 三条变异各摘一种戳形态必须逐条翻红**、T13 混合形态真日志端到端不判红)。**这一把尺子自己产出过一封假信**(2026-09-30 04:01):H3 原只认「完整日期」与 `[HH:MM:SS]` 两种戳,而 `monitor.log` 行首是**裸 `HH:MM:SS`** ⇒ 反向扫描跳过全部真实最新行、拿 2.24 小时前的旧行当"最新戳" ⇒ 判 stale-cache 并派发"本机时钟与声明不符"。⇒ **通用规矩:判据失效不只有"安静"一种表现,"吵闹但说错话"同样致命,而且它还会占用人的注意力去查一个不存在的漂移**;凡"从日志里取最新一条"的判据,必须把**该日志实际在用的每一种书写形态**都列入识别集,并由形状锁(T12)钉住"少任何一种即红"。真·旧缓存(戳落后 8 小时)仍判红,放宽形态没有改弱判据。
- **教育欠费口径单一出口对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **165**,blocking,`stagedTriggers=apps/api/src/`,紧急跳过 `HUSKY_SKIP_EDU_ARREARS_SINGLE_SOURCE=1`):`scripts/check-edu-arrears-single-source.mjs` 钉两件事 —— **AR1** 欠费算式只许住在账目出口 `apps/api/src/services/edu-ledger.ts` 里,别处再写一遍 `eduEnrollment.totalFee` 与 `eduEnrollment.paidAmount` 之间的减法/比较即红;**AR2** `paid_amount` / `next_due_date` 是派生缓存,`update(eduEnrollment)` 的 set 里裸写它们即红。算式收口成出口导出的 `arrearsSqlExpr()` 与 `hasArrearsCond()` 两个构造函数,所以名单分页与定时扫描既保住 SQL 性能、又不留第二份文本(修的是"8 处各写一遍、其中一处漏下限 0 于是超缴被算成负欠费、汇总被冲小"那一型)。**判据面只遮注释、保留字符串** —— 列引用住在 SQL 模板的内插里,连字符串一起抹就等于对本型失明(守门 134/135 同一课)。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged` 判索引、两旗同给 exit 2、取不到不回落、**枚举到 0 个射程文件判死不记绿**);棘轮锚点 = 该文件 HEAD 自身存量,所以"存量非零"也不会被算成本次的红 —— 这正是它可以直挂 blocking 的前置条件(先现读真仓 exit 0 再接线,不是先接线再放宽)。取证 `--self-test` 18 条(构造面成对正反例,含"注释里的同类描述不得红""其它表同名派生列不在射程"两条反向锁)+ §22c 镜像 7 例 —— 其中**阳性对照钉出处 ref 而不是钉 HEAD**:把重构前那枚父提交的 `edu-ai-management.ts` 喂同一判据必须命中,因为账还清那天"HEAD 上还能量到存量"这句话当场失效,而判据失效的表现永远是安静。行内出口 `arrears-single-source-exempt: <原因>`(须带原因、只救本行或紧邻上一纯注释行,已同笔登记进守门 108 的 30 天存活期档 —— 没有到期档的豁免出口等于无人看管的出口)。**两条本门第一次自跑抓到的自身缺陷值得照抄**:① 块注释里逐字写出注释闭合序列 ⇒ 注释提前结束、词法一路漂到三十行外的反引号才炸(报错误导成"那行代码有问题");② `gitRaw` 的真实签名是 `(args, root, opts)`,按 `(root, args)` 写会让枚举返回 undefined ⇒ 扫到 0 个文件,而"0 个"读起来与"都没违规"一模一样 —— 全靠"空枚举判死"这条才没让它伪装成绿。
  - **C4 容器包含关系(2026-09-28 票㉖)**:类别证据除名字/键名/显式 `rnRadiusFor.*` 外还要看**这个元素装在谁里面** —— 唯一出口 `scripts/lib/jsx-scope.mjs`(结构扫描 JSX 元素树,给每个元素算祖先链;解析不出 fail-closed 报名),分类住在 `radius-roles.classifySurfaces()`:自称 card 而最近带类别的祖先是模态载体 ⇒ 按 panel 判,**只在恰好一个 card 信号时改判**,同名既当过面又当过卡 ⇒ `contested` 只报名不改判,`z-popover` 这类层叠档名不算身份。同批把注释/字符串区间收进 `code-mask.maskedSpans()` 唯一实现(`maskCommentsAndStrings` 与 `radius-roles.maskFaces` 都由它投影,改建判据前用 400 个真仓 HEAD 文件做逐字节 A/B)。台账现值一律跑 `node scripts/check-radius-role-conformance.mjs --json` 现读(票㉖ 把 991 处存量清到 0 并把 538 键重锚为全 0);**真仓阳性对照钉在清偿前的出处 ref 而不是 HEAD** —— 账还完那天"HEAD 上还能量到"这条前提当场失效,由镜像 T18 钉死,判据自身不得跟着账一起消失。
- **自动归档**:`scripts/archive-completed-tasks.mjs` 扫描**已完成**条目(阈值以脚本缺省为准:2026-09-28 起缺省 0 = '完成即归档',这是用户当天的决定,不是缺陷),post-commit 钩子自动 `--auto-commit`。归档 commit 设 `IHUI_ARCHIVE_COMMIT=1` 防递归。
  - **大批量阀门只挡自动档,但"全批或不动"是错的默认**(2026-09-28 改):旧写法在积压 >25 条或 >256 KB 时整批拒绝,于是超阈值之后**每次自动档重算同一批再拒绝** —— 实测 HEAD 面积压 3 条 / 592,001 B,归档机制对存量结构性失效而账面一路绿(参照 §5c 水印门"单次缺口 >200 拒绝自动回写"同一条设计,但那条阀只能对**一次动作**生效,不能当常态)。现 `selectWithinBudget` 按**最旧优先贪心取前缀**(单批仍不超 25 段 / 256 KB,余量点名并报数、下一轮继续),只有"最旧一条自身就超预算"才回到拒绝路径且不拆条目搬;退出码仍 0(自动档少做一件事不是错误,不得把钩子链弄红),`--allow-mass` 只放宽字节预算这一件事。
- **规矩**:`apps/api` 里任何"因为这是内部调用,所以跳过某道校验"的分支,**判据必须是验证过凭据的布尔,不得是"某个凭据头在不在"**。实测病灶(2026-09-28 由票#23 收口):`plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return` —— 任何客户端带一个同名头(值随便填)即可对**全部路由**拿到 CSRF 豁免;而该豁免与被它跳过的动作之间鉴权强度不对齐,正是"认证不等于授权"那一型。
- **唯一出口**:`apps/api/src/utils/internal-principal.ts` 的 `isVerifiedInternalServiceCall` / `isVerifiedAgentControlInternalCall` / `isVerifiedInternalMachineCall`。三者复用 `plugins/internal-service-token.ts` 里那一份 `secretsEqual`(SHA-256 定长摘要 + `timingSafeEqual`),**禁止在别处再写第二把比较**;配置缺失一律 **fail-closed**(没配密钥 = 谁都不免),不得写"没配就放行"的兜底。
- **同一条豁免与它所跳过的校验必须共用同一个判定实现**:`routes/agent-control.ts` 的 `/execute` 与 CSRF 钩子现在都调 `isVerifiedAgentControlInternalCall`。两处各写一遍必然漂开,而漂开的表现是"钩子放行、控制面拒绝"或反过来 —— 两种都比直接 403 难查(§"两处算同一件事必漂移"同族)。
- **越权用例必须断言"未发出查询/未产生副作用"**,只断言 403 会放过"先改了再抛 403"与"授权判定发生在查库之后"两种写法(与本节"已登录不等于可以动这条数据"是同一条禁令)。
- **已知无尺子的一格**(如实登记,别误以为这里有门):"新内部凭据族又写成按存在性放行"这一型目前**零判据**,票#23 只修了量到的那一处;判据草案与前置见 PROJECT_PLAN G-367。
- **规矩**:凡是"用户显式退出/会话失效之后,某些历史勾选不得再生效"的判据,它的**标记位不得与凭据同层同删除路径** —— 同层会被 `clearAll` 一起删掉,等于自己否决自己。RN 侧落点:`apps/mobile-rn/src/lib/token.ts` 的持久标记 `ihui-session-logged-out`(AsyncStorage),凭据在 SecureStore,两层,理由写在该文件注释里。
- **判据唯一出口**:`apps/mobile-rn/src/lib/auto-login-policy.ts` 的 `shouldAttemptAutoLogin(deps)`(依赖注入三个读口:登出标记 / 自动登录勾选 / 盘上是否还有凭据)。单独成文件的理由不是整洁:渲染整张 `LoginScreen` 去测它要 mock WebView / react-native-svg / 导航栈,那种测试断在第一处无关依赖上,**证不了判据本身**。
- **写序**:标记**先于**删除凭据落盘(排在同一条串行链内)。顺序错了就等于每次都按内存默认值判;而反过来(先删凭据再落标记)会留下"凭据没了、下次冷启动仍静默重登"的原缺陷。两步之间进程被杀的后果是"标记在、凭据可能还在"⇒ 自动登录被抑制(保守方向);读取失败时**不**抑制,那会把"存储抖动"变成"用户的自动登录悄悄没了"。
- **待收口**:web 与小程序同型未收(判据目前只在 RN 一端)。按 §3 共享层优先,该判据应提到 `packages/shared` 由三端各注入存储 adapter,**不得每端各抄一份 if** —— 台账见 PROJECT_PLAN G-363。
- **待收口**:web 与小程序同型未收(判据目前只在 RN 一端)。按 §3 共享层优先,该判据应提到 `packages/shared` 由三端各注入存储 adapter,**不得每端各抄一份 if** —— 台账见 PROJECT_PLAN G-370。
- **迁移记账结构守门**(49): `scripts/check-migration-bookkeeping.mjs`(blocking) —— 离线 B1-B5:journal tag↔drizzle/*.sql 双向双射、tag 唯一、when 严格递增唯一、idx 唯一、journal 结构完整,违反 exit 1;`--db` 追加 B6-B9 库内双射(行数/created_at 集合/max 相等/hash 合法唯一);**判定面**(2026-09-28 收口到本仓现行口径,同 36/70/77/83/93/98/103/118/124):全量档判 **HEAD blob**(journal 与 .sql 清单同面同轮经 `scripts/lib/face-reader.mjs` 的 `catBatch`/`gitRaw`,后者只做枚举不读 blob)、`--staged` 判索引 blob、磁盘降为 `--worktree` 逃生舱(人工与"验生成器写回"),两面旗同给 ⇒ **exit 2 判死**;任一面取不到(路径不存在 / 非 blob / git 失败 / 枚举到 0 个 .sql / journal 0 条)⇒ exit 2 点名且**不回落**另一个面 —— 回落就是把"没判"写成"判过了"。三面各加"判定面自证行",逐条点名未跟踪 .sql 与仅工作树脏的受控路径,只报数不判红(§12e:别人的在飞改动不是提交者的错,但也不读成"这道红不存在")。**代价如实登记**:默认档不再"看见未 add 的迁移",那一格移交 `--worktree` + B10(空闲判据)。B1–B9 判据一字未动;psql 不可用时汇总行写「库内双射**未判定**∶原因」,不再声称已对照。真跑:全量 exit 0「296 条,判定面=head」、`--staged` exit 0、`--staged --worktree` exit 2、`--self-test` 44/44、镜像 `node --test` 20 例,`check-gate-face-discipline` 对本门分类由 loose-fs 翻成 `face`;`--db` 那一维本机不可用(8801/8802/8810/8811 实测零监听),所以它在本机是**未判定**,不是通过;紧急跳过 `HUSKY_SKIP_MIGRATION_BOOKKEEPING=1`(脚本自读);另被 ci.yml run 步引用。
- ⚠️ 本行是「待收口:web 与小程序同型未收」那条规则在**并发并集**下留下的副本(两份只差台账号),规则正文以带 `PROJECT_PLAN G-370` 的那一行为准;此处只留痕不删行(§1 禁止无声删除),而失效的 `G-363` 指针必须消掉 —— 按旧号找到的是别人那一格,比没有指针更糟。
- **旁路落地(object-space / commit-tree)不跑钩子 ⇒ "HEAD 是否绿"是调用方自己的责任**(2026-09-28 O81 票㉙ 实测):`scripts/object-space-land.mjs` / `live-doc-edit.mjs` / `git-sync-converge` 走临时索引 + `commit-tree` + CAS,**结构上绕过整条提交链**。这不是缺陷(它就是它存在的理由:不碰共享工作树与别人在飞的东西),但代价必须补:**落地后要在 HEAD 面自己复跑受影响判据 + 相关端 typecheck**,否则提交链上那 150+ 道门对这批文件等于从未存在。实测代价两处:票㉘ 那 134 文件里留下一处死 `rnRadius` import(web `tsconfig` 开了 `noUnusedLocals` ⇒ TS6133,RN 端不开所以同一型在另一端静默),以及一处注释仍写改之前的 `rpx(12)` 而现值已 16px(注释比无注释更坏 —— 它教人按旧值复核)。**取证例规**:扫"import 了某名而正文无取用"时,**清单只能在原文上找、用法只能在遮罩面找** —— 先 mask 再匹配模块名会把 `'@ihui/design-tokens'` 抹成空串,表现为恒 0 命中(0 命中先怀疑尺子,再相信世界)。
- **守门 150 的三条后续(2026-09-28 O81 票㉘/㉙,现行口径以这一格为准)**:
  - **类别证据五级,弱证据永不判红**:显式声明(`rnRadiusFor.<角色>` / `radius-role:<角色>`) > 元素强证据(`<Card>` 标签、样式键名词干) > **身份通道**(新增第四档:`role=` 经 ARIA→角色映射、`data-testid` 词干、类名里的 `ui-<role>` 标记、`bg-popover` 色档) > 组件名档(只认 JSX **根**容器,且按**最内层声明作用域**归属 —— 内联小组件不被外层组件名顶判) > 颜色弱证据。**低置信证据只能开工作队列,不能开红点**(按名/按色推断实测判错率两到七成)。出口因此是"把类别写进代码",不是"挂免检标记" —— 全仓 156 站据此把 `componentUndetermined` 66→0、`weakFindings` 93→0、`undetermined` 1→0。
  - **C4 容器判据按"文件级浮层身份"生效**(`modalFile` = 面板词干 ∪ `MODAL_TAG_SUFFIXES` ∪ `OVERLAY_KEY_STEMS`,**三套词表必须共用一份**:首版只查词干,`LoginPopUp` 整族漏过,由自检反手抓出)。页面文件里对话框内的 `<Card>` 是**内容卡不是浮层体**,不得改判 —— 这条边界由成对用例钉住(正例:浮层文件改判;反例 73b:页面文件不改判)。**收紧前它把 9 个文件 / 19 站盖在"0 违规"下面**:判据比仓库激进时,红的不是仓库,是承诺。
  - **阳性对照必须钉出处,不得钉 HEAD**:`PROBE_REF=acf1927e96` + 镜像 T18 反向锁。账还清那天,"HEAD 上还能量到存量"这句话当场失效,而判据失效的表现永远是安静(镜像测试 `--self-test` 现 105 条)。
  - **同批配平的两处**:`RADIUS_ROLES` 现 8 角色(新增 `popover→md` / `bubble→2xl`),而 `radius.d.ts` 的 `RadiusRole` 是**手抄的第二份** —— 守门 77 的 A 判据当时只比档值不比角色名,于是新角色在类型面整族隐身(HEAD 里已有一处 `rnRadiusFor.bubble` 因此是**已入库的 TS2339**)。现由门 77 新增 **A4 角色名两处对账**(缺档 / 清单腐烂 / 声明不见 / 表清空 四态各有正反例)。**表加了新列,审表的门必须同批改**。守门 108 同批补**归档面**:`.ihui-agent/archive/**` 是记录不是正在生效的豁免,而它按 `:(exclude)*.md` 剥文档、绕得过后缀 `.md.new` —— **按扩展名排除文档这件事本身有洞**,改成按目录意图排除并单独报名条数。
  - **同批配平的两处**:`RADIUS_ROLES` 现 8 角色(新增 `popover→md` / `bubble→2xl`),而 `radius.d.ts` 的 `RadiusRole` 是**手抄的第二份** —— 守门 77 的 A 判据当时只比档值不比角色名,于是新角色在类型面整族隐身(HEAD 里已有一处 `rnRadiusFor.bubble` 因此是**已入库的 TS2339**)。现由门 77 新增 **A4 角色名两处对账**(缺档 / 清单腐烂 / 声明不见 / 表清空 四态各有正反例)。**表加了新列,审表的门必须同批改**。守门 108 同批补**存档面**(`.ihui-agent/archive/**` 是记录不是正在生效的豁免;它的候选枚举按 `:(exclude)*.md` 剥文档、绕得过后缀 `.md.new` —— **按扩展名排除文档这件事本身有洞**,改成按目录意图排除并单独报名条数)。⚠️ **实现归一后本侧让位**:同一晚两条会话各写一份同一条判据,合流时取的是覆盖更宽的那一版(`archiveFace` 独立旗标接进 5 个判点,成对用例 G09–G12 + 镜像 T25 反向锁"活文件同一条过期豁免照旧无条件红");本侧那份留在历史里不 revert,机制与出处仍成立,**但判据只有一份实现** —— 两句同题登记若都留着,下一个人会去"修"没坏的那一份。
  - **规矩:块内未勾选行必须"移出块",不得"改判据放过"。** 那些行按 §1 的 F4 口径「两条都是没做完的事、绝不动勾选」就是活账;把它们随已完成块搬进归档件 = 派单口径静默少行。本轮实测教训:520 行里 329 行带归并指针,一度被误判成"派单口径已逐出 ⇒ 归档器与派单口径不同形",据此要放宽 `entryHasOpenRows` —— 该改动作废并留档,因为"逐出派单口径"是为了**不重复派单**,不是说它做完了。唯一无损出路是把 `- [ ]` 行移到台账末尾的 `待办剥离区`:只换位置,不删行、不改勾选、不动其余顺序,落地前四条守恒断言(区标题不得自认已完成 ∧ 行多重集零消失 ∧ 新增只能是区头 ∧ 派单口径未勾选择不缩水)必须全绿。
  - **C6 几何定性(2026-09-28 票㉚):形状是量出来的,不是谁声明的。** `正方盒 + 半径≥短边一半 ⇒ 真圆装饰件`,
    自动出了角色档射程(HEAD 现读 153 处),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊` ——
    胶囊这一侧**豁免标记一律被忽略**(`exemptionIgnored` 专记这一格),因为"不允许有胶囊"是产品级定档,
    不是可申报的偏好。全仓 `radius-role-exempt` 存量已由 11 归零:正方点/头像靠几何自动归类,真胶囊
    (半高搜索框 / 关注按钮 / 两处未读数徽章)改回该取的角色档 —— 未读徽章带 `minWidth + paddingHorizontal`,
    数字到两位就是药丸,而 §4 的数字徽章模板与小程序端 `UnreadBadge` 本就是 `rounded-md`,所以这两处
    **同时也是两端分叉**。盒子几何因此从门 11 抽成共用层 `scripts/lib/box-geometry.mjs`(两门量同一件事,
    两处各写一遍必然在窗口范围 / rpx 折算 / "同表达式即方形"上漂开;镜像有形状锁)。
    **一条不许误读的现状**:胶囊候选目前**只开队列、不判红** —— 盒形量算仍会把子节点的 `h-4 w-4` 算进父元素
    (`elementWindow` 从开标签向下取 6 行),HEAD 现读 14 处候选里 **9 处是这一型假阳**(`w-40` 下拉菜单被量成
    160×10、`h-9 px-4` 按钮被量成 16×36)。升判据的前置写死在判定处注释:盒形必须**只取该元素自己的属性区**
    (到开标签闭合 `>` 为止,不含任何子节点),并由"子节点尺寸不得算进父盒"的成对用例钉住。
    在那之前把它挂成 blocking,就是把"会喊错的尺子"换成"人人跳门"(§12e 同型)。
  - ⚠️ 本行是同一条"同批配平的两处"登记的**旧副本**(2026-09-28 一次 union 合并按"行重数取 max"把被就地改写掉的那一版又插了回来 —— 删除活不过并发合并,这是本仓记过的那一型)。现行文本见本节内带"实现归一后本侧让位"那一条,勿照本行执行;保留只为不丢行(§12)。
- **自动归档**:`scripts/archive-completed-tasks.mjs` 扫描完成 ≥7 天的条目,post-commit 钩子自动 `--auto-commit`。归档 commit 设 `IHUI_ARCHIVE_COMMIT=1` 防递归。
  - **大批量阀门只挡自动档**:`--auto-commit` 下一次 >25 条或 >256 KB 即拒绝并打印实测数字(退出码 0 —— 自动档少做一件事不是错误,不得把钩子链弄红),需人工 `--allow-mass` 放行。依据实测稳态一次 7 条 / 15,168 B / 全文 0.61%;超量只可能是格式又漂或积压一次放开(参照 §5c 水印门"单次缺口 >200 拒绝自动回写"同一条设计)。
- **自动归档**:`scripts/archive-completed-tasks.mjs` 扫描完成 ≥7 天的条目,post-commit 钩子自动 `--auto-commit`,归档 commit 设 `IHUI_ARCHIVE_COMMIT=1` 防递归。
- **按凭据存在性豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 注册时取到 **157**,并发会话可能挪号;**blocking**,`stagedTriggers=apps/api/src/plugins` + `apps/api/src/routes` + `apps/api/src/utils`,紧急跳过 `HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS=1`):`scripts/check-credential-presence-bypass.mjs`(G-459)拦的是**"把某个凭据类头/cookie 在场当成安全判定的结论"**这一型 —— 立因不是假想:`apps/api/src/plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return`,头名在场即对**全部路由**跳过 CSRF,任何进程带这个头(值随便填)就拿到免死金牌;#23 改成 `isVerifiedInternalMachineCall(request)`(唯一出口 `apps/api/src/utils/internal-principal.ts`,复用那一份 `secretsEqual`:SHA-256 定长摘要 + `timingSafeEqual`,配置缺失 fail-closed)。但**型还在** —— 下一个内部凭据族照样能写成 `if (request.headers['x-anything']) return`,而 typecheck / lint / 其余门全都不响。三条设计约束:① **判据必须覆盖门自己认可的写法**,条件里出现 `===`/`secretsEqual`/`timingSafeEqual`/`isVerified…(`/`verify…(`/`startsWith(`/`.has(` 一律放过(把修法形态判红等于逼人 `--no-verify`);② **凭据名不写死站点清单**,按**词元**比对(`x-id-token` 算,`tokenizer`/`hashtags` 不算),某站是否"已裁过"由台账 `scripts/data/credential-presence-exemptions.json` 判 —— 条目缺 `reason`/`reviewBy`、复核日过期、登记了而被审面没命中(清单腐烂)三种都是红;行内出口 `credential-presence-exempt: <原因>`(须带原因,只救本行或紧邻上行),该族**同笔**登记进守门 108 的 `FAMILY_LIFETIME_DAYS`(30 天,待偿的裁决债)—— 不登记就走 90 天默认档,而第一处真被写出的行内豁免还会被 108 的 E4 判成"新引入的未登记豁免族"(一道门自己的合法出口被邻居钉红 = 两道门互咬,`radius-role`/`border-ink` 同课),这条跨文件锁由镜像 T10 钉住,不靠人记得;③ **判不出就报名** —— 括号配平失败、取不出名字、体起始跨行算不出 ⇒ 落「未判定」逐条点名,`--strict` 下有未判定即 exit 2 拒绝出合格证。口径同 70/77/83/98/101/103/118/135:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给判死、取不到不回落、枚举到 0 个候选判死不记绿;清单与正文同面同轮;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(判据看代码面,而豁免标记写在注释里 ⇒ 标记在原文行上判)。手动 `node scripts/check-credential-presence-bypass.mjs [--staged|--worktree|--json|--strict|--self-test]`。**已知无尺子的一格**(如实登记,别误以为这里有门):"新内部凭据族又写成按存在性放行"在**本门射程之外**(它只覆盖 `apps/api` 三目录)目前零判据,判据草案与前置见 PROJECT_PLAN G-367。
  5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。本仓的 `t(name, cond)` 普遍用 `!!cond` 判通过,而 **`() => {...}` 永远是 truthy** —— 所以 `t('某判据有牙', () => A5(src).violations.length === 1)` 这条断言**从写下起从未求值**,账面记的是"绿",量到的是"这个函数对象存在"。门 150 一次量到 **8 条**(用例 73b 与 98–104,由同日票㉙ 写下并以"105 例自检全绿"入账)。更糟的是它们**同时**踩在第二个洞上:断言体用的 helper `A5` 声明在它们之后(TDZ),真求值会当场抛 —— "没跑过"与"跑不通"叠在一起,而账面只有 ✅。**修法三层同补,缺一层都会复发**:① harness 判 `typeof cond === 'function'` 直接记红并写明"cond 是函数 ⇒ 断言从未求值(应写成 `(() => {...})()`)`;② helper 提到所有用例之前定义;③ 用例一律 `(() => {...})()` 立即求值。**为什么这比"少一条测试"严重**:一条恒绿的断言会替人做出"这一格已被看过"的判断,而它结构上不可能红 —— 与守门 70/76/81 记过的"判据失效的表现永远是安静"是同一条禁令,只是这次失效的不是判据而是**自检的记账**。取证:镜像 **T23** 钉 harness 的函数分支,变异回 `if (cond)` 即红。
- **凭据在场即豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **157**,blocking,`stagedTriggers=apps/` + `packages/`,紧急跳过 `HUSKY_SKIP_GATE_PRESENCE_EXEMPTION=1`):`scripts/check-gate-presence-exemption.mjs` 钉的是票 #23 收口后**零判据**的那一型 —— `apps/api/src/plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return`(头名在场即整块跳过 CSRF,等于把防线换成一个字符串),已改成 `if (await isVerifiedInternalMachineCall(request)) return`(出口 `utils/internal-principal.ts`,secretsEqual + timingSafeEqual,fail-closed);而"下一次在任何安全钩子/中间件里再写凭据存在即豁免"与正确写法在 typecheck/lint/单测下**完全同形**。射程 = `apps/api/src/plugins/**` + `apps/api/src/middleware/**`(不存在时自然为空)+ apps/packages 里文件名含 csrf/auth/permission/rate-limit/internal 的源码;**测试面/夹具显式排除并说明代价**:本门 FIXTURES 与镜像测试逐字含违例形态,扫到自己就是"门把自己立项那一型当仓库违规"的恒红(守门 131 同课),代价是写在测试目录里的真豁免本门看不见。三态不可混:**命中**(存在性判断 ⇒ 裸放行 `return` / `return true` / `return next()`,分支内无验证调用)/ **放过**(条件或分支体里出现 secretsEqual / timingSafeEqual / isVerified* / verify* / jwt.verify / compare*)/ **判不出**(条件含解析不了的 helper 调用、动态属性名 `headers[变量]`、与凭据字段混项的叶、验证调用被前导取反 ⇒ 逐条点名计数,`--strict` 下有未判定即 exit 2 拒绝出合格证)。两条**合法反向对照**各由自检钉死:「不适用即继续」`if (!headers['x-foo']) return`(前导取反落 absence 档,与豁免方向相反)与"存在性判断之后紧跟验证"。**阳性对照 = 这门的存在理由**:`--self-test` 把修复前的 csrf.ts 历史 blob(`6fb790eb70a8^`,按被审面从 git 对象库现取,不依赖工作树)喂同一判据,必须点名那行 x-internal-service-token;同一站点改成验过才免后必须落 passed(构造面双向锁,镜像 T6)。口径同 70/77/83/98/101/103/118/135:全量判 **HEAD blob**、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"**不回落**、枚举到 0 个候选**判死不记绿**;判据面先经 `scripts/lib/code-mask.mjs` 的 `maskCommentsStringsAndRegex` 剥注释/字符串/正则体(遮罩唯一实现,不得留第二份 —— 注释里逐字引用的旧写法 `x-internal-service-token` 在 HEAD 仍存在,不许被读成站点)。**存量套「该文件 HEAD 自身违规数」棘轮**:立项现读 HEAD 命中 **1** 处(`csrf.ts` 的 `x-goog-api-key` 头存在即免,注释载明防线在路由侧 mapGeminiAuth,属有记录的既定豁免而非本门新造)—— 当场判红就是与任何提交无关的恒红门(§12e),故全量默认档只报数 exit 0,问责跑 `--strict`;`--staged` 回退规则同 135(文档类提交结构上无射程内文件 ⇒ 回退 HEAD 全量并喊出来,不判"无法判定")。行内出口 `presence-exempt: <原因>`(须带原因,裸标记不放行,只救本行或紧邻上一纯注释行);**如实登记:该族尚未进守门 108 的 FAMILY_LIFETIME_DAYS**(该文件在本票文件清单外),当前落 108 的默认 90 天存活档,定档另计一票。取证:`--self-test`(夹具 20 条正反成对 + 历史/HEAD 双对照 + 覆盖面成对 + 回退四路构造面 + 空枚举判死)+ §22c 镜像 10 例(T1 装车证明读 **HEAD 面** runner、T2 摘线不得被读成已装车、T3 遮罩不得有第二份、T4 取材面形状锁含"两面旗同给 exit 2"端到端、T5 真仓 HEAD 阳性对照、T6 阳性构造双向锁、T7 棘轮锚现读、T8 回退只认"无射程内文件"、T9 豁免须带原因且计数、T10 自检装车)+ 一次**私有索引注入**现场复跑:往 csrf.ts 的索引副本加一条 `if (request.headers['x-just-trust-me']) return` ⇒ `--staged` exit 1 点名该文件(2 处 vs 锚点 1),共享索引与工作树未动。刻意不判并登记(宁漏不误报):条件先存进变量再判(如 `const authToken = ...cookies?.auth_token` 后 `if (authToken) return`,单跳回溯不在射程)、头值做 `==`/`===` 字面比较而非常数时间比较(那属"比较是否安全"另一维)、`continue` 形态放行、Python 侧同类形态(守门 117 管那一层的另一型)。
    自动出了角色档射程(现读数一律跑本门末行,本文不钉数字),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊` ——
    **现行口径(2026-09-28 票㉛ 已升为判红)**:胶囊这一侧要**三个条件同时成立**才判 —— ① 盒形**只取该元素自己的属性区**
    (到开标签闭合 `>` 为止,不含任何子节点)且**闭合可确定**;② **非正方盒**;③ **短边 ≥ 16px**。
    第 ① 条的由来:宽窗口 `elementWindow` 从开标签向下取 6 行,会把子节点的 `h-4 w-4` 算进父盒
    (`w-40` 下拉被量成 160×10、`h-9 px-4` 按钮被量成 16×36)⇒ 现由 `boxDimsOwn` 单独承担,判不出闭合即**只报名不判红**。
    第 ③ 条是装饰族的物理下限:细于 16px 的进度条 / 骨架行 / 指示点仍**只开队列**(§4 明令不得方档化,判红等于逼设计改方角)。
- **SVG 圆角同样受管(B5)**:`rx` / `ry` 会产生圆角,JSX 内联 SVG 必须写 `rx={rnRadius.xs}`;**静态 `.svg` 资产**没有 JS 通道,取值须等于档位值(偏档就近吸附、等距取小),确属图形轮廓则把该资产目录声明进守门 77 的 `OUT_OF_SCOPE` 并写明理由 —— **没有行内标记这条路**(2026-09-29 O81 票㊵ 把圆角豁免通道整体废除,写标记本身由该门 B8 判红)。
- **真圆/胶囊:靠量出来的形状,不靠标记(2026-09-29 O81 票㊵ 收口)**:头像 / 装饰点 / 红点 / 进度环 / Switch 拇指 / 半高胶囊输入框**不得方档化把形状改坏**。写法只有两种 —— `size / 2` 几何表达式,或取等于档位值的档。判据按盒形量:**正方盒 + 半径 ≥ 半边 ⇒ 几何真圆**(出了角色档射程,不需要任何自述);**非正方盒上半径 ≥ 短边一半 ⇒ 胶囊,判红且没有任何出路**(项目定档:不允许胶囊、不允许豁免);量不到盒形 ⇒ 判"无法确认",出路是把盒尺寸写进同一作用域。旧文档要求的行内 `radius-exempt` / `radius-role-exempt` 标记**已整体废除**:它们不再改变任何结论,而写下一行标记这件事由守门 77 的新判据 **B8** 判红(通道废除要两半才成立 —— 拆出口 + 拦回写;票㉜ 摘完 47 处存量之后 HEAD 上又长出过 2 处,只拆不拦就会被下一个人悄悄接回来)。
- **宽度下限不是定值(2026-09-29 O81 票㊼ 立)**:`minWidth` / `min-w-*` 给的是**下限**,内容多一位就变宽。
  于是 `minWidth: X` + 水平内边距 + `borderRadius = X / 2` 渲染出来是**两端全圆的胶囊**,不是头像那种真圆 ——
  计数徽章与附件按钮两处就是这么活着的(§4 本就明令计数徽章禁半高胶囊)。判据口径三条,缺一不可:
  ① `minWidth` 单独出现**不判**胶囊(它可以正好等于高度,量不出就别下结论);
  ② `minWidth ∧ 水平内边距` ⇒ 宽度必然 > 高 ⇒ 不得再按"可证正方盒"声称几何真圆;
  ③ `width` 写死时,加多少内边距都改不了盒宽(不得把 648 处真圆顶成胶囊红)。
  同批补齐 RN 的 `paddingHorizontal` / `paddingLeft|Right` 取材 —— 旧实现只认 Tailwind 的 `px-N`,
  同一件事两种书写只跟一半,这一型在尺子上是**静默**的(与"改了 web 忘了 App"同因)。
- **跳门次数只有一个真值来源:`.workbuddy/safe-commit-attestation.jsonl`**(每条含 `kind`=mine/not-ours/unattributed、`failedGates`、`ranFullBatch`、`declaredFiles`)。要回答"这周有多少次提交是在检查全废的状态下进的库",从这里量,不凭感觉;`ranFullBatch=false` 比跳门更严重(那一批检查压根没跑)。**此前全仓没有任何一处统计过这个总量**,所以"修好三道"完全可能只是"修好我恰好看到的三道"——量算入口 `scripts/alert-volume-report.mjs`(该文件不在位即属**未交付**,调用方必须报"未判定",不得拿 0 当结论)。
- **图源池与境外图片 URL 对账**(guardian id **159**,blocking,`stagedTriggers=packages/database/ + apps/ + packages/shared/ + packages/app/ + packages/ui-react/`,紧急跳过 `HUSKY_SKIP_IMAGE_SOURCE_POOL=1`):`scripts/check-image-source-domains.mjs` 判两件事。**① 境外图片 URL 不得再进渲染与 seed 路径** —— 域名清单从**被审面**的池文本现读,本门刻意不 import 图源池:import 走磁盘而问责走 HEAD/索引,两面混取会让报告说的和提交里的不是同一份代码(守门 118 那一型),而"判据 import 被审对象"等于池被清空时门跟着一起瞎。提交档 = 零容忍清单 + **每文件 HEAD 自身存量**棘轮,seed 数据存量(现读 53 处)只报数 ⇒ 接线不产生恒红面。**② 两份实现必须逐字等值** —— `packages/database`(platform, rank 20)层序上不得 import `packages/shared`(composite, rank 30),它用的是逐字镜像 `packages/database/seed/image-source-pool.ts`;镜像只在"机器强制它不能漂"时才不是第二份真相:本门按**同一个被审面**逐键比三张清单(漂移即红、一侧取不到判"无法判定"而不是"两份相同"),而函数行为那一维由 `node --test scripts/tests/image-source-pool-parity.test.mjs` 真执行 shared / 镜像 / 本门**三方**在同一份语料上同判来钉(清单等值而判据各写各的 = "数据同值、行为分叉";突变去掉子域名后缀匹配即 fail 1,还原即 7/7)。旧路径 `packages/database/scripts/check-image-source-domains.mjs` 留着做**零 import 的转发壳** —— 它是守门 30c 的保护区路径,且 `pnpm --filter @ihui/database check:image-domains` 是已发布的问责入口,删它等于给按旧命令派单的人留一个 MODULE_NOT_FOUND。空枚举 / 取不到 ⇒ exit 2,不记绿。
- **半径上限:一刀切 ≤ 2xl(16px)(2026-09-29 用户定档)**:任何圆角半径都不得超过档位表最大档 `2xl = 16px`,**"它是圆的"不构成理由** —— 48dp 头像上的 `rounded-full` 等效半径 24px,按本条判红。立这条的直接起因:上方那条只管"胶囊/豁免",所以正圆一族从来没被量过大小,用户实拍"圆角过大"而当且仅当这条判据存在时才答得出来。两条判据按**书写形态**切开、互不重复计数:写死的数值/档位半径由守门 77 的 **B9** 判,全圆写法(`rounded-full` / `rounded-pill` / `50%` / `9999px`,含 RN StyleSheet 的 camelCase 形态)的等效半径由守门 11 的 **C7** 判,两者共用 `lib/box-geometry.mjs` 的 `ownShortSidePx` 那一份盒形尺。出路只有两条:取 ≤ 2xl 的档,或把盒缩小到短边一半 ≤ 16px。
- **任务状态一致性(2026-09-26 立,守门 130 配套)**:计划文档被当台账用而**没有主键约束**,这是"完成的为什么还在这里、还有重复"的唯一根因 —— §1 上一条让每个 agent 追加登记,同一任务编号被不同批次各写一份,做完只翻自己那一批那份,其余副本永久烂成"未勾选";而既有防护链(门 71 防丢行 / 84 防写回旧版 / 100 防合并吞文件 / 工作区自愈)**全部单向防丢,没有一道判"状态分叉"**,并集合并又是"每行重数取 max",所以改写必然产出两份。三条规矩:
- **为什么必须有「仅隐写」这一态**:L3 的设计理由原文就是"**删除可见横幅仍可检出**"——它是一张"有人抹掉了署名"的探测网。而判据此前只看载荷可否解码,于是"把可见层整块删掉"在 CI 上表现为**完全通过**,防线被反过来用成免检理由(2026-09-29 实测 HEAD 面 4 份即此型:两份通篇无版权行、两份只剩载荷标记行而两行版权文案不见)。⇒ **通用规矩:一个安全机制的"存在性判据"必须把它自己声明的失效形态算成一维红,否则"它只在被破坏时才起作用"这件事永远无法被验证。**判据还必须是**结构式**而非逐字式:逐字比规范文案会把 `apps/ai-service/**` 那种收尾标点不同的合法横幅误判成"无署名"—— 假阳的代价是让人去改本来正常的署名。
- **L3 的位置不是不变量,"独立成行且可解码"才是**(2026-09-29 定档):注入时它落在文件末尾,而向末尾追加内容的正常编辑会把它顶到中部 —— 那是允许形态,不判红(全仓实测 467 个跟踪文件处于该形态;要求全部回位等于重写几百个文件,换来的只是形状)。**但追加型生成器必须带旗**:`node scripts/watermark.mjs inject --reseat-tail <file>` 只摘纯隐写行、在末尾补一条,并**自证其余行一字未动**,自证不过就拒写。刻意**不借道清洗通道**归位:那是块注释状态机,删的行不止隐写行 —— 实测一次带旗注入把紧随其后的条目标题一并吞掉。归档件实测四行孤儿标记就是这么攒出来的。
- **切片不得把水印结构行搬进别的文件**(唯一实现 `scripts/lib/watermark-lines.mjs`,注入器 / 归档生成器 / 旁路落地器三个消费者共用):从受水印保护的文件里按行区间取内容时,尾部那行不可见标记会随切片进产物 —— 搬进去的不只是"一行垃圾",而是**另一个文件的归属标记**。"哪一行算横幅 / 哪一行算隐写 / 规范文案长什么样"只许有一份;在任何一端再抄一份正则就是第二个真相(§22c 同一条理由)。旁路落地器的水幕判据另有五态,其中 **`repaired`(基线署名不可认而构造内容带规范横幅)是放行并报名** —— 否则"补回被抹掉的署名"这件正事会被自己的守卫拦住。
- **合并吞并的修复出口 `scripts/union-converge.mjs`**(2026-09-24 立,守门 100 的另一半)—— 判据只说"红了",不修就等于把红留给下一个人(§12e 同型)。构造是**集合运算不是启发式**:合并树 = 本侧整棵树 ∪ 对侧「相对共同基底自己动过的路径」逐个取对侧版本 ∪ 活文档(PROJECT_PLAN / AGENTS / README)按「每行重数 = max(ours, theirs)」union;**对侧的删除不随合并传播**(确要删必须在合并之后显式 `git rm`,那才是 A1 认得的合法形态)。落地前自证 `丢本侧路径 = 0 ∧ 丢对侧路径 = 0 ∧ 三份文档未存活行 = 0`,落地后**用守门 100 本人的 A1 复核这枚合并**(用它的判据验它的产物);写盘一律临时索引 + commit-tree + CAS update-ref,从不 checkout、不碰共享工作区(§12d)。触发点两处:① `git-sync-converge` 的 merge-tree **冲突分支**先交它归并,收不住才退回"需人工"(人工最容易犯的错就是选边,而本次事故正是这么来的);② `git-guardian` 每轮的 `auditMergeAdditionLoss()` 调 `check-merge-addition-loss.mjs --all-new` 增量台账 —— 提交链上那道门按设计只判未推的合并,而 converge / 手工 commit-tree **不跑钩子**,别人造好推来的合并只能靠这一层看到一次(**只判不修**:自动重做合并风险远大于收益)。取证 `--self-test` 8 例 + §22c 镜像测试 4 例(含"选边式合并必须判失败"反向对照与两条挂点装车证明);手动:`node scripts/union-converge.mjs [--apply|--theirs <sha>|--self-test]`。
- **上限判据必须量得到"跨行书写的盒子"(2026-09-29 定档)**:全圆写法(`rounded-full` / `50%` / `9999px`)的等效半径来自**盒子短边**,而尺寸与半径常分写两行(尺寸在 className 行、半径在 style 行)。盒形取材原本只有"本行"与"本行所属对象字面量"两档,于是这一族整族量不到 —— 而在一条**判红**判据那里,"量不到"与"没超标"读起来一模一样:一枚 48px 的圆只要把尺寸写到上一行,就永远不进射程。现补第三档:取该元素**自己的属性区**(到开标签闭合为止,不含任何子节点),且只在闭合可确定时下结论。同批修掉闭合判据里把 `/` 当运算符前导排除的那一格 —— 它让所有跨行自闭合标签(`/>`)永远闭不上,HEAD 实测 4 处"全圆写法但量不到盒形"里 2 处正是它。**出路只有一条:把尺寸写进同一作用域**;且"量不到"一律**逐条点名**(只给计数的话,下一个人既找不到站点也无法证明这一格被清偿)。
- **测不到的尺寸:上限由构造封顶,不由推断放行(2026-09-29 定档)**:全圆写法(`rounded-full` / `50%`)的等效半径等于盒短边一半,而当短边**由属性/参数驱动**时(例:加载器彩点 `toRpx(dotSize)`),静态尺子只能报"量不到"—— 它不该猜,也不许被读成"没超标"。这一族的唯一正解是把上界写进**共享几何源**并封顶(例:`packages/shared/src/ui/colorful-loader-spec.ts` 的 `COLORFUL_LOADER_MAX_DOT_PX = 32`,即等效半径正好等于半径上限 2xl),再由一条常驻测试把它钉住:该测试从 `radius.js` 现读最大档来断言耦合(不在测试里抄第二份数字),并做 0..4000 全域扫描 + "现存档逐值不变"对照(封顶只应收掉本来就会越界的超大档,不得顺手改观感)。写这条测试时它自己抓到两处会产出假绿的形式:相对层数写死少爬一层会把"耦合已断"报成 ENOENT;档位表里最大那一档在源码里是**带单引号的键**,不含引号的解析式把它整条漏掉 ⇒ "最大档"读成 12 而断言自洽通过。⇒ 通用规矩:**凡"从别处解析数字"的判据必须自带覆盖面自证(读到的条数 == 块内该形态出现次数),否则漏读最大项会让尺子朝着一个错数一路报绿。**
- **HEAD 悬空具名导入对账**(98):`scripts/check-dangling-local-imports.mjs`(blocking,2026-09-24 立)—— 与守门 77 B6 同族的**另一半**:77 管"用了标识符却没 import",本门管"`import { X } from './y'` 而 y 根本不导出 X"。两者都只在编译期可见,而 `pnpm typecheck` 只跑共享工作区 —— 工作区恰好是旧基线时**两边都不红**。2026-09-24 一天内各中一次:`rnRadius['2xl']` 两处未 import 让真机 release 包启动即 SIGABRT(Metro 不查类型,打包成功≠能跑);`PermissionTierRow` 经 `git log --all -S` 证明**从未在任何提交里存在过**,而引用它的注释还写着"已抽到 ChatDisclosure"。判据 D1(具名导入无对应导出)/ D2(相对路径解析不到)/** D4(影子 `.js`:被 `./x.js` 说明符字面命中的入库 `X.js`,且同 stem `X.ts`/`X.tsx` 也在库,或该 `.js` 自身含 TS 专有语法 —— 2026-09-29 立,载体的那场 2 小时 39 分生产崩溃见台账 G-815908;`.d.ts` 刻意不算同伴,否则 `radius.js`+`radius.d.ts` 这类合法"JS 实现 + 类型伴侣"每次提交必被拦;无人指向的同名对只报数不判红;现库读数 0 ⇒ 零容忍、无豁免族)**;口径与 77/83/90 一致:全量判 **HEAD blob**、`--staged` 判索引 blob、**棘轮锚点恒为该文件 HEAD 自身违规数**(把锚点写成 0 会让存量 300+ 文件整片报红 ⇒ 逼人 `--no-verify` ⇒ 全部守门作废);宁漏不误报 —— `export *` 目标不可枚举即放过,缩进/注释/模板字符串里的 import 形态一律不判。真仓 HEAD 实测 8042 源文件、悬空 6 处且**全在测试面**,生产代码 0 处。取证 `--self-test` 19 例 + §22c 镜像测试 5 例(含"存量只允许落在测试文件"与 runner 装车证明)。紧急跳过 `HUSKY_SKIP_DANGLING_IMPORTS=1`。
- **收敛链的三道"崩溃 ≠ 裁决"口径(2026-09-29 立,G-815406 / G-814402 / G-815922 三票同批落地)**:`scripts/git-sync-converge.mjs` 是本机**唯一**的合并+推送出口,而它依赖的台账闸门尺子(`planStateRegressions`,住在 `scripts/union-converge.mjs`)与它是**两条不同的失败面** —— 一旦用静态顶层 `import` 把整条依赖图拉进来,任一依赖崩了就**连与台账闸门无关的快路径一起死**,账面只剩一截 Node 堆栈(2026-09-29 实测:他人未提交的 `scripts/lib/plan-task-index.mjs` 副本少 2 个导出 ⇒ 全队推送通道停摆,而 `git status`、typecheck、156 道门全都不响)。三条不可漂的写法:**① 闸门尺子异步装载** —— `ensureUnionModule()` 失败**不拦启动**,只让**第①把尺子**落 `[台账闸门未判定]` 前缀的点名行,调用方按该前缀单独一支 exit 1(fail-closed:不得推进合并,也**绝不**返回 `[]` 冒充"没有放大")。**② 归并子进程分四态**,唯一实现 `classifyUnionAttempt(text)` → `landed / undetermined / crashed / need-human`(措辞出口 `unionOutcomeMessage`):`crashed` 的两种形状是 Node 模块/异常指纹**或一个字都没打**,而它**绝不许**被说成"需人工" —— 那一句会把人引去手工解冲突,而手工最容易犯的错是**选边**(2026-09-24 就有一枚选边合并抹掉对侧 35 个独有路径、净 −12014 行,提交信息还写着"每一行均存活")。两处归并出口(冲突分支、无冲突但状态被放大分支)**必须同引这一份判据**,不得各写一遍字符串匹配。**③ 显式点名 `--theirs` 时"本地纯落后"不算无事可做** —— 调用方点了目标,唯一能回答"要不要合"的是 `isAncestor(theirs, head)`;旧短路会打印"无需合并"并 exit 0,一次已验证交付差点这么没了(G-814402)。**取子进程输出必须 stdout ⊕ stderr 首行**(import 期崩溃时 stdout 是空的,只回吐 `message` 就只剩 `Command failed` 一行)。**两条配套纪律**:(a) 归并器"取不到远端当次真值 / 目标对象不在本机"两支一律落 `undetermined` + `exit 2` + 机器可读 token `UNDETERMINED 未判定`,出路文案只许一份实现(`unreachableObjectGuidance`;缺本机 HEAD 时只给免联网那一条,不得印出跑不通的命令);(b) **"这条锁这段时间没响"有两种成因 —— 判据没牙,或判据根本没跑**,后者在账面上完全安静,所以定责前先把**两侧都取 HEAD 版**配起来跑一遍再逐名比红名单。**验收载体**:成对镜像 `scripts/tests/union-converge.test.mjs` 的 R-A…R-Q 与 `scripts/tests/git-sync-converge.test.mjs`(T1…T7),含变异自证(摘掉 `!explicit` 或摘掉 stderr 拼接必须翻红)。
- **维护性写归属/CAS/回报计数三态对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准,`blocking`,紧急跳过 `HUSKY_SKIP_WRITE_OWNER_PREDICATE=1`):`scripts/check-write-owner-predicate.mjs` 量的是"**一条 UPDATE 有没有把它不拥有的列一起写掉**"这一型 —— 上游出处 `apps/zcode-cli/packages/adapters/src/storage/session-store/repositories/sessions.ts:285-306`:同一条语句里**归属列在 where、被改列的旧值也在 where(CAS)、SET 只有它自己的那一列**,回报口径是 `Number(result.changes) === 1`(改了几行问库,不问请求);成文理由写在 `:285-286`——"路径自愈曾复用全字段 updateSession,把读取快照中的标题、权限、回滚和归档状态覆盖到并发新值上"。**我方现状(HEAD 面现读,数字一律以命令末行为准)**:审判面 578 文件 / 写站点 **1106**,三态 `sql-scoped` / `pre-scoped` / `missing` / `undetermined` 的读数跑 `node scripts/check-write-owner-predicate.mjs` 自取。四条不可漂的设计:① **`missing` 不等于敞口** —— 守门 134 的 B2 放过通道承认"调用方已用带 owner 的预查询把 id 集限死"是正确写法,所以判据允许**同文件一跳**与**跨文件沿 import 一跳**(沿 `134` 那份 `moduleSpecCandidates`/`parseImportBindings`,**本门内不得再抄一份** —— 两处实现必漂移),放行条件是"**全部**可解析调用点都带证据",一带一无仍判 `missing`(一个守法调用方不给整条 SQL 发合格证);② **CAS 与回报两维只报数、永不进判红集合**(哪一屏该强制 CAS 属人工裁决,判红=给全站 update 造恒红门,与 134 的分工由镜像 T17 钉死);③ **提交链走差值棘轮**(锚点=该文件 HEAD 自身 `missing` 存量,**新文件锚点 0** —— 旧写法对新增文件无差别读 HEAD 面会当场抛"无法判定",于是每一次"新加一个 db 文件"的提交都被恒挡);④ **`--strict` 有未判定即 exit 2**(拒绝出具合格证;它就是手动/CI 问责档,刻意不串进 `check:all`,因为现读仍有未判定堆积)。**落地过程本身值得记两笔**:接手时那份实现"头注写跨文件一跳、代码只查同一函数体",属**文实不符**的第三种形态(不是漏实现,是实现与自己的说明相反,而账面全绿);另一处是 `decide` 旧写法 `if (!strict) return 0` ⇒ 提交链档**零拦截**而读起来像"跑过了"(守门 70 那条"不带参数永远 exit 0"同型)。两处都由镜像测试与构造面用例现形,不是靠人读代码发现的。**已知判不了格**:SET 里"多带了一列"需要人工规则(零判据);经 `registerCrud`/路由表间接派发的 db 函数找不到静态调用点(落 `undetermined`);裸 SQL 的"什么算一条写"沿用 134 那份 ⇒ 它的接收者白名单不含 `dbScoped`,那一型**两侧都看不见**,射程决策归 134 持有人。
- **工作队列必须有死亡机制(强制,2026-09-29 立)**:任何"只报数不判红"的工作队列(弱证据待裁 / 未判定 / 在账未接线)都必须带**具名裁决账** —— 每条四件套 `锚点(内容锚,不含行号)+ reason + owner + reviewBy`,三条红:字段不齐(站点继续可见,不因写坏而免检)、到期未复裁(站点**回到队列**)、站点已不在被审面(**清单腐烂**)。理由:`- [ ]` 式的开放队列只增不减,读报告的人会把它当"已被想过",而判据失效的表现永远是安静(与守门 70/76/81/118 同一条禁令)。**裁决账不是豁免通道**:豁免裁定"这一处不算错",裁决只登记"这一处证据不足,由具名的人限期再看" —— 两者失效方向不同,不得互相借用,更不得用它绕过"无行内豁免通道"这类明令。**同一轮记的还有收口口径**:把重复实现搬进共用层时,要搬的是**整条链**——派生与头解析一起搬;只把派生委派给层的 transport、解析留在门里,按守门 118 的定性仍是半接线(「管子共用不等于面共用」)。层内的形态锁按**字面**逐项判 stdio[0]=pipe / `input: Buffer.from(` / 数字 timeout / maxBuffer,`gitRaw` 委派与 `timeout,` 简写它都读不到 —— 这不是判据过严,而是它拒绝"靠别处保证了"这种推理(`stdio[0]='ignore'` 喂清单时 git 不报错,只表现为"每个对象都取不到")。红在这样的锁上时**修实现不修锁**;而行为是否未变,不得用被改动的脚本自证,要拿 `git archive` 出的旧实现(脚本**连它 import 的层**)在同一被审面上跑 A/B 逐字比对。
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。本仓的 `t(name, cond)` 普遍用 `!!cond` 判通过,而 **`() => {...}` 永远是 truthy** —— 所以 `t('某判据有牙', () => A5(src).violations.length === 1)` 这条断言**从写下起从未求值**,账面记的是"绿",量到的是"这个函数对象存在"。门 150 一次量到 **8 条**(用例 73b 与 98–104,由同日票㉙ 写下并以"105 例自检全绿"入账)。更糟的是它们**同时**踩在第二个洞上:断言体用的 helper `A5` 声明在它们之后(TDZ),真求值会当场抛 —— "没跑过"与"跑不通"叠在一起,而账面只有 ✅。**修法三层同补,缺一层都会复发**:① harness 判 `typeof cond === 'function'` 直接记红并写明"cond 是函数 ⇒ 断言从未求值(应写成 `(() => {...})()`)`;② helper 提到所有用例之前定义;③ 用例一律 `(() => {...})()`立即求值。**为什么这比"少一条测试"严重**:一条恒绿的断言会替人做出"这一格已被看过"的判断,而它结构上不可能红 —— 与守门 70/76/81 记过的"判据失效的表现永远是安静"是同一条禁令,只是这次失效的不是判据而是**自检的记账**。取证:镜像 **T23** 钉 harness 的函数分支,变异回`if (cond)` 即红。
- **源码替换符(U+FFFD)对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 164,并发会话可能挪号;**blocking**,`stagedTriggers=apps/` + `packages/` + `scripts/` + `sdks/`,紧急跳过 `HUSKY_SKIP_REPLACEMENT_CHARS=1`,问责档 `node scripts/check-replacement-chars.mjs --strict`):`scripts/check-replacement-chars.mjs` 补的是 AGENTS §3「字节级 UTF-8 完整性」那一族的**缺失维度** —— 守门 4c 判非法字节序列,而 `U+FFFD` 编成 `EF BF BD` **完全合法**,所以"合法编码承载损坏内容"这一型在它上面结构隐形(2026-09-29 实测 HEAD 面 5 个文件 718 处、百余道门全绿)。三判据:**RC1** 索引面按「该文件 HEAD 自身处数」套棘轮(存量只报数,新增/加回才红 —— 与改动无关的恒红门只会逼人 `--no-verify` 连带废掉全部守门,§12e)/ **RC2** 全量档判 HEAD blob 且逐文件点名存量 / **RC3** 取材失败计「未判定」并点名、枚举到 0 个源码文件**判死不记绿**。损坏形态实测是「多字节字 + 紧随其后的一个低 ASCII(空格/换行/数字)」整体换成 `U+FFFD`+`?` —— 连注释内部换行与 ASCII 数字一起吞,所以找回只能按注释区间整段搬,门自带该出口 `--recover <path>`(按「代码括号」= 紧邻前/后一行代码,找最近零损坏祖先的原文;取不到就如实报名,**禁止编造**)。**已知看不见的一格**:同场事故还会把非 ASCII 换成裸 `?`(实测 534 处),与正当问号不可区分 ⇒ 不判,也不报"已确认没有"。取证 `--self-test` 10 条(含阳性对照与"取不到不得冒充 0")+ §22c 镜像 `node --test scripts/tests/check-replacement-chars.test.mjs`(T1/T2 读 **HEAD 面** runner 的装车与摘线双向锁 / T3 取材面形状锁 / T4 遮罩只许一份 / T5-T6 判据有牙成对例)。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧四个 npm 生命周期(`predev`/`dev:clean`/`dev:stable`/`prebuild`)仍无 owner 可声明、因而身份两元对它们无从对账(上面"如实登记未完成的部分"那条继续成立)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **排空步骤已在提交链上(2026-09-28 接)**:`.husky/post-commit` 在调归档器**之前**先跑 `node scripts/ledger-strip-open-rows.mjs --apply`。理由:"标题写着已完成、体内还裹着未勾选登记"的块是归档器**结构上不许搬**的那一类(搬走等于把别人正开着的活账归档),没有这一步,"完成即归档"只对干净块成立,而实测大部分块都不干净(台账是几百次并发登记写出来的)。三条安全前提写在这里,下一个人不必重新猜:① 该脚本走**对象空间**(`commit-tree` + CAS),而 `commit-tree` **不跑钩子** ⇒ 结构上不会自触发递归,与归档器同一形态;② 它自带五条零损失不变量(行数不变 ∧ 消失 0 ∧ 非空行净新增只可能是区头 ∧ 被拒块归零 ∧ 勾选两态不缩水),任一不成立就**整批不落**并 exit 非零,所以"钩子跑了"不等于"台账被改了";③ 它**排在归档器前面**,同一轮里排空完就能搬。应急开关 `HUSKY_SKIP_LEDGER_STRIP=1` 只关这一步,不影响归档(两级开关刻意分开;`HUSKY_SKIP_ARCHIVE=1` 关的是整块,含本步)。5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。 **但"拿到了 sha"不等于能拿它做判据(2026-09-29 补,G-473 同窗口踩两次)**:`ls-remote` 只问引用、**不下载对象** ⇒ "用显式 SHA 操作"这条路上必须先插一步 `git fetch --no-tags origin <该 sha>`(或直接跑 `node scripts/git-sync-converge.mjs`,它先 fetch 再合)。漏了不报错,只会**误判**:`merge-base --is-ancestor <远端sha> HEAD` 因对象缺失而失败 ⇒ 被读成"不是祖先"(一次 fetch 被说成一次真分叉需人工);`rev-list --left-right --count <远端sha>...HEAD` 失败 ⇒ 被读成"无 ahead commit,可能是 shallow clone 等异常状态"(把没取回说成仓库形态坏了)。三处已改成显式"未判定 + 一步出路":`union-converge`(CLI 退 2,且本器绝不代跑 fetch)、`git-push-guard`(异常措辞前先 `cat-file -t`)、`git-sync-converge`("未判定"不再被并进"亦判需人工")。取证:`node --test scripts/tests/converge-object-presence-g473.test.mjs` 6 例(摘掉探测那一支的变异会翻红两条)。
  自动出了角色档射程(HEAD 现读 153 处),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊` ——
  **一条不许误读的现状**:胶囊候选目前**只开队列、不判红** —— 盒形量算仍会把子节点的 `h-4 w-4` 算进父元素
  (`elementWindow` 从开标签向下取 6 行),HEAD 现读 14 处候选里 **9 处是这一型假阳**(`w-40` 下拉菜单被量成
  160×10、`h-9 px-4` 按钮被量成 16×36)。升判据的前置写死在判定处注释:盒形必须**只取该元素自己的属性区**
  (到开标签闭合 `>` 为止,不含任何子节点),并由"子节点尺寸不得算进父盒"的成对用例钉住。
  在那之前把它挂成 blocking,就是把"会喊错的尺子"换成"人人跳门"(§12e 同型)。
  **三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 文件末尾独立不可见行。
  **判据(2026-09-12 加严)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。四态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ `未覆盖`。后三者 `watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
- **为什么必须有「仅隐写」这一态**:L3 的设计理由原文就是"**删除可见横幅仍可检出**"——它是一张"有人抹掉了署名"的探测网。而判据此前只看载荷,于是"把 L1/L2 整块删掉"这件事在 CI 上表现为**完全通过**,防线被反过来用成了免检理由。实测 HEAD 面两份文件是这一型:一份 34,580 行的归档件通篇无横幅,另一份是 `apps/api/src/routes/embed-proxy.ts`。⇒ **通用规矩:一个安全机制的"存在性判据"必须把它自己声明的失效形态算成一维红,否则它只在被破坏时才"起作用"这件事永远无法被验证。**
- **L3 的位置不是不变量,"独立成行且可解码"才是**(2026-09-29 定档):向文件末尾追加内容的正常编辑会把 L3 顶到中部 —— 那是允许形态,不判红(全仓实测 467 个跟踪文件处于该形态,要求全部回位会重写几百个文件而换来的只是形状)。**但追加型生成器必须带旗**:`node scripts/watermark.mjs inject --reseat-tail <file>` 会把标记请回末行,理由是"生成产物里的标记一路往下沉,每一轮都多一行没人管" —— 归档件实测四行孤儿标记就是这么攒出来的。
- **切片不得把水印结构行搬进别的文件**(2026-09-29 立,唯一实现 `scripts/lib/watermark-lines.mjs`):从受水印保护的文件里按行区间取内容时,尾部那行不可见标记会随切片进产物。识别判据(哪一行算横幅 / 哪一行算隐写)**只允许有一份**,注入器与归档生成器共用该模块;在任何一端再抄一份正则就是第二个真相。
- **RN 包装器双层页头对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **140**,blocking,`stagedTriggers=apps/mobile-rn/`,紧急跳过 `HUSKY_SKIP_RN_DOUBLE_HEADER=1`):`scripts/check-rn-double-header.mjs` 钉的是同一屏**两条页头** —— RN 屏包装器自渲染 `<NavBar>`,而它渲染的共享屏(`@ihui/rn-app`,含 `as` 别名)内部又自带一份页头 ⇒ 两个返回箭头 + 两个标题(用户实拍在设置页)。**导航面不背这个锅**:HEAD 面全 `apps/mobile-rn` 只有两处 `headerShown`,均为 `false`,所以判据不看导航配置。三条设计点:① **抑制通道必须从调用点实参识别** —— `hideHeader` / `renderHeader` / `nestedInScrollView` 三族,缺这一层则 8 个候选里 3 个是假阳(实测);② **`as` 别名必须解析**,否则设置页那一站直接漏判;③ **合规族反向对照** —— 同一扫描把 163 处判为合规(共享屏自带页头而 RN 屏不加 NavBar),本门不是"逢 NavBar 即红"。**修复出口只有一条**:给共享屏补抑制通道并在调用点传入;**禁止删任意一层** —— 共享页头是 163 个消费点的唯一页头,包装器 NavBar 在 Settings/Profile 上承载 SideMenu 抽屉唯一入口,而删包装器会让 loading 态失去唯一返回手段(实测 `packages/app/src/features/course-detail` 的 loading 分支只有 spinner)。行内出口 `double-header-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、枚举到 0 个判死。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(含 T7 私有索引注入的端到端双向锁:注入违规必红、只写进注释必绿、豁免出口必须真能用)。**存量按"该文件 HEAD 自身计数"套棘轮**,立项现读 5 处 / 5 文件只报数 —— 当场判红就是恒红门。**两条落地教训**:① 本门由子代理写完判据与测试后**停在轮次上限**,注册块缺失 ⇒ 镜像 T1/T2 恒红而门体自跑 exit 0,即"门存在、判据对、无人调度";② 注册落地用的是 `scripts/gate-registry-insert.mjs`(commit-tree + CAS,**不碰工作树**),而 `scripts/guardian-runner.mjs` 的工作树副本当场就落成了父提交版本(`git status` 只显 ` M`,一次不带 pathspec 的提交就会把新注册块写回旧版),由 `node scripts/heal-worktree-tracked.mjs --align-drift` 对齐 —— 旁路交付注册表之后必须复验工作树面,不能只看索引。
- **RN 包装器双层页头对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **144**(注册时曾占 140,与并发会话撞号后按"后来者改号"挪到现值),blocking,`stagedTriggers=apps/mobile-rn/`,紧急跳过 `HUSKY_SKIP_RN_DOUBLE_HEADER=1`):`scripts/check-rn-double-header.mjs` 钉的是同一屏**两条页头** —— RN 屏包装器自渲染 `<NavBar>`,而它渲染的共享屏(`@ihui/rn-app`,含 `as` 别名)内部又自带一份页头 ⇒ 两个返回箭头 + 两个标题(用户实拍在设置页)。**导航面不背这个锅**:HEAD 面全 `apps/mobile-rn` 只有两处 `headerShown`,均为 `false`,所以判据不看导航配置。三条设计点:① **抑制通道必须从调用点实参识别** —— `hideHeader` / `renderHeader` / `nestedInScrollView` 三族,缺这一层则 8 个候选里 3 个是假阳(实测);② **`as` 别名必须解析**,否则设置页那一站直接漏判;③ **合规族反向对照** —— 同一扫描把 163 处判为合规(共享屏自带页头而 RN 屏不加 NavBar),本门不是"逢 NavBar 即红"。**修复出口只有一条**:给共享屏补抑制通道并在调用点传入;**禁止删任意一层** —— 共享页头是 163 个消费点的唯一页头,包装器 NavBar 在 Settings/Profile 上承载 SideMenu 抽屉唯一入口,而删包装器会让 loading 态失去唯一返回手段(实测 `packages/app/src/features/course-detail` 的 loading 分支只有 spinner)。行内出口 `double-header-exempt: <原因>`(须带原因,已挂守门 108 的 30 天到期档)。口径同 70/77/83/98/101/103/118:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两旗同给 exit 2、取不到 exit 2 不回落、枚举到 0 个判死。取证 `--self-test` 与 §22c 镜像各以命令末行现读为准(含 T7 私有索引注入的端到端双向锁:注入违规必红、只写进注释必绿、豁免出口必须真能用)。**存量按"该文件 HEAD 自身计数"套棘轮**,立项现读 5 处 / 5 文件只报数 —— 当场判红就是恒红门。**两条落地教训**:① 本门由子代理写完判据与测试后**停在轮次上限**,注册块缺失 ⇒ 镜像 T1/T2 恒红而门体自跑 exit 0,即"门存在、判据对、无人调度";② 注册落地用的是 `scripts/gate-registry-insert.mjs`(commit-tree + CAS,**不碰工作树**),而 `scripts/guardian-runner.mjs` 的工作树副本当场就落成了父提交版本(`git status` 只显 ` M`,一次不带 pathspec 的提交就会把新注册块写回旧版),由 `node scripts/heal-worktree-tracked.mjs --align-drift` 对齐 —— 旁路交付注册表之后必须复验工作树面,不能只看索引。
- **旁路落地/跳门留痕的写入者有四家,合起来才是总量:`.workbuddy/safe-commit-attestation.jsonl`**。①**跳门决策**由 `safe-commit` 写(kind=mine/not-ours/unattributed,含 `failedGates`、`ranFullBatch`、`declaredFiles`);②**旁路落地**由 `object-space-land.mjs` / `live-doc-edit.mjs` / `plan-tasks-merge.mjs`(六个落地站点共用一处包装)/ `union-converge.mjs --apply` 写同一本台账(kind=bypass-landing,`gatesRun:false` + `landedSha` + `source`),因为 commit-tree + CAS **结构上不跑任何钩子**。旧写法"只有一个真值来源"会让人把"当天 0 条"读成"今天没人跳门",比不写这句更糟 —— **条数一律现读**(2026-09-29 晚间实测:台账 424 条,当日 bypass 89 枚 / unknown 1039 枚 / normal 0 枚),本文不钉数字。要回答"这周有多少次提交是在检查全废的状态下进的库",跑只读统计器 `scripts/plan-bypass-ledger-report.mjs --since <日期>`,它按日输出 normal/skipped/bypass/unknown 四态;**任一态取不到就点名"未判定",绝不以 0 冒充**(钩子日志自 `323f5b0b73` 起**整档流式读**(超上限才退回尾部并量出被跳过字节 —— 那一格仍须喊出来))。`ranFullBatch=false` 仍比跳门更严重(那一批检查压根没跑)。**两条判读边界(2026-09-29 由实测纠正,别按旧措辞推)**:① 旧句"plan-tasks-merge 的落地站点未接②(承 G-725)"已结 —— 它现在六个站点都写留痕(出口 `attestLanding`,次序由 `landingAttestationStructure` 当场判);② 该统计器**刻意不把合并提交计进四态**(合并必有多父,与"跳门"不是同一件事),所以 union-converge 那条留痕的价值在"人工审计时能说出这枚合并是旁路落的"与 §12f 的总量口径,**不在把 unknown 归零** —— 按②去期待数字变化就是把尺子读歪。**第三条正证(`c9f23d691a` 起)**:引用事务见证 —— `.husky/reference-transaction` 在 `committed` 阶段把每个 `refs/heads/*` 的新 sha 追加进 `.workbuddy/commit-witness.log`;`--no-verify` 只跳校验型钩子、**跳不掉引用事务钩子**(同一条机制正是 stash-guard 拦得住 `git stash` 的理由)。于是 unknown 第一次拆成三格:本机可疑(有见证无匹配轮)/ 别机或更早(无见证)/ **无从分(见证文件取不到 —— 这一格必须存在,否则"这台机没记"会被读成"这台机没发生过")**。拆出来的是**待读名单不是指控**:"跑了门但文件回显不逐字等值"(并发会话同窗口 staged、lint-staged 改写过)与"根本没跑"在两行文本上同形。实测:本机首两行见证就是 `c9f23d691a`(commit-tree+update-ref 的旁路落地)与紧随其后的合并 —— 证明旁路通道也被记到。所以"修好三道"完全可能只是"修好我恰好看到的三道"——门禁红的量算入口另有其物:`scripts/alert-volume-report.mjs`(该文件不在位即属**未交付**,调用方必须报"未判定",不得拿 0 当结论)。
- **跨端 Button 共享档位值的唯一源(2026-09-29 O92 立)**:`packages/design-tokens/src/button-variants.ts` 导出 `SHARED_BUTTON_BASE_CLASS` / `SHARED_BUTTON_VARIANT_CLASSES`(default / destructive / outline / ghost)/ `SHARED_BUTTON_SIZE_CLASSES`(sm / lg),`packages/ui-react/src/components/button.tsx` 与 `packages/ui-native/src/button.tsx` 各自那次 `cva()` 必须先 spread 这三张表、再追加或覆盖平台修饰。**禁止在任一端再抄一份同档 class string** —— 本节上方"跨端样式同步铁律"治的就是"改了手机 web 没改",而共享单元之所以是**配置对象**而不是 cva 实例:cva 没有官方 compose / merge,各端对共享档的平台修饰必须能覆盖,只能由两端各自展开后 build。主 CTA 档一律 `bg-cta` + `text-cta-foreground` 成对(见本节"品牌 CTA / 主按钮色同源"),`ui-native` 早期写的 `bg-primary` + `text-primary-foreground` 已归位 —— 两档在亮/暗档案下取值全等,所以这是语义归位不是改观感,且 `bg-cta` 由 `tailwind-preset.js` 的 `cta.DEFAULT` / `cta.foreground` 真实供给(该文件头注写明未注册时它会**静默生成不出任何规则**)。**当前耦合边界要如实说,不得把它读成"改一处两端都跟"**:上面三张表里 `outline` / `ghost` / `size lg` 在 web 侧是**整档覆盖**,共享值目前只约束 `ui-native`;把这三档并成同值属全端观感决策(要同时动两端观感并跨端回归),**不得为让那句共享承诺成立去改任一端取值,也不得把共享值改成某一端的特化值**。
- **图片占位内容对账**(手动问责档,`warn`,**刻意不接提交链** —— 它判的是部署机上 gitignored 目录的内容,提交者结构上满足不了,挂 blocking 就是每台每次被逼 `--no-verify` 并连带废掉链上全部检查,§12e 同型;因此也**没有**紧急跳过变量,它不在钩子链上):`scripts/check-image-placeholders.mjs` —— 拦的是"灰底占位图在四层监控全绿的情况下长期无人知道"这一型:`deploy/cdn-server.js` 对 **0 字节文件与不存在的路径都回 HTTP 200 + 一张灰底占位图**(响应头 `X-Placeholder`),所以监控/构建/网关/浏览器没有一层会红,而用户看到的是灰块。判据是把 `apps/miniapp-taro/src/constants/remote-icons.ts` 点名的 URL 集合与 `deploy/server-root` 的实际字节做**集合差**,三态绝不并桶:真图 / 空文件(=灰块)/ **点名而盘上连空文件都没有**(最坏的一格,单独报)。立票实测(2026-09-29,数字一律现读勿照抄):登记表 289 张、盘上 372 张 = 真图 188 + 空 184,其中被代码点名的空图 175、点名而无文件 8。问责入口 `pnpm check:image-placeholders`(有空图即 rc=1)、逐条清单 `pnpm check:image-placeholders:list`(每条给出 URL 与登记表里点它的位置)。修的东西只有一个正解:**按清单交真图**(服务实时读盘、换图不重启,最长等 CDN 缓存过期)——**禁止**为把计数降绿去放宽判据、自造或手绘图标冒充(AGENTS「品牌/平台图标必须是官方真实图标」禁令来自 2026-09 那次真实交付事故)、或把代码里的 URL 改指到别的图(那是拿同名不同物冒充恢复)。同批修掉两处"绿灯不等于验过"的机制:独立监控的公网图片探测原先打在**根本不经过源站**的路径上(next.config 只反代 `/remote-images/*`,实测本机 :80 对该路径回 137B 占位而公网回 174B 且来自 web),`cdn-bootstrap.ps1` 的自检探针用的也是 0 字节路径 ⇒ 永远通过。
- **活文档「按旧副本提交会写回他人已入库行」对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 166,并发会话可能挪号;blocking,`stagedTriggers=PROJECT_PLAN.md` + `AGENTS.md` + `README.md`,紧急跳过 `HUSKY_SKIP_LIVE_DOC_PATHSPEC=1`):`scripts/check-live-doc-pathspec.mjs` 把本节那条「提交活文档前必须做工作树 ⊇ HEAD 行级对账」**第一次变成机器判据** —— 此前它只有散文、没有门,而散文在本仓的失效形态永远是安静(守门 70/76/81 同族)。判据一句话:**被提交进去的那一份必须逐字含住 HEAD 已有的非空行**;三态由 `scripts/lib/live-doc-classify.mjs` 那一份实现给(2026-09-29 从 `merge-live-doc.mjs` **原样搬出** —— 归并器与这道门问的是同一个问题,两处各算一遍"什么算丢了"必然漂开,§22c;搬动经**同义证明**而不是"新自检绿":同一份夹具分别喂 HEAD 版与工作树版,CLI 报告与退出码逐字同形。自检只证明新实现自洽,不证明它与被搬走的那一份同义)。**只有 `lost` 参与判红**:`superseded` 是有人把这行就地改写了(正当编辑),`stale` 是工作副本停在更旧的前缀(要人工取 HEAD 形态,`--apply` 反而造出新旧并存)—— 把这两档判红就是逼人 `--no-verify` 连带废掉全部守门(§12e)。`archivedExempt` 是唯一一条合法出路:原文逐字存在于 `.ihui-agent/archive/**`(§1 归档两步走本来就搬行),归档面按**被审面**枚举而非 readdirSync(盘上有而面上没有不构成删行凭据,守门 13c A1/A2 同一条),归档面取不到 ⇒ 那些行判「未判定」而不是"没豁免所以红"。口径同 70/77/83/98/101/103/118:缺省全量档判 **HEAD^→HEAD**(它审"最新一枚已入库提交有没有写回",即旁路落地那一型;因与当前提交无关 ⇒ **只报数**,问责跑 `--strict`)/ `--staged` 判**索引 blob** 且**只判索引≠HEAD 的那几份文档**(与本次提交无关的门不得替别人挡路,同 135 的回退口径;索引==HEAD 记"跳过"而**不记通过)/ `--worktree` 仅人工取证档(磁盘常年滞后 ⇒ 这一档几乎必红,是取证面不是问责面)/ 两面旗同给 exit 2 / 取不到不回落 / **枚举到 0 份在仓文档判死**。刻意**不给行内豁免** —— 这一型没有"合法丢行"的写法。乘性成本上限 `MISSING_CLASSIFY_CAP=600` 是一条**会响的护栏**:超上限报「未判定」并点名数量,不静默跳过也不冒红("跳过判定"与"判通过"在账面上必须不同形)。取证 `--self-test` 11 条 + §22c 镜像 10 例(每条 cond 均为已求值布尔;T1 装车/摘线方向锁读 **HEAD 面** runner、T2 唯一实现形状锁、T3/T8 端到端"旧副本交上去必红、取回 HEAD 形态必绿"、T4 归档豁免成对"同一行只因归档面有没有它而一绿一红"、T6 无提交判死、T10 上限会响)。**两条落地教训**:① 注册条目与门体**同枚入库**(`gate-registry-insert.mjs` 只落 runner 一个路径,先注册后补脚本 = HEAD 里出现指向不存在脚本的注册,而守门 89 的 R1/R2/R4 对这一格失明);本枚改用 `object-space-land` + `LAND_BLOBS` 把 8 个路径压进一枚,而 runner 条目仍 **import `gate-registry-insert` 的那一份构造器**(`buildEntry`/`spliceInto`/`nextIdOf`),不在落地器里另写第二套注册表格式;② 镜像夹具第一版按名字手列依赖清单,少拷 `lib/gitdir.mjs`(`face-reader` 自己 import 它)⇒ 五条端到端用例在**收集期** MODULE_NOT_FOUND、被 `counts()` 折成 `lost=-1`,读起来像"判据判红"而实际是夹具缺件(与 `plan-collide-renumber` 那格同型)—— 现改为整层 `scripts/lib` 递归拷:**按名字列闭包必然随每一次 lib 演进再漂一遍**。
- **turn 序号分配点上锁对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 本枚落地取到 **167**,并发会话可能挪号;blocking,`stagedTriggers=apps/api/src/`,2026-09-29 立,G-818) —— `scripts/check-turn-ordinal-lock.mjs`:凡 `max(${chatMessages.turnOrdinal})` 与 `insert(chatMessages)` 同体者,同体内必须有 `.for('update')`(行锁,update/share 同认)或 `db.transaction(`(体内或体声明前缀);同体 = 最深共同体为语句性块(前缀含 `=>`/`function` 或尾词 ∈ try/catch/if/else/do/for/while/switch),对象字面量假体/异体/insert 先于 max 一律跳过(宁漏不误报)。双视图(同一台分词器 `scripts/lib/code-mask.mjs#scanSpans` 的两个投影,不得再自带分词器):视图A = `maskCommentsAndStrings`(注释/字符串/模板整段遮白)做括号切体+查锁;视图B = 门内 `maskCommentsKeepTemplates`(遮注释+普通字符串+正则体,**模板原文保留** —— max 聚合恰写在 SQL 模板里,`blankStrings` 连插值一起抹会整型失明);两视图等长,行号直通。取材面同守门 77/83/118 口径:全量档判 HEAD blob 只报数(存量红 = `services/patrol-scheduler.ts:198`),`--staged` 判索引 blob 且只咬本次改动动过的文件(diff --cached ACMR;未触及 `apps/api/src/` ⇒ 回落全量只报数),取不到 ⇒ exit 2 不冒红不记绿。自检:`node scripts/check-turn-ordinal-lock.mjs --self-test`(16 例,正反成对);镜像:`node --test scripts/tests/check-turn-ordinal-lock.test.mjs`(10 例,含临时仓端到端 staged 棘轮双向);紧急跳过 `HUSKY_SKIP_TURN_ORDINAL_LOCK=1`(跳过即放弃"分配点上锁"不变量,须在提交信息写明理由与清偿票)。
- **唯一例外位:输入框/选择控件的聚焦态描边取墨档(2026-09-30 用户定档)** —— 上面那句禁的是**静态**描边;"当前正在输入哪个框"要一眼可见,所以聚焦态**必须**取随主题反转的墨档(web `--color-primary` 亮纯黑/暗纯白,RN `tokens.brand.DEFAULT` 同档;类名写 `focus:border-primary` / `focus-visible:border-primary` / `focus-within:border-primary`)。**守门 83 的 R8 按"书写位"放行(下面的"键名"只指**声明聚焦的样式档名**,不是靠组件名猜元素类型)** —— 认三种结构性形态:类名前缀是 `focus*:`(含 `group-`/`peer-`)、CSS 声明所属选择器带 `:focus*` 伪类、RN 命中行或其上一行是 `focused ? {` 这类聚焦条件对象,**或命中行所属 style 键名本身就是聚焦档(`inputFocused: {` / `phoneRowFocused: {` —— 它的条件在调用点 `focused && styles.inputFocused`、描边在样式表里,只看命中行会整族漏掉;LoginScreen 两处就是这样被误计债 2 的)**。**边界(刻意的):`active:` / `:active` / `isActive ?` 一律不算例外** —— 用户定的是"只恢复输入框聚焦态",选中胶囊与当前 tab 的墨档描边仍在禁止之列(越界放行等于替用户选另一个方案)。静态位取墨档照旧判红,由自检 R8-M8h…M8k2 成对钉住(合法位放过 / 静态判红 / 同一行混写只计静态 / active 不豁免)。
- **角色档判据必须能"够到"真实写法,否则账面一路报绿(2026-09-30 实测并修)**:`scripts/lib/radius-roles.mjs` 的 `declarationRanges` 曾取"匹配行起第一个花括号"当函数体起始 —— `(props)` 解构写法里那个花括号是**参数对象**,于是行号归属算错、守门 150 的组件名档(C5)对多数 React 组件永久不生效。现读口径三条:① 判"归属类判据是否生效"看**两个互补计数**(该档判了几处 + 该档因何未启用几处),**同 0 ⇒ 判据根本没跑**,不是"跑了判不出";② 修 parser 之后必须先证覆盖面不减(本轮 2752 站 → 2752 站、丢失 0),再谈存量;③ 存量读数一律现读(修后 HEAD 面 62 处 / 52 族,三枚提交内清零;终读 `node scripts/check-radius-role-conformance.mjs --strict` = 判红 0 族 / 角色档不一致 0 处 / 弱证据待裁 0 / 未判定 0)。**清存量时不得顺手改判据,也不得把 card 一律改成某个档** —— 按名/按色推断的类别证据实测判错率两到七成,只能开队列逐条裁(同本节上方"弱证据"口径)。
- **跨端 Button 共享档位值的唯一源(2026-09-29 O92 立;同日按用户拍板改成「基座 + 追加」)**:`packages/design-tokens/src/button-variants.ts` 导出 `SHARED_BUTTON_BASE_CLASS` / `SHARED_BUTTON_VARIANT_CLASSES`(default / destructive / outline / ghost)/ `SHARED_BUTTON_SIZE_CLASSES`(sm / lg),`packages/ui-react/src/components/button.tsx` 与 `packages/ui-native/src/button.tsx` 各自那次 `cva()` 必须先 spread 这三张表、**再在基座之外追加**平台修饰。**禁止在任一端再抄一份同档 class string,也禁止把共享键整档写成字面量** —— 整档覆盖会让共享值只约束"没覆盖的那一端",而账面读起来仍像两端同源(这就是本票消灭的形态);常驻锁 `scripts/tests/shared-button-tier-base.test.mjs`(读 HEAD 面,键集从 `button-variants.ts` 现读推导、不建第二份名单)把这一条钉死:共享键被任一端写成字面量即红,T4 阳性对照用构造文本证明判据有牙、T5 反向对照证明"基座+追加"不误报。共享单元之所以是**配置对象**而不是 cva 实例:cva 没有官方 compose / merge,各端的平台修饰必须能追加,只能由两端各自展开后 build。主 CTA 档一律 `bg-cta` + `text-cta-foreground` 成对(见本节"品牌 CTA / 主按钮色同源"),`ui-native` 早期写的 `bg-primary` + `text-primary-foreground` 已归位 —— 两档在亮/暗档案下取值全等,所以这是语义归位不是改观感,且 `bg-cta` 由 `tailwind-preset.js` 的 `cta.DEFAULT` / `cta.foreground` 真实供给(该文件头注写明未注册时它会**静默生成不出任何规则**)。**耦合边界(现读口径,别再读成旧那句"web 整档覆盖")**:表里每一档只放**两端共同持有的那组原子** —— `outline` 的基座是 `border border-input`(两端都有),web 在其外追加 `bg-background shadow-sm hover:bg-accent hover:text-accent-foreground`、RN 追加 `bg-transparent text-foreground`(RN 没有 CSS 继承,底与字色必须显式给);`ghost` 与 `size lg` 两端今天的**共同集合为空**,所以共享值是**空串**,受管的只有键名与"不许整档另写",这两档的取值并未跨端绑定。空串是如实登记,不是待修的空洞:写成某一端的特化值等于把一个端的观感推给另一个端,而并档(让 `ghost`/`lg` 真同值)属全端观感决策 —— **未拍板前不得单方面收敛任一端取值,也不得把共享值改成特化值**。本票"观感一字未变"不是靠读代码声明的:改动前后两端每个档位最终 class 的 **token 多重集**逐档对账(30 档全等),同一把尺子喂一处真变异(ghost 多写一个 token)必红并点名该档、还原必绿。
  - **旁路工具的三个契约,每个都对应一次自伤(2026-09-30 实测)**:① **`LAND_MSG` / `LIVE_MSG` 是消息文本,不是文件路径** —— 把路径传进去不会报错,产出的提交主题就是那串路径而正文为空(实测枚 `0758800b74`);它已入库、内容正确,按 §22 不重写历史,只能由后续提交与台账行补可读性。要落文件必须先把内容读回来再传。② **取证包装器 `scripts/run-evidence.mjs` 不经 shell 直接 spawn,而 Node 拒 spawn `.cmd`**(`spawn EINVAL`,包装器 rc 2、证据文件只有 START 行 ⇒ 没有 RC 行,`--verify` 判 truncated)⇒ 端 typecheck 的取证写法是 `-- node <ROOT>/node_modules/typescript/bin/tsc --noEmit -p <pkg>/tsconfig.json`,不是 `pnpm.cmd --filter … typecheck`。③ **"整份内容等于某个历史版本"不等于"回退他人行"**:`scripts/object-space-land.mjs` 的祖先对账按整 blob 等值拒落,于是"把某档恢复到它被改错之前的形态"这类正当修复被自己拦住(实测 5 个 `apps/web/src/components/ai/*-panel.tsx`)。出路是一条**可机械核验**的声明 `LAND_CLASH_SCOPE`:调用方按自己的判据独立声明本次会移动的行,与祖先↔基线实际差集**逐项等值**才放行并逐条点名;不等 ⇒ 照旧拒落并打出没被覆盖的那几行(那些才是会被写回旧态的他人行);坏 JSON / 射程外声明 ⇒ exit 2。多重集口径只有一份(`scripts/lib/stale-content-analysis.mjs` 的 `lineDeltaMaps`,而 `lineDelta` 是它的投影 ⇒ 计数与样本同源),守门 84 与本器共用。**规矩:守卫给不出"到底会回退哪些行"时,拒绝措辞本身就是缺陷** —— 修法是把判据的量纲修对,而不是套 `LAND_ALLOW_STALE` 那面旗(它会把真的滞后副本一起放掉)。
- **聚焦描边的唯一实现(2026-09-30 加法补记,不改写上一行)**:RN 与跨端共享屏的输入框一律走 `packages/app/src/components/TextField.tsx`(经 `@ihui/rn-app` 导出;`colorScheme` 是**必填** prop 而非 `= 'light'` 默认值,与 `BackChevron` 同一条规矩 —— 守门 91 只把带默认值的形参认作静默脱主题开关,必填改由 `tsc` 在调用点强制)。**写法**:常态恒占 `borderWidth: 1 / borderColor: transparent`,聚焦时**只换颜色**取 `tokens.brand.DEFAULT`(亮纯黑/暗纯白),与 web 端 `border border-input` → `focus-visible:border-primary` 同形(换色不换宽 ⇒ 不产生 1px 布局跳动);确需加粗由 `focusedStyle` 在最后覆盖(登录页那处仍走 2px,是它自己的定稿,不是第二条例外)。**为什么必须走它而不是端内再写一份 `useState(focused)`**:§4 上面那条"聚焦位允许墨档"是**例外位的定义**,而例外位要有载体才算数 —— 各端各写一份时,对账门只能证明"这一端也有聚焦态",证不了"两端取同一档",这正是本轮用户报"所有输入框的激活态描边没了"的真实成因(RN 侧只有登录页写过聚焦逻辑,其余 65 个文件里 47 个连 `onFocus` 都没有,而原生 TextInput 默认无聚焦反馈、web-shell 又压掉了浏览器 outline)。**已迁**:47 文件 / 89 站点。**未覆盖,如实登记,不得读成已收口**:① 18 个 RN/共享文件此刻被别人在飞改动压在 `<TextInput>` 行或 import 块上,按 §12 只报不动;② **小程序端 44 个含 `<Input>/<Textarea>` 的文件仍完全没有聚焦态**(该端这一层尚未接进任何共享件,本轮一行没动)—— 三端统一这件事今天的真实进度是"RN/共享层已收进唯一实现,小程序端整半边未开始"。**守门**:门 83 的 R3 与 R8 现在认**同一把**聚焦尺(`rnLineIsFocusGuard`,不在 R3 里另抄正则 —— 两处算同一件事必然漂开),所以按规矩写出的 `focused ? { borderColor: brand.DEFAULT }` 不会被记成"未配对前景的实底",而去掉该守卫或选中态(Active)仍照计;门 102 的 S0 第 6 条看着这份实现(被摘线、被改成别的档、或无人 import 即红)。**禁止**:为消红去放宽这两处判据。
  - **守门 128 的第三条腿 = web(2026-09-30 立,`scripts/check-cross-end-ui-parity.mjs` 的 WD 维)**:§4「跨端样式同步铁律」第一句就写着「web 与 miniapp-taro 视觉必须完全一致」,而 128 的配对源只有 miniapp 与 RN 两面 —— 即**用户报"搜索输入井三端 4/6/8px"时,那三格里只有一格在射程内,另外两格(小程序 ↔ web)结构上永不可见**。现补:`webPair` = **同名同文件**配对的 web 侧(与主腿同为 `pairKey` 归一、同受 `candRank` 选腿判据约束,绝不新增 `normKey`/`platformSuffix` 变体),并复用主腿 `scan()` 的圆角档读取(含各组件自己 import 的样式表),所以 web 与 RN/小程序用**同一把尺子**,不是第二套解析。**四条不许漂的写法**:① **锚点四键单立**(`webCounts` / `webRadiusCounts` / `webElementRadiusCounts` / `webWaivers`)—— web 与主腿的锚点**永远不得互相顶账**(自检 WD2 成对锁:主腿降一格不得放行 web 上升,反之亦然;两把尺子互相改基准是本仓最贵的一类事故);② **首锚例外只开一次**(`anchorRegression` 认"该键空 → 有"为一次入账),此后只降不升,所以下一个人不能拿"首锚"当永久通道;③ **整腿未判定 ⇒ 大声并 exit 1,不得借另两腿的绿出合格证**,也**不得连带 kill 掉另两腿的结论**(web 腿崩 ⇒ `blocked.web` 点名 + 人读行 `⚠️ 整腿未判定`,而 `blocked.main` 仍照常打印);④ 读数边界必须跟数字一起印 —— **WD 只接了 RD(文件级档集合)量纲**,几何 / 元素名(RE)/ 图标载体三维在 web 腿**未判**,所以 `web 腿 … 判红 0 / 未判定 0` 这句**不得被读成"web 与小程序已同值"**;`undeterminedSide` 只计一侧无档的情形(那可能只是另一侧没写档),另一侧有档而集合不同照旧是真分叉(`Loading` 8 vs 2 就是这么量出来的)。**同批被这次开腿逼出的落地器缺陷**(与判据无关,但同属"账面绿而结论无效"):阳性对照的载体 `runAtRef()` 把"跑到"判成 `rc === 0`,而退出码里的 1 是**相对当今天台账的棘轮红了** —— 给 web 腿钉上首锚之后,任何一枚早于今天的 ref 必然超锚,于是那函数会把"有结论"读成"跑不动"(T24 当场翻红)。正解不是把锚点调大、也不是换一枚 ref,而是把"跑到"改判成**结构完整的判定**(rc 0/1 皆可,但 `findings` 必须非空且 JSON 可解),并由 **T24b 反向锁**钉住"根本不存在的 ref 仍须判没跑到"—— 只放宽退出码而不加存在性断言,等于让"崩在半路而恰好打了点字"冒充结论。**同批补的第二维 = 分腿拆对(`pairingRejects.<族>.legs`)**:装上 web 腿后立刻撞到 —— 拆对名单原本是**一张全局名单**,而 collect 里主腿与 web 腿共用同一个 `rejSet`,所以"只想拆 web 那份同名死副本"的一条声明会连带把**主腿在册的配对**一起摘线(实测 LoginPopUp / UserInfoCard 在主腿有账)。表现不是报错,是那一维静默归零 ⇒ 缺省不写 legs = 两腿都拆(既有 `FloatBox` 声明逐字不改行为),而 ②"仍挂锚点"与 ③"整族找不到"两条判据都必须**按腿各查各的表**:用主腿名单去核 web 键会同时造出假红(逼人删掉正当声明)与放过真腐烂。`anchorRegression` 同批改:豁免短路原本只读 `prior.waivers`,等于"给这条腿写了理由就能让那条腿的锚点凭空蒸发",现两腿各用自己的表。**12 族读数已逐条裁毕**(现读 `node scripts/check-cross-end-ui-parity.mjs` 的 web 腿行:拆对 4 / 带理由豁免 6 / 改像素 1 / 留在账上待拍板 1):拆对的是 Loading·VoiceInput·LoginPopUp·UserInfoCard(全部经人工查明是零消费者死副本或同名不同物,**这是个案裁决不是机制**,机制化 = 给 web 腿补端入口可达性剔除,见台账 G-978049③);豁免的 6 族里 `ConfirmDialog` 与 `SearchBar` 的"仅小程序档"其实是**尺子没跟到包 import 那一层**(web 侧同值住在 `packages/ui-react` 的 dialog/button/search-input 里),理由文本把这点写明,免得下一个人去给单端补数字;改像素的是 `Tooltip`(小程序输入气泡裸 `rounded`=8 → `rounded-md`,与 web 同档,也才是 popover 档);**唯一刻意不豁免**的是 `VideoPlayer` —— 它的 4 是单侧自绘 chrome(可豁免),但 8 是同一元素两端不同档(web 舞台 lg vs 小程序/RN 直角),方向属全端观感决策,让它留在账上可见(锚点 2)而不是用一条家庭式豁免把真分叉一起遮掉。顺带点名一处**裸 `rounded` 不进提取式**的盲区(补认会同时移动 77/150/128 三本账 ⇒ 必须同批重锚,已单列 G-978049②而不是顺手改)。
- **经对象空间通道"往上落"时,声明路径的底稿必须取自被审面,不得取自磁盘副本(2026-09-28 立,主会话自己撞上的一型)**:`scripts/object-space-land.mjs` 的 CAS 循环、"零损失"结构等值断言、`git show --name-only` 回读,**保护的都是"别人在索引与树里的行"**;而对我自己声明的那几个路径,它按**工作树字节**取版本。于是磁盘副本滞后时,一次只打算"加 4 行"的提交会顺带把并行会话当天按守门 KR 合法删除的 25 行写回、并删掉 HEAD 里 3 行活内容 —— `git status`、diff 行数、typecheck、链上 181 道门全部报绿(实测 `c99b163ee5`,由另一路会话在收它自己的孤儿键时反手指认,不是任何一道门抓到的)。**"整份 blob 逐字节等于某祖先版本才算陈旧"那一判据(守门 84 / `heal-worktree-tracked` 用的就是它)对这一型结构上失明**,因为落地内容是 `旧副本 ⊕ 我的新行`,不等于任何历史版本;可判的形态是**行级复归** —— 某行 ① base 面没有 ② 要落的内容里有 ③ 该路径某祖先里有(真新增的行第三条不成立,所以这条判据不会把正当编辑判红)。**两条落地纪律**:① 改代码文件先 `git checkout HEAD -- <path>`(动之前把磁盘那份留底到 `.ihui-agent/tmp/`)再在 HEAD 现值上编辑,或改用 `live-doc-edit.mjs` 的整行改写档 —— 它本来就从被审面取底稿,这也是活文档必须走它而不是走 pathspec 的又一个理由;② 落地后一律 `git show --numstat <新提交>^..<新提交>` 逐枚复验,出现意料之外的删除行就当场回补(§12 那句"提交前的报告有时效边界"在这里同样成立)。**修复那枚提交的形态值得照抄**:做成**纯删除、零新增**(`24446960ce`,224 行删除 / 0 行新增)—— 零新增在结构上就不可能再把任何人的行写回旧态,而复验只需要看 numstat 一眼。
- **但 web 侧的 `focus:border-*` / `border-input` 这类 Tailwind 边框颜色工具类今天整体不渲染(2026-09-30 实测;整站收口是待决票 G-978058,须用户拍板)** —— `apps/web/app/globals.css` 顶部那条 `* { border-color: var(--color-border) }` 未被 @layer 包裹,而**未分层声明恒胜 `@layer utilities` 里的声明,与 specificity 无关**;静态编译产物里 `* => var(--color-border)` 落在 unlayered 顶层,`.border-input` / `.focus:border-*` / `.focus-within:border-*` 全在 utilities 层。真机已证伪两站(`/memory` 的 `focus:border-foreground/30`、`/models/trajectory` 的 `focus:border-primary/50`):确认 `document.activeElement === el` 之后 `borderTopColor` 仍是 `--color-border`。**⇒ 上面那条"web 写 `focus-visible:border-primary` 与 RN 同形"目前只是账面同形** —— RN/共享层走 StyleSheet(无 CSS 层)那侧真生效,web 这侧不生效;判"三端已统一"之前必须先量 web 的计算值。**要一处描边真的渲染,唯一姿势** = 语义类 + `globals.css` 里的**未分层**规则(AI 输入卡的 `.ai-input-shell*` 是范例,枚 `5bb7af94be`,同文件 Checkbox 2026-08-13 早按这同一条结论收过),**不得**为此删掉那条 `*` 规则 —— Tailwind v4 的 preflight 不设 `border-color`,删了等于把全站未显式取色的默认描边推给 `currentColor`(= 文字色),比现状更糟。**取证的两条纪律**:① 判级联归属要用 AST/编译产物,不要拿"类名写在元素上"当生效证据(本仓"产物存在 ≠ 能力在线"同一条禁令);② 读聚焦态必须确认焦点真落到目标上(`document.activeElement === el`),否则"颜色没变"这条读数什么也没证明 —— 本会话第一版探针就命中了不可聚焦的包装节点,差点据此把结论写成系统性缺陷。
- **web 侧曾经整族不渲染的 border 颜色工具类已根治(2026-09-30,`globals.css` 那条 `*{border-color}` 已移进 `@layer base`,枚 `dfa31872ca`,G-978058 已收口)** —— 记的是这一型为什么会长期无人发现: `apps/web/app/globals.css` 顶部那条 `* { border-color: var(--color-border) }` 未被 @layer 包裹,而**未分层声明恒胜 `@layer utilities` 里的声明,与 specificity 无关**;静态编译产物里 `* => var(--color-border)` 落在 unlayered 顶层,`.border-input` / `.focus:border-*` / `.focus-within:border-*` 全在 utilities 层。真机已证伪两站(`/memory` 的 `focus:border-foreground/30`、`/models/trajectory` 的 `focus:border-primary/50`):确认 `document.activeElement === el` 之后 `borderTopColor` 仍是 `--color-border`。当时上面那条"web 写 `focus-visible:border-primary` 与 RN 同形"在 web 侧是**账面同形**(RN 走 StyleSheet 无 CSS 层,那侧一直真生效)—— 移层之后两侧都生效,但"三端已统一"这类结论仍必须量计算值才许登记,不得以类名在位当生效证据。**一处描边要真的渲染,现在直接写 Tailwind 工具类即可**(移层后 utilities 胜 @layer base);`.ai-input-shell*` 那组未分层语义类保留(它承载"同一元素四态取哪三档"的定稿,枚 `5bb7af94be`),**但不得再把它当"唯一姿势"去新增第三份未分层规则** —— 需要跨态定档时优先扩这组,普通取色写类名。**不得**回到删掉那条 `*` 规则的老路 —— Tailwind v4 的 preflight 不设 `border-color`,删了等于把全站未显式取色的默认描边推给 `currentColor`(= 文字色),比现状更糟。**取证的两条纪律**:① 判级联归属要用 AST/编译产物,不要拿"类名写在元素上"当生效证据(本仓"产物存在 ≠ 能力在线"同一条禁令);② 读聚焦态必须确认焦点真落到目标上(`document.activeElement === el`),否则"颜色没变"这条读数什么也没证明 —— 本会话第一版探针就命中了不可聚焦的包装节点,差点据此把结论写成系统性缺陷。
- **旁路落地/跳门留痕的写入者有四家,合起来才是总量:`.workbuddy/safe-commit-attestation.jsonl`**。①**跳门决策**由 `safe-commit` 写(kind=mine/not-ours/unattributed,含 `failedGates`、`ranFullBatch`、`declaredFiles`);②**旁路落地**由 `object-space-land.mjs` / `live-doc-edit.mjs` / `plan-tasks-merge.mjs`(六个落地站点共用一处包装)/ `union-converge.mjs --apply` 写同一本台账(kind=bypass-landing,`gatesRun:false` + `landedSha` + `source`),因为 commit-tree + CAS **结构上不跑任何钩子**。旧写法"只有一个真值来源"会让人把"当天 0 条"读成"今天没人跳门",比不写这句更糟 —— **条数一律现读**(2026-09-29 晚间实测:台账 424 条,当日 bypass 89 枚 / unknown 1039 枚 / normal 0 枚),本文不钉数字。要回答"这周有多少次提交是在检查全废的状态下进的库",跑只读统计器 `scripts/plan-bypass-ledger-report.mjs --since <日期>`,它按日输出 normal/skipped/bypass/unknown 四态;**任一态取不到就点名"未判定",绝不以 0 冒充**(钩子日志过大只读尾部时 normal 是**下界**,这一维也必须被喊出来)。`ranFullBatch=false` 仍比跳门更严重(那一批检查压根没跑)。**两条判读边界(2026-09-29 由实测纠正,别按旧措辞推)**:① 旧句"plan-tasks-merge 的落地站点未接②(承 G-725)"已结 —— 它现在六个站点都写留痕(出口 `attestLanding`,次序由 `landingAttestationStructure` 当场判);② 该统计器**刻意不把合并提交计进四态**(合并必有多父,与"跳门"不是同一件事),所以 union-converge 那条留痕的价值在"人工审计时能说出这枚合并是旁路落的"与 §12f 的总量口径,**不在把 unknown 归零** —— 按②去期待数字变化就是把尺子读歪。所以"修好三道"完全可能只是"修好我恰好看到的三道"——门禁红的量算入口另有其物:`scripts/alert-volume-report.mjs`(该文件不在位即属**未交付**,调用方必须报"未判定",不得拿 0 当结论)。
  - **守门 128 的第三条腿 = web(2026-09-30 立,`scripts/check-cross-end-ui-parity.mjs` 的 WD 维)**:§4「跨端样式同步铁律」第一句就写着「web 与 miniapp-taro 视觉必须完全一致」,而 128 的配对源只有 miniapp 与 RN 两面 —— 即**用户报"搜索输入井三端 4/6/8px"时,那三格里只有一格在射程内,另外两格(小程序 ↔ web)结构上永不可见**。现补:`webPair` = **同名同文件**配对的 web 侧(与主腿同为 `pairKey` 归一、同受 `candRank` 选腿判据约束,绝不新增 `normKey`/`platformSuffix` 变体),并复用主腿 `scan()` 的圆角档读取(含各组件自己 import 的样式表),所以 web 与 RN/小程序用**同一把尺子**,不是第二套解析。**四条不许漂的写法**:① **锚点四键单立**(`webCounts` / `webRadiusCounts` / `webElementRadiusCounts` / `webWaivers`)—— web 与主腿的锚点**永远不得互相顶账**(自检 WD2 成对锁:主腿降一格不得放行 web 上升,反之亦然;两把尺子互相改基准是本仓最贵的一类事故);② **首锚例外只开一次**(`anchorRegression` 认"该键空 → 有"为一次入账),此后只降不升,所以下一个人不能拿"首锚"当永久通道;③ **整腿未判定 ⇒ 大声并 exit 1,不得借另两腿的绿出合格证**,也**不得连带 kill 掉另两腿的结论**(web 腿崩 ⇒ `blocked.web` 点名 + 人读行 `⚠️ 整腿未判定`,而 `blocked.main` 仍照常打印);④ 读数边界必须跟数字一起印 —— **WD 只接了 RD(文件级档集合)量纲**,几何 / 元素名(RE)/ 图标载体三维在 web 腿**未判**,所以 `web 腿 … 判红 0 / 未判定 0` 这句**不得被读成"web 与小程序已同值"**;`undeterminedSide` 只计一侧无档的情形(那可能只是另一侧没写档),另一侧有档而集合不同照旧是真分叉(`Loading` 8 vs 2 就是这么量出来的)。**同批被这次开腿逼出的落地器缺陷**(与判据无关,但同属"账面绿而结论无效"):阳性对照的载体 `runAtRef()` 把"跑到"判成 `rc === 0`,而退出码里的 1 是**相对当今天台账的棘轮红了** —— 给 web 腿钉上首锚之后,任何一枚早于今天的 ref 必然超锚,于是那函数会把"有结论"读成"跑不动"(T24 当场翻红)。正解不是把锚点调大、也不是换一枚 ref,而是把"跑到"改判成**结构完整的判定**(rc 0/1 皆可,但 `findings` 必须非空且 JSON 可解),并由 **T24b 反向锁**钉住"根本不存在的 ref 仍须判没跑到"—— 只放宽退出码而不加存在性断言,等于让"崩在半路而恰好打了点字"冒充结论。**同批补的第二维 = 分腿拆对(`pairingRejects.<族>.legs`)**:装上 web 腿后立刻撞到 —— 拆对名单原本是**一张全局名单**,而 collect 里主腿与 web 腿共用同一个 `rejSet`,所以"只想拆 web 那份同名死副本"的一条声明会连带把**主腿在册的配对**一起摘线(实测 LoginPopUp / UserInfoCard 在主腿有账)。表现不是报错,是那一维静默归零 ⇒ 缺省不写 legs = 两腿都拆(既有 `FloatBox` 声明逐字不改行为),而 ②"仍挂锚点"与 ③"整族找不到"两条判据都必须**按腿各查各的表**:用主腿名单去核 web 键会同时造出假红(逼人删掉正当声明)与放过真腐烂。`anchorRegression` 同批改:豁免短路原本只读 `prior.waivers`,等于"给这条腿写了理由就能让那条腿的锚点凭空蒸发",现两腿各用自己的表。**12 族读数已逐条裁毕**(现读 `node scripts/check-cross-end-ui-parity.mjs` 的 web 腿行:拆对 4 / 带理由豁免 6 / 改像素 1 / 留在账上待拍板 1):拆对的是 Loading·VoiceInput·LoginPopUp·UserInfoCard(全部经人工查明是零消费者死副本或同名不同物,**这是个案裁决不是机制**,机制化 = 给 web 腿补端入口可达性剔除,见台账 G-978049③);豁免的 6 族里 `ConfirmDialog` 与 `SearchBar` 的"仅小程序档"其实是**尺子没跟到包 import 那一层**(web 侧同值住在 `packages/ui-react` 的 dialog/button/search-input 里),理由文本把这点写明,免得下一个人去给单端补数字;改像素的是 `Tooltip`(小程序输入气泡裸 `rounded`=8 → `rounded-md`,与 web 同档,也才是 popover 档);**同日按用户拍板收口的一族 = `VideoPlayer`**(拍板原话「要统一有小圆角」):三端视频舞台一律取 `lg`(8px,也是 `RADIUS_ROLES` 容器那一档),web 现档未动、小程序三处舞台容器 + RN 舞台 style 跟上来 ⇒ web 腿只剩"仅 web 档 4"(自绘控制 chrome,与主腿既有豁免同型),台账该族锚点随实测 2→1。**顺带一条本族才成立的书写规矩:微信 `<Video>` 是原生组件,父级 `overflow-hidden` 结构上裁不到它 ⇒ 圆角必须同时给到它自己的 className,只给容器会出现"容器圆、画面方";RN 同形(`overflow:'hidden'` + `borderRadius: rnRadius.lg`,不写字面量)。**仍开着的一格:`packages/app/src/features/video-player/VideoPlayerScreen.tsx` 的 `noUrlBox`(同一 16:9 槽位的空态框)落地时被并行会话持有(` M`)⇒ 按 §12 只报不动,由持有方收尾时一并带上。顺带点名一处**裸 `rounded` 不进提取式**的盲区(补认会同时移动 77/150/128 三本账 ⇒ 必须同批重锚,已单列 G-978049②而不是顺手改)。
- **旁路落地声明面对账**(170,blocking,紧急跳过 HUSKY_SKIP_BYPASS_LANDING_SCOPE=1):scripts/check-bypass-landing-scope.mjs —— 84 的另一半:84 拦提交链内「暂存内容 == 历史版本」(--staged 面),本门拦**旁路通道**(object-space-land / live-doc-edit / commit-tree+CAS,结构上不跑钩子)落地后的「留痕声明面 ⊉ 实际面 ∧ 射程外路径写回旧 blob」。留痕日志(.workbuddy/safe-commit-attestation.jsonl,G-725)是声明意图的唯一机器可读面;红判据 = landedSha 实际改动路径 ⊄ declaredFiles ∧ 某射程外路径 blob == 该路径某祖先版本(G-978069 复裁判据的落地);新内容搭车/新增文件只报数不判红;取材失败/合并提交/窗口截断 ⇒ 未判定逐条点名(--strict exit 2);增量台账 .workbuddy/bypass-landing-scope-audited.json 每枚只判一次(判红不反复红,台账不是豁免)。自检 --self-test 19 例;镜像 node --test scripts/tests/check-bypass-landing-scope.test.mjs;取证含真事回放:45cb17e2b0 合成声明面 ⇒ 点名 5 条写回(两 VideoPlayer / AGENTS / PROJECT_PLAN / cross-end-ui-parity-baseline)。
- **合并复活已删文件对账**(168):`scripts/check-merge-deletion-resurrection.mjs`(blocking,2026-09-30 立)—— 守门 100 的**另一半**:100 拦「合并吞掉对侧独有新增」,本门拦「**本侧显式删除的路径被合并整批写回**」——「对侧的删除不随合并传播」的孪生面是「我们的删除在对侧眼里就是对侧还留着这个路径」。立项实测定型两次:`apps/miniapp-taro/src/components/SectionHeader.tsx` 显式删除(零消费者、零深导入)落地两次都被并集合并写回。判据两条、取证不依赖同一前件:**DR1** P ∈ tree(共同基底) ∧ P ∈ 对侧父 ∧ P ∉ 本侧父 ∧ P ∈ 结果 ⇒ 判红;**DR2** 补 DR1 在多轮「删除→复活」上的结构性失明(第一轮复活后分叉点已推进到已删一侧,下一枚合并的基底里已没有它)—— P ∉ 本侧父 ∧ P ∈ 结果 ∧ 本侧可达范围内存在一笔非合并提交删掉了它 ⇒ 判红(对侧独有新增拿不出删除出处 ⇒ 不判,那是守门 100 的地盘;真仓形态相同的 `9a6e4de011` 正因查不到删除出处被正确放过);同一路径两条取证只计一条(两份基线互相顶掉,真值就读不出)。正当豁免两条、都带证据且非永久免检:① 删除提交信息含显式短语 `intentional-delete:`(证据住提交对象里,每次现读,结构上烂不成免检表);② `scripts/data/deletion-survival-allowlist.json` 带 `path+reason+reviewBy` 且未过期(缺字段/过期/形状漂判红)。未判定档逐条点名,`--strict` 下有未判定即 exit 2 拒绝出合格证;「窗口内无合并」与「解析失败」不并桶(前者如实报 EMPTY WINDOW 不记绿,后者 exit 2)。口径同 100:默认只判未入 origin/main 的合并,历史事故不得把后来每次提交钉红;增量台账 `.workbuddy/merge-deletion-resurrection-audited.json` 让每枚合并只判一次,`--limit N` 留给人工回看。取材一律经 `scripts/lib/face-reader.mjs`(守门 52/80/118 各判的那一型在本门结构上不出现)。取证 `--self-test` 43 例 + §22c 镜像测试 11 例(含 T1 方向锁:未注册时头注必须自称「尚未接线」,注册后注册项必须读到 blocking+skipEnv 成套)。紧急跳过 `HUSKY_SKIP_MERGE_DELETION_RESURRECTION=1`。
- **门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25 立) —— **门数与分档读数一律现读,本文不钉数字**(跑 `node scripts/check-gate-face-discipline.mjs --json` 看 `counts`;2026-09-26 那次实测是 face 28 / loose 71(其中 half-wired 1)/ unknown 38 / no-content 49 —— 写死它下次收紧就变成假账。**那 1 道半接线同日收口了**:`check-rn-global-css-sync` 换成走层的 `catBatch` 三面取材,收口后现读 `half-wired` 应为 0 —— 这条判据落地当天就产出了第一次真实修复,不是只多了一批红)。按磁盘判的门的结构性缺陷:共享工作树常年滞后 HEAD ⇒ 同一份 HEAD 代码在"恒红"与"假绿"之间来回跳,并把错数写回棘轮基线(守门 83 的 R3 登记一天内被整文件回退三次即此型)。**判据锚在"本次改动动过的门"上**(结构棘轮,不留会腐烂的白名单),全量档只报数 —— 一次性把 74 道判红就是逼各会话合法 `--no-verify`、连带废掉全部守门。**2026-09-26 收紧:"引了 `face-reader`" 不再构成合规** —— 旧判序第一刀是 `FACE_IMPORT_RE` 命中即判 `face`,于是"import 了层、内容却仍由自己 `git show` / 磁盘 `readFileSync` 取"这种**半接线**被系统性放过。实证不是假想:门 36 与门 124 都 import 了 `face-reader`(只用 `gitRaw`),却都**默认按磁盘判**;把两者改成 `catBatch` 判 HEAD 之后,本门分类读数一字未变 —— 一道自称守取材面纪律的门,对它立项要防的那一型完全失明。现判序:① 真调用层的**读取入口**(`catBatch` / `readWorktreeFile`,含 `as` 别名与 `import * as ns` 命名空间形态)⇒ `face`;② 引了层却没用它读内容、而内容来自自派生 git 或磁盘 ⇒ **`half-wired`(提交档判红)**;③ 其余照旧 `loose-git` / `loose-fs`。**只有非读取导出不算**:`selectFace` / `gitBinary` / `gitErrText` / `catBatchOids` / `catBatchSizes` 都拿不到正文,把它们当读取凭证就等于没收紧。反向护栏同等重要:别名与多行导入必须认(否则新判据对合法写法产假阳,`T10` 钉死);`usesLayerRead` 导出给镜像用,**禁止**在测试里再抄一份判据(§22c)。**同日两处精度修正(都是量出来的假阳,不是审美)**:① "读内容"与"枚举"必须分开 —— `cat-file -e`(存在性)、`ls-tree --name-only`(路径清单)、`rev-list`/`merge-base` 不产生 blob 正文,把它们算成"散写读内容"会把只做枚举的门钉成半接线(它一处 blob 都没读);**假阳比漏报更贵** —— 它指使人去"修"没坏的东西,还把判据口径说歪成"数字很多"。② 仓库锚点必须落在**路径函数实参**里:旧写法的 `|\bROOT\s*,` 一支会把 `function audit(limit = 400, cwd = ROOT)` 读成"以仓库根拼路径"。③ 借层的 transport 自己读内容(`gitRaw(['show', spec])`)同样算半接线 —— 管子共用不等于面共用。**已知空档如实登记**:锚点只认大写 `ROOT`,故 `join(root, …)` 这类小写模块根写法落进 `unknown`(不判红)。试过放宽(74→93),其中两格是假阳(一格读 `.workbuddy` 运行台账、一格读按设计只存在于部署机的 gitignore 副本),**已收回**:判据分不清"读被审内容"与"读运行态",就不该假装分得清并据此判红;补它要认识 gitignore 语义(参照 `check-prod-bundle-shadow` 的 S2),不是一行正则的事。两层遮噪方向不同,别混用:认 face-reader 导入与 git 动词必须"只遮注释、保留字符串"(模块说明符和 `cat-file` 本身就是字符串),认 `readFileSync` 调用必须"注释与字符串都遮"(`markHidden` 那一档连字符串一起抹,直接套它会让 F1/F2 双双假绿)。取不到 ⇒ exit 2,全量面枚举到 0 道门判死。自检与镜像例数以命令末行为准(本枚落地时 24 例 / 镜像 11 例,含 F16b 与 T9–T11 的收紧锁);紧急跳过 `HUSKY_SKIP_GATE_FACE_DISCIPLINE=1`。 **清单与内容同面 —— 本门曾犯它守的那条规矩(2026-09-30 修)**:全量档的**门清单**此前取 `git ls-files`(那是**索引**)而**内容**取 `HEAD:<path>`,于是任何人一次 `git add` 一份尚未提交的新门脚本,本门在 HEAD 档就**必然**得到 `取不到 ⇒ exit 2 无法判定`,把整面结论作废 —— 一台与提交者无关的恒未判定门(§12e,`exit 2` 在提交链里与判红同罪)。现:全量档清单与内容同取 HEAD 树(`ls-tree -r --name-only -z HEAD`),`--staged` 档清单(`diff --cached`)与内容(索引 blob)同取索引;「已 staged 未入库」的门脚本单列 **`在飞未入库 N`** 逐条报名(别人的在飞交付,不在本面射程,**只报数不判红、不参与退出码**,也绝不读成「这一格不存在」)。取证:自检新增 F11–F15(两档清单命令形状各一条 + pending 三态三条),变异对照 = 把 `listArgsFor(‘head’)` 退回 `ls-files` ⇒ 自检 rc 由 0 翻 1(现跑实测);真仓 HEAD 面由 `exit 2 / 取不到 1` 变 **exit 0 / 取不到 0 / 在飞未入库 1**,`--staged` 面 rc 0。注:本条在 AGENTS 里有**两份措辞不同的副本**(命中 2 次 / 文本 2 种 ⇒ 本器按整行等值逐项改写,不留一份旧口径;归并成一份另计账)。
- **门脚本取材面纪律对账**(118):`scripts/check-gate-face-discipline.mjs`(blocking,`stagedTriggers=scripts/`,2026-09-25 立) —— **门数与分档读数一律现读,本文不钉数字**(跑 `node scripts/check-gate-face-discipline.mjs --json` 看 `counts`;2026-09-26 那次实测是 face 28 / loose 74(含半接线 1)/ unknown 35 / no-content 49 —— 写死它下次收紧就变成假账)。按磁盘判的门的结构性缺陷:共享工作树常年滞后 HEAD ⇒ 同一份 HEAD 代码在"恒红"与"假绿"之间来回跳,并把错数写回棘轮基线(守门 83 的 R3 登记一天内被整文件回退三次即此型)。**判据锚在"本次改动动过的门"上**(结构棘轮,不留会腐烂的白名单),全量档只报数 —— 一次性把 74 道判红就是逼各会话合法 `--no-verify`、连带废掉全部守门。**2026-09-26 收紧:"引了 `face-reader`" 不再构成合规** —— 旧判序第一刀是 `FACE_IMPORT_RE` 命中即判 `face`,于是"import 了层、内容却仍由自己 `git show` / 磁盘 `readFileSync` 取"这种**半接线**被系统性放过。实证不是假想:门 36 与门 124 都 import 了 `face-reader`(只用 `gitRaw`),却都**默认按磁盘判**;把两者改成 `catBatch` 判 HEAD 之后,本门分类读数一字未变 —— 一道自称守取材面纪律的门,对它立项要防的那一型完全失明。现判序:① 真调用层的**读取入口**(`catBatch` / `readWorktreeFile`,含 `as` 别名与 `import * as ns` 命名空间形态)⇒ `face`;② 引了层却没用它读内容、而内容来自自派生 git 或磁盘 ⇒ **`half-wired`(提交档判红)**;③ 其余照旧 `loose-git` / `loose-fs`。**只有非读取导出不算**:`selectFace` / `gitBinary` / `gitErrText` / `catBatchOids` / `catBatchSizes` 都拿不到正文,把它们当读取凭证就等于没收紧。反向护栏同等重要:别名与多行导入必须认(否则新判据对合法写法产假阳,`T10` 钉死);`usesLayerRead` 导出给镜像用,**禁止**在测试里再抄一份判据(§22c)。两层遮噪方向不同,别混用:认 face-reader 导入与 git 动词必须"只遮注释、保留字符串"(模块说明符和 `cat-file` 本身就是字符串),认 `readFileSync` 调用必须"注释与字符串都遮"(`markHidden` 那一档连字符串一起抹,直接套它会让 F1/F2 双双假绿)。取不到 ⇒ exit 2,全量面枚举到 0 道门判死。自检与镜像例数以命令末行为准(本枚落地时 24 例 / 镜像 11 例,含 F16b 与 T9–T11 的收紧锁);紧急跳过 `HUSKY_SKIP_GATE_FACE_DISCIPLINE=1`。 **清单与内容同面 —— 本门曾犯它守的那条规矩(2026-09-30 修)**:全量档的**门清单**此前取 `git ls-files`(那是**索引**)而**内容**取 `HEAD:<path>`,于是任何人一次 `git add` 一份尚未提交的新门脚本,本门在 HEAD 档就**必然**得到 `取不到 ⇒ exit 2 无法判定`,把整面结论作废 —— 一台与提交者无关的恒未判定门(§12e,`exit 2` 在提交链里与判红同罪)。现:全量档清单与内容同取 HEAD 树(`ls-tree -r --name-only -z HEAD`),`--staged` 档清单(`diff --cached`)与内容(索引 blob)同取索引;「已 staged 未入库」的门脚本单列 **`在飞未入库 N`** 逐条报名(别人的在飞交付,不在本面射程,**只报数不判红、不参与退出码**,也绝不读成「这一格不存在」)。取证:自检新增 F11–F15(两档清单命令形状各一条 + pending 三态三条),变异对照 = 把 `listArgsFor(‘head’)` 退回 `ls-files` ⇒ 自检 rc 由 0 翻 1(现跑实测);真仓 HEAD 面由 `exit 2 / 取不到 1` 变 **exit 0 / 取不到 0 / 在飞未入库 1**,`--staged` 面 rc 0。注:本条在 AGENTS 里有**两份措辞不同的副本**(命中 2 次 / 文本 2 种 ⇒ 本器按整行等值逐项改写,不留一份旧口径;归并成一份另计账)。
- **AI 面板装载守门**(guardian id 以 runner 现值为准 —— 重注册取 **171**(首注册 169 因门体缺件被 0bb59b0bf2 救援摘除,门体随 d5c54f4499 入库后补对账),blocking,`stagedTriggers`=GlobalShell.tsx + ai-side-panel.tsx,2026-09-30 立) —— `scripts/check-ai-panel-mount-guards.mjs`:AI 面板首屏延迟根治防回潮门。R1:`GlobalShell.tsx` 的 dynamic loading 占位与 Suspense fallback 必须复用 `AiPanelPlaceholder` 组件本体,外层几何 class 与 `--ai-panel-width` 一字不动(动 = 历史 0.21 CLS 复发),内部必须真骨架(空 div = 回退);R2:`ai-side-panel.tsx` 的 8 个重组件(agent-task-progress-pane / environment-info-popover / ai-terminal-dock / question-dialog / ai-side-panel-tools / workspace-permission-dialog / compaction-status-bar / brand-icon)只准 `dynamic()`/`React.lazy()` 挂载、不得静态 import(brand-icon 仅豁免 `import type` —— 类型导入编译期擦除);`inferVendor` 只准从 `@/components/ai/vendor-infer` 导入(与 brand-icon 同模块导入会把 95+ 个 @lobehub/icons 拖回面板主 chunk)。自检:`node scripts/check-ai-panel-mount-guards.mjs --self-test`(44 例);紧急跳过 `HUSKY_SKIP_AI_PANEL_MOUNT_GUARDS=1`(跳过即放弃不变量,提交信息须写明理由)。
  - **落点(2026-09-30 改,用户拍板"项目产物不外流"):`git worktree add --detach .worktrees/wt-<任务名>`**(项目内 `.worktrees/`,已 gitignore;旧落点 `../IHUI-AI-wt-<任务名>` 即盘根散落目录,2026-09-30 已清理 13 个残留,并立盘根卫生巡检档 `check-disk-root-hygiene.mjs`(warn,判"项目产物是否外流到盘根"与"worktree 落点是否合法"两维;出口 = git-guardian 每 30 分钟巡检 + `node scripts/check-disk-root-hygiene.mjs --strict` 手动问责,刻意不接提交链 —— 判的是机器/磁盘状态,提交者结构上满足不了)防回潮)。detached HEAD,不占分支名,不违反 §9b
- **守门脚本速查补登(2026-10-01,守门 89 `check-gate-wiring.mjs` 接线层对账 R4 收口:以下 52 枚在五处权威接线点(scripts/guardian-runner.mjs / scripts/lib/pre-commit-hook.js / .husky/ / package.json / CI)已接线,而本速查此前通篇未点名 —— "文档看不见的门会被重复造或绕过",现按主题归堆补齐;mode 与跳过变量一律以 runner 注册条目与 pre-commit-hook 现值为准,编号勿照抄本节)**
  - **API key 泄露**〔scripts/check-api-key-leak.mjs · blocking〕—— 扫描面硬编码密钥/令牌(基础面 id 1)。
  - **schema drift**〔scripts/check-db-schema-drift.mjs · blocking〕—— 数据库 schema 漂移对账(基础面 id 3)。
  - **packages 陈旧 dist**〔scripts/check-stale-dist.mjs · blocking〕—— 源码改动后 dist 未重建(基础面 id 4)。
  - **dist UTF-8 BOM**〔scripts/check-dist-encoding.mjs · blocking〕—— 产物编码完整性(基础面 id 4b)。
  - **api-client UTF-8 完整性**〔scripts/check-api-client-utf8.mjs · blocking〕—— 跨端 API 客户端编码对账(基础面 id 4c)。
  - **skipResponseSanitization 裸响应泄露**〔scripts/check-sanitizer-bypass.mjs · blocking〕—— 关闭脱敏的旁路(基础面 id 6;修此类红禁止加 skipResponseSanitization 消红)。
  - **依赖碎片化**〔scripts/check-dedupe.mjs · blocking〕—— 重复依赖对账(基础面 id 7)。
  - **safeParse 静默忽略**〔scripts/check-safe-parse.mjs · blocking〕—— Zod safeParse 吞错(基础面 id 9)。
  - **CSS 颜色 token 嵌套**〔scripts/check-input-border-var.mjs · blocking〕—— 样式面颜色必须走 token(UI/样式 id 17)。
  - **原生 title tooltip 违规**〔scripts/check-native-title-tooltip.mjs · blocking〕—— §4「禁用原生提示窗」配套(UI/样式 id 18)。
  - **Tailwind class 冲突**〔scripts/check-tailwind-class-conflict.mjs · blocking〕—— 类名冲突对账(UI/样式 id 20)。
  - **侧边栏宽度一致性**〔scripts/check-sidebar-width-consistency.mjs · blocking〕—— 侧边栏宽度 + 端口注册表(UI/样式 id 24a)。
  - **z-index 层叠防护**〔scripts/check-z-index-guard.mjs · blocking · HUSKY_SKIP_Z_INDEX_GUARD〕—— 第三方 IDE 注入/遮罩 fade-in/窗口按钮等效压暗(UI/样式 id 27,详见上方速查 27/28 条)。
  - **全屏遮罩 z-index 层级**〔scripts/check-overlay-zindex.mjs · blocking · HUSKY_SKIP_OVERLAY_ZINDEX〕—— 防 fixed inset-0 + z-50 复发(UI/样式 id 28)。
  - **button 文字换行**〔scripts/check-shrinkable-text-button.mjs · warn-only(pre-commit-hook.js 直挂 · HUSKY_SKIP_BUTTON_WRAP_CHECK)〕—— flex 父容器下 button 文字缺 shrink-0/whitespace-nowrap 被压缩换行(UI/样式)。
  - **ui-react 组件复用对账**〔scripts/check-ui-react-usage.mjs · blocking · HUSKY_SKIP_UI_REACT_USAGE〕—— 端内自实现 Dialog/Card/Form 即提示(UI/样式 id 88)。
  - **迁移完整性(7 大类 29 子项)**〔scripts/check-api-migration-completeness.mjs · blocking〕—— API 迁移清单对账(工程约束 id 15)。
  - **mypy 类型检查**〔scripts/check-mypy.mjs · blocking〕—— ai-service Python 类型回退防护(工程约束 id 35,详见上方「Python 类型」条)。
  - **mobile-rn screen 迁移完整性**〔scripts/check-rn-app-migration.mjs · blocking〕—— 防独立实现回升(工程约束 id 39)。
  - **PROJECT_PLAN.md 体积**〔scripts/check-project-plan-size.mjs · warn〕—— 计划文档体积与防误删(工程约束 id 13b)。
  - **整树删除拦截**〔scripts/check-mass-deletion.mjs · blocking · HUSKY_SKIP_MASS_DELETION_GUARD〕—— 索引 vs HEAD 文件存续性,大规模删除即拦(工程约束 id 65)。
  - **活文档点名路径存续性对账**〔scripts/check-live-doc-references.mjs · blocking · HUSKY_SKIP_LIVE_DOC_REFERENCES〕—— 正文反引号点名的仓内路径必须在被审面在位(工程约束 id 184)。
  - **中文术语机翻残留**〔scripts/check-zh-term-quality.mjs · blocking〕—— 竞品 zh 包机翻残留实测教训(i18n id 58)。
  - **ICU 语法跨端可用性**〔scripts/check-icu-locale-support.mjs · blocking〕—— 非 web 端取词引擎只支持四形子集(i18n id 59)。
  - **语言包文件存在性与合法性对账**〔scripts/check-i18n-messages-exist.mjs · blocking · HUSKY_SKIP_I18N_MESSAGES_EXIST〕—— 40 项语言包清单(i18n id 87)。
  - **miniapp-taro 端 i18n 死 key**〔scripts/scan-miniapp-taro-dead-i18n-keys.mjs · warn-only(pre-commit-hook.js 四端循环 · HUSKY_SKIP_I18N_DEAD_KEY_OTHER)〕—— 判定面=索引 blob,死 key 逐端报数(i18n)。
  - **mobile-rn 端 i18n 死 key**〔scripts/scan-mobile-rn-dead-i18n-keys.mjs · warn-only(同上四端循环 · HUSKY_SKIP_I18N_DEAD_KEY_OTHER)〕—— 同上(i18n)。
  - **extension 端 i18n 死 key**〔scripts/scan-extension-dead-i18n-keys.mjs · warn-only(同上四端循环 · HUSKY_SKIP_I18N_DEAD_KEY_OTHER)〕—— 同上(i18n)。
  - **desktop 端 i18n 死 key**〔scripts/scan-desktop-dead-i18n-keys.mjs〕—— 薄壳,委托统一入口 `scan-dead-i18n-keys.mjs --target=desktop`;desktop 无 JS i18n,统一入口自动跳过(exit 0);接线对账按 `${target}` 模板形态计入,而 pre-commit 循环实为 miniapp-taro/mobile-rn/extension 三端(web 端另有独立段),desktop 并不在循环内 —— 如实登记,待归属会话裁决(i18n)。
  - **design-tokens 同步对账**〔scripts/check-design-tokens-sync.mjs · 仅 CI 接线(弱一档)〕—— 由 `.github/workflows/8end-consistency-cert.yml` 与 `scripts/run-8end-consistency-cert.mjs` 调用,不在提交链,无跳过变量(i18n/tokens)。
  - **对话流元素覆盖守门**〔scripts/check-chat-element-coverage.mjs · blocking〕—— 锚点漂移/契约事件两端不齐/清单条目倒退等五判据(对话流 id 57,详见上方速查 57 条)。
  - **工具活动行双时态覆盖**〔scripts/check-tool-activity-coverage.mjs · blocking〕—— 键形合规 + 覆盖率 ratchet(工具面 id 60)。
  - **工具注册表完整性守门**〔scripts/check-tool-registry-integrity.mjs · blocking(pre-commit-hook.js 直挂 · HUSKY_SKIP_TOOL_REGISTRY_INTEGRITY)〕—— 工具可达面 ↔ 本地注册表 ↔ 前端委托实现三面对齐(工具面)。
  - **桌面安装器资产三方对账**〔scripts/check-installer-assets.mjs · blocking · HUSKY_SKIP_INSTALLER_ASSETS_GUARD〕—— 安装器资产引用 ↔ 打包清单 ↔ 5 档落盘三方对齐(桌面端 id 61)。
  - **SSE 双解析器漏接对账**〔scripts/check-sse-parser-parity.mjs · blocking · HUSKY_SKIP_SSE_PARSER_PARITY〕—— 帧覆盖 ratchet + 未接帧须交代归属(SSE id 63)。
  - **水印载荷/横幅行语法对账**〔scripts/check-watermark-syntax.mjs · blocking · HUSKY_SKIP_WATERMARK_SYNTAX〕—— 零宽字符必须被注释包裹(溯源水印 id 95)。
  - **工具 schema 投影完整性**〔scripts/check-tool-schema-projection.mjs · blocking · HUSKY_SKIP_TOOL_SCHEMA_PROJECTION〕—— 工具描述符 JSON schema 投影键序 canonical/拒绝通道在位/A-B 两面一致(工具面 id 179)。
  - **admin 面特权判定一致性**〔scripts/check-admin-gate-consistency.mjs · blocking · HUSKY_SKIP_ADMIN_GATE_GUARD〕—— O13b roleId>=1 收敛,裸 roleId 判定/本地重定义 requireAdmin 即红(能力面 id 53)。
  - **配置表名存在性**〔scripts/check-config-table-existence.mjs · blocking · HUSKY_SKIP_CONFIG_TABLE_GUARD〕—— COMPUTE_ALLOWED_TABLES 必须与 drizzle schema 对齐(能力面 id 75)。
  - **迁移账本带外对象防回潮**〔scripts/check-migration-ledger-drift.mjs · warn · HUSKY_SKIP_MIGRATION_LEDGER_GUARD〕—— journal 序号全集 vs 账本行全集(能力面 id 76)。
  - **跨端 storage-adapter parity**〔scripts/check-cross-store-parity.mjs · blocking(pre-commit-hook.js 直挂,无跳过变量)〕—— 五份 storage/auth-store 输入一致性,判定面=索引 blob(能力面)。
  - **能力矩阵对账守门**〔scripts/check-capability-matrix.mjs · blocking(pre-commit-hook.js 直挂 · HUSKY_SKIP_CAPABILITY_MATRIX)〕—— 默认关 feature env 必须登记 capability_matrix.py 台账,判定面=索引 blob(能力面)。
  - **能力位不得客户端自供守门**〔scripts/check-capability-field-not-client-supplied.mjs · blocking · HUSKY_SKIP_CAPABILITY_FIELD_NOT_CLIENT_SUPPLIED〕—— 客户端不得自报 capability 字段,能力位只准服务端下发(能力面 id 176)。
  - **诊断面脱敏守门**〔scripts/check-diagnostic-redaction.mjs · blocking · HUSKY_SKIP_DIAGNOSTIC_REDACTION〕—— 诊断/错误上报面不得携带未脱敏敏感串(b76 系 id 174)。
  - **进程退出码归因守门**〔scripts/check-process-exit-attribution.mjs · blocking · HUSKY_SKIP_PROCESS_EXIT_ATTRIBUTION〕—— exit 1/非零退出必须可归因到显式 failure kind/错误码(b76 系 id 175)。
  - **SSE 帧水印守门**〔scripts/check-sse-frame-watermark.mjs · blocking · HUSKY_SKIP_SSE_FRAME_WATERMARK〕—— SSE 流消费环必须带 fromSeq>=1 帧水印接线,摘线即红(b76 系 id 177)。
  - **列表截断诚实守门**〔scripts/check-list-cap-honesty.mjs · blocking · HUSKY_SKIP_LIST_CAP_HONESTY〕—— UI 列表截断必须如实交代截断帽(b76 系 id 178)。
  - **嵌套 additionalProperties 闭集守门**〔scripts/check-nested-additional-properties.mjs · blocking · HUSKY_SKIP_NESTED_ADDITIONAL_PROPERTIES〕—— 嵌套层 schema 闭集声明不得被摘(b76 系 id 180)。
  - **错误文案有界化守门**〔scripts/check-error-message-bounded.mjs · blocking · HUSKY_SKIP_ERROR_MESSAGE_BOUNDED〕—— 错误文案出边界的 JSON.stringify 内插必须有界或带 ref= 基线(b76 系 id 181)。
  - **控制面 strict 棘轮门**〔scripts/check-wire-strictness.mjs · blocking · HUSKY_SKIP_WIRE_STRICTNESS〕—— z.object+.strict+.catch 总和只减不增,禁 passthrough 降红(b76 系 id 182)。
  - **分片上传聚合预算棘轮门**〔scripts/check-upload-budget.mjs · blocking · HUSKY_SKIP_UPLOAD_BUDGET〕—— 装配检与落盘追加检各 >=1 且常量在 PROTOCOL_UPLOAD_LIMITS 登记,防死表回潮(b76 系 id 183)。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,**尚未接提交链**(2026-09-30 曾一度接进 runner,复跑后已摘出;重新接线由台账"解阻前置"管,现行定级与取材口径见下方完整原文条;重新接线时才随条目声明应急跳过变量) · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **图片占位内容对账**〔scripts/check-image-placeholders.mjs · 手动问责档 warn,刻意不接提交链(判的是部署机上 gitignored 目录的内容,提交者结构上满足不了;问责入口 `pnpm check:image-placeholders` / `--list`,现行定级与判据见下方完整原文条)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L519–L519
  - `git worktree add --detach ../IHUI-AI-wt-<任务名>`(detached HEAD,不占分支名,不违反 §9b)
  - 收编后立即 `git worktree remove ../IHUI-AI-wt-<任务名>` + `git worktree prune`
- **🚫 agent 禁止手写 `git push`(2026-09-18 立,"已推完还在等"事故根治)**:①origin 的推送由 post-commit 钩子 `git-push-guard.mjs` 自动完成(内置 ahead 检测+推送+回读验证,幂等)——commit 落地即已推送,**手动盲推必撞 already-pushed 非快进报错**并诱发后台反复干等;②Gitee/GitCode 由 `mirror-to-cn.yml` CI 在 push 后自动镜像(+每日 2 次兜底),**本地手推镜像仓=违反架构**且制造 DIVERGED 竞态。③收尾核验同步状态**只允许** `node scripts/git-push-converge.mjs`(只读判定,六态 ALREADY/PUSHING/PUSHED/SKIP/BEHIND/DIVERGED,不做任何非必要推送);仅在 guard/CI 均失效的应急场景才人工推,且必须先跑该脚本确认状态。多会话并发期:本会话交付已被远端包含(merge-base --is-ancestor 验证)即为完成,本地 HEAD 落后不追、不与并发会话抢 reset/ff。
- **⚡ 推送异步化(2026-09-18 立,根治第二段)**:guard 检测到 ahead 时默认 spawn 后台 worker 推送(pre-push 质量门不降级,实测单遍 216.8s)并立即返回——**commit 命令秒回,不再被推送拖住**;状态在 `.workbuddy/push-state.json`(running/done/failed,失败由下次 guard 自动重试),converge 读该状态显示 PUSHING;核验若见 PUSHING=正在推,等 1-2 分钟再查即可,勿手动干预。强制同步推送:GUARD_ASYNC=0。(2026-09-18 晚修复:异步 spawn 的 fd 误用 `out.close()` 必抛 TypeError→静默回退同步推送+双重推送竞态,已改 `closeSync`;push-gate 缓存改**内容指纹键控**(HEAD:apps/HEAD:packages 子树+脏文件内容 hash),合并/文档提交不再重跑全量门。)
- **🔄 主动收敛(2026-09-18 晚立,根治第三段)**:推送遇 non-FF(并发会话推力)时**禁止手工 fetch/merge/push 循环**(实测 3 轮 25 分钟),统一跑 `node scripts/git-sync-converge.mjs`(默认 3 轮:fetch→祖先判定→`merge-tree --write-tree` 索引层合并(**零触碰他人未提交文件**)+commit-tree+update-ref→guard 推送,直至收敛;冲突才需人工)。只读核验仍用 `git-push-converge.mjs`。
- **🪟 后台进程禁弹窗(2026-09-18 晚立)**:Windows 下 `spawn(detached:true)` + 控制台程序**必弹新 cmd 窗口**——所有 detached spawn 必须带 `windowsHide: true`(guard worker/锁心跳/desktop-dev-saas 已全量修复,仓库既有正例 dev-with-warmup.mjs)。**同理所有 `shell:true` 的 spawnSync/execSync 必须带 `windowsHide: true`**——shell 派生链无 CREATE_NO_WINDOW 时,pnpm -r typecheck 等会为每个子包拉起可见 cmd.exe(2026-09-18 WMI 实锤:一次全量 typecheck ~30 次闪窗,13 处派生点已全量修复)。新增派生点遗漏此参数=用户桌面反复弹窗事故。**⚠️ 默认终端=Windows Terminal 时上述措施全部失效(2026-09-18 深夜 WMI 二次实锤根治)**:WT 宿主忽略 SW_HIDE(windowsHide 无效)且不理 HKCU\Console 离屏档案,同一负载实测 135 个可见窗口样本;已把 `HKCU\Console\%%Startup` 的 DelegationConsole/DelegationTerminal 双双设为 `{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}`(Windows 控制台主机 conhost)——conhost 尊重 SW_HIDE 与离屏档案,复跑同负载可见窗口归零。**禁止改回 WT 委托**;排查弹窗先查此键,取证脚本=.ihui-agent/tmp/console-watch.ps1(WMI 事件+15ms 可见窗口采样,后台 PS 任务约 2 分钟即被杀,足够覆盖一次守门链)。
  - ⚠️ **上面那句给的取证脚本从未入库(2026-09-27 实测三面 + 历史全零命中)**:`.ihui-agent/tmp/console-watch.ps1` 在工作树不存在、`git ls-files` 零命中、`HEAD` 树零条、`git log --all --diff-filter=A -- '*console-watch*'` 也拿不到 ⇒ 它从来没有被版本化过,只是某次会话留在本机临时目录的产物,换机/重装/他人接手一律跑不通。这与本仓记过的 `pnpm c-drive:clean-ours`(根 `package.json` 从来没有这个脚本)、守门 46/11c/50 的假 `HUSKY_SKIP_*` 通道是同一条禁令的反面形态:**文档不得写跑不通的出路**。**在这条指针被换成真实入口之前**,弹窗这一维在提交链上只有**静态**那道门(`scripts/check-no-visible-spawn.mjs`,守门 52,判"派生控制台程序时有没有写 `windowsHide`"),而**静态判据绿不等于运行时不弹窗** —— 五次复发里至少有两次(WT 委托那一次、fork 风暴那一次)恰恰是"逐点参数都补了、现象仍在",只能靠运行时量窗口样本定性。登记事实:**"运行时可见窗口计数"这一维当前无版本化尺子**;补它的一票已立项,落地判据 = `git cat-file -e HEAD:<探针脚本>` 成立且该脚本真能报出"此刻开着的窗口"(阳性对照,不接受恒 0)。
- **🧬 机器级根治 windowsHide 默认值(2026-09-20 立,"git 窗口一直弹"第四次复发收口)**:实测 Node v24 `child_process` 的 `windowsHide` **默认 `false`**;当父进程**无控制台**(detached+文件 fd 的 guard worker、计划任务、Qoder/Trae/WorkBuddy 等 GUI 宿主派生)时,派生任何控制台程序(git/cmd/pnpm/node)**必分配一个可见控制台**。逐点补参数不可持续(仅 `scripts/`+`.husky/` 热路径就 74 处漏,全仓 ~600 处,且新写脚本继续产生)。**根治**:`node scripts/install-console-window-hook.mjs --apply` —— 把 `scripts/lib/windows-hide-default.mjs` 复制到**仓库外**稳定目录(默认 `<TEMP 上级>/ihui-node-hooks`,本机 = `D:\caches\ihui-node-hooks`;放仓库外是因为 `NODE_OPTIONS` 指向的文件一旦缺失会让**全机所有 node 进程启动失败**,仓库移动/删除不得波及。钩子源文件**必须是 `.mjs`**:`.gitignore` 第 207 行 `*.cjs` 会把 `.cjs` 静默忽略,§23 同类陷阱,一旦被忽略其他机器就装不上),并以 `--import=file:///...` 写入 HKCU 用户级 `NODE_OPTIONS`。钩子给 7 个派生方法补默认 `windowsHide: true`,**显式传 `false` 一律尊重**;落盘前必先用暂存副本做"node 能否带此 NODE_OPTIONS 启动 + 7 个方法是否真被替换"双试跑,不通过则**拒绝写入**,原值备份于同目录 `node-options-backup.json`。实测**ESM 具名导入 `import { spawnSync }` 同样被拦**(loader 早于 builtin ESM facade 创建)。**换机/重装/新克隆后必须跑一次 `--apply`**;`--verify` 巡检不写盘,`--remove` 卸载。**不得因此省略逐点参数**:干净 checkout、他人机器、CI 没有此 env 时仍靠显式 `windowsHide: true` 生效,新增派生点照旧必须写。
- **🔒 机制守门(2026-09-20 同日补,防第 5 次复发)**:`scripts/check-no-visible-spawn.mjs`(guardian-runner 第 **52** 项,blocking)——括号配平提取 `spawn/spawnSync/exec/execSync/execFile/execFileSync/fork` 调用,**首参可"肯定"为控制台程序**(白名单含 git/node/pnpm/cmd/pwsh/schtasks/ffmpeg…,并覆盖绝对路径与"路径+参数"两种形态)且 options 缺 `windowsHide` 即拦截。刻意**宁漏不误报**(未知变量名一律放过),避免阻塞他人提交;测试代码路径降为 warn(测试由终端 runner 派生,子进程继承已有控制台不新分配窗口)。`--staged` 供 pre-commit 用(runner 自动下发),`node scripts/check-no-visible-spawn.mjs` 为全量审计,`--self-test` 跑 14 例逻辑自检。§22c 镜像测试:`scripts/tests/check-no-visible-spawn.test.mjs`。
- **🔗 异步推送配套适配(2026-09-18 晚)**:①check-push-sync(pre-commit #29)遇 push-state running(未过期)放行——否则每次 commit 后 270s 窗口内的下一次 commit 必被 #29 阻塞→--no-verify→80 项守门全跳过;②guard worker 串行化:在途 worker 未落定时新 worker 先等后跑,杜绝多个 270s typecheck 并行打满 CPU。③**推送门中断分类(2026-09-18 深夜)**:typecheck 进程被外部杀死(CTRL_C 注入/宿主清树,exit 3221225786/141/143/130/137,实测日志 39 次)≠ 类型检查结论——check-typecheck 按**临时失败 exit 75** 退出→guardian-runner 原样传播→pre-push 写 `.workbuddy/push-gate-last-result.json` 标记+exit 75→guard worker 见新鲜 75 标记**先带 hook 重试**拿真实结论,仍失败才按用户规则 --no-verify 兜底(此前"被杀"被误判成"他人代码失败"直接绕过真实门禁)。④降级判据第三兜底 `head-diff`:push-scope 与暂存区均为空时用 `git diff --name-only origin/main HEAD` 推断推送范围(实测 4 例"无可判定改动范围,无降级"硬拦);⑤push-state 死 pid 自愈:guard 启动发现 running+死 pid → 顺手改写 failed 终态。⑥**typecheck 再入守卫(2026-09-18 深夜,WMI 实测 24.6 万 cmd/2min fork 风暴根治)**:根包 `pnpm typecheck`=typecheck-full.mjs,pnpm -r 一旦把根包纳入执行即无限递归(每层再起 pnpm -r,指数级进程树,实测 ~2000 cmd/秒持续 26 分钟,即用户"一直闪弹 cmd"的主源);typecheck-full 已加 `IHUI_TYPECHECK_FULL_CHILD` 环境变量再入守卫(子层检测到立即 exit 0),勿删。
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\``式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟`#`像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人`--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged`判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证`--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace`返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒`--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- **C5 组件名档(2026-09-28 票㉗)**:根容器按**它自己的**组件名判类别,取证次序 = 元素自身名字/样式键 > 组件名 > 颜色实用类(`bg-card`/`text-*` 只是背景档,不是身份)。两条防假阳的规矩必须同时成立:① **只在 JSX 根启用**,子元素不吃(按钮装在面板里仍是 control);② 归属按 `declarationRanges()` + `ownerOfLine()` 取**最内层声明** —— 不加这一层,同文件内联的小组件会被外层组件名顶判(实测一次造出 79 处假阳,`ModelConfigDialog.tsx` 里的 `RatioSelector` 就是那一型)。名字给不出唯一角色 ⇒ **不启用并逐条报名**(清单在 `--json` 的 componentUndetermined 字段,`--all` 逐行打印),绝不猜一档;该档摘线时镜像 **T19** 必读红,不得被"合规 3211 处"冒充。
  同批修掉两处**取证越界**,它们的症状都不是"多算",而是"把能判的格写成判不出":`classStringsInLine` 原按"距 class 锚点 ≤60 字符连采 3 段字符串"收类名,会把**兄弟属性**的值(`data-testid="plan-review-panel"`)当类名收,凭空造出一条类别证据去撞 `bg-card` ⇒ HEAD 唯一那处 role-conflict 就是这么来的;现要求两段字面量之间只可能是 `cn(` / `,` / `||` / `?` / `+` 这类表达式连接符,出现 `=` / `>` / `}` 即停(镜像 **T21** 成对:兄弟属性不得采、同属性第二段不得漏)。`jsx-scope` 词法器原把 `return <X/>`(不套括号)的前一词元当标识符 ⇒ 判成泛型/比较式,整棵子树不建元素而只计 corrupt;现按"可接表达式的关键字"(return/yield/await/else/do/throw/in/of)放行,同时 **`a < b` 必须仍不建元素**(镜像 **T20** 成对,幽灵祖先比漏判更贵)。修后 HEAD 面 corrupt **443→344**(99 个文件的容器维回到可判),并同批清偿 C5 新露出的 6 处 panel 根容器(md/lg→xl)。
  问责入口 `pnpm check:radius-role`(走 `--strict`);`--strict` 现 exit 0 —— 未判定归零之后,这道门不再拒绝出合格证。**还有一格刻意不做**:`弱证据待裁` 那一族(类别信号只有颜色实用类、元素无名也非组件根)不得批量改档 —— 那是拿 20–70% 判错率的证据重新设计界面,正解是给它补真实身份,门只逐条报名。
  **三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 独立不可见行(注入时落在文件末尾)。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/**`仅隐写`(有可解码载荷但可见横幅整块不见)**/ `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ **`仅隐写`(有可解码载荷但可见横幅整块不见)** / `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧四个 npm 生命周期(`predev`/`dev:clean`/`dev:stable`/`prebuild`)仍无 owner 可声明、因而身份两元对它们无从对账(上面"如实登记未完成的部分"那条继续成立)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **按凭据存在性豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 注册时取到 **157**,并发会话可能挪号;**blocking**,`stagedTriggers=apps/api/src/plugins` + `apps/api/src/routes` + `apps/api/src/utils`,紧急跳过 `HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS=1`):`scripts/check-credential-presence-bypass.mjs`(G-459)拦的是**"把某个凭据类头/cookie 在场当成安全判定的结论"**这一型 —— 立因不是假想:`apps/api/src/plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return`,头名在场即对**全部路由**跳过 CSRF,任何进程带这个头(值随便填)就拿到免死金牌;#23 改成 `isVerifiedInternalMachineCall(request)`(唯一出口 `apps/api/src/utils/internal-principal.ts`,复用那一份 `secretsEqual`:SHA-256 定长摘要 + `timingSafeEqual`,配置缺失 fail-closed)。但**型还在** —— 下一个内部凭据族照样能写成 `if (request.headers['x-anything']) return`,而 typecheck / lint / 其余门全都不响。三条设计约束:① **判据必须覆盖门自己认可的写法**,条件里出现 `===`/`secretsEqual`/`timingSafeEqual`/`isVerified…(`/`verify…(`/`startsWith(`/`.has(` 一律放过(把修法形态判红等于逼人 `--no-verify`);② **凭据名不写死站点清单**,按**词元**比对(`x-id-token` 算,`tokenizer`/`hashtags` 不算),某站是否"已裁过"由台账 `scripts/data/credential-presence-exemptions.json` 判 —— 条目缺 `reason`/`reviewBy`、复核日过期、登记了而被审面没命中(清单腐烂)三种都是红;行内出口 `credential-presence-exempt: <原因>`(须带原因,只救本行或紧邻上行),该族**同笔**登记进守门 108 的 `FAMILY_LIFETIME_DAYS`(30 天,待偿的裁决债)—— 不登记就走 90 天默认档,而第一处真被写出的行内豁免还会被 108 的 E4 判成"新引入的未登记豁免族"(一道门自己的合法出口被邻居钉红 = 两道门互咬,`radius-role`/`border-ink` 同课),这条跨文件锁由镜像 T10 钉住,不靠人记得;③ **判不出就报名** —— 括号配平失败、取不出名字、体起始跨行算不出 ⇒ 落「未判定」逐条点名,`--strict` 下有未判定即 exit 2 拒绝出合格证。口径同 70/77/83/98/101/103/118/135:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给判死、取不到不回落、枚举到 0 个候选判死不记绿;清单与正文同面同轮;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(判据看代码面,而豁免标记写在注释里 ⇒ 标记在原文行上判)。手动 `node scripts/check-credential-presence-bypass.mjs [--staged|--worktree|--json|--strict|--self-test]`。**已知无尺子的一格**(如实登记,别误以为这里有门):"新内部凭据族又写成按存在性放行"在**本门射程之外**(它只覆盖 `apps/api` 三目录)目前零判据,判据草案与前置见 PROJECT_PLAN G-367。5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。本仓的 `t(name, cond)` 普遍用 `!!cond` 判通过,而 **`() => {...}` 永远是 truthy** —— 所以 `t('某判据有牙', () => A5(src).violations.length === 1)` 这条断言**从写下起从未求值**,账面记的是"绿",量到的是"这个函数对象存在"。门 150 一次量到 **8 条**(用例 73b 与 98–104,由同日票㉙ 写下并以"105 例自检全绿"入账)。更糟的是它们**同时**踩在第二个洞上:断言体用的 helper `A5` 声明在它们之后(TDZ),真求值会当场抛 —— "没跑过"与"跑不通"叠在一起,而账面只有 ✅。**修法三层同补,缺一层都会复发**:① harness 判 `typeof cond === 'function'` 直接记红并写明"cond 是函数 ⇒ 断言从未求值(应写成 `(() => {...})()`)`;② helper 提到所有用例之前定义;③ 用例一律 `(() => {...})()`立即求值。**为什么这比"少一条测试"严重**:一条恒绿的断言会替人做出"这一格已被看过"的判断,而它结构上不可能红 —— 与守门 70/76/81 记过的"判据失效的表现永远是安静"是同一条禁令,只是这次失效的不是判据而是**自检的记账**。取证:镜像 **T23** 钉 harness 的函数分支,变异回`if (cond)` 即红。
  自动出了角色档射程(现读数一律跑本门末行,本文不钉数字),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊` ——
  **现行口径(2026-09-28 票㉛ 已升为判红)**:胶囊这一侧要**三个条件同时成立**才判 —— ① 盒形**只取该元素自己的属性区**
  (到开标签闭合 `>` 为止,不含任何子节点)且**闭合可确定**;② **非正方盒**;③ **短边 ≥ 16px**。
  第 ① 条的由来:宽窗口 `elementWindow` 从开标签向下取 6 行,会把子节点的 `h-4 w-4` 算进父盒
  (`w-40` 下拉被量成 160×10、`h-9 px-4` 按钮被量成 16×36)⇒ 现由 `boxDimsOwn` 单独承担,判不出闭合即**只报名不判红**。
  第 ③ 条是装饰族的物理下限:细于 16px 的进度条 / 骨架行 / 指示点仍**只开队列**(§4 明令不得方档化,判红等于逼设计改方角)。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,**尚未接提交链**(2026-09-30 曾一度接进 runner,复跑后已摘出;重新接线由台账"解阻前置"管) · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **图片占位内容对账**〔scripts/check-image-placeholders.mjs · 手动问责档 warn,刻意不接提交链(问责入口 `pnpm check:image-placeholders`)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L519–L519
  - **落点(2026-09-30 改,用户拍板"项目产物不外流"):`git worktree add --detach .worktrees/wt-<任务名>`**(项目内 `.worktrees/`,已 gitignore;旧落点 `../IHUI-AI-wt-<任务名>` 即盘根散落目录,2026-09-30 已清理 13 个残留并立盘根卫生巡检档 `check-disk-root-hygiene.mjs`(warn,判"项目产物是否外流到盘根"与"worktree 落点是否合法"两维;出口 = git-guardian 每 30 分钟巡检 + `--strict` 手动问责,刻意不接提交链 —— 判的是机器/磁盘状态,提交者结构上满足不了)防回潮)。detached HEAD,不占分支名,不违反 §9b
  - **同批登记的两条实测事实已于 2026-10-01 双双结案(照旧文派单会去修不存在的东西)**:(a) **备份靠什么认证 —— 已答上**:唯一读口是 `Get-Content (node scripts/secret-path.mjs db-backup ihui-backup.txt) -First 1` 拿只读角色 `beifen` 的口令喂 `$env:PGPASSWORD`,`pg_dump -w` 显式拒绝交互提示;2026-09-24 那次"任何带控制台上下文的 `pg_dump`/`psql` 会阻塞在口令提示"是真现象,但成因是**当时脚本写死 `$dbPw=""` 而 pg_hba 已改 scram**,不是"这台机没有口令" —— 当天最后一份"成功"dump 其实是假的。当日实测两库产出 126.79 MB / 0.2 MB。兜底仍在:`apps/api/src/services/alert-check-service.ts` 巡检两条备份链目录。(b) **调度器判成败与重试 —— 已修,而且是三层**:① 按 `$LASTEXITCODE` 判成败、失败不再打"备份完成"(`f84ee1bae7`,09-24);② 失败后 30 分钟 ×4 重试(`43a0b7c01c`,10-01 14:16);③ **PG 就绪门** `pg_isready` 有界等 120s(`IHUI_PG_READY_TIMEOUT_SEC` 可覆),超时**先发告警再 `exit 1`**(`42d1b6d1c6`),根掉"开机竞态:PG 起来 1 秒后备份就跑"。**这三层里第三层的存在理由不是"等数据库",而是"别把没跑成写成跑完了"** —— 而今天真正吃掉 6 小时的机制在层之外:**PowerShell 在进程启动时把 `-File` 那份脚本读进内存,改了文件不动运行中的进程**。10-01 03:00 那班因机器未开机没跑,09:08"启动即备份"失败后按**当时还没有重试层的旧运行副本**一路睡到次日,第一份成功 dump 直到 15:15 才出现(日志 `[2026-10-01 15:08:06] 上轮备份失败,30 分钟后重试(第 1/4 次)` 是重试层落地并重启后的第一条)。**所以"改完生产脚本"的收尾动作是两件事,不是一件**:镜像运行副本(由守门 104 `check-prod-bundle-shadow.mjs` 在提交链上逐字节钉住)∧ **重启该服务**(无尺子可判的那一半,直到本条为止)。
- **服务二进制路径存续性对账**〔scripts/check-service-binary-paths.mjs · warn(runner 现值,勿据此升档)· HUSKY_SKIP_SERVICE_BINARY_PATHS〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L153–L153
- **服务二进制路径存续性对账**(`scripts/check-service-binary-paths.mjs`,定级 **warn**,紧急跳过 `HUSKY_SKIP_SERVICE_BINARY_PATHS=1`,问责跑 `--strict`):钉的是**"外部自升级把 Windows 服务的二进制路径烂掉"**这一型 —— RSSHub 就是被 `~\.workbuddy\binaries\node\versions\<新版本>` 整目录换掉后**静默停了 3 天,期间没有任何告警**,而全仓没有一处会问"服务声明要跑的那个 exe 今天还在不在"。判据只读三态:**可判存在 / 确认缺失 / 未判定**,枚举服务键后按 nssm 的 `Parameters\Application` 逐条判存在性;"一个 nssm 服务都没枚举到"与"派生失败"都是 `未判定`,**不得被读成通过**(把没判写成判过了是本仓最高频失效型,与门 94/103/118 各自记录过的同一条禁令)。**定级 warn 是设计前提而不是偷懒**:服务路径属**机器状态**,提交者结构上满足不了,挂 blocking 就是每台每次被逼 `--no-verify`、连带全部守门对该提交作废(§12e 同型)。**两条实测教训留在门体头注**:① nssm 的输出面是 **UTF-16**,按 UTF-8 读会得到乱码结论;② PowerShell `-Command` 里注册表路径的**反斜杠会被吃掉**(`HKLM:` + 带反斜杠的整串塌成 `HKLM:SYSTEMCurrentControlSetServices`)⇒ 整面恒空进而伪装成"未判定",必须用正斜杠,已由注释与测试双向钉住。本机(开发机)现读 **0 个 nssm 托管服务** ⇒ 这一维在该机只能报未判定,这是如实而不是故障。 **新增第四维「运行时代码新鲜度」(2026-10-01,`43a0b7c01c` 那轮重试层落地当天由真事故逼出)**:nssm 服务的进程启动时刻 vs 它 `-File` 加载的那份脚本的 mtime —— **进程比脚本旧 ⇒ 线上正在跑旧代码,而账面已修**(立因:PowerShell 在启动时把脚本读进内存,改文件不动运行中的进程;当天 09:08 那班备份失败后没触发 30 分钟重试,正是因为那个进程读到的是重试层落地前的旧运行副本,而**全链没有一处会喊**)。判据三态:`fresh` / `stale`(计入问题,并点名是哪份脚本、差了多少分钟)/ 未判定(服务非 RUNNING、pid=0、读不到启动时刻、AppParameters 里判不出加载目标、读不到脚本 mtime —— 五档各有各的措辞,不得并桶)。**射程边界必须跟读数一起说**:只判启动时加载的那一份,那份脚本运行时用 `&` 调的子脚本按调用时刻生效,不在本维;非 nssm 脚本型服务(直接跑 .exe 的导出器/监控件)整维未判定,所以"stale=0"不得被读成"所有服务都新"。取证:自检 82 条(RF-1…RF-10 成对)+ 镜像 22 例(例数一律现跑该命令末行,本文不钉数字),其中 **RF-5 用 .NET 权威字面量 `621355968000000000`(=Unix 纪元)核换算**—— 本门第一版把 0001 纪元错用成 FILETIME 的 1601 纪元,真机把所有服务打印成 3626 年,而拿同一个常数正反各算一遍的旧断言一路报绿;变异自证 = 把常数改回错值 ⇒ 自检 81/1、镜像 21/1 各自翻红且红的正是这一条。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,当前不在提交链上 · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190 —— 本行旧副本的元数据曾写着一个调度器文件名、一个阻塞级标记与一个应急跳过变量,那是 2026-09-30 一度挂上、复跑整轮后又被撤下的那条声称;撤下后端 89 的 R2 对**每一次**提交判红(声称挂上而五处权威点零命中),而恒红门的唯一结局是各会话跳钩子、连带链上其余全部对账对该提交作废(§12e/§12f 同型)。按现状改写、不删行(§1 禁止无声删除);该门的现行条目见本节内标着「尚未挂上提交链」的那几条,将来要不要重新挂上由台账那条解阻前置管。
- **不响应服务的自愈动作器 `scripts/heal-unresponsive-services.mjs`(常驻出口 + 动作,不是守门;由 git-guardian 每 30 分钟派发,不进提交链)** —— 2026-10-01 机主拍板的三层重启方案第三层(台账 G-978121)。它存在的理由是一条结构性事实:**本机 22 个服务里 21 个由 nssm 包装,SCM 的恢复动作对它们永远不会触发**(死的是子进程,`nssm.exe` 仍活着 ⇒ 服务状态一直 Running),而 nssm 自带的退出重启只覆盖"子进程退出"那一型;这台机真出过的两次事故都不在里面 —— 一次是**路径烂掉使子进程根本起不来**(RSSHub 静默停 3 天),一次是**进程在而端口不应答**。两种形态的共同点是"没有任何一行错误日志会红",所以判据不能是日志,只能是"端口该应答而不应答"。三条不可漂的写法:**① 判据只有一份** —— 应答/服务态一律取守门 153 `check-service-binary-paths.mjs --json` 的现值,动作器内**不得出现第二把尺子**(`net.connect` / `createConnection`),重启后的复验也回跑同一把门(镜像 T3 拿一段真调用先证明这把扫描有牙,再断言本文件干净 —— 反过来写就是恒绿式自我认证);**② 未判定绝不换动作** —— 门的应答维给不出结论(含"任务型无端口")时既不记故障也不清零,只报名;让一把量不到的尺子获得重启生产的权力,是本类动作器最坏的失效方向。**③ 名单是台账不是常量** —— `scripts/data/service-auto-restart.json` 每条必须 `name+reason+owner+reviewBy` 四件齐,缺字段/空表/JSON 坏 ⇒ **整轮拒动并 exit 2**(空表不等于"都没有"),复核日过期只挡那一条不挡整轮;刻意不入名单的写理由:IHUI-PG 与 IHUI-REDIS(一次 GC 或换流窗口的短暂不应答就重启数据库/缓存,风险与判据强度不成比例)、IHUI-PG-BACKUP(它"启动即备份",重启时机不该由拉起策略决定)、IHUI-GIT-GUARD(动作器就跑在这个执行体内,自重启=自杀)。动作节奏:连续 2 轮才动 + 每台 60 分钟冷却 + 每 60 分钟窗口全局最多 2 台 —— 防重启风暴(本仓 fork 风暴实测 24.6 万 cmd/2min,同一条教训)。健康也逐台报名:否则"7 台都应答"与"循环根本没跑到"在读数上完全同形。取证:`--self-test` 16 条 + §22c 镜像 `scripts/tests/heal-unresponsive-services.test.mjs` 9 例(T1 守护定义 + 两调用点各一枚、T2 未接线不得自称已接线、T6 四臂共用同一份累计状态使唯一变量是判据、变异自证 = 把「未判定」映射改成「不应答」则 T6 必红)。**生效边界与读数一律现跑**:`node scripts/heal-unresponsive-services.mjs`(只读)/ `--apply`;nssm 参数由进程启动时读取 ⇒ 改完不重启等于没改,重启时机归机主。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 当前无应急跳过变量(它不在钩子链上) · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
   `scripts/lib/gitdir.mjs:36-37` 解析 git 二进制的首选;`.qoder-cn` 承载本项目记忆与工作区状态)。
(被 `scripts/lib/gitdir.mjs:36-37` 当 git 二进制首选解析)不得搬,其 `logs`/`traces` 约 2GB 属该 IDE
`scripts/release-desktop-local.mjs:67,74` 需要 `%USERPROFILE%\.tauri\ihui-updater.key`,本机无 `.tauri`
- ⚠️ 本行是「错误码文本判分支对账」那条现行登记的**重复副本**，而它那份〔…〕元数据写的是「已接提交链 + 判红档 + 有跳过变量」，现读 `git grep -c check-error-code-not-text-matching HEAD --注册表 = **空** ⇒ 这句撒谎正被守门 89 的 R2 每次提交判红（红的是文档撒谎，不是门缺件）。现行条目见本节内那句开头为「尚未接提交链」的那一条（它写明该门为何被摘出、重新接线的前置，以及应急变量届时随条目声明）；保留本行只为不丢行（§12），勿照它派单。
- **错误码文本判分支对账**(**尚未接提交链** —— 本轮一度按 判红档 接进 注册表,现读整轮守门复跑后已摘出;重新接线由台账那条"解阻前置"管):`scripts/check-error-code-not-text-matching.mjs` 拦的是**"把文本当判据决定失败分支"**这一型 —— `if (err.message.includes('429')) retry()` 看着能跑,实际把重试/降级/拒绝的走向绑在第三方的一句英文上;上游改了措辞、或同一个码在另一条链路上换了文案,分支就静默换人,而 typecheck/lint/单测全都不会红(本仓"失效形态是安静"那一族)。四条算子同形认(`.includes/.indexOf/.lastIndexOf/.startsWith/.endsWith/.search/.match/.test` + 正则宿主 `/re/.test(x)`):参数必须是**字面量**才判(标识符实参一律放过,`RETRYABLE_CODES.includes(code)` 是正当形态);**不要求**必须摆在 if/三元里 —— "先算 severity 再按它分流"的两步式是本仓实际写法,要求分支上下文会让整型隐身,误伤面由"文本来源表达式 + 字面量实参"两道窄口径兜住。**取材遮噪方向与本仓多数门刻意相反**:只遮注释、**保留字符串**(本门要判的就是那个字面量,连字符串一起抹 = 对该型全盲而账面报"零违规" —— 最贵的假绿);两档共用 `scripts/lib/code-mask.mjs` 那一份分词器,任何一门不得留第二份遮噪。跨行拼接(算子在一行、字面量在下一行)**只数不判红但必须打印**("单行判据的已知盲区"不报名就会被读成"仓库里没有这种形态")。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"不回落、**枚举到 0 个候选判死不记绿**;棘轮锚点 = **该文件 HEAD 自身违规数**(HEAD 现读扫描 1036 文件 · 文本判分支 156 处:literal-in-text-op=132 / regex-receiver-on-text=18 / status-as-text=4 / regex-in-text-op=2 —— 存量当场判红就是恒红门)。行内出口 `error-code-exempt: <原因>`(**须带原因**,已登记进守门 108 的 **30 天**存活期档 —— 它豁免的是"待偿的补码债":出路只有给抛出方补 `ToolError(code,…)` 或把判定挪到结构化字段,不给 365 天结构性定性、也不走 90 天默认档)。**唯一出口是数据驱动的需求表** `OUTLET_REQUIREMENTS`(两处文件:`apps/cli/src/tools/index.ts` 的再导出 + `apps/cli/src/tools/failure-classification.ts` 的原声明),出口被摘线时本门判"没有出路"并**参与退出码**;本门自己还没入库那一格报 `self-not-landed`,不冒充 ok 也不冒充 stripped。**为什么摘出来(现读事实,不是预防措施)**:`git show HEAD:apps/cli/src/tools/failure-classification.ts` 取不到该文件 ⇒ 出口在位性判据在**干净 HEAD** 上判红,而它一红每台每次都被逼 `--no-verify`、一次绕过等于该提交上约 194 道守门全部作废(§12e/§12f 同型,这次是我自己接早了造的)。**判据与出口表都没有放宽** —— 判据要的出口是对的,只是还没入库;门体、镜像测试(11/11 RC=0)、`--self-test`(26 条 RC=0)、守门 108 那一档豁免全部原样留在面上。**一条落地过程本身值得抄的教训**:这台尺子由上一轮代理写完就停在轮次上限,自报"自检 26 条全绿 / 已注册 runner 第 162 项",主会话现读发现它**连解析都没过**(第 491 行 `ok('…', () => {` 用了普通箭头函数、闭合却按兄弟块的 IIFE 形态写成 `})())` ⇒ 整个文件 SyntaxError ⇒ 任何"跑过"的记录都是叙述),另有一处自检夹具**绕过权威入口**(`detectOutlet(text)` 单参调用,缺省回落到需求表第一行的名单 ⇒ 对第二行需求恒判 stripped;生产路径一直传 `req.exports`,所以是夹具错不是判据错)与一处镜像测试自身的 Windows 缺陷(`await import(绝对路径)` 未走 `pathToFileURL` ⇒ 恒抛,而抛错会被读成"这一格判过了";同文件第 27 行对门体已经走对了,唯独漏这一处)。三处都改完才有资格说判据存在。应急跳过变量在重新接线时随条目声明为 `该条目声明的应急变量`。
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\``式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟`#`像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人`--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged`判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证`--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace`返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒`--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- **错误码文本判分支对账**(⚠️ 本行是该门的**重复登记副本**,它替自己那一份旧决定背书,而那份决定早已被就地改写:本门当前**不在提交链**。这种"副本比现行条目更肯定"的形态,后果不是账面难看 —— 守门 89 的 R2 会按副本那句去查五处权威点,查不到即对**每一次**碰文档/脚本的提交判红,而恒红的唯一出路是各会话跳门、连带链上全部对账作废(§12e/§12f 同型)。现行条目见本节内"尚未接提交链"那一条,勿照本行执行;保留只为不丢行(§1 禁止无声删除)。)
- **小程序端「源 ↔ 派生产物」存续性对账**(105):`scripts/check-miniapp-generated.mjs`(**blocking;2026-09-25 由 warn 升档,前置条件实测达成**)—— 管四件"生成器造好了但没人跑"的派生产物:i18n 离线包 `remote-locales.gen.ts`、`static/images/icons/*.svg`、`LineIcon/icons.ts`、`assets/tabbar/*.png`。四条判据:**G1** 源里有而产物没有 ⇒ 判红(端上会缺词、图片 404 且**不报错**);**G2** 产物里有而源已无 ⇒ 默认只报数,`--strict` 才判红(删产物属 §7 删除安全);**G3** `icons.ts` 有键而 `icons/` 无同名 svg ⇒ **永不判红**(图标源在"图片全量外置 CDN"那轮被清空,判红等于造一道恒红门);**G4** 模板字符串拼出的动态路径结构上判不了 ⇒ 如实计数,绝不静默成"看起来全绿"。取材面同 70/77/83/98/101:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 只作人工与 dev 链逃生舱;取不到 ⇒ **exit 2「无法判定」**。定级 warn 的实证理由:立项当天全量面就有 8 处 G1 + 9 处孤儿在红,挂 blocking 会让每次提交都被逼 `--no-verify`(§12e 那型);**升 blocking 的前置条件是 G1 存量清零 —— 该前置已于 2026-09-25 实测达成并升档**(全量面 `未发现漏生成`、exit 0;R2 孤儿与 G3 图标按设计**不是** blocking 判据,默认只报数,所以升档不新增任何恒红面)。升档的直接起因是本会话自己改了 i18n 键名(`policyForbidsExecution` → `policySkipsApproval`)却没重生成离线包,门判出 4 处 B2 却因为是 warn 而**没拦住任何提交**,于是静默上线一版"小程序取不到该词"的产物 —— **warn 的代价不是"少一道闸",是"门判对了也没人被打断"**。修法单命令、不需要人工裁:`pnpm --filter @ihui/miniapp-taro gen:i18n`。取证 `--self-test`(15 例,成对正反例、零副作用);紧急跳过 `HUSKY_SKIP_MINIAPP_GENERATED=1`;它同时被 `dev-weapp.mjs` 消费 —— 判据要在有人跑它的那一刻才成立。
- **小程序端「源 ↔ 派生产物」存续性对账**(105):`scripts/check-miniapp-generated.mjs`(**warn,刻意不 blocking**)—— 管四件"生成器造好了但没人跑"的派生产物:i18n 离线包 `remote-locales.gen.ts`、`static/images/icons/*.svg`、`LineIcon/icons.ts`、`assets/tabbar/*.png`。四条判据:**G1** 源里有而产物没有 ⇒ 判红(端上会缺词、图片 404 且**不报错**);**G2** 产物里有而源已无 ⇒ 默认只报数,`--strict` 才判红(删产物属 §7 删除安全);**G3** `icons.ts` 有键而 `icons/` 无同名 svg ⇒ **永不判红**(图标源在"图片全量外置 CDN"那轮被清空,判红等于造一道恒红门);**G4** 模板字符串拼出的动态路径结构上判不了 ⇒ 如实计数,绝不静默成"看起来全绿"。取材面同 70/77/83/98/101:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 只作人工与 dev 链逃生舱;取不到 ⇒ **exit 2「无法判定」**。定级 warn 的实证理由:立项当天全量面就有 8 处 G1 + 9 处孤儿在红,挂 blocking 会让每次提交都被逼 `--no-verify`(§12e 那型);**升 blocking 的前置条件是 G1 存量清零**。取证 `--self-test`(15 例,成对正反例、零副作用);紧急跳过 `HUSKY_SKIP_MINIAPP_GENERATED=1`;它同时被 `dev-weapp.mjs` 消费 —— 判据要在有人跑它的那一刻才成立。
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。本仓的 `t(name, cond)` 普遍用 `!!cond` 判通过,而 **`() => {...}` 永远是 truthy** —— 所以 `t('某判据有牙', () => A5(src).violations.length === 1)` 这条断言**从写下起从未求值**,账面记的是"绿",量到的是"这个函数对象存在"。门 150 一次量到 **8 条**(用例 73b 与 98–104,由同日票㉙ 写下并以"105 例自检全绿"入账)。更糟的是它们**同时**踩在第二个洞上:断言体用的 helper `A5` 声明在它们之后(TDZ),真求值会当场抛 —— "没跑过"与"跑不通"叠在一起,而账面只有 ✅。**修法三层同补,缺一层都会复发**:① harness 判 `typeof cond === 'function'` 直接记红并写明"cond 是函数 ⇒ 断言从未求值(应写成 `(() => {...})()`)`;② helper 提到所有用例之前定义;③ 用例一律 `(() => {...})()`立即求值。**为什么这比"少一条测试"严重**:一条恒绿的断言会替人做出"这一格已被看过"的判断,而它结构上不可能红 —— 与守门 70/76/81 记过的"判据失效的表现永远是安静"是同一条禁令,只是这次失效的不是判据而是**自检的记账**。取证:镜像 **T23** 钉 harness 的函数分支,变异回`if (cond)` 即红。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧四个 npm 生命周期(`predev`/`dev:clean`/`dev:stable`/`prebuild`)仍无 owner 可声明、因而身份两元对它们无从对账(上面"如实登记未完成的部分"那条继续成立)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,**尚未接提交链**(2026-09-30 曾一度接进 runner,复跑后已摘出;重新接线由台账"解阻前置"管,现行定级与取材口径见下方完整原文条;重新接线时才随条目声明应急跳过变量)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **按凭据存在性豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 注册时取到 **157**,并发会话可能挪号;**blocking**,`stagedTriggers=apps/api/src/plugins` + `apps/api/src/routes` + `apps/api/src/utils`,紧急跳过 `HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS=1`):`scripts/check-credential-presence-bypass.mjs`(G-459)拦的是**"把某个凭据类头/cookie 在场当成安全判定的结论"**这一型 —— 立因不是假想:`apps/api/src/plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return`,头名在场即对**全部路由**跳过 CSRF,任何进程带这个头(值随便填)就拿到免死金牌;#23 改成 `isVerifiedInternalMachineCall(request)`(唯一出口 `apps/api/src/utils/internal-principal.ts`,复用那一份 `secretsEqual`:SHA-256 定长摘要 + `timingSafeEqual`,配置缺失 fail-closed)。但**型还在** —— 下一个内部凭据族照样能写成 `if (request.headers['x-anything']) return`,而 typecheck / lint / 其余门全都不响。三条设计约束:① **判据必须覆盖门自己认可的写法**,条件里出现 `===`/`secretsEqual`/`timingSafeEqual`/`isVerified…(`/`verify…(`/`startsWith(`/`.has(` 一律放过(把修法形态判红等于逼人 `--no-verify`);② **凭据名不写死站点清单**,按**词元**比对(`x-id-token` 算,`tokenizer`/`hashtags` 不算),某站是否"已裁过"由台账 `scripts/data/credential-presence-exemptions.json` 判 —— 条目缺 `reason`/`reviewBy`、复核日过期、登记了而被审面没命中(清单腐烂)三种都是红;行内出口 `credential-presence-exempt: <原因>`(须带原因,只救本行或紧邻上行),该族**同笔**登记进守门 108 的 `FAMILY_LIFETIME_DAYS`(30 天,待偿的裁决债)—— 不登记就走 90 天默认档,而第一处真被写出的行内豁免还会被 108 的 E4 判成"新引入的未登记豁免族"(一道门自己的合法出口被邻居钉红 = 两道门互咬,`radius-role`/`border-ink` 同课),这条跨文件锁由镜像 T10 钉住,不靠人记得;③ **判不出就报名** —— 括号配平失败、取不出名字、体起始跨行算不出 ⇒ 落「未判定」逐条点名,`--strict` 下有未判定即 exit 2 拒绝出合格证。口径同 70/77/83/98/101/103/118/135:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给判死、取不到不回落、枚举到 0 个候选判死不记绿;清单与正文同面同轮;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(判据看代码面,而豁免标记写在注释里 ⇒ 标记在原文行上判)。手动 `node scripts/check-credential-presence-bypass.mjs [--staged|--worktree|--json|--strict|--self-test]`。**已知无尺子的一格**(如实登记,别误以为这里有门):"新内部凭据族又写成按存在性放行"在**本门射程之外**(它只覆盖 `apps/api` 三目录)目前零判据,判据草案与前置见 PROJECT_PLAN G-367。5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。
  **现行口径(2026-09-28 票㉛ 已升为判红)**:胶囊这一侧要**三个条件同时成立**才判 —— ① 盒形**只取该元素自己的属性区**
  第 ③ 条是装饰族的物理下限:细于 16px 的进度条 / 骨架行 / 指示点仍**只开队列**(§4 明令不得方档化,判红等于逼设计改方角)。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,**尚未接提交链**(2026-09-30 曾一度接进 runner,复跑后已摘出;重新接线由台账"解阻前置"管)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,当前不在提交链上(2026-09-30 曾一度注册,复跑整轮检查后已撤下;要重新挂上,先清台账那条"解阻前置"并同枚声明应急跳过变量)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,当前不在提交链上〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190 —— 本行旧副本的元数据曾写着一个调度器文件名、一个阻塞级标记与一个应急跳过变量,那是 2026-09-30 一度挂上、复跑整轮后又被撤下的那条声称;撤下后端 89 的 R2 对**每一次**提交判红(声称挂上而五处权威点零命中),而恒红门的唯一结局是各会话跳钩子、连带链上其余全部对账对该提交作废(§12e/§12f 同型)。按现状改写、不删行(§1 禁止无声删除);该门的现行条目见本节内标着「尚未挂上提交链」的那几条,将来要不要重新挂上由台账那条解阻前置管。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 当前无应急跳过变量(它不在钩子链上)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,尚未接提交链(重新接线由台账"解阻前置"管)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
    **三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 独立不可见行(注入时落在文件末尾)。
    **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/**`仅隐写`(有可解码载荷但可见横幅整块不见)**/ `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
    **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ **`仅隐写`(有可解码载荷但可见横幅整块不见)** / `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
  自动出了角色档射程(现读数一律跑本门末行,本文不钉数字),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊`——
  (到开标签闭合`>`为止,不含任何子节点)且**闭合可确定**;② **非正方盒**;③ **短边 ≥ 16px**。
  第 ① 条的由来:宽窗口`elementWindow`从开标签向下取 6 行,会把子节点的`h-4 w-4` 算进父盒
  (`w-40` 下拉被量成 160×10、`h-9 px-4`按钮被量成 16×36)⇒ 现由`boxDimsOwn` 单独承担,判不出闭合即**只报名不判红**。
  - **落点(2026-09-30 改,用户拍板"项目产物不外流"):`git worktree add --detach .worktrees/wt-<任务名>`**(项目内 `.worktrees/`,已 gitignore;旧落点 `../IHUI-AI-wt-<任务名>` 即盘根散落目录,2026-09-30 已清理 13 个残留并立盘根卫生巡检档 `check-disk-root-hygiene.mjs`(warn,刻意不接提交链)防回潮)。detached HEAD,不占分支名,不违反 §9b
- **通道"可用"必须被量到,不能靠"没人记下失败"(2026-10-02 立,补 §5e 唯一无人答的那一维)**:
  告警寄不出去最坏的形态不是响亮地报错,而是**安静地少一封**。旧判据链只能回答"有没有生产者写下过投递失败标记",
  而 `check-credential-health` 那份 UNDELIVERED 标记**会被下一次任意成功投递清掉** ⇒ "某条告警从来没寄出去过"
  在标记被清的那一刻起彻底隐形(2026-10-01 实测:`孤儿删除引用巡检命中` 台账里记着 `delivered:false`,
  而巡检 P8 同一轮打印"无未送达标记")。现补两维,都在 `scripts/check-ops-patrol.mjs`(告警档,刻意不进提交链 ——
  它判的是机器状态,挂 blocking 就是每台每次被逼 `--no-verify` 连带废掉全部守门,§12e):
  ① **P9 邮件通道活性**:每 24h 真探一次 `node apps/api/scripts/notify-deploy-failure.ts --probe` =
  SMTP 连接 + EHLO + 认证后立即关闭(nodemailer `transporter.verify()`,**零投递**);Resend 没有零投递的
  核验出口 ⇒ 如实落 `undetermined`,要确证得显式 `--probe-deliver` 真发一封。退出码三档 **0=至少一条通道
  确证可用 / 1=确证失败 / 2=未判定**,而 2 既不得读成"通道坏了"也不得读成"通道好了"。
  ② **P10 未送达欠账**:扫 `.workbuddy/*notify-state*.json`(生产者侧投递台账,巡检**只读不改**),凡
  `delivered:false` 且过了 30 分钟宽限期 ⇒ 逐条点名"身份 + 完整 40 位指纹 + 那一笔的绝对时刻"。
  这条队列的出口是 `scripts/data/undelivered-debt-acks.json` 的**五件套** `alert + fp + atTs + reason + owner + reviewBy`:
  缺字段 / 到期 / 时刻不符都不放行。**`atTs` 这一维是必须的** —— 判据明令点名串只写绝对时刻(发信指纹吃 detail,
  写"已挂 N 分钟"会逐轮生成新指纹 = 同一件故障每轮一封新信),所以同一件故障**复发时指纹往往就是同一个**,
  少了时刻这一档就等于一次裁决变成一个告警名的永久静音(机主明令「抑制告警必须有终态」同一条禁令)。
  两条纪律一并写下:**(a) 不回补投递旧告警** —— 对一件已复验消除的故障补发一封旧告警,按机主明令
  「重复告警就是缺陷」是新噪声而不是清偿;要补发的只有"故障仍在"的那种,而判据只负责把它摆到人前。
  **(b) 裁决账的 owner 必须写真实裁决人**:值守会话能复验、能落证据,但"这件事我不需要补发"是机主的决定,
  代理不得替他签这个字(本机首跑的 2 条欠账因此把 owner 写成值守会话并附复验命令,到期自动回红)。
  - **那一格已补上(2026-10-02 落地;读数一律现跑,别照本行派单)**:上面"四处没法声明 owner"这件事的事实改了 ——
    ① 正常 `pnpm dev`:锁的持有者改成 `scripts/dev-with-warmup.mjs` **自己**(它先 `acquire({ownerPid: process.pid, token})`
    再起 next dev),并派一个 detached 的 `deploy-lock.mjs heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;
    退出走 `quit()` 带凭据交还。**共存档**(别的 dev 已持锁)时既不派心跳也不释放 —— 归属由 `lockOwnedBy(token)`
    现读,不靠"我调过 acquire 就算我的"(那一档会让心跳替别人的锁永久续期)。
    ② `dev:clean` / `dev:stable`:它们的 shell 与 next dev 同生死,是有效持有者,现带 `--with-heartbeat`。
    ③ `pnpm --filter @ihui/web build`:改走 `deploy-lock.mjs run --mode build -- <原命令一字不改>`。
    **实测的旧形态**是"prebuild 里 acquire,而 prebuild 是另一条生命周期脚本、它的 shell 在 build 开始前就退了"
    ⇒ 整个 `next build` 期间那把锁的主人是个死进程,按现行"悬挂锁不限锁龄立即抢占"这条谁都能秒抢:
    **账面有锁,实际没保护**(8-09 那两个构建同时写 `.next` ⇒ 8801 短暂 502 的那一型在这条入口上仍可发生)。
    `run` 把"拿锁 + 跑命令 + 交还"收进同一个进程:命令退出码原样传播(被信号终止按 128+N 记失败,
    不许 npm/CI 把"没构建出来"读成"构建成功"),命令根本起不来(rc=127)也必须把锁收口。
    **再入守卫** = 环境变量 `IHUI_DEPLOY_LOCK_HELD`(由 `run` 注入子进程):上层已持这把锁时子层**不再 acquire**
    —— 同 mode 互斥会让它等自己直到超时,那是一台纯粹自造的部署冻结(与根 `IHUI_TYPECHECK_FULL_CHILD` 那条
    fork 风暴守卫同一条设计)。`--with-heartbeat` 缺省关闭,且关闭时**一次额外读盘、一次派生都不做**
    —— 现存调用方(build-next-prod.ps1 等)行为逐字不变,由自检 AH5 + 镜像钉住。
    **仍开着的一格(如实登记,不得读成已全覆盖)**:`dev` 链里 `clean-turbopack-cache.mjs` 跑在启动器拿锁**之前**,
    那一段清缓存与并发构建的重叠窗口与本票改动前同样存在(改前那把 predev 锁也覆盖不到它);
    而部署环走的是 `deploy/win/.deploy.lock` —— 与项目根 `.deploy.lock` 是**两把不同的锁**(别混),
    它自带的判活/心跳不受本票影响。取证:`node scripts/deploy-lock.mjs --self-test`(HB/AH/RU 三族,条数以末行为准)
    + `node --test scripts/tests/deploy-lock.test.mjs`(HB-M1…M9,含"摘掉再入守卫就会白等超时"的端到端臂)。
- **错误码文本判分支对账**〔注册表· scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 判红档 · 该条目声明的应急变量〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**〔门体 scripts/check-error-code-not-text-matching.mjs · 遮罩 lib/code-mask.mjs · **尚未挂上提交链**(本行旧副本原先写有调度器文件名、blocking 级标记与一个应急跳过变量,那是 2026-09-30 一度注册、复跑整轮后已撤下的那条声称;撤下后三者均不成立,而 89 的 R2 会因这句谎对**每一次**提交判红 ⇒ 恒红门,§12e/§12f。按现状改写、不删行,现行条目见本节标着「尚未接提交链」的那几条;要不要重新挂上由台账"解阻前置"管)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**(**尚未接提交链** —— 本轮一度按 blocking 接进 `scripts/guardian-runner.mjs`,现读整轮守门复跑后已摘出;重新接线由台账那条"解阻前置"管):`scripts/check-error-code-not-text-matching.mjs` 拦的是**"把文本当判据决定失败分支"**这一型 —— `if (err.message.includes('429')) retry()` 看着能跑,实际把重试/降级/拒绝的走向绑在第三方的一句英文上;上游改了措辞、或同一个码在另一条链路上换了文案,分支就静默换人,而 typecheck/lint/单测全都不会红(本仓"失效形态是安静"那一族)。四条算子同形认(`.includes/.indexOf/.lastIndexOf/.startsWith/.endsWith/.search/.match/.test` + 正则宿主 `/re/.test(x)`):参数必须是**字面量**才判(标识符实参一律放过,`RETRYABLE_CODES.includes(code)` 是正当形态);**不要求**必须摆在 if/三元里 —— "先算 severity 再按它分流"的两步式是本仓实际写法,要求分支上下文会让整型隐身,误伤面由"文本来源表达式 + 字面量实参"两道窄口径兜住。**取材遮噪方向与本仓多数门刻意相反**:只遮注释、**保留字符串**(本门要判的就是那个字面量,连字符串一起抹 = 对该型全盲而账面报"零违规" —— 最贵的假绿);两档共用 `scripts/lib/code-mask.mjs` 那一份分词器,任何一门不得留第二份遮噪。跨行拼接(算子在一行、字面量在下一行)**只数不判红但必须打印**("单行判据的已知盲区"不报名就会被读成"仓库里没有这种形态")。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"不回落、**枚举到 0 个候选判死不记绿**;棘轮锚点 = **该文件 HEAD 自身违规数**(HEAD 现读扫描 1036 文件 · 文本判分支 156 处:literal-in-text-op=132 / regex-receiver-on-text=18 / status-as-text=4 / regex-in-text-op=2 —— 存量当场判红就是恒红门)。行内出口 `error-code-exempt: <原因>`(**须带原因**,已登记进守门 108 的 **30 天**存活期档 —— 它豁免的是"待偿的补码债":出路只有给抛出方补 `ToolError(code,…)` 或把判定挪到结构化字段,不给 365 天结构性定性、也不走 90 天默认档)。**唯一出口是数据驱动的需求表** `OUTLET_REQUIREMENTS`(两处文件:`apps/cli/src/tools/index.ts` 的再导出 + `apps/cli/src/tools/failure-classification.ts` 的原声明),出口被摘线时本门判"没有出路"并**参与退出码**;本门自己还没入库那一格报 `self-not-landed`,不冒充 ok 也不冒充 stripped。**为什么摘出来(现读事实,不是预防措施)**:`git show HEAD:apps/cli/src/tools/failure-classification.ts` 取不到该文件 ⇒ 出口在位性判据在**干净 HEAD** 上判红,而它一红每台每次都被逼 `--no-verify`、一次绕过等于该提交上约 194 道守门全部作废(§12e/§12f 同型,这次是我自己接早了造的)。**判据与出口表都没有放宽** —— 判据要的出口是对的,只是还没入库;门体、镜像测试(11/11 RC=0)、`--self-test`(26 条 RC=0)、守门 108 那一档豁免全部原样留在面上。**一条落地过程本身值得抄的教训**:这台尺子由上一轮代理写完就停在轮次上限,自报"自检 26 条全绿 / 已注册 runner 第 162 项",主会话现读发现它**连解析都没过**(第 491 行 `ok('…', () => {` 用了普通箭头函数、闭合却按兄弟块的 IIFE 形态写成 `})())` ⇒ 整个文件 SyntaxError ⇒ 任何"跑过"的记录都是叙述),另有一处自检夹具**绕过权威入口**(`detectOutlet(text)` 单参调用,缺省回落到需求表第一行的名单 ⇒ 对第二行需求恒判 stripped;生产路径一直传 `req.exports`,所以是夹具错不是判据错)与一处镜像测试自身的 Windows 缺陷(`await import(绝对路径)` 未走 `pathToFileURL` ⇒ 恒抛,而抛错会被读成"这一格判过了";同文件第 27 行对门体已经走对了,唯独漏这一处)。三处都改完才有资格说判据存在。应急跳过变量在重新接线时随条目声明为 `HUSKY_SKIP_ERROR_CODE_TEXT_MATCHING`。
- **错误码文本判分支对账**〔scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 手动档,当前不在提交链上(曾一度挂上、复跑整轮后被撤下;现行条目见本节内标着「尚未接提交链」的那几条)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**(**尚未接提交链** —— 本轮一度按 blocking 接进 `scripts/guardian-runner.mjs`,现读整轮守门复跑后已摘出;重新接线由台账那条"解阻前置"管):`scripts/check-error-code-not-text-matching.mjs` 拦的是**"把文本当判据决定失败分支"**这一型 —— `if (err.message.includes('429')) retry()` 看着能跑,实际把重试/降级/拒绝的走向绑在第三方的一句英文上;上游改了措辞、或同一个码在另一条链路上换了文案,分支就静默换人,而 typecheck/lint/单测全都不会红(本仓"失效形态是安静"那一族)。四条算子同形认(`.includes/.indexOf/.lastIndexOf/.startsWith/.endsWith/.search/.match/.test` + 正则宿主 `/re/.test(x)`):参数必须是**字面量**才判(标识符实参一律放过,`RETRYABLE_CODES.includes(code)` 是正当形态);**不要求**必须摆在 if/三元里 —— "先算 severity 再按它分流"的两步式是本仓实际写法,要求分支上下文会让整型隐身,误伤面由"文本来源表达式 + 字面量实参"两道窄口径兜住。**取材遮噪方向与本仓多数门刻意相反**:只遮注释、**保留字符串**(本门要判的就是那个字面量,连字符串一起抹 = 对该型全盲而账面报"零违规" —— 最贵的假绿);两档共用 `scripts/lib/code-mask.mjs` 那一份分词器,任何一门不得留第二份遮噪。跨行拼接(算子在一行、字面量在下一行)**只数不判红但必须打印**("单行判据的已知盲区"不报名就会被读成"仓库里没有这种形态")。口径同 70/77/83/98/101/103/118:全量判 **HEAD blob**、`--staged` 判**索引 blob**、`--worktree` 仅人工、两面旗同给 exit 2、取不到判"无法判定"不回落、**枚举到 0 个候选判死不记绿**;棘轮锚点 = **该文件 HEAD 自身违规数**(HEAD 现读扫描 1036 文件 · 文本判分支 156 处:literal-in-text-op=132 / regex-receiver-on-text=18 / status-as-text=4 / regex-in-text-op=2 —— 存量当场判红就是恒红门)。行内出口 `error-code-exempt: <原因>`(**须带原因**,已登记进守门 108 的 **30 天**存活期档 —— 它豁免的是"待偿的补码债":出路只有给抛出方补 `ToolError(code,…)` 或把判定挪到结构化字段,不给 365 天结构性定性、也不走 90 天默认档)。**唯一出口是数据驱动的需求表** `OUTLET_REQUIREMENTS`(两处文件:`apps/cli/src/tools/index.ts` 的再导出 + `apps/cli/src/tools/failure-classification.ts` 的原声明),出口被摘线时本门判"没有出路"并**参与退出码**;本门自己还没入库那一格报 `self-not-landed`,不冒充 ok 也不冒充 stripped。**为什么摘出来(现读事实,不是预防措施)**:`git show HEAD:apps/cli/src/tools/failure-classification.ts` 取不到该文件 ⇒ 出口在位性判据在**干净 HEAD** 上判红,而它一红每台每次都被逼 `--no-verify`、一次绕过等于该提交上约 194 道守门全部作废(§12e/§12f 同型,这次是我自己接早了造的)。**判据与出口表都没有放宽** —— 判据要的出口是对的,只是还没入库;门体、镜像测试(11/11 RC=0)、`--self-test`(26 条 RC=0)、守门 108 那一档豁免全部原样留在面上。**一条落地过程本身值得抄的教训**:这台尺子由上一轮代理写完就停在轮次上限,自报"自检 26 条全绿 / 已注册 runner 第 162 项",主会话现读发现它**连解析都没过**(第 491 行 `ok('…', () => {` 用了普通箭头函数、闭合却按兄弟块的 IIFE 形态写成 `})())` ⇒ 整个文件 SyntaxError ⇒ 任何"跑过"的记录都是叙述),另有一处自检夹具**绕过权威入口**(`detectOutlet(text)` 单参调用,缺省回落到需求表第一行的名单 ⇒ 对第二行需求恒判 stripped;生产路径一直传 `req.exports`,所以是夹具错不是判据错)与一处镜像测试自身的 Windows 缺陷(`await import(绝对路径)` 未走 `pathToFileURL` ⇒ 恒抛,而抛错会被读成"这一格判过了";同文件第 27 行对门体已经走对了,唯独漏这一处)。三处都改完才有资格说判据存在。应急跳过变量在重新接线时随条目声明为 `当前无应急跳过变量(它不在钩子链上)`。
- **错误码文本判分支对账**〔scripts/guardian-runner.mjs · scripts/check-error-code-not-text-matching.mjs · scripts/lib/code-mask.mjs · 尚未接提交链(刻意,判据与出口表见正文)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- **错误码文本判分支对账**〔门体 scripts/check-error-code-not-text-matching.mjs · 遮罩 lib/code-mask.mjs · **尚未挂上提交链**(本行旧副本原先写有调度器文件名、阻塞级标记与一个应急跳过变量,那是 2026-09-30 一度注册、复跑整轮后已撤下的那条声称;撤下后三者均不成立,而 89 的 R2 会因这句谎对**每一次**提交判红 ⇒ 恒红门,§12e/§12f。按现状改写、不删行,现行条目见本节标着「尚未接提交链」的那几条;要不要重新挂上由台账"解阻前置"管)〕……完整原文: .ihui-agent/archive/AGENTS_slimmed-sliceA-2026-09-30.md L190–L190
- ⚠️ 本行是「错误码文本判分支对账」的**重复副本**(2026-10-02 union 合并把本侧已就地改写的旧文本带回,与本节内同门现行条目逐字同源);旧元数据不再逐字复存(避免与 R2 判据词表撞形);不删行(§1),正文以现行条目为准。
IHUI-AI 是全栈 AI 平台(TS Monorepo + pnpm workspace + Turborepo),9 端清单(2026-10-02 增 apps/assistant):
- `apps/assistant`(智汇助手:独立 Tauri 2 桌面端,2026-10-02 整合自 MIT 开源项目 Trae-workbuddyAssistant,自带 Python/PS sidecar,数据目录独立)
  - **这一段的事实(2026-10-02 按当次 `apps/web/package.json` 现读改写,不得再照旧文派单)**:旧句"`predev` / `dev:clean` / `dev:stable` / `prebuild` 四处**没法声明 owner**…dev 侧靠硬上限兜底、不是已有心跳"已经整体不成立 —— 今天这四条里没有一条拿锁:`predev` = 陈旧 stash 巡检、`prebuild` = extension 构建 + 下载同步(都不再出现 `deploy-lock.mjs acquire`);三条 dev 入口(`dev` / `dev:clean` / `dev:stable`)统一走 `scripts/dev-with-warmup.mjs`,由启动器**自己** `acquire({ownerPid: process.pid, token})` 再起 `next dev`,并派一个 detached 的 `deploy-lock.mjs heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;`pnpm --filter @ihui/web build` 改走 `deploy-lock.mjs run --mode build`(拿锁、跑命令、交还同进程,命令退出码原样传播)。所以事实是:**build 侧与 dev 侧都已有活着的主人 + 心跳**,剩下的不是"没心跳"而是"这一维没有守门" —— 它判的是锁此刻归谁,属机器状态,挂进提交链就是每台每次被逼 `--no-verify`,故刻意不接(§12e 同型)。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧**已无「拿锁却声明不出 owner」的 npm 生命周期** —— `predev` 只跑陈旧 stash 巡检、`prebuild` 只做 extension 构建与下载同步,两者今天都不再出现 `deploy-lock.mjs acquire`;`dev` / `dev:clean` / `dev:stable` 三条入口统一走 `scripts/dev-with-warmup.mjs`,由启动器自己 `acquire({ ownerPid: process.pid, token })` 再起 `next dev`,并派一个 detached 的 `heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;`pnpm --filter @ihui/web build` 那把走 `deploy-lock.mjs run --mode build`(拿锁、跑命令、交还在同一个进程里,命令退出码原样传播)。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧**已无「拿锁却声明不出 owner」的 npm 生命周期** —— `predev` 只跑陈旧 stash 巡检、`prebuild` 只做 extension 构建与下载同步,两者今天都不再出现 `deploy-lock.mjs acquire`;`dev` / `dev:clean` / `dev:stable` 三条入口统一走 `scripts/dev-with-warmup.mjs`,由启动器自己 `acquire({ ownerPid: process.pid, token })` 再起 `next dev`,并派一个 detached 的 `heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;`pnpm --filter @ihui/web build` 那把走 `deploy-lock.mjs run --mode build`(拿锁、跑命令、交还在同一个进程里,命令退出码原样传播)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧**已无「拿锁却声明不出 owner」的 npm 生命周期** —— `predev` 只跑陈旧 stash 巡检、`prebuild` 只做 extension 构建与下载同步,两者今天都不再出现 `deploy-lock.mjs acquire`;`dev` / `dev:clean` / `dev:stable` 三条入口统一走 `scripts/dev-with-warmup.mjs`,由启动器自己 `acquire({ ownerPid: process.pid, token })` 再起 `next dev`,并派一个 detached 的 `heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;`pnpm --filter @ihui/web build` 那把走 `deploy-lock.mjs run --mode build`(拿锁、跑命令、交还在同一个进程里,命令退出码原样传播)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧**已无「拿锁却声明不出 owner」的 npm 生命周期** —— `predev` 只跑陈旧 stash 巡检、`prebuild` 只做 extension 构建与下载同步,两者今天都不再出现 `deploy-lock.mjs acquire`;`dev` / `dev:clean` / `dev:stable` 三条入口统一走 `scripts/dev-with-warmup.mjs`,由启动器自己 `acquire({ ownerPid: process.pid, token })` 再起 `next dev`,并派一个 detached 的 `heartbeat --watch-pid <启动器 pid>` 每 30s 续 `ts`;`pnpm --filter @ihui/web build` 那把走 `deploy-lock.mjs run --mode build`(拿锁、跑命令、交还在同一个进程里,命令退出码原样传播)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
    **那一格已于 2026-10-02 收口**:`dev` / `dev:clean` / `dev:stable` 三条入口统一成"唯一一条命令 = 启动器",
    改 next dist 的预取守卫、缓存治理与 `--purge` 整清全部排在 acquire 之后、spawn 之前 ⇒ 既在锁内、又在 next dev 之前(不违反「严禁运行中清缓存」那条铁律)。
    + `node --test scripts/tests/deploy-lock.test.mjs`(HB-M1…M10:M10 是"破坏性步骤必须在 acquire 之后、next dev 之前"的顺序形状锁,RU6d/HB-M8 各带"退回旧形态必读红"的反向对照)。
- **计划文档 commit 引用可兑现性对账**(`scripts/check-plan-sha-resolvable.mjs`,**warn 级、刻意不接提交链**,手动问责 `pnpm check:plan-sha`;紧急跳过无需——它不在钩子链上):量的是「`PROJECT_PLAN.md` 里那些 `提交 \`abc1234\``式引用**今天还能不能真的问到对象**」。**三态绝不并桶**:可解析 / **取不到**(对象库里没有 = 指针已腐烂)/ **形状判不出**(全数字 8 位像日期或 CI run id、长度 13–39 像 integrity 片段、紧跟`#`像色值)—— 第三种既有独立计数也有锁,**不得**并进腐烂(那是把"没看清"写成"有问题"),也**不得**并进通过(那是把"没看清"写成"没问题")。立项现读:可解析 999 / 取不到 61 枚·97 次出现 / 判不出 58 枚·115 次;而**票面那句"44 枚"在任何口径下都不是 44**(全量 61、出现 97、只算有锚点 49、只算反引号+提交式 46、长度≥9 的 39、≤8 的 22)⇒ 派单与问责一律以命令末行现读为准,不采台账数字。**为什么 warn**:"引用能否兑现"与"本次提交改了什么"无关,挂 blocking 就是一台恒红门,唯一结局是逼人`--no-verify` 连带废掉全部守门(§12e 同型);`--strict` 现读 exit 1 —— 那是它**拒绝出具合格证**,不是仓库故障。**明令禁止**`--update` 式"把当前读数冻成基线":那等于给腐烂发通行证。口径同 70/77/83/98/101/103/118(全量判 HEAD blob、`--staged`判索引、两旗同给 exit 2、取不到判"无法判定"、枚举到 0 条判死),取证`--self-test` 32 条 + §22c 镜像 10 例。**写尺子过程中由自检抓出并锁死的三处判据自身缺陷**(都会以"数字看着合理"的形态存在,只有构造面能抓):`startsWith` 判序写反 ⇒ 全仓短 sha 静默落未判定;`selectFace`返回对象而非面名 ⇒ 三面同读 HEAD;索引档漏前导冒号 ⇒`--staged` 恒"取不到"。**未闭环的一格**:腐烂桶里混着形状相同但语义不同的东西(`6b8cd0f6` 实为 user_id、`c12612dc` 实为 adb 设备号),按形状判不出 ⇒ 需逐条人工定性才能从桶里摘出;`.ihui-agent/archive/*` 未纳入射程。
- **守门 134 第三次补强(2026-09-27,承上一条 B2/扩键登记,数字一律现读勿照抄)**:把 **裸 SQL 写链**并进写链池。病灶的载体不止 drizzle 方法链 —— `db.execute(sql\`DELETE FROM … WHERE …\`)`也是写;此前判据只把`.delete()/.update()`当写链,裸 SQL 只当"证据"(认里面的`RETURNING`字样),于是"SQL 发了写、命中 0 行、响应回布尔 true"这一格无人看守。现由`findRawSqlWriteChains` 一份实现同时供**写链判定**与**RETURNING 证据**(`hasRawSqlReturning` 降为它的投影,禁止两处各扫一遍),**V 与 B1 同看这一维**;B1 只认**缺 RETURNING** 的那一条,带 RETURNING 的按证据放过 ⇒ 不新增恒红面;括号配平不到时"同体有 ack 才落未判定(U3,`--strict` 拒绝出合格证),否则只报数"。
- ✅ **自检 / 镜像 harness 的 "cond" 必须是已求值的布尔,不是函数**(2026-09-28 立,门 150 票㉛ 实测)。本仓的 `t(name, cond)` 普遍用 `!!cond` 判通过,而 **`() => {...}` 永远是 truthy** —— 所以 `t('某判据有牙', () => A5(src).violations.length === 1)` 这条断言**从写下起从未求值**,账面记的是"绿",量到的是"这个函数对象存在"。门 150 一次量到 **8 条**(用例 73b 与 98–104,由同日票㉙ 写下并以"105 例自检全绿"入账)。更糟的是它们**同时**踩在第二个洞上:断言体用的 helper `A5` 声明在它们之后(TDZ),真求值会当场抛 —— "没跑过"与"跑不通"叠在一起,而账面只有 ✅。**修法三层同补,缺一层都会复发**:① harness 判 `typeof cond === 'function'` 直接记红并写明"cond 是函数 ⇒ 断言从未求值(应写成 `(() => {...})()`)`;② helper 提到所有用例之前定义;③ 用例一律 `(() => {...})()`立即求值。**为什么这比"少一条测试"严重**:一条恒绿的断言会替人做出"这一格已被看过"的判断,而它结构上不可能红 —— 与守门 70/76/81 记过的"判据失效的表现永远是安静"是同一条禁令,只是这次失效的不是判据而是**自检的记账**。取证:镜像 **T23** 钉 harness 的函数分支,变异回`if (cond)` 即红。
- **🚫 agent 禁止手写 `git push`(2026-09-18 立,"已推完还在等"事故根治)**:①origin 的推送由 post-commit 钩子 `git-push-guard.mjs` 自动完成(内置 ahead 检测+推送+回读验证,幂等)——commit 落地即已推送,**手动盲推必撞 already-pushed 非快进报错**并诱发后台反复干等;②Gitee/GitCode 由 `mirror-to-cn.yml` CI 在 push 后自动镜像(+每日 2 次兜底),**本地手推镜像仓=违反架构**且制造 DIVERGED 竞态。③收尾核验同步状态**只允许** `node scripts/git-push-converge.mjs`(只读判定,六态 ALREADY/PUSHING/PUSHED/SKIP/BEHIND/DIVERGED,不做任何非必要推送);仅在 guard/CI 均失效的应急场景才人工推,且必须先跑该脚本确认状态。多会话并发期:本会话交付已被远端包含(merge-base --is-ancestor 验证)即为完成,本地 HEAD 落后不追、不与并发会话抢 reset/ff。
- **⚡ 推送异步化(2026-09-18 立,根治第二段)**:guard 检测到 ahead 时默认 spawn 后台 worker 推送(pre-push 质量门不降级,实测单遍 216.8s)并立即返回——**commit 命令秒回,不再被推送拖住**;状态在 `.workbuddy/push-state.json`(running/done/failed,失败由下次 guard 自动重试),converge 读该状态显示 PUSHING;核验若见 PUSHING=正在推,等 1-2 分钟再查即可,勿手动干预。强制同步推送:GUARD_ASYNC=0。(2026-09-18 晚修复:异步 spawn 的 fd 误用 `out.close()` 必抛 TypeError→静默回退同步推送+双重推送竞态,已改 `closeSync`;push-gate 缓存改**内容指纹键控**(HEAD:apps/HEAD:packages 子树+脏文件内容 hash),合并/文档提交不再重跑全量门。)
- **🔄 主动收敛(2026-09-18 晚立,根治第三段)**:推送遇 non-FF(并发会话推力)时**禁止手工 fetch/merge/push 循环**(实测 3 轮 25 分钟),统一跑 `node scripts/git-sync-converge.mjs`(默认 3 轮:fetch→祖先判定→`merge-tree --write-tree` 索引层合并(**零触碰他人未提交文件**)+commit-tree+update-ref→guard 推送,直至收敛;冲突才需人工)。只读核验仍用 `git-push-converge.mjs`。
- **🪟 后台进程禁弹窗(2026-09-18 晚立)**:Windows 下 `spawn(detached:true)` + 控制台程序**必弹新 cmd 窗口**——所有 detached spawn 必须带 `windowsHide: true`(guard worker/锁心跳/desktop-dev-saas 已全量修复,仓库既有正例 dev-with-warmup.mjs)。**同理所有 `shell:true` 的 spawnSync/execSync 必须带 `windowsHide: true`**——shell 派生链无 CREATE_NO_WINDOW 时,pnpm -r typecheck 等会为每个子包拉起可见 cmd.exe(2026-09-18 WMI 实锤:一次全量 typecheck ~30 次闪窗,13 处派生点已全量修复)。新增派生点遗漏此参数=用户桌面反复弹窗事故。**⚠️ 默认终端=Windows Terminal 时上述措施全部失效(2026-09-18 深夜 WMI 二次实锤根治)**:WT 宿主忽略 SW_HIDE(windowsHide 无效)且不理 HKCU\Console 离屏档案,同一负载实测 135 个可见窗口样本;已把 `HKCU\Console\%%Startup` 的 DelegationConsole/DelegationTerminal 双双设为 `{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}`(Windows 控制台主机 conhost)——conhost 尊重 SW_HIDE 与离屏档案,复跑同负载可见窗口归零。**禁止改回 WT 委托**;排查弹窗先查此键,取证脚本=.ihui-agent/tmp/console-watch.ps1(WMI 事件+15ms 可见窗口采样,后台 PS 任务约 2 分钟即被杀,足够覆盖一次守门链)。
  - ⚠️ **上面那句给的取证脚本从未入库(2026-09-27 实测三面 + 历史全零命中)**:`.ihui-agent/tmp/console-watch.ps1` 在工作树不存在、`git ls-files` 零命中、`HEAD` 树零条、`git log --all --diff-filter=A -- '*console-watch*'` 也拿不到 ⇒ 它从来没有被版本化过,只是某次会话留在本机临时目录的产物,换机/重装/他人接手一律跑不通。这与本仓记过的 `pnpm c-drive:clean-ours`(根 `package.json` 从来没有这个脚本)、守门 46/11c/50 的假 `HUSKY_SKIP_*` 通道是同一条禁令的反面形态:**文档不得写跑不通的出路**。**在这条指针被换成真实入口之前**,弹窗这一维在提交链上只有**静态**那道门(`scripts/check-no-visible-spawn.mjs`,守门 52,判"派生控制台程序时有没有写 `windowsHide`"),而**静态判据绿不等于运行时不弹窗** —— 五次复发里至少有两次(WT 委托那一次、fork 风暴那一次)恰恰是"逐点参数都补了、现象仍在",只能靠运行时量窗口样本定性。登记事实:**"运行时可见窗口计数"这一维当前无版本化尺子**;补它的一票已立项,落地判据 = `git cat-file -e HEAD:<探针脚本>` 成立且该脚本真能报出"此刻开着的窗口"(阳性对照,不接受恒 0)。
- **🧬 机器级根治 windowsHide 默认值(2026-09-20 立,"git 窗口一直弹"第四次复发收口)**:实测 Node v24 `child_process` 的 `windowsHide` **默认 `false`**;当父进程**无控制台**(detached+文件 fd 的 guard worker、计划任务、Qoder/Trae/WorkBuddy 等 GUI 宿主派生)时,派生任何控制台程序(git/cmd/pnpm/node)**必分配一个可见控制台**。逐点补参数不可持续(仅 `scripts/`+`.husky/` 热路径就 74 处漏,全仓 ~600 处,且新写脚本继续产生)。**根治**:`node scripts/install-console-window-hook.mjs --apply` —— 把 `scripts/lib/windows-hide-default.mjs` 复制到**仓库外**稳定目录(默认 `<TEMP 上级>/ihui-node-hooks`,本机 = `D:\caches\ihui-node-hooks`;放仓库外是因为 `NODE_OPTIONS` 指向的文件一旦缺失会让**全机所有 node 进程启动失败**,仓库移动/删除不得波及。钩子源文件**必须是 `.mjs`**:`.gitignore` 第 207 行 `*.cjs` 会把 `.cjs` 静默忽略,§23 同类陷阱,一旦被忽略其他机器就装不上),并以 `--import=file:///...` 写入 HKCU 用户级 `NODE_OPTIONS`。钩子给 7 个派生方法补默认 `windowsHide: true`,**显式传 `false` 一律尊重**;落盘前必先用暂存副本做"node 能否带此 NODE_OPTIONS 启动 + 7 个方法是否真被替换"双试跑,不通过则**拒绝写入**,原值备份于同目录 `node-options-backup.json`。实测**ESM 具名导入 `import { spawnSync }` 同样被拦**(loader 早于 builtin ESM facade 创建)。**换机/重装/新克隆后必须跑一次 `--apply`**;`--verify` 巡检不写盘,`--remove` 卸载。**不得因此省略逐点参数**:干净 checkout、他人机器、CI 没有此 env 时仍靠显式 `windowsHide: true` 生效,新增派生点照旧必须写。
- **🔒 机制守门(2026-09-20 同日补,防第 5 次复发)**:`scripts/check-no-visible-spawn.mjs`(guardian-runner 第 **52** 项,blocking)——括号配平提取 `spawn/spawnSync/exec/execSync/execFile/execFileSync/fork` 调用,**首参可"肯定"为控制台程序**(白名单含 git/node/pnpm/cmd/pwsh/schtasks/ffmpeg…,并覆盖绝对路径与"路径+参数"两种形态)且 options 缺 `windowsHide` 即拦截。刻意**宁漏不误报**(未知变量名一律放过),避免阻塞他人提交;测试代码路径降为 warn(测试由终端 runner 派生,子进程继承已有控制台不新分配窗口)。`--staged` 供 pre-commit 用(runner 自动下发),`node scripts/check-no-visible-spawn.mjs` 为全量审计,`--self-test` 跑 14 例逻辑自检。§22c 镜像测试:`scripts/tests/check-no-visible-spawn.test.mjs`。
- **🔗 异步推送配套适配(2026-09-18 晚)**:①check-push-sync(pre-commit #29)遇 push-state running(未过期)放行——否则每次 commit 后 270s 窗口内的下一次 commit 必被 #29 阻塞→--no-verify→80 项守门全跳过;②guard worker 串行化:在途 worker 未落定时新 worker 先等后跑,杜绝多个 270s typecheck 并行打满 CPU。③**推送门中断分类(2026-09-18 深夜)**:typecheck 进程被外部杀死(CTRL_C 注入/宿主清树,exit 3221225786/141/143/130/137,实测日志 39 次)≠ 类型检查结论——check-typecheck 按**临时失败 exit 75** 退出→guardian-runner 原样传播→pre-push 写 `.workbuddy/push-gate-last-result.json` 标记+exit 75→guard worker 见新鲜 75 标记**先带 hook 重试**拿真实结论,仍失败才按用户规则 --no-verify 兜底(此前"被杀"被误判成"他人代码失败"直接绕过真实门禁)。④降级判据第三兜底 `head-diff`:push-scope 与暂存区均为空时用 `git diff --name-only origin/main HEAD` 推断推送范围(实测 4 例"无可判定改动范围,无降级"硬拦);⑤push-state 死 pid 自愈:guard 启动发现 running+死 pid → 顺手改写 failed 终态。⑥**typecheck 再入守卫(2026-09-18 深夜,WMI 实测 24.6 万 cmd/2min fork 风暴根治)**:根包 `pnpm typecheck`=typecheck-full.mjs,pnpm -r 一旦把根包纳入执行即无限递归(每层再起 pnpm -r,指数级进程树,实测 ~2000 cmd/秒持续 26 分钟,即用户"一直闪弹 cmd"的主源);typecheck-full 已加 `IHUI_TYPECHECK_FULL_CHILD` 环境变量再入守卫(子层检测到立即 exit 0),勿删。
- **C5 组件名档(2026-09-28 票㉗)**:根容器按**它自己的**组件名判类别,取证次序 = 元素自身名字/样式键 > 组件名 > 颜色实用类(`bg-card`/`text-*` 只是背景档,不是身份)。两条防假阳的规矩必须同时成立:① **只在 JSX 根启用**,子元素不吃(按钮装在面板里仍是 control);② 归属按 `declarationRanges()` + `ownerOfLine()` 取**最内层声明** —— 不加这一层,同文件内联的小组件会被外层组件名顶判(实测一次造出 79 处假阳,`ModelConfigDialog.tsx` 里的 `RatioSelector` 就是那一型)。名字给不出唯一角色 ⇒ **不启用并逐条报名**(清单在 `--json` 的 componentUndetermined 字段,`--all` 逐行打印),绝不猜一档;该档摘线时镜像 **T19** 必读红,不得被"合规 3211 处"冒充。
  同批修掉两处**取证越界**,它们的症状都不是"多算",而是"把能判的格写成判不出":`classStringsInLine` 原按"距 class 锚点 ≤60 字符连采 3 段字符串"收类名,会把**兄弟属性**的值(`data-testid="plan-review-panel"`)当类名收,凭空造出一条类别证据去撞 `bg-card` ⇒ HEAD 唯一那处 role-conflict 就是这么来的;现要求两段字面量之间只可能是 `cn(` / `,` / `||` / `?` / `+` 这类表达式连接符,出现 `=` / `>` / `}` 即停(镜像 **T21** 成对:兄弟属性不得采、同属性第二段不得漏)。`jsx-scope` 词法器原把 `return <X/>`(不套括号)的前一词元当标识符 ⇒ 判成泛型/比较式,整棵子树不建元素而只计 corrupt;现按"可接表达式的关键字"(return/yield/await/else/do/throw/in/of)放行,同时 **`a < b` 必须仍不建元素**(镜像 **T20** 成对,幽灵祖先比漏判更贵)。修后 HEAD 面 corrupt **443→344**(99 个文件的容器维回到可判),并同批清偿 C5 新露出的 6 处 panel 根容器(md/lg→xl)。
  问责入口 `pnpm check:radius-role`(走 `--strict`);`--strict` 现 exit 0 —— 未判定归零之后,这道门不再拒绝出合格证。**还有一格刻意不做**:`弱证据待裁` 那一族(类别信号只有颜色实用类、元素无名也非组件根)不得批量改档 —— 那是拿 20–70% 判错率的证据重新设计界面,正解是给它补真实身份,门只逐条报名。
  **三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 独立不可见行(注入时落在文件末尾)。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/**`仅隐写`(有可解码载荷但可见横幅整块不见)**/ `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ **`仅隐写`(有可解码载荷但可见横幅整块不见)** / `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
- **按凭据存在性豁免对账**(guardian id 以 `scripts/guardian-runner.mjs` 现值为准 —— 注册时取到 **157**,并发会话可能挪号;**blocking**,`stagedTriggers=apps/api/src/plugins` + `apps/api/src/routes` + `apps/api/src/utils`,紧急跳过 `HUSKY_SKIP_CREDENTIAL_PRESENCE_BYPASS=1`):`scripts/check-credential-presence-bypass.mjs`(G-459)拦的是**"把某个凭据类头/cookie 在场当成安全判定的结论"**这一型 —— 立因不是假想:`apps/api/src/plugins/csrf.ts` 曾写 `if (request.headers['x-internal-service-token']) return`,头名在场即对**全部路由**跳过 CSRF,任何进程带这个头(值随便填)就拿到免死金牌;#23 改成 `isVerifiedInternalMachineCall(request)`(唯一出口 `apps/api/src/utils/internal-principal.ts`,复用那一份 `secretsEqual`:SHA-256 定长摘要 + `timingSafeEqual`,配置缺失 fail-closed)。但**型还在** —— 下一个内部凭据族照样能写成 `if (request.headers['x-anything']) return`,而 typecheck / lint / 其余门全都不响。三条设计约束:① **判据必须覆盖门自己认可的写法**,条件里出现 `===`/`secretsEqual`/`timingSafeEqual`/`isVerified…(`/`verify…(`/`startsWith(`/`.has(` 一律放过(把修法形态判红等于逼人 `--no-verify`);② **凭据名不写死站点清单**,按**词元**比对(`x-id-token` 算,`tokenizer`/`hashtags` 不算),某站是否"已裁过"由台账 `scripts/data/credential-presence-exemptions.json` 判 —— 条目缺 `reason`/`reviewBy`、复核日过期、登记了而被审面没命中(清单腐烂)三种都是红;行内出口 `credential-presence-exempt: <原因>`(须带原因,只救本行或紧邻上行),该族**同笔**登记进守门 108 的 `FAMILY_LIFETIME_DAYS`(30 天,待偿的裁决债)—— 不登记就走 90 天默认档,而第一处真被写出的行内豁免还会被 108 的 E4 判成"新引入的未登记豁免族"(一道门自己的合法出口被邻居钉红 = 两道门互咬,`radius-role`/`border-ink` 同课),这条跨文件锁由镜像 T10 钉住,不靠人记得;③ **判不出就报名** —— 括号配平失败、取不出名字、体起始跨行算不出 ⇒ 落「未判定」逐条点名,`--strict` 下有未判定即 exit 2 拒绝出合格证。口径同 70/77/83/98/101/103/118/135:全量判 HEAD blob、`--staged` 判索引 blob、`--worktree` 仅人工、两面旗同给判死、取不到不回落、枚举到 0 个候选判死不记绿;清单与正文同面同轮;遮罩只引 `scripts/lib/code-mask.mjs` 那一份(判据看代码面,而豁免标记写在注释里 ⇒ 标记在原文行上判)。手动 `node scripts/check-credential-presence-bypass.mjs [--staged|--worktree|--json|--strict|--self-test]`。**已知无尺子的一格**(如实登记,别误以为这里有门):"新内部凭据族又写成按存在性放行"在**本门射程之外**(它只覆盖 `apps/api` 三目录)目前零判据,判据草案与前置见 PROJECT_PLAN G-367。5. `git ls-remote` 为远端真值唯一来源,本地 `origin/main` 引用异常时用显式 SHA 操作,勿信本地引用。
  自动出了角色档射程(现读数一律跑本门末行,本文不钉数字),**这一格不再需要任何标记**;`非正方盒 + 半径≥短边一半 ⇒ 胶囊` ——
  **现行口径(2026-09-28 票㉛ 已升为判红)**:胶囊这一侧要**三个条件同时成立**才判 —— ① 盒形**只取该元素自己的属性区**
  (到开标签闭合 `>` 为止,不含任何子节点)且**闭合可确定**;② **非正方盒**;③ **短边 ≥ 16px**。
  第 ① 条的由来:宽窗口 `elementWindow` 从开标签向下取 6 行,会把子节点的 `h-4 w-4` 算进父盒
  (`w-40` 下拉被量成 160×10、`h-9 px-4` 按钮被量成 16×36)⇒ 现由 `boxDimsOwn` 单独承担,判不出闭合即**只报名不判红**。
  第 ③ 条是装饰族的物理下限:细于 16px 的进度条 / 骨架行 / 指示点仍**只开队列**(§4 明令不得方档化,判红等于逼设计改方角)。
- **同一机制已落到部署锁(2026-09-27,ZCode 吸收线批次 50;机制与三条不许漂的写法见 §5b 的"判活再加一环"条)**:`.deploy.lock` 的 meta 除 `pid/ownerPid/ts` 外再记 `host` + `pidStart`,而 **`pidStart` 量的必须是"判活所问的那个 pid"**(有 `--owner-pid` 就是 owner,没有才退回 CLI pid)—— 主体写错(例如去量调用方自己)会让每次对账都假报"已复用",那条"确证"就变成"确证可以抢",比没有身份凭据更糟;镜像自测 S41/S49 各钉一头。判读三态里**只有 `mismatch` 多给一条抢占授权**(先归档现场再抢,不必把锁龄爬到 30min 硬上限,且不走"持有者已退出"的秒抢通道),`match` / `unverifiable` / 旧 meta 没记身份两元 ⇒ **与改动前逐字同结论**;`--owner-pid` 与 `HARD_CAP_MS` 一字未动,只是从"唯一出路"降级成"第二道兜底"。`check` 与 `release` **不**现测(只读快路径 / 删锁永远是持有者自己的动作),`check` 只如实打印锁里记着什么并补一句"未现测、身份未对账" —— **打印了 `pidStart` 不等于核对过身份**。二次确认那一轮必须把同一个 `identity` 一起传进去,否则"只靠身份确证成立"的那次抢占会在二次确认时被读成"判据不再成立",新通道永远只喊不抢。取证(本会话当轮现跑):`node scripts/deploy-lock.mjs --self-test` 63 条全绿、`node --test scripts/tests/proc-identity.test.mjs` 19 例、既有三条锁镜像 50 例、`node scripts/git-lock.mjs check` rc 0、`node scripts/deploy-lock.mjs check` rc 1(真仓此刻确有 `mode=dev` 的锁,该命令按设计"有锁即 1",新输出行如实标出 `host=(未记) pidStart=(未记)` 与"旧 meta 无凭据 ⇒ 身份无从对账");真实进程对照:新起进程现测启动时刻与当刻 epoch **delta=0**、不存在 pid ⇒ `unverifiable`、人为把记录值提前 700s ⇒ `mismatch`,单次现测 **211ms**、5s 内命中缓存 0ms。**本批没做**:未新增守门(它判的是"锁此刻归谁",属机器状态,挂进提交链就是与提交内容无关的恒红门,§12e 同型);dev 侧四个 npm 生命周期(`predev`/`dev:clean`/`dev:stable`/`prebuild`)仍无 owner 可声明、因而身份两元对它们无从对账(上面"如实登记未完成的部分"那条继续成立)。- **上面那条"落地判据"本日已满足(2026-09-27)**:版本化探针 `scripts/visible-window-probe.mjs` 已入库,`git cat-file -e HEAD:scripts/visible-window-probe.mjs` 成立,且它在本机一次性快照里真量到过 **18 个可见窗口 / 1254 条进程记录 / 畸形行 0** —— 不是恒 0。三态硬要求写在实现里:每一维区分"量到 N"/"量不到(点名原因)"/"确实是 0";PowerShell **被自己的 timeout 终止 / ENOENT / 非零退出** 三种失败各有各的文案(实现收尾时正是先把"超时"误标成"派生失败"才补上这条分流)。手动入口 `node scripts/visible-window-probe.mjs --snapshot | --watch <ms> [--json]`,镜像测试 `node --test scripts/tests/visible-window-probe.test.mjs`。**它刻意不进提交链与 `check:all`**:判的是机器运行时状态,提交者结构上满足不了 ⇒ 挂 blocking 就是每台每次被逼 `--no-verify`、连带约 185 道门对该提交作废(§12e 同型)。**上面那句"静态门绿不等于运行时不弹窗"仍然成立** —— 探针给的是"有没有弹"的事实,不替代门 52 的"新代码有没有写 windowsHide"。
  **三层水印**(`scripts/watermark.mjs`):L1 可见横幅(`// © 2026 IHUI AI …` + `// Provenance-watermarked. …`)、L2 横幅内零宽载荷(`// [IHUI-AI-PROVENANCE]:<zw>`)、L3 独立不可见行(注入时落在文件末尾)。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/**`仅隐写`(有可解码载荷但可见横幅整块不见)**/ `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
  **判据(2026-09-12 加严,2026-09-29 补第五态)**:载荷必须**可解码且等于 `WATERMARK_TEXT`**,仅"存在"不算数。五态:`完好` / `残迹`(有横幅无载荷)/ `载荷损坏`(存在但解码不符)/ **`仅隐写`(有可解码载荷但可见横幅整块不见)** / `未覆盖`。除"完好"外,`watermark.mjs verify` 与 `check-watermark-coverage` 均 **exit 1**。
  `scripts/lib/gitdir.mjs` 的 `resolvePortableGitCandidates()`(候选序 = 环境变量 > versions 目录扫描 > 旧 1.2.0 兜底)解析 git 二进制的首选;`.qoder-cn` 承载本项目记忆与工作区状态)。
  (被 `scripts/lib/gitdir.mjs` 的 `resolvePortableGitCandidates()` 当 git 二进制首选解析)不得搬,其 `logs`/`traces` 约 2GB 属该 IDE
  `scripts/release-desktop-local.mjs` 需要 `%USERPROFILE%\.tauri\ihui-updater.key`,而**旧句"本机无 `.tauri` ⇒ 桌面发布在此机必 `exit 1`"已于 2026-10-01 实测作废**:`.tauri\ihui-updater.key` 348 B、`ihui-updater-password.txt` 33 B **都在位**,mtime 2026-09-07(早于该句登记日 09-23 三周)⇒ 登记当时就不成立;那句还写着 `:67,74` 的行号指针,而 §1 明令证据指针禁写行号(任何一次 append 后都会挪位)。桌面端能否发布在本机的真判据是"跑一次取签名密钥的那条路径并看它报什么",不是这条散文
    - `node --test scripts/tests/deploy-lock.test.mjs`(HB-M1…M9,含"摘掉再入守卫就会白等超时"的端到端臂)。
  IHUI-AI 是全栈 AI 平台(TS Monorepo + pnpm workspace + Turborepo),9 端清单(2026-10-02 增 apps/assistant):
- **加载彩点族收为方点(强制,2026-09-30 用户定档)**:用户复看了两处疑点的复现图后拍板「不允许出现任何胶囊型」,同批改了两处 —— ① web 聊天输入卡浮窗折叠态的 `h-1 w-10 rounded-full` 拖拽手柄(40×4 宽扁盒,全仓最后一处判红胶囊,由在途改动删除);② 小程序 ColorfulLoader 彩点 `borderRadius: '50%'` 改为**方点**(不写任何 borderRadius)。**为什么彩点不能写最小档**:默认档点径 4px,档位表最小档 xs=2px 恰为半边 ⇒ 写档位半径就是全圆,方角是这一族唯一非圆形态;`COLORFUL_LOADER_MAX_DOT_PX` 上限保留,但半径语义(等效半径=2xl)退位,仅剩点径上界一义。同批摘除端内零 import 死副本 `apps/miniapp-taro/src/components/ColorfulLoader.tsx`(规格注释两次声称"已摘除"而文件仍在 HEAD,假陈述当场做成事实)。**注意边界**:本条只收"非正方盒的胶囊 + 装饰彩点"这两族,**正方真圆**(头像/Switch 拇指/几何 `size/2` 正圆点)不在此列 —— 上方"真圆/胶囊"条仍按几何判,勿把头像方档化。守门 11 C7 对彩点这型的"量不到"报数随方点化自然消失;若未来再出现同型动态尺寸全圆写法,仍按"测不到的尺寸"条走共享源封顶,不得回退成 `50%`。
IHUI-AI 是全栈 AI 平台(TS Monorepo + pnpm workspace + Turborepo),8 端清单(2026-10-03 减 apps/assistant:签到助手服务端化并入 ai-service,见 apps/ai-service/app/services/checkin_* 与 web /checkin 页):
  IHUI-AI 是全栈 AI 平台(TS Monorepo + pnpm workspace + Turborepo),8 端清单(2026-10-03 减 apps/assistant:签到助手服务端化并入 ai-service,见 apps/ai-service/app/services/checkin_* 与 web /checkin 页):
