|
18 | 18 | # Needs `jq` (to build fixtures) and `node` (the two real predicates run). No |
19 | 19 | # pnpm install, no build: measured against a worktree with no `node_modules`. |
20 | 20 | # |
21 | | -# ⚠️ ONE COUPLING, STATED SO A FAILURE IS NOT MISREAD. The |
22 | | -# `pure-regeneration-only ⇒ ALLOWED` case uses a REAL register-lifted path |
23 | | -# (`.claude/workflows/docs-accuracy-audit.js`, the #9866 row) rather than a |
24 | | -# stub, because the requirement under test is precisely "this guard must never |
25 | | -# re-close the zero-approval path the register clears" and a stub cannot show |
26 | | -# that. The consequence: the case needs that artifact to be in sync with its own |
27 | | -# generator on the tree it runs against — which is what the required |
28 | | -# `check:docs-audit-scope` gate keeps true. If this one case fails while the |
29 | | -# rest pass, look there first; re-run |
30 | | -# node scripts/pm/check-governed-merges.mjs --test .claude/workflows/docs-accuracy-audit.js |
31 | | -# and read what the register says before touching this matrix or the hook. |
| 21 | +# ⚠️ THE PURE-REGENERATION CASE IS AN AGREEMENT ASSERTION, NOT A FIXED VERDICT, |
| 22 | +# and that is a repair rather than a preference: the first revision of this file |
| 23 | +# hard-coded `expect allow` against the one exception row that was cheap to |
| 24 | +# lift, and that row was retired upstream hours later — the case then went red |
| 25 | +# over a register change the hook had nothing to do with. Copying a verdict out |
| 26 | +# of the register makes this matrix a second register. It now ASKS the register |
| 27 | +# and requires the hook to answer the same way; see that block for the detail. |
32 | 28 |
|
33 | 29 | set -uo pipefail |
34 | 30 |
|
@@ -71,7 +67,15 @@ approved_at() { # approved_at <login> <sha> |
71 | 67 | NO_REVIEWS='[]' |
72 | 68 | GOVERNED_FILES="$(files_of AGENTS.md packages/spec/src/index.ts)" |
73 | 69 | CLEAR_FILES="$(files_of packages/spec/src/index.ts README.md)" |
74 | | -REGEN_FILES="$(files_of .claude/workflows/docs-accuracy-audit.js)" |
| 70 | +# The incident's own file class, and four of them, the way it actually happened: |
| 71 | +# `skills/*/references/_index.md` is a governed `skills/**` path whose generator |
| 72 | +# (`gen:skill-refs`) owns it, so a byte-exact regeneration is lifted and needs no |
| 73 | +# approval at all. Every path here must be one the generator DECLARES — a skill |
| 74 | +# absent from its map is hand-authored content that stays governed, which is the |
| 75 | +# ruling's own limit and not a bug to route around. |
| 76 | +REGEN_PATHS="skills/objectstack-data/references/_index.md skills/objectstack-query/references/_index.md skills/objectstack-ui/references/_index.md skills/objectstack-api/references/_index.md" |
| 77 | +# shellcheck disable=SC2086 |
| 78 | +REGEN_FILES="$(files_of $REGEN_PATHS)" |
75 | 79 |
|
76 | 80 | F_UNAPPROVED="$(fixture governed-unapproved "$GOVERNED_FILES" "$NO_REVIEWS")" |
77 | 81 | F_PINNED="$(fixture governed-pinned "$GOVERNED_FILES" "$(approved_at os-zhuang "$HEAD_SHA")")" |
@@ -169,14 +173,48 @@ echo "== nothing governed in the diff: allowed, and no review is ever consulted |
169 | 173 | expect allow 'an ordinary diff enqueues freely' \ |
170 | 174 | "$(mcp $AUTO 14070)" "OS_GOVERNED_ENQUEUE_FIXTURE=$F_CLEAR" |
171 | 175 |
|
172 | | -echo "== PURE REGENERATION clears with ZERO approvals — the register decides, not this hook ==" |
173 | | -# Real path, real lift (see the coupling note in this file's header): the #9866 |
174 | | -# row, byte-exact against its own generator recomputed on this tree. The hook |
175 | | -# must never be what re-closes it (maintainer 2026-09-01: 纯生成的指针行 … |
176 | | -# 不需要我审核吧). |
177 | | -expect allow 'a governed-register path the generator certifies byte-exact, zero reviews' \ |
| 176 | +echo "== PURE REGENERATION: the hook must AGREE with the register, never re-decide ==" |
| 177 | +# The requirement (maintainer 2026-09-01: 纯生成的指针行 … 不需要我审核吧) is that |
| 178 | +# this hook never re-closes a zero-approval path the register clears. Pinned |
| 179 | +# against a REAL exception-row candidate, and pinned as AGREEMENT rather than as |
| 180 | +# a fixed verdict, for a reason this matrix learned the hard way: an earlier |
| 181 | +# revision hard-coded `expect allow` on the one row that was cheap to lift, that |
| 182 | +# row was RETIRED upstream the same day (its surface left the governed fence |
| 183 | +# entirely), and the case then failed for a reason that had nothing to do with |
| 184 | +# the hook. A verdict copied from the register is a second register. |
| 185 | +# |
| 186 | +# So: ask the register what it says about this path list on THIS tree, and |
| 187 | +# require the hook to answer the same way. |
| 188 | +# exit 0 => lifted (the generator toolchain is present and the bytes match) |
| 189 | +# => the hook MUST allow with zero reviews. This is the branch CI |
| 190 | +# takes, where dependencies are installed. |
| 191 | +# exit 3 => not lifted (fail-closed: no toolchain, or a hand edit) |
| 192 | +# => the hook MUST refuse, exactly as the register asked. |
| 193 | +# Either way the property under test holds: the hook contributes no judgment of |
| 194 | +# its own about the exemption. |
| 195 | +# shellcheck disable=SC2086 |
| 196 | +node "$repo_root/scripts/pm/check-governed-merges.mjs" --test $REGEN_PATHS >/dev/null 2>&1 |
| 197 | +regen_rc=$? |
| 198 | +if [ "$regen_rc" -eq 0 ]; then |
| 199 | + regen_want=allow |
| 200 | + regen_branch='LIFTED — byte-exact regeneration, so zero approvals must pass (the toolchain is present here)' |
| 201 | +else |
| 202 | + regen_want=block |
| 203 | + regen_branch="NOT lifted (exit $regen_rc, fail-closed: no generator toolchain, or a hand edit) — the refusal must stand" |
| 204 | +fi |
| 205 | +printf ' .. register verdict on the four %s: %s\n' 'skills/*/references/_index.md' "$regen_branch" |
| 206 | +expect "$regen_want" 'the hook agrees with the register about an exception-row candidate' \ |
178 | 207 | "$(mcp $AUTO 14070)" "OS_GOVERNED_ENQUEUE_FIXTURE=$F_REGEN" |
179 | 208 |
|
| 209 | +# ...and the structural half, which no fixture can go stale on: the ONLY branch |
| 210 | +# that can reach a refusal is the register's own "governed" exit code. Anything |
| 211 | +# the register clears leaves through `exit 0` before a single review is read. |
| 212 | +if grep -qE '^[[:space:]]*0\)[[:space:]]*exit 0' "$hook"; then |
| 213 | + pass=$((pass + 1)); printf ' ok wired a cleared predicate verdict exits before any review is read\n' |
| 214 | +else |
| 215 | + fail=$((fail + 1)); printf ' FAIL the hook no longer allows unconditionally on a cleared predicate verdict\n' |
| 216 | +fi |
| 217 | + |
180 | 218 | echo "== the Bash spellings reach the same decision ==" |
181 | 219 | expect block 'gh pr merge <n> -R owner/repo' \ |
182 | 220 | "$(bash_call 'gh pr merge 13794 -R objectstack-ai/objectstack --squash')" \ |
@@ -257,7 +295,7 @@ echo "== a generated-exception row on a repo with no checkout to recompute again |
257 | 295 | # against another's files would be worse than not answering: fail open, say so. |
258 | 296 | expect allow 'an exception-row path in a repo this container cannot resolve' \ |
259 | 297 | "$(mcp $AUTO 999 objectstack-ai cloud)" \ |
260 | | - "OS_GOVERNED_ENQUEUE_FIXTURE=$(fixture cross-repo-regen "$(files_of skills/objectstack-spec/references/_index.md)" "$NO_REVIEWS")" |
| 298 | + "OS_GOVERNED_ENQUEUE_FIXTURE=$(fixture cross-repo-regen "$(files_of skills/objectstack-data/references/_index.md)" "$NO_REVIEWS")" |
261 | 299 |
|
262 | 300 | echo "== the deliberate exception switch ==" |
263 | 301 | expect allow 'OS_ALLOW_GOVERNED_ENQUEUE=1 on the blocking case' \ |
|
0 commit comments