Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration by os-sam · Pull Request #14069 · objectstack-ai/objectstack · GitHub
Skip to content

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration - #14069

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps
Sep 1, 2026
Merged

ci(governed-guard): install the generator toolchain so the queue leg can lift a pure regeneration#14069
os-sam merged 1 commit into
mainfrom
claude/issue-14063-guard-job-deps

Conversation

@os-sam

Copy link
Copy Markdown
Collaborator

Fixes#14063

What this changes

The merge_group leg of Governed Surface Queue Guard installed no dependencies, so every #11705 register row — the generator-owned files inside skills/** — failed closed there: they recompute by running the generator's own --check through pnpm … 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 regenerated references/_index.md was therefore still governed at queue time and needed a pinned maintainer approval, while the seat-side --test lifted 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):

纯生成的指针行(spec 源变更后再生成的 references/_index.md) 不需要我审核吧

So the job now acquires pnpm through the shared ./.github/actions/setup-pnpm composite, restores the pnpm store from the same cache key lint.yml uses, and runs pnpm install --frozen-lockfile.

Three decisions inside that are load-bearing:

  • No --filter. The packages the recompute needs are GENERATED_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: every verify.pkg in the register must be covered by the install line.
  • No build step, and that is measured rather than assumed — both verify rows run through tsx off their TypeScript sources and their relative ./lib/* imports; nothing consults any package's dist/.
  • Every toolchain step is 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_request leg 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 — still exit 0 on 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_APPROVERS approval pinned to the current head. The workflow name: / check context Governed Surface Queue Guard is 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:

stepwallhow
pnpm install --frozen-lockfile7sfresh worktree, warm store, through scripts/pm/os-verify-lock.sh
the skill-refs generator's --check (what the recompute runs)1spnpm --filter @objectstack/spec exec tsx scripts/build-skill-references.ts --check --generated-manifest=…, exit 0, 9 declared outputs
any build0snone is needed — measured by running the generator above in a worktree where nothing had been built

What that means for a queue build, stated honestly: I cannot measure GitHub's runners from here. The added CI work is the setup-pnpm composite (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 its timeout-minutes: 10. The real price is runner minutes per queue build and per PR run, not queue latency. The first build after a pnpm-lock.yaml change pays a cold store restore.

Verification — all at 7d02199a, the final commit

  • node scripts/pm/check-governed-queue-guard.mjs --self-test✓ … 129 cases pass (exit 0, captured before any pipe).
  • Gate family derived from the real diff, not recalled: 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 1 could not read GITHUB_EVENT_PATH. The local reading for this script is its --self-test, above.
    • Green includes the ones this diff most disturbs: pnpm check:pnpm-acquisition (census now records this job at governed-surface-guard.yml:155 under cached · 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-wide eslint . --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.
  • Governed-surface self-check on this very diffnode scripts/pm/check-governed-merges.mjs --test over 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-object differing from the HEAD blob), ran the self-test, restored with git checkout HEAD -- …, and proved the restore (git diff HEAD empty and blob equal to the HEAD blob). Predicted direction: red. All five went red, each naming its own assertion:

mutationself-test
delete the Install dependencies stepexit 1 — the-job-installs-the-workspace-dependencies-the-recompute-runs-on: (no pnpm install step) + the ordering pin
drop continue-on-error from the installexit 1 — every-toolchain-step-DEGRADES-…: missing continue-on-error: Install dependencies
add --filter @objectstack/objectui to the installexit 1 — the-install-covers-the-register-row-without-restating-it for both register rows
move the install after the live judgmentexit 1 — the-toolchain-is-in-place-BEFORE-the-live-judgment…: {"selfTest":2,"install":7,"judgment":6}
make the lift fail OPEN (stillGoverned = new Set())exit 1 — the fail-closed end-to-end cases flip to {"conclusion":"clear","exitCode":0} and catch it

The 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):

  • a certified recompute ⇒ CLEAR, exit 0, zero approvals and zero API calls (measured with spies that throw, the same way the existing ordering guarantee is), and the notes name the ruling that lifted each of the four files;
  • an uncertified recompute ⇒ still REFUSED (exit 3), for both real reasons: no toolchain, and generator drift — each with its stated reason in the log;
  • a hand-authored skills/*/SKILL.md beside a certified regeneration ⇒ still REFUSED, and the refusal names the hand-authored path without naming the lifted ones;
  • a recompute that throws ⇒ propagates into EXIT_CANNOT_RUN rather 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 register verify.pkg without restating one; and every toolchain step carries continue-on-error — with a non-emptiness assertion so that pin cannot pass vacuously if the steps are ever reordered.

Scope notes

Generated by Claude Code


Generated by Claude Code

…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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cdsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude