Repository navigation
Commit b9598e9
test(hooks): pin guard-governed-enqueue's "no checkout available" fail-open (#18423)
Fixes #18367
## 维护者速读(草稿)
**改了什么**
只改一个文件:`.claude/hooks/guard-governed-enqueue.selftest.sh`,新增一个自检用例(4
条断言)。守卫脚本 `guard-governed-enqueue.sh` 一个字节未动。
**为什么改**
该守卫有一条「放行」分支:它拿到一条生成物例外行,却找不到目标仓的任何检出来重算这行的来源,于是打印警告并放行。这条分支在任何环境下都没有用例钉住——原先声称覆盖它的那个用例其实到不了:它用的路径与本树自己的生成器逐字节相等,注册表直接豁免,守卫在更上游的「谓词已清」出口就退出了,stderr
全空。也就是说,这条放行分支可以被悄悄删掉或写错而全绿。新用例把它钉住:同一个注入的空目录,换成一条任何生成器都不声明的 `skills/**`
路径,注册表在哪里问都答「受管 + 一条例外行」,守卫因此进入放行分支并打印那句话。
**风险与代价(含回滚)**
风险低:纯测试面,不改任何运行时判定;守卫的行为、放行/拦截口径完全不变。用例是自封闭的(自己造空目录、自己造 fixture),在装了
`node_modules` 的工作树和本文件自称运行的裸树上都成立——两边注册表给的理由不同,但前提(exit 3
带一条例外行)相同,已分别实测。代价是自检从 62 条变 66 条,整轮约多跑 5 秒。回滚 = revert 本 PR 的唯一
commit,不留任何残留(无生成物、无配置、无 changeset)。
**席位意见**
(留空)
**你要做的**
一个动作:确认这条放行分支值得被钉住,然后批准(governed surface,本 PR 保持 draft,不由任何 AI 席位落地)。
## What the case pins
The hook's fail-open sentence is `PR_REF may hit a generated-surface
exception row, and no checkout of OWNER/REPO is available to recompute
its provenance on` (warn + allow). Reaching it needs two things at once:
no checkout of the target repo resolves, AND the register still answers
governed (exit 3) carrying an exception row. The existing cross-repo
case supplies the first and is lifted out of the second, which is why
its own comment says, correctly, that it does not reach this branch.
The new case keeps the injected empty root the case above already builds
and changes only the path shape:
```
skills/zz-no-such-skill/references/_index.md
```
Measured in this installed worktree:
```
$ node scripts/pm/check-governed-merges.mjs --test --json skills/zz-no-such-skill/references/_index.md ; echo EXIT=$?
EXIT=3
exceptions: 1
reason: "`pnpm --filter @objectstack/spec gen:skill-refs` does not write
skills/zz-no-such-skill/references/_index.md — it is not among the 9 file(s)
that generator declared on this tree ... fail closed: the path stays governed"
```
and in a checkout with no `node_modules` (the shape this self-test
documents itself as running under):
```
EXIT=3
exceptions: 1
reason: "the generator declared no output set (the generator's own --check exited 254) ...
fail closed: the path stays governed"
```
Two reasons, one precondition — exit 3 carrying an exception row — and
that precondition is all the branch reads, so the case is hermetic in
both directions. The hook, fed that payload with the empty root
injected, answers:
```
HOOK_EXIT=0
1 parent 55fd5ee commit b9598e9
1 file changed
Lines changed: 61 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
470 | 470 | | |
471 | 471 | | |
472 | 472 | | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
473 | 534 | | |
474 | 535 | | |
475 | 536 | | |
| |||
0 commit comments