Skip to content

ci(test831): 文档站行号 pin 的下限门(守住不再变多,不解决 #831) - #843

Open
vansin wants to merge 4 commits into
mainfrom
ci/doc-source-pin-guard
Open

ci(test831): 文档站行号 pin 的下限门(守住不再变多,不解决 #831)#843
vansin wants to merge 4 commits into
mainfrom
ci/doc-source-pin-guard

Conversation

@vansin

@vansin vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

#831 的下限。不解决 #831,先把这句放最前面。

它承诺什么、不承诺什么

上一条评论量出来的:docs-site 下 141 处 blob/<ref>/<file>#L<N> 引用全部钉在 main,零个钉在不可变 commit。钉 main 的锚点每次重构都会漂,而漂了不会有任何东西报错。

这道门只保证一件事:已知失效的那批不会变多。

召回率是实测的,不是估计的 —— 拿 #831 里已人工确认失效的 10 条回测判据:

  tools.ts#L521   抓到(平凡行)
  tools.ts#L286   ✘ 漏掉   network_id: z.string().max(200).optional(),
  tools.ts#L646   ✘ 漏掉   cpu_pct: processCpuPct,
  tools.ts#L571   抓到(平凡行)
  tools.ts#L911   ✘ 漏掉   message_id: z.string().min(1).max(200),
  tools.ts#L244   ✘ 漏掉   FROM skillhub_skills WHERE network_id = ?1`;
  tools.ts#L271   ✘ 漏掉   const reviewer = !callerTokenIsNetwork && (role === …
   auth.ts#L99    抓到(平凡行)
   push.ts#L11    抓到(平凡行)
  index.ts#L253   抓到(越界)

  抓到 5/10

漏掉的都指向一行长得很正常的代码,只是不是它声称的那一行。别拿这个 job 的绿色去论证 #831 已解决 —— 这句话同时写在 checker 文件头、run.sh 头、qa.yml 的 job 注释和基线文件头,四个地方。

判据与基线

scripts/check-doc-source-pins.py 判三类:文件不存在 / 行号越界 / 那一行是平凡行(}}],);、空行、某段注释的中间一行)。第三类的理由:没有人会故意把说明文字的锚点钉在 } 或空行上。

docs/doc-source-pins-baseline.txt 记当前已知失效的 32 条,语义是只许缩小:

  • 出现基线之外的新失效 → 红
  • 基线里某条已经修好 → 也红,并要求删掉它

第二条是刻意的。不这么做基线会变成坟场,修好的和没修的混在一起,数字再也不说明任何事。

套件

tests/test831-doc-source-pins,alpine 按 digest 钉版,--network none 可跑,不碰网络也不碰任何真实节点。

[L0] denominator
  listing_mode=walk
  scanned_doc_files=106   pin_occurrences=141   pin_doc_pairs=139
  unique_pins=70   broken_pins=32   baseline_entries=32
  OK  walk 路径与 git 路径给出同一份清单
[L1] clean tree passes                      rc=0
[L2] MUTATION_RED new-out-of-range-pin      rc=1  → 复原后回绿 ✓
[L3] MUTATION_RED stale-baseline-entry      rc=1  → 复原后回绿 ✓
[L4] 3 条已知盲区仍未被判据覆盖(与 5/10 一致)
RESULT: PASS      exit_code=0

几点值得单独说:

  • L0 的分母对齐:镜像里没有 .git,checker 走目录遍历;这一层断言它与仓库里 git ls-files 得出同一份清单。分叉了就红 —— 否则「容器里绿」推不出「仓库里绿」。
  • L2/L3 都验了复原后回绿,不然那个红可能来自变异之外的东西。
  • L4 是边界断言:那 3 条已知盲区必须仍然判不出来。哪天有人「改进」判据,这里会红,提醒他去更新 5/10 那个数,而不是让边界悄悄漂移。

报告 docs/tests/report-test831.txt 自带 runsh_blob=6d4dca00…,可用 git rev-parse a7b12780:tests/test831-doc-source-pins/run.sh 独立比对。

已接进 CI

.github/workflows/qa.yml 新增 doc-source-pins job 与 4 条触发路径。没接进 CI 的门只是装饰。

