Skip to content

Commit c56592b

Browse files
os-samclaude
andauthored
ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration (#14069)
The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row (generator-owned files inside skills/**) failed closed there: a spec PR carrying its regenerated references/_index.md stayed governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted the identical diff in a dev container. The script header filed that trade as not taken; the maintainer took it (2026-09-01). The job now acquires pnpm through the shared composite and runs a plain pnpm install --frozen-lockfile - no --filter, so the register's verify.pkg set is not restated in YAML, and no build, because both generators run through tsx off their TypeScript sources. Every toolchain step is continue-on-error: a broken install degrades to exactly the fail-closed behaviour that preceded it instead of newly making ordinary queue builds depend on the registry. Nothing about the exemption is softened: the recompute is still the register's own byte-exact one, every error path still refuses, and hand-authored governed content still needs the pinned approval. Claude-Session: https://claude.ai/code/session_015adLit3ZYASJiXwxKG78Wi Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8dc22d6 commit c56592b

3 files changed

Lines changed: 312 additions & 23 deletions

File tree

.github/workflows/governed-surface-guard.yml

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,14 @@ jobs:
8080
fetch-depth: 0
8181

8282
# Pinned to the same major and spelling as every other setup-node in this
83-
# repo. `actions/setup-node@v5`+ defaults `package-manager-cache: true`,
84-
# which shells out to pnpm and hard-errors in a job like this one that
85-
# installs no package manager — see the Part-of Closing-Keyword Guard's
86-
# note for the measured failure. No install step and no corepack: the
87-
# script is dependency-free and imports only sibling `scripts/**` modules.
83+
# repo, and it keeps its literal `node-version` (rather than
84+
# `node-version-file`) for the measured reason the Part-of Closing-Keyword
85+
# Guard records: `actions/setup-node@v5`+ defaults
86+
# `package-manager-cache: true`, which shells out to pnpm and hard-errors
87+
# before any `run:` line when pnpm is not yet on PATH. This job now DOES
88+
# acquire pnpm, but strictly after this step — the same order lint.yml has
89+
# been green on for months — so nothing here may start depending on pnpm
90+
# existing yet.
8891
- name: Setup Node.js
8992
uses: actions/setup-node@v7
9093
with:
@@ -100,6 +103,79 @@ jobs:
100103
- name: Guard predicate self-test
101104
run: node scripts/pm/check-governed-queue-guard.mjs --self-test
102105

106+
# ── the generator toolchain — the #11705 rows can now actually recompute ─
107+
#
108+
# The register's `verify` rows (generator-owned files inside `skills/**`)
109+
# recompute by running the tree's OWN generator through `pnpm … exec tsx`.
110+
# Until now this job installed nothing, so that recompute answered "the
111+
# generator toolchain is not available in this environment" on every run
112+
# and the path stayed governed — a spec PR carrying its regenerated
113+
# `references/_index.md` needed a pinned maintainer approval at merge-group
114+
# time even though the seat-side `--test` lifted the same diff in a dev
115+
# container. The script header filed that trade as not taken; the
116+
# maintainer took it (2026-09-01, verbatim):
117+
#
118+
# > 纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧
119+
#
120+
# ⭐ NO `--filter` here, deliberately. The set of packages the register's
121+
# recompute needs is `GENERATED_SURFACE_EXCEPTIONS[].verify.pkg`, and a
122+
# filter list in this file would be a SECOND copy of it — one that goes
123+
# stale silently the day a row is added, in the direction that reads as
124+
# compliance (the new row's generator fails to spawn, the path stays
125+
# governed, and nothing says the install was the reason). A full install
126+
# restates nothing, so a register row reaches this job for free — the same
127+
# reason the script imports the predicate instead of reimplementing it.
128+
# The self-test pins that: every `verify.pkg` in the register must be
129+
# covered by the install line below.
130+
#
131+
# ⛔ NO build step, and that is measured rather than assumed: both `verify`
132+
# rows run through `tsx`, which reads the generator's TypeScript sources
133+
# and their relative `./lib/*` imports directly — nothing consults any
134+
# package's `dist/`. Measured on a warm store: install 7s, the skill-refs
135+
# `--check` 1s. A build would be pure cost with nothing depending on it.
136+
#
137+
# ⭐ EVERY step in this block is `continue-on-error` — this is the load-
138+
# bearing half, not defensive decoration. The guard's own promise is that
139+
# "a diff that touches nothing governed is never blocked by an outage", and
140+
# a hard-failing install would newly make EVERY ordinary queue build depend
141+
# on the npm registry and the cache service. With `continue-on-error`, a
142+
# broken install degrades to exactly the behaviour this job had before this
143+
# block existed: the generator cannot spawn, `runSinkGenerator` returns its
144+
# stated "toolchain is not available" reason, and the path stays GOVERNED.
145+
# Fail-closed on the governed path, unaffected on the clear one, and the
146+
# verdict stays inside the script — where the exit contract lives — instead
147+
# of becoming an opaque red step nobody can read a governance answer out of.
148+
#
149+
# Both legs install. The `pull_request` leg is a FORECAST of the queue
150+
# verdict ("the merge-queue run of this same check will REFUSE it"), so a
151+
# leg that cannot recompute forecasts a refusal that will not happen — the
152+
# false-alarm direction, which trains seats to hold PRs that need no
153+
# holding. Same diff, same answer, on both legs and at the seat.
154+
- name: Setup pnpm
155+
uses: ./.github/actions/setup-pnpm
156+
continue-on-error: true
157+
158+
- name: Get pnpm store directory
159+
shell: bash
160+
continue-on-error: true
161+
run: |
162+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
163+
164+
# Deliberately the same key as lint.yml's: one store, one entry, and this
165+
# job is a cache READER in the steady state rather than a second writer.
166+
- name: Setup pnpm cache
167+
uses: actions/cache@v6
168+
continue-on-error: true
169+
with:
170+
path: ${{ env.STORE_PATH }}
171+
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
172+
restore-keys: |
173+
${{ runner.os }}-pnpm-store-v3-
174+
175+
- name: Install dependencies
176+
continue-on-error: true
177+
run: pnpm install --frozen-lockfile
178+
103179
# The live judgment. Everything it reads arrives through `env:` or the
104180
# event payload on disk — no `${{ }}` interpolation into the shell line,
105181
# so no PR-controlled text ever becomes part of a command.

scripts/pm/check-governed-merges.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,8 +1182,13 @@ export function sinkGeneratorVerdict({ path, entry, run }) {
11821182
* Every failure — no toolchain, spawn error, unreadable manifest, drift —
11831183
* returns `ok: false` with the reason, and `sinkGeneratorVerdict` turns that
11841184
* into a governed verdict. ⚠️ "No toolchain" is a real environment here, not a
1185-
* hypothetical: the merge-group guard job installs no dependencies (see the
1186-
* header), so there it fails closed on every run.
1185+
* hypothetical. It used to be the merge-group guard job's PERMANENT state: that
1186+
* job installed nothing, so every row with a `verify` failed closed there while
1187+
* the seat-side `--test` lifted the same diff. #14063 gave the job an install
1188+
* (maintainer 2026-09-01, quoted in `check-governed-queue-guard.mjs`'s header),
1189+
* and made every step of it `continue-on-error` — so this branch is now the
1190+
* DEGRADED path rather than the normal one, and it degrades to exactly the
1191+
* fail-closed behaviour that preceded it.
11871192
*/
11881193
export function runSinkGenerator(root, entry) {
11891194
let dir;

0 commit comments

Comments
 (0)