Uh oh!
There was an error while loading. Please reload this page.
ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069
Merged
Merged
Conversation
…can lift a pure regeneration 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. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015adLit3ZYASJiXwxKG78Wi
os-sam
marked this pull request as ready for review
September 1, 2026 02:49
os-sam
enabled auto-merge
September 1, 2026 02:49
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#14063
What this changes
The
merge_groupleg ofGoverned Surface Queue Guardinstalled no dependencies, so every #11705 register row — the generator-owned files insideskills/**— failed closed there: they recompute by running the generator's own--checkthroughpnpm … exec tsx, and with no toolchain the recompute answered "the generator toolchain is not available in this environment" on every run. A spec PR carrying its regeneratedreferences/_index.mdwas therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side--testlifted the identical diff in a dev container. Two tools, one register, two answers.The guard's own header filed that trade as "not taken" because no ruling covered it. The maintainer ruled it (2026-09-01, verbatim and untranslated):
So the job now acquires pnpm through the shared
./.github/actions/setup-pnpmcomposite, restores the pnpm store from the same cache keylint.ymluses, and runspnpm install --frozen-lockfile.Three decisions inside that are load-bearing:
--filter. The packages the recompute needs areGENERATED_SURFACE_EXCEPTIONS[].verify.pkg; a filter list in the YAML would be a second copy of that set, going stale silently the day a row is added — and in the direction that reads as compliance (the new row's generator fails to spawn, the path stays governed, and nothing says the install was the reason). A full install restates nothing, so a register row reaches this job for free. The self-test pins it: everyverify.pkgin the register must be covered by the install line.verifyrows run throughtsxoff their TypeScript sources and their relative./lib/*imports; nothing consults any package'sdist/.continue-on-error: true. This is the half that keeps the guard's existing promise. Without it, a registry outage, a cold cache or a PR with an out-of-sync lockfile would newly redden merge groups that touch nothing governed — on the one check whose entire job is to refuse. With it, a broken install degrades to exactly the behaviour this job had before the block existed: the generator cannot spawn, the recompute states why, and the path stays GOVERNED. Fail-closed on the governed path, unaffected on the clear one, and the verdict stays inside the script where the exit contract lives.Both legs install, deliberately. The
pull_requestleg is a forecast of the queue verdict ("the merge-queue run of this same check will REFUSE it"), so a leg that cannot recompute forecasts a refusal that will not happen. Its behaviour is otherwise untouched — stillexit 0on every approval state, still no head read, still byte-identical wording.Nothing about the exemption is softened: the recompute is still the register's own byte-exact one against this build's own tree, never a stored baseline; every error path still refuses with its existing exit code; and hand-authored governed content — including a hand edit sitting in the same commit as a certified regeneration — still needs the
GOVERNED_APPROVERSapproval pinned to the current head. The workflowname:/ check contextGoverned Surface Queue Guardis unchanged, and the self-test still asserts that literal.Measured cost of the install
Container measurements, shared box (parallel agents on the same cores, so read these as an order of magnitude rather than idle-box figures), warm pnpm store, fresh worktree at
7d02199a:pnpm install --frozen-lockfilescripts/pm/os-verify-lock.sh--check(what the recompute runs)pnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputsWhat that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the
setup-pnpmcomposite (network-free on a warm Corepack cache), a store-cache restore, and the install — the same preamble five other jobs in this repo already run. It does not move the queue build's critical path: this job is parallel with jobs that take minutes, and it keeps itstimeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after apnpm-lock.yamlchange pays a cold store restore.Verification — all at
7d02199a, the final commitnode scripts/pm/check-governed-queue-guard.mjs --self-test→✓ … 129 cases pass(exit 0, captured before any pipe).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack→ 31 families + 2 convention-triggered (this diff edits gate scripts). All 32 harvested commands run; 30 exit 0. The two non-zero are NOT MEASURED, not failures, and each says so in its own words:node scripts/check-test-completeness.mjs→ exit 3, its own "nothing was measured" code: "there is no local log to hand it, so the local reading for this gate is NOT MEASURED".node scripts/pm/check-governed-queue-guard.mjs(the live CI invocation, run with no event payload) → exit 1could not read GITHUB_EVENT_PATH. The local reading for this script is its--self-test, above.pnpm check:pnpm-acquisition(census now records this job atgoverned-surface-guard.yml:155undercached · composite, 24 sites),check:node-version,check:required-contexts,check:pm-governed-merges,check:pm-dispatch-gates,bare-root-worklist --self-test,check:self-test-wired,check:self-test-workflow-commands,check:workflow-status-functions.pnpm lint(repo-wideeslint . --no-inline-config) → exit 0 in 81s. No narrowing, so no narrowing argument is owed.node scripts/check-nul-bytes.mjs→ OK over 7678 files; plus a direct control-byte scan of the three edited files, zero hits.node scripts/pm/check-governed-merges.mjs --testover the three changed paths:✅ NOT governed, 0 of 3 paths hit the register, exit 0. Ordinary queue landing applies.Reverse verification — the new pins were made to fail on purpose
Each leg mutated the tree from the committed state, proved the mutation reached disk (occurrence counts plus a
git hash-objectdiffering from the HEAD blob), ran the self-test, restored withgit checkout HEAD -- …, and proved the restore (git diff HEADempty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:Install dependenciesstepthe-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step)+ the ordering pincontinue-on-errorfrom the installevery-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies--filter @objectstack/objectuito the installthe-install-covers-the-register-row-without-restating-itfor both register rowsthe-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}stillGoverned = new Set()){"conclusion":"clear","exitCode":0}and catch itThe last one is the important one: it is what shows the new end-to-end cases are measuring the refusal rather than restating it.
New self-test coverage (129 cases)
Behaviour, run through the whole decision — lift, then
runGuard, then the exit code — over the #13794 diff shape (a spec source edit plus its four regenerated pointer files in one commit):skills/*/SKILL.mdbeside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;EXIT_CANNOT_RUNrather than reading as a lift.Environment, pinned against the YAML on disk (a green self-test over a workflow that had silently lost its install would be the loudest possible false negative): the install exists and is
--frozen-lockfile; pnpm comes from the shared composite; the toolchain sits after the self-test and before the live judgment; the install covers every registerverify.pkgwithout restating one; and every toolchain step carriescontinue-on-error— with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.Scope notes
scripts/pm/**gate scripts) — the textbookskip-changesetcase thelint.ymlcomment describes. The label is applied on this PR.scripts/pm/check-governed-merges.mjsis touched for one docblock only:runSinkGenerator's "no toolchain" note asserted, as a present-tense fact, that the guard job installs nothing. This PR makes that false, so the note now says the branch is the degraded path and what it degrades to. No behaviour change in that file.AGENTS.mdPrime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 still tells seats that "even a pure-regeneration PR requests its pinned approval proactively — the queue-time leg installs no dependencies". That sentence goes stale the moment this lands, butAGENTS.mdis itself a governed surface, so folding it in here would fork this PR into the draft-only maintainer-merge lane. It also has to reflow at net zero lines under theAGENTS.mdratchet. AGENTS.md PD #14 still tells seats to pre-request a pinned approval for a pure regeneration — stale once the queue guard installs its toolchain (#14063) #14067 remains open for that amendment; it is not addressed here.Generated by Claude Code
Generated by Claude Code