⚠️ #803 也在改 qa.yml,但它改的是 recovered-suites 里的步骤顺序,这里是在文件末尾追加新 job + 在触发路径列表里插 4 行,合并顺序上应该是机械并集。

途中修掉自己两个错

  1. 第一版 Dockerfile 里的 python:3.12-slim digest 是我编造的,不对应任何真实镜像。换成本地实际拉到并核对过的 alpine:3.20 digest,python3 由 apk 装(3.12.13)。
  2. checker 第一版把「(pin, 文档) 对数」当成「引用总数」打印,得到 139,而原始出现次数是 141。是同一份数据我先后量出两个数才发现的 —— 现在两个数都打印,并在函数 docstring 里写明它们不是一回事。

#831 量出来的:docs-site 下 141 处 blob/<ref>/<file>#L<N> 引用**全部**钉在
main 上,零个钉在不可变 commit。钉 main 的锚点每次重构都会漂,而漂了不会有
任何东西报错 —— 读者点进去看到一行毫不相干的代码,文档仍然理直气壮。

🔴 这道门守的是下限,不解决 #831。它只保证已知失效的那批不会变多。
   召回率是实测的,不是估计的:拿 #831 里已人工确认失效的 10 条回测,
   抓到 5、漏掉 5。漏掉的都指向一行长得很正常的代码,只是不是它声称的那一行 ——
   那类只有人读上下文才判得出。别拿这道门的绿色去论证 #831 已解决;
   #831 的解决方案是把行号锚点换成符号锚点。

判据(scripts/check-doc-source-pins.py,判据与边界写在文件头):
  1. 文件不存在
  2. 行号越界
  3. 那一行是"平凡行" —— } / }], / ); / 空行 / 某段注释的中间一行
     理由:没有人会故意把说明文字的锚点钉在 } 或空行上

基线 docs/doc-source-pins-baseline.txt 记当前已知失效的 32 条,语义是**只许缩小**:
  - 出现基线之外的新失效 → 红(这是这道门存在的理由)
  - 基线里某条已经修好 → 也红,要求删掉它
    不这么做基线会变成坟场:修好的和没修的混在一起,数字再也不说明任何事

套件 tests/test831-doc-source-pins(alpine 按 digest 钉版,--network none 可跑):
  L0 分母:镜像内走目录遍历,断言与仓库里 git ls-files 得出同一份清单
     (106 文件 / 70 唯一 pin / 141 处出现)。分叉了就红 —— 否则"容器里绿"
     推不出"仓库里绿"
  L1 干净树必须绿
  L2 witnessed-red:新增一个越界 pin → 红在"新的失效 pin"上;复原后回绿
  L3 witnessed-red:基线塞一条不失效的条目 → 红在"已经不再失效"上;复原后回绿
  L4 边界断言:那 3 条已知盲区必须仍然判不出来。哪天有人"改进"判据这里会红,
     提醒他去更新文档里 5/10 那个数,而不是让边界悄悄漂移

已接进 .github/workflows/qa.yml(新增 doc-source-pins job + 4 条触发路径)——
没接进 CI 的门只是装饰。

途中修掉自己两个错:
- 第一版 Dockerfile 里的 python:3.12-slim digest 是我编造的,不对应任何真实
  镜像。换成本地实际核对过的 alpine:3.20 digest,python3 由 apk 装。
- checker 第一版把「(pin, 文档) 对数」当成「引用总数」打印,得到 139,而原始
  出现次数是 141。是同一份数据我先后量出两个数才发现的;两个数现在都打印。
镜像按 SOURCE_COMMIT=a7b12780 构建;报告自带 runsh_blob,可用
  git rev-parse a7b1278:tests/test831-doc-source-pins/run.sh
独立比对。套件原样输出,未手工编辑。

RESULT: PASS  exit_code=0
MUTATION_RED new-out-of-range-pin rc=1
MUTATION_RED stale-baseline-entry rc=1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ccde2842a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/check-doc-source-pins.py Outdated
Comment on lines +115 to +116
_ref, path, line = match.groups()
pins.setdefault((path, int(line)), set()).add(rel)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify each pin against its captured ref

When a document follows the checker's suggested remediation and changes a main URL to an immutable commit, _ref is discarded here and classify() still reads the current checkout. A valid historical link will therefore be reported as broken whenever that file or line is absent/trivial in HEAD; conversely, an invalid historical link can pass if the same line looks valid in HEAD. Preserve the ref and resolve the referenced revision, or explicitly exclude immutable refs from this drift gate.

