Uh oh!
There was an error while loading. Please reload this page.
ci: a standing caller for the required-set sweep, and five gates that declare the root file they read - #10011
Merged
Merged
Conversation
`--verify-required-set` (#9642) shipped with no caller: the only thing CI ran was its offline self-test, so the registry-vs-live diff existed and measured nothing. Ruled A+B on the card. A. `.github/workflows/required-set-patrol.yml` — scheduled twice daily (`23 4,16 * * *`), plus workflow_dispatch and a pull_request run scoped to the patrol and the script it calls, following half-state-patrol.yml's posture: least privilege (`contents: read` — the read's price is `metadata=read`), findings never fail anything, and the job goes red ONLY when the sweep could not run or its report could not be delivered. Exit code captured with no pipe in between, so the 0-swept / 2-environment split survives. The report lands in the run log and step summary and nowhere else: a new tracker is refused, and the existing anchor (#9857) is owned end-to-end by half-state-patrol's generator. A completed sweep that finds drift exits 0 and would show a green tick, so it emits a `::warning::` annotation keyed on the ABSENCE of the report's own clean mark — which fails toward a false alarm, never a false all-clear. B. Header note in `scripts/check-required-contexts.mjs`: the live half is not runnable from a dev seat. Re-measured 2026-08-19 — bare run answers HTTP 401 (the #7412 proxy trap), and with the documented `--use-env-proxy` remedy it answers HTTP 403, the seat's own egress policy. The proxy hint correctly goes silent once the flag is set, so a dev sees a bare 403 with no explanation of its seat class. NOT VERIFIED there is the environment, not the tree. The self-test's "live mode stays OFF the required path" block gains the presence half it lacked: an absence cannot tell "deliberately off the required path" from "wired nowhere at all". It now sweeps all of .github/workflows for callers (not just the two files `sources` carries), pins that the only one is the patrol, that the patrol declares no `merge_group:` trigger (the mechanical proxy for "can never be validly required-ized" — assertion 6), that no REQUIRED_CONTEXTS row names it, and that it still keys its annotation on the clean mark this file renders. 115 -> 119 assertions. Part of #9678
…read #9964 established the per-gate escape hatch for a population that is a repo-root FILE: dispatch-gates' extractor requires a path separator, so a bare `AGENTS.md` literal is a WORD and contributes no watch hint — the gate reads the file while naming nothing that can match it. The remedy is to declare the subtree spelling (`<file>/**`), which `extractWatchHints` already accepts and `collapseHint` reduces back to that one path. Measured under #9979, nine of those (family, root file) pairs are genuine and all but the ratchet's were still invisible. Each gate now declares its own, in its own idiom, pinned by its own self-test: check:pm-skill-id-lint AGENTS.md (EXTRA_FILES) check:pm-governed-merges AGENTS.md, CLAUDE.md (the two `exact` rows) check:pm-governed-prose AGENTS.md (PROSE_SURFACES) check:docs-audit-scope AGENTS.md (AGENTS_REL) check:required-contexts AGENTS.md (INSTRUCTION_SURFACES) check:doc-anchors README.md, ARCHITECTURE.md (EXTRA_SOURCES) Every declaration is PROVENANCE, never a lookup key — each gate opens its real constant, and the glob spelling appearing there would send it reading a path that does not exist (silently, in doc-anchors' case, since `existsSync` filters the extra sources out). Both halves are pinned per file: the declaration covers the real population, and nothing declared is a value the gate looks up. Zero changes to `extractWatchHints` or `hintCovers` — the bare-`*.md` widening stays refused, and the new cases pin that these declarations bought no part of it (`examples/AGENTS.md` and the cross-root pairs stay unreached). Derivation, measured before -> after: AGENTS.md 1 -> 7 families (6 in lint.yml + the #9678 patrol) README.md 0 -> 1 ARCHITECTURE.md 0 -> 1 CLAUDE.md 0 -> 1 LICENSE 0 -> 0 (control) examples/AGENTS.md 0 -> 0 (control) Self-tests: dispatch-gates 314 -> 332, skill-id-lint 10 -> 14, governed-merges 77 -> 81, governed-prose 24 -> 28, check-audit-scope 22 -> 24, required-contexts 119 -> 123, doc-anchors 17 -> 22 assertions. check:doc-anchors' "by judgment" docblock now points at its declaration; its COVERAGE is unchanged (251 fragment links across 399 sources, same as before). Part of #9979
…s missing Found by reverse verification of the block added in this branch: deleting required-set-patrol.yml made the self-test die on an uncaught ENOENT instead of reporting. Two consequences, both the #4690 family: - the `exactly one workflow runs the live read` failure was recorded and never printed, and every assertion after it never ran — a stack trace where the gate's own authored verdict belongs; - `!/merge_group/` on an unread file is vacuously TRUE, so the assertion that the patrol can never be validly required-ized would have passed about a workflow that does not exist. The file is now read through `existsSync`, its absence is its own named assertion, and each downstream case carries `patrolPresent` so none of them can go green on a file nobody read. Re-measured under the same ablation: 4 named failures, no throw. 123 -> 124 assertions. Part of #9678
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-warren
marked this pull request as ready for review
August 19, 2026 14:41
os-warren
enabled auto-merge
August 19, 2026 14:42
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9678
Fixes#9979
Family dispatch of two ruled cards that share one file (
scripts/check-required-contexts.mjs). Per-card commits; both halves verified at head9b479c512.#9678 — a standing caller for
--verify-required-set(ruled A+B)--verify-required-setshipped with no caller: the only thing CI ran was its offline self-test, so the registry-vs-live diff existed and measured nothing.A.
.github/workflows/required-set-patrol.yml— scheduled23 4,16 * * *(twice daily; the live set is changed by hand a few times a month), plusworkflow_dispatchand apull_requestrun scoped to the patrol and the script it calls. Followshalf-state-patrol.yml's posture:contents: readonly. The read's documented price ismetadata=read, below anything a workflow can toggle, and this job writes no issue, label or comment;Where the report lands, decided and stated: the run log and the run's step summary, and nowhere else. A new tracker is refused outright, and the one existing anchor (#9857) is owned end-to-end by half-state-patrol's generator — a second writer would make each patrol silently discard the other's findings. The honest cost of that choice is that a completed sweep finding drift exits 0 and shows a green tick, so the run emits a
::warning::annotation (annotations render on the run list). It keys on the absence of the report's own clean mark, which fails toward a false alarm and never toward a false all-clear.B. Header note in
scripts/check-required-contexts.mjs— the live half is not runnable from a dev seat. Re-measured 2026-08-19 from this container, both documented spellings:The 401 is the known proxy trap and the script already explains it. The 403 is the finding: with the documented remedy applied, the seat's own egress policy refuses api.github.com. The proxy hint correctly goes silent once the flag is set, so what a dev actually sees is a bare 403 with no explanation of its seat class. The header now says so, and says that a NOT VERIFIED there is the environment, not the tree. The earlier "the required SET is readable to an ordinary agent seat" paragraph is corrected in place rather than deleted — it was true for the seat class that measured it.
Pins added, and why they are presences. The existing self-test block asserted only absences (
lint.yml/ci.yml/ no package script runs the flag), and an absence cannot tell "deliberately off the required path" from "wired nowhere at all" — which is what this mode actually was. It now also sweeps all of.github/workflows(not the two filessourcescarries — a second caller in some third workflow is exactly what the absences cannot see) and pins: exactly one caller and it is the patrol; the patrol declares nomerge_group:trigger — the mechanical proxy for "can never be validly required-ized", since assertion 6 of this pin is that every required context's workflow carries one; noREQUIRED_CONTEXTSrow names it; and it still keys its annotation on the clean mark this file renders (one exported constant, so the report and its reader cannot diverge).#9979 — five more gate families declare the repo-root file they read
Applying the pattern #9978 landed, per gate, with zero changes to
extractWatchHintsorhintCovers— the bare-*.mdwidening stays refused, and the new cases pin that these declarations bought no part of it.check:pm-skill-id-lintAGENTS.mdEXTRA_FILEScheck:pm-governed-mergesAGENTS.md,CLAUDE.mdexactrowscheck:pm-governed-proseAGENTS.mdPROSE_SURFACEScheck:docs-audit-scopeAGENTS.mdAGENTS_RELcheck:required-contextsAGENTS.mdINSTRUCTION_SURFACEScheck:doc-anchorsREADME.md,ARCHITECTURE.mdEXTRA_SOURCESEvery declaration is provenance, never a lookup key: each gate opens its real constant, and the glob spelling appearing there would send it reading a path that does not exist — silently in doc-anchors' case, since
existsSyncfilters the extra sources out. Both halves are pinned per file: the declaration covers the real population, and nothing declared is a value the gate looks up.check:doc-anchors' "still has to reach this gate by judgment" docblock now points at its declaration. Its COVERAGE is unchanged: 251 fragment links across 399 sources, identical before and after.Derivation, measured before → after (
node scripts/pm/dispatch-gates.mjs FILE):AGENTS.mdREADME.mdARCHITECTURE.mdCLAUDE.mdLICENSE(control)examples/AGENTS.md(control)Total families 114 → 115 across 26 → 27 workflow files; the new one is the patrol from #9678.
Reverse verification — directions predicted in writing first
(i) Empty
check-doc-anchors' declaration (the REQUIRED gate, the repo's only fragment coverage). Predicted: README/ARCHITECTURE derivation returns to zero; doc-anchors reddens in its self-test, not its sweep; dispatch-gates reddens on the two positive halves only. All three observed:Note the direction is not uniform, and was predicted as such: dispatch-gates' self-test reports more failures while README.md's derivation reports fewer families. Every negative half stayed green — the declaration bought its own file and nothing else. Restored via
git checkout HEAD --, both gates re-run green.(ii) Delete the patrol workflow. Predicted red on the new presence assertions, and predicted the AGENTS.md derivation drops to 6. The derivation behaved as predicted — and the first run of this ablation found a real defect in the code added by this branch: the block died on an uncaught ENOENT instead of reporting, so the
exactly one workflowfailure was recorded and never printed, every later assertion never ran, and!/merge_group/on an unread file is vacuously true — a false green about a workflow that does not exist. Fixed in9b479c5; re-measured under the same ablation:(iii) The workflow, validated mechanically — GitHub Actions cannot be executed locally, and this repo has no actionlint; its workflow gates are
check:workflow-status-functions,check:node-version,check:shard-attestationandcheck:required-contexts, all green below. Beyond those: the file parses under the repo's ownyamlpackage (triggers / permissions / jobs / steps inspected,merge_groupabsent), all fourrun:blocks passbash -n, and the annotate step was executed as real bash against real rendered reports in both directions:Gates — all at head
9b479c512Union derived by
node scripts/pm/dispatch-gates.mjswith no arguments (15 families), every one run, exit codes captured before any pipe:check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:doc-anchors✅ check-doc-anchors: 251 internal #fragment link(s) across 399 source file(s) all resolve to a real headingcheck:docs-audit-scope✓ check-audit-scope self-test: 24 cases pass.check:node-versioncheck-node-version: OK (30 setup-node step(s) across 27 workflow(s), all on Node 22).check:pm-dispatch-gates✓ dispatch-gates self-test: 332 cases pass.check:pm-governed-merges✓ check-governed-merges --self-test: 81 assertions ...check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces ... and claim no others.check:pm-skill-id-lint✓ check-skill-id-lint: 15 file(s) clean (pattern /#[0-9]{3,}/g).check:required-contexts✓ check-required-contexts: 6 required context name(s) pinned across 2 workflow(s); 5 instruction surface(s) scanned against 2 retired name(s)check:shard-attestation✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).check:workflow-status-functionscheck-workflow-status-functions: OK (scanned 27 workflow file(s), 46 job(s), 24 job-level if: expression(s) ...)check:nul-bytescheck-nul-bytes: OK (scanned 6341 text file(s) ... no raw ASCII control bytes).node scripts/check-cross-package-test-inputs.mjsnode scripts/check-shard-attestation.mjs✓ check-shard-attestation: 2 aggregate gate(s) ...node scripts/docs-audit/check-affected-docs.mjsSelf-test counts, before → after: dispatch-gates 314 → 332 · check-required-contexts 115 → 124 · check-governed-merges 77 → 81 · check-governed-prose 24 → 28 · check-audit-scope 22 → 24 · check-skill-id-lint 10 → 14 · check-doc-anchors 17 → 22 assertions (that file's self-test prints no case counter; counted from its source).
Scope
Not governed —
scripts/**and.github/workflows/**only. Held to the dispatch exclusion list: no extractor /hintCoverschange, the new workflow is never required or merge-blocking anywhere, doc-anchors' coverage is unchanged, and nothing under.claude/is touched. No changeset — nothing here is published;skip-changesetapplied.Generated by Claude Code