Useful? React with 👍 / 👎.

Comment thread scripts/check-doc-source-pins.py Outdated
Comment on lines +130 to +132
if line > len(content):
return ("line-out-of-range", f"(文件只有 {len(content)} 行)")
text = content[line - 1]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject zero-valued line anchors

When a new link uses #L0, the regex accepts it but this check only rejects values above the file length. Python then evaluates content[-1], so if the target file ends with a nontrivial line and no trailing newline, the checker reports the malformed, non-one-based GitHub anchor as valid and the floor gate remains green. Treat line numbers below 1 as out of range.

Useful? React with 👍 / 👎.

Comment thread scripts/check-doc-source-pins.py Outdated
Comment on lines +175 to +176
new = sorted(set(broken) - baseline)
fixed = sorted(baseline - set(broken))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain baseline entries until their links are repaired

When a source edit shifts a known-broken main anchor from a trivial line onto ordinary but unrelated code, classify() stops including it in broken and this subtraction labels it fixed. CI then explicitly tells the maintainer to delete the baseline entry even though the document was untouched and the anchor remains invalid, moving the known defect into the checker's documented blind spot. A baseline entry should only be removed when the corresponding document link is removed or changed, not merely when its current target stops matching the heuristic.

Useful? React with 👍 / 👎.

Comment on lines +123 to +124
target = REPO / path
if not target.is_file():

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep source paths inside the repository

When a document contains a path such as ../../etc/passwd, joining it directly to REPO allows filesystem traversal outside the checkout. For example, blob/main/../../etc/passwd#L1 reads /etc/passwd; because its first line is nontrivial, the checker reports this invalid repository link as healthy. Reject absolute paths and .. components, or resolve the target and verify that it remains beneath REPO before reading it.

Useful? React with 👍 / 👎.

#843 的四条,全部成立。修了判据却没有断言等于没修,所以每条都加了 L5 里的
一个断言(注入 → 期望的红/绿 → 复原 → 回绿)。

① ref 被丢掉(最重的一条)
   第一版把 URL 里的 ref 解析出来就扔了,一律拿当前检出去判。后果是:**有人
   按这个工具自己给的建议、把 main 改成不可变 commit,反而会被判成失效** ——
   那条链接在它自己的 commit 上是对的,在 HEAD 上未必。反过来,历史版本里
   本来就错的链接,也可能因为 HEAD 恰好长得对而蒙混过关。
   改:钉了 7–40 位 hex ref 的引用不属于这道门(它管会漂的引用),单独计数
   pins_on_immutable_ref。断言:注入一个在 HEAD 上必然越界的 SHA pin,门仍绿。

② #L0 没被挡
   只挡了上界,Python 的 content[-1] 会读到最后一行 —— 最后一行非平凡时,一个
   畸形的非 1-based 锚点被判成健康。改成 line < 1 也算越界。

③ 基线语义写反了
   原来是 fixed = baseline - broken:判据不再标某条,就叫人删基线条目。但源码
   一漂,一个**仍然错**的锚点会从「平凡行」挪到「普通但不相干的一行」,判据就
   标不出它了 —— 文档一个字没动。照原规则 CI 会主动要求删掉这条已知缺陷,
   等于把它推进本工具自己的盲区。
   改:条目只在**文档里那个引用不存在了**时才判为可删(gone = baseline -
   present);仍被引用但判据标不出来的单列为 drifted 警告,保留在基线里、也不
   计入绿色。

④ 路径穿越
   文档里写 blob/main/../../etc/passwd#L1 时,直接拼到 REPO 上会读出仓库外的
   文件,而 /etc/passwd 第一行非平凡 —— 一个根本不指向本仓的链接被判成健康。
   改:拒绝绝对路径与 .. 分量,并核解析后仍在 REPO 之下,类别 path-escapes-repo。

L3 的断言文案跟着 ③ 一起改了:它塞进基线的 auth.ts#L1 没有任何文档引用,
所以现在走的是「对应的引用已经不在文档里了」这条。

验证(容器内,--network none):
  [L5] ① 不可变 ref 被排除且单独计数(pins_on_immutable_ref=1),门仍绿
       ② #L0 判为 line-out-of-range rc=1
       ④ 仓库外路径判为 path-escapes-repo rc=1
       ③ 引用仍在文档里时不判为可删,并给出 drifted 警告
  MUTATION_RED new-out-of-range-pin rc=1
  MUTATION_RED stale-baseline-entry rc=1
  RESULT: PASS  退出码 0
source_commit=c6338f272a0474d84dc7f22c0fd482b9ca5de77a
按 pre-pr-selfcheck §12:改动改变了套件下次跑看到的东西,报告要一起更新。
RESULT: PASS  exit_code=0
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

四条全部成立,已修(c6338f27),每条配一个断言 —— 修了判据却没有断言等于没修。

① ref 被丢掉 —— 这条最重

第一版把 URL 里的 ref 解析出来就扔了,一律拿当前检出去判。后果是:

有人按这个工具自己给的建议、把 main 改成不可变 commit,反而会被判成失效。 那条链接在它自己的 commit 上是对的,在 HEAD 上未必。反过来,历史版本里本来就错的链接,也可能因为 HEAD 恰好长得对而蒙混过关。

这等于我一边在报告里劝人「钉 SHA 或改符号锚点」,一边让门去惩罚照做的人。

改:钉了 7–40 位 hex ref 的引用不属于这道门(它管的是会漂的引用),单独计数 pins_on_immutable_ref

断言:注入一个在 HEAD 上必然越界的 SHA pin,门必须仍然绿且计数 +1。

#L0

只挡了上界,Python 的 content[-1] 会读到最后一行 —— 最后一行非平凡时,一个畸形的非 1-based 锚点被判成健康。改成 line < 1 也算越界。

③ 基线语义写反了

原来是 fixed = baseline - broken:判据不再标某条,就叫人删基线条目。

但源码一漂,一个仍然错的锚点会从「平凡行」挪到「普通但不相干的一行」,判据就标不出它了 —— 而文档一个字没动。照原规则 CI 会主动要求删掉这条已知缺陷,等于把它推进本工具自己的盲区。你这条抓得很准。

改成:

gone    = baseline - present     # 文档里那个引用不存在了 → 才判为可删(红)
drifted = (baseline ∩ present) - broken   # 仍被引用、但判据标不出来 → 保留在基线里

drifted 单独打警告并保留,不删、也不当成绿色成绩。

④ 路径穿越

blob/main/../../etc/passwd#L1 直接拼到 REPO 上会读出仓库外的文件,而 /etc/passwd 第一行非平凡 —— 一个根本不指向本仓的链接被判成健康。改:拒绝绝对路径与 .. 分量,并核解析后仍在 REPO 之下,类别 path-escapes-repo

验证

[L5] the four review findings each have an assertion
  ① 不可变 ref 被排除且单独计数(pins_on_immutable_ref=1),门仍绿
  ② #L0 判为 line-out-of-range rc=1
  ④ 仓库外路径判为 path-escapes-repo rc=1
  ③ 引用仍在文档里时不判为可删,并给出 drifted 警告(pin=agent-network/bin/cli.ts#L61)
MUTATION_RED new-out-of-range-pin rc=1
MUTATION_RED stale-baseline-entry rc=1
RESULT: PASS      退出码 0

L3 的断言文案跟着 ③ 一起改了:它塞进基线的 auth.ts#L1 没有任何文档引用,所以现在走「对应的引用已经不在文档里了」这条。

报告已一并刷新(08c95a8a)。

⚠️ 下游两个 PR 需要跟着走

#844 / #845 基于这个分支,判据变了它们要重新验证。我下一轮把这个分支合进那两个再各跑一次,不会让它们带着旧判据的绿色进 main。

vansin pushed a commit that referenced this pull request Aug 13, 2026
上一轮我在 #843 里改了判据(四条审查缺陷),而这个 PR 基于它 —— 也就是说它
当时带着旧判据跑出来的绿色。这次把 ci/doc-source-pin-guard 合进来重跑,
L5 的四条断言都在,分母仍是 53/107。

  RESULT: PASS  exit_code=0
  L5 ①不可变 ref 排除 ②#L0 越界 ③drifted 保留 ④路径穿越

合并时报告文件冲突(两边都重新生成过)。报告是产物,解法是合完重新生成,
不是手工挑行 —— 手工合出来的报告不对应任何一次真实运行。

顺带:合并未提交时那次构建红在 blob 绑定上(镜像里的 run.sh 与 HEAD 声称的
不是同一份),说明那道绑定确实在起作用。
vansin pushed a commit that referenced this pull request Aug 13, 2026
这个 PR 基于 #844,#844 基于 #843 —— 上一轮我在 #843 改了判据(四条审查缺陷),
这两个 PR 当时都带着旧判据跑出来的绿色。逐级合下来重跑,判据是新的、分母是
本 PR 的 27/53、L5 四条断言都在。

  RESULT: PASS  exit_code=0
  L5 ①不可变 ref 排除 ②#L0 越界 ③drifted 保留 ④路径穿越

报告冲突同样按合完重新生成处理 —— 它是产物,手工合出来的报告不对应任何一次
真实运行。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

一个还没人知道的跨 PR 耦合:#810 / #834 合入后,这道门会红

本轮没有新写代码,做的是一次合并前的耦合测量 —— 结果是三个 PR 之间有一处硬耦合,谁先合谁就得多做一步,而现在四个 PR 里都没有写这件事。

测出来的

#845 的树上(已含 #843 的门 + 已改完的 mcp-tools / rest 锚点),模拟 #810#834 各自的改动,然后跑门:

FAIL: 4 个基线条目对应的引用已经不在文档里了,请从基线里删掉
  server/src/auth.ts#L184      ← #810 删掉了引用它的那一行
  server/src/index.ts#L253     ← #834 把它重锚到不可变 SHA(22ed1886)
  server/src/tools.ts#L521     ← #810 删掉
  server/src/tools.ts#L571     ← #810 删掉

pins_on_immutable_ref=2
unique_pins=14   broken_pins=1   baseline_entries=5
退出码=1

也就是说:#810#834 合入之后,docs/doc-source-pins-baseline.txt 必须同步删掉对应条目,否则 CI 的 doc-source-pins job 会红。

这不是门的 bug,正是它按设计在做的事 —— 基线只许缩小,修好了就得删,不删会变成坟场(见 #843)。但它构成一个合并顺序上的动作项,需要有人知道。

每种顺序下要做什么

顺序 需要的额外动作
#843 先合,#810 / #834 后合 后者的 PR 里加一行:从基线删掉对应条目
#810 / #834 先合,#843 后合 #843 的基线重新生成一次即可(那 4 条本来就不会进去)

两种都很小,但都不能忘。忘了的表现是「一个只改文档的 PR 把 CI 弄红了」,而红的原因看起来跟那个 PR 毫无关系。

顺带一个好消息

合完之后机械可证的失效面从 32 降到 1(只剩 server/src/push.ts#L38,#810 没动它)。#831 里我写的「至少 32 个已失效,真实更大」,那个 32 到这里基本清完了 —— 剩下的是判据看不见的那部分,仍要人读。

这次测量本身也栽了一跤

第一遍我用 tail -14 看门的输出,把 FAIL 列表的第一行截掉了,于是只看到 3 条、还以为 auth.ts#L184 有什么特殊之处。是重新不截断地跑了一遍才对上。

同一件事在 docs/pre-pr-selfcheck.md §13 里刚写过(「输出的截断」),今天第二次栽在它上面。另外那次 git ls-files … | xargs command grep 静默返回空 —— command 是 shell 内建,xargs 找不到这个可执行文件,于是「文件里没有」和「我的管道坏了」打印出来是同一片空白。

vansin pushed a commit that referenced this pull request Aug 13, 2026
上一轮我在 #843/#810/#834 上贴了一条跨 PR 耦合提醒:那两个 PR 一合,基线里
对应的条目会变 stale,门就红在「请从基线里删掉」。提醒是散文,执行的人还是
得自己去数该删哪几条 —— 把一个机械操作交给了记忆力。

这一轮把它变成一条命令:

  python3 scripts/check-doc-source-pins.py . --write-baseline

🔴 这个开关离「一键把门变绿」只差一个条件判断,所以它**只许缩小**:
   重算若会引入基线里没有的条目(= 出现了新的失效 pin),它拒绝写并退出非零。
   新失效该做的是把链接改对,不是追认进基线。

L7 两个方向都断言,不只测它能用:
  ① 干净树:不改写,报「基线已经是最新的」,且文件字节未动
  ② 注入一个新失效 pin:必须拒绝(rc≠0),且**确认基线没被写**
     —— 只断言"它红了"不够,要断言"它红了而且没写"
  ③ 造一个「引用消失」场景(把某条 pin 的引用改钉不可变 SHA):
     必须删对、条数变小、表头注释保留、门随后转绿

顺带在门红的提示里直接给出这条命令,不让人再去翻文档。

验证(容器内,--network none):
  [L7] ① 干净树:不改写,报「已是最新」
       MUTATION_RED write-baseline-refuses-new-failure rc=1
       ③ 引用消失时删对了(5 → 4),表头保留,门转绿
       复原后回绿 ✓
  RESULT: PASS  退出码 0

一处说明:这个改动逻辑上属于 #843 的脚本,但落在链尾(#845)。理由是 #843#844#845 是一条依赖链,改在链首要把两级重新合并重跑一遍;而三个 PR 是按序
合进 main 的,落在链尾到达 main 的时间相同。写在这里免得有人以为放错了地方。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

把上一条的耦合提醒变成一条命令(d9517384,落在链尾 #845)

上一条我贴了「#810 / #834 合入后基线会变 stale,门会红」的测量。但那是散文 —— 执行合并的人还是得自己去数该删哪几条,等于把一个机械操作交给记忆力

现在是:

python3 scripts/check-doc-source-pins.py . --write-baseline

门红的时候也会直接把这条命令打出来,不用再回来翻评论。

🔴 它只许缩小

这个开关离「一键把门变绿」只差一个条件判断,所以:重算若会引入基线里没有的条目(= 出现了新的失效 pin),它拒绝写并退出非零。 新失效该做的是把链接改对,不是追认进基线。

L7 两个方向都断言,不只测它能用:

[L7] --write-baseline shrinks only
  ① 干净树:不改写,报「已是最新」
  MUTATION_RED write-baseline-refuses-new-failure rc=1
  ③ 引用消失时删对了(5 → 4),表头保留,门转绿
  复原后回绿 ✓
RESULT: PASS      退出码 0

②那一条特意断言了两件事:它红了,而且基线文件字节未动。只断言「它红了」不够 —— 一个先写盘再报错的实现同样会红,而那正是最坏的情况。

③ 用的场景是把某条 pin 的引用改钉不可变 SHA,也就是 #834 的做法 —— 不是编的。

位置说明

这个改动逻辑上属于本 PR(#843)的脚本,但落在链尾 #845。理由:#843#844#845 是一条依赖链,改在链首要把两级重新合并重跑;而三个 PR 按序合进 main,落在链尾到达 main 的时间相同。写在这里免得有人以为放错了地方。

vansin pushed a commit that referenced this pull request Aug 13, 2026
我在建 test831 时写过「没接进 CI 的门只是装饰」,下一轮建 test846 时自己就没接。
是这轮做 qa.yml 协调分析、查各 PR 各改了什么时发现的 —— 不是别人提的。

新增 doc-claims job + 3 条触发路径,形状与 #843 的 doc-source-pins 一致
(同一个锚点后追加、job 附在文件末尾)。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

qa.yml 的四处改动我摊开对照过了,冲突面只在两处 paths 清单、且是机械并集;建议合并顺序 #803#843#846。完整对照贴在 #803 的这条评论

vansin pushed a commit that referenced this pull request Aug 13, 2026
上一轮我给出的是「冲突了怎么解」。这一轮做的是让冲突不发生。

冲突源于所有人都追加在同一处:
  paths  五个 PR 都插在 - 'tests/test746-setup-bun-pin/**' 之后
  job    #843 与本 PR 都追加在文件末尾(#803 插在 qa: 之前,#798/#801 插在 59 行)

改动:
  paths  改插到 - 'server/**' 之后 —— 距离 test746 九行,超出 git 默认上下文窗口
  job    从文件末尾挪到 jobs: 之后(这个位置没有别的 PR 用)

paths 是集合、jobs 是映射,位置变化不改变行为。结构断言(每个 job 有 runs-on
与非空 steps)已跑过。

这样合并时不需要任何人去解那个「公共上下文属于双方」的冲突 —— 那个坑我在
#803 上写清楚了,但最好的处理是不让人踩到它。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

这个 PR 是我写的,自审没有独立性,所以我做的是评审者会想要的那件事:跑变异,证明它不是假绿
另外发现一条我自己此前说错的范围,一并写在这里。

变异证据

基线(未变异):

pin_doc_pairs=139  unique_pins=70  broken_pins=32  baseline_entries=32
OK: 失效 pin 32 个,基线 32 条 —— 没有新增,也没有该清的残留。      退出码 0

变异A —— 新增一条失效 pin(这道门的立身之本:守住不再变多):

在 docs-site/docs/community.md 末尾追加一条
  .../blob/main/server/src/index.ts#L99999

FAIL: 1 个新的失效 pin(不在基线里)
pin_doc_pairs=140  unique_pins=71  broken_pins=33  baseline_entries=32      退出码 1

见证真红,而且三个计数都跟着动了(139→140 / 70→71 / 32→33),不是靠一句 FAIL 唬人。

变异B —— 清空基线文件(基线不该能被抹掉换绿):

: > docs/doc-source-pins-baseline.txt
broken_pins=32  baseline_entries=0                                          退出码 1

fail-closed。

🔴 我第一发变异打偏了,值得记下来

第一次我把那条假 pin 追加到 docs/architecture.md,门报绿、退出码 0、broken_pins 纹丝不动(32)
差点被我读成「门瞎了」。真实原因是 check-doc-source-pins.py:54:

DOC_ROOT = "docs-site"

docs/ 根本不在扫描根内 —— 是我的变异打在了门的范围之外。
「变异后没变红」有两种可能:门瞎,或者变异打偏。不先分清这两种,结论就是错的。

🔴 由此暴露的范围限制:这道门不覆盖 docs/

我在 #850 / #851 里量过:docs/ 下按 blob/maincli.ts 行号的引用共 15 条,
其中锚里带符号名、可机器判定的 11 条 —— 漂移 11 条,仍然对的 0 条,分布在

docs/architecture.md        docs/node-lifecycle.md
docs/design-auth-network.md docs/pitfalls.md

这四个文件全部在 DOC_ROOT="docs-site" 之外,这道门一条都照不到。

我在上一轮汇报里说过「#843 是那 11 条漂移唯一的机械解」—— 这句话是错的,两个方向都错:

  1. 范围:docs/ 不在扫描根内;
  2. 类别:那 11 条是「行号在范围内、但指的不是声称的那个符号」,
    而本文件头部已经写明这道门抓不到这一类(实测召回率 5/10)。

所以合了这个 PR,docs/ 下那 11 条依然要靠人肉发现。

建议

这个 PR 别扩范围(它的标题就写着「不解决 #831」,范围小是它能绿的原因)。
DOC_ROOT 是个单点常量,把 docs/ 纳进来是独立的一步 —— 纳进来会立刻多出一批失效 pin
需要进基线,那是另一次改动、另一次评审。我会另开一条跟踪。

CI 现状:mergeable=MERGEABLEmergeState=CLEAN、全部 check SUCCESS
(含它自己那道 doc source-pin floor (Docker))。目前只有 codex bot 的一条评审,
缺一次人的深审 —— 我写的,不该我批。

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

量了一下这道门当前扫描根内有没有盲区 —— 因为它对读不出的文件是静默跳过的:

except (OSError, UnicodeDecodeError):
    continue

实测(与 checker 的 SCANNED_SUFFIXES 同口径,排除 node_modules / dist / .vitepress/cache):

docs-site/   扫描面 106 个文件   UTF-8 解码失败 0 个

当前 DOC_ROOT="docs-site" 下没有任何文件会被静默跳过 —— 这道门现在报的覆盖是实的,不打折。

(对照:若按 #852DOC_ROOT 扩到 docs/,扫描面变成 181 个文件、其中 1 个会被静默跳过 —— 就是 docs/qa/weekly/2026-W19.md,PR #858 正在修它。所以那是扩范围时要先解决的前置条件,不是本 PR 的问题。)

顺带补一句给评审:这个 except … continue 本身不算 bug(读不出的文件确实没法检查),但它是静默的 —— 建议将来把跳过的文件数打印出来(skipped=N),否则「扫了多少」和「看见了多少」这两个数在输出里是分不开的。这与本 issue 家族一贯的「分母承重」是同一条原则。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants