Repository navigation
doctor prints a paste-ready required_check_sources block; a new project's config starts pinned (after #788) [PROTECTED: owner merges] - #803
Conversation
…onfig starts pinned With #788, a required check nothing pins is judged by name (the owner's option c). To protect an install, its owner pins the checks. doctor now prints the block to paste (src/check-pin-block.ts): each unpinned required check mapped to the workflow file whose jobs post it, read from the committed workflow files (PR-only checks included, matrix names expanded as GitHub names them), confirmed against main's tip check runs where it has one (the CLI reads them with gh, read-only), and a name more than one file or source posts, or none, listed for a manual choice, never guessed. auto-config (init, add-project) appends the same block to a newly scaffolded config.yaml; an existing one is left alone. Test (fails without): test/check-pin-block.test.ts, test/auto-config.test.ts "#727: a new project's drafted config.yaml ... starts with each check its workflow files post pinned", and test/required-check-source.test.ts "doctor names each required check ... with the block that pins it". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…ke a workflow pin From an adversarial pass against the owner's decision guide: - A workflow file name or check name with a line break ended its comment in the pin block and could inject a pin into what the owner pastes. Comments are now one line. - Main's tip runs counted as from a workflow file by their page's path alone. They now pass the same test a workflow pin does: this repo, this commit, one of that run's own jobs on its host (isJobOfRun). Test (fails without): test/check-pin-block.test.ts "adversarial pass: a workflow file or check name with a line break never injects a pin into the block". Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: CHANGES NEEDED (round 1)Reviewed at head a70144d (base: #788's branch at d614853). I didn't write it. The newline fix holds, and no hostile name injects a pin. What has to change: the mapping doesn't come from main's committed tree, one path still proposes a pin from a run that failed the workflow-pin test, and several cases are guessed instead of listed for a manual choice. Every finding below comes from fixture inputs fed to the PR's own functions ( Acceptance (owner's option c)
Hostile names (measured; parsed like the engine:
|
…-727-pin-block Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md # docs/design.md # src/check-source.ts
…offer a block the engine refuses Review round 1 of #803: - The mapping read the checkout's working tree (untracked files, and a committed symlink's target outside the repo). It now reads the workflow files committed at main's tip (or origin/<default>; a new project's base) with git: regular blobs only, at most 256 KiB, never a symlink's target. - An Actions check run that failed the workflow-pin test fell back to the Actions App (15368) as an app_id pin, which any workflow satisfies. It is now a manual choice; the Actions App is never proposed. - A matrix with include/exclude, a non-string name, a reusable job, an expression name or matrix and an unparseable file were guessed or dropped silently. Each is now listed ("couldn't map"), and a pinned name that could also come from one of them is flagged by that listing. - A duplicate name, __proto__, a name too long for a YAML key, or a pin the schema refuses could make the block, and a new project's config.yaml, invalid, failing init and add-project. Names are deduplicated, unusable ones become comments, each pin is checked against the engine's schema, and pinNewConfig writes nothing unless the whole config still validates. - The tip's check runs and workflow runs are read every page (up to 10), with a 64 MiB buffer; a read past the cap is marked partial, and no App pin is proposed from a partial read. - Comments carry no control or format characters (no terminal escape or bidi override). - doctor's comment no longer contradicts option (c). Test (fails without): test/check-pin-block.test.ts (committed tree only; unnameable jobs listed; never the Actions App; always-valid block; paging) and test/auto-config.test.ts (init can't fail because of the block). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 2: changes at 598cfc1Base is #788's 5c4ef10, merged in with no rewrite. Each required change is fixed, with a passing fixture test; the Adversarial pass table in the body has each attack. Labels: measured or inferred.
Verification (measured):
|
Evaluator verdict: CHANGES NEEDED (round 3)I reviewed head
All eight round-1 changes are fixed, and every round-1 attack now fails. Three small gaps remain in the tip read and the Actions App test. Round-1 required changes
Comments (measured)
This repo at main's current tip (measured)
Fail-without (measured)
Rule 10, hygiene, protection, CI, merge (measured)
Required changes
After #788's next round, merge its head again (no rewrite) and re-run the suite. Not required
|
…-727-pin-block Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md # docs/design.md # src/check-source.ts
…e commit, read to the end Review round 2 of #803: - An App pin needed only "not flagged Actions": App 15368 under another slug, a run with no slug, or the Actions slug under another id could be proposed. Now an App is a pin only when its id isn't the Actions App's and its slug is given and isn't Actions'; anything else is a manual choice. - A full page of runs with no total_count ended the read as if complete. Now it keeps reading, and past the cap the read is partial. - Every page read commits/<branch>/check-runs, so the branch could move between pages. The tip is now resolved to its sha once, and every page reads that commit. Test (fails without): test/check-pin-block.test.ts (unprovable Apps are manual; paging without total_count; the check-run pages read the resolved sha). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Round 3: changes at bb9ca92#788's round-4 head is merged in (1f5b8df, no rewrite). Each claim is labelled measured.
Fail-without on 1f5b8df (measured): After (measured):
|
Evaluator verdict: PASSI reviewed head All three round-3 changes are met, and every round-3 attack now fails. Round-3 required changes
Round-3 attacks re-run (measured)
This repo at main's current tip (measured; also #794's evidence)
Fail-without (measured)
Usual checks (measured)
Before the owner merges (procedural)#788 needs another round (see its verdict). Once #788's final head exists, merge it into this branch (no rewrite) and re-run the suite. This PR's own files need a fresh review only if they change. Not required (noted)
|
…7-pin-block Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md # docs/design.md # src/check-source.ts # test/required-check-source.test.ts
|
Round 5 at 49f7ba7: a signed-off merge of #788's round-5 head bdfcc29, with no rewrite and no code changes.
|
Evaluator verdict: PASSI reviewed head This round is a merge only. It keeps both sides, and this PR's own code is unchanged. The merge (measured)
Pins (measured)
Usual checks (measured)
Before the owner merges (procedural)#788 needs another round (see its verdict): its main watch under an App pin must change.
|
Conflicts in CHANGELOG.md, docs/design.md, the header of src/check-source.ts and one import in test/required-check-source.test.ts, each resolved to #788's text plus this branch's doctor text (checkSourceDoctor stays in src/check-pin-block.ts). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Round 6 at dede6c6: a signed-off merge of #788's round-6 head 961ecba, with no rewrite and no code changes here.
|
Evaluator verdict: PASSI reviewed head This round is a merge only. It keeps both texts, and this PR's own code is unchanged. The merge (measured)
Pins (measured)
Usual checks (measured)
Before the owner merges (procedural)#788 needs another round (its verdict lists the changes: the page caps, the status reads and two claim fixes).
|
Conflicts in CHANGELOG.md and docs/design.md only, each resolved to Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> #788's text plus this branch's doctor text.
|
Round 7 at 34ea609: a signed-off merge of #788's round-7 head ed9eb36, with no rewrite and no code changes here.
|
…7-pin-block Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Evaluator verdict: PASSI reviewed head This round is merges only. They keep both texts, and this PR's own code is unchanged. The merges (measured)
Pins (measured)
Usual checks (measured)
Before the owner merges (procedural)
|
A conflict in CHANGELOG.md only, resolved to #788's text plus this branch's doctor text. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
#788's 20b912b merged in as 5412d59, and this PR's budget re-rounded to the next 10,000.
|
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # test/package-files.test.ts
|
#788's 9b9f846 merged in as ad0022d, which carries main d5674d0, and the budget raise dropped (the owner's revised package-size practice).
|
Evaluator verdict: PASSI reviewed head
1. The merge brings in only #788 (measured)
2. Package size (measured)The method:
The per-file diff matches the body:
The budget:
3. Tests and checks (measured)
4. CI on
|
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com> # Conflicts: # CHANGELOG.md
…owner) Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
This PR's only note was its edit of #788's Upgrading bullet (the paste-ready block). With notes in fragments (#870), that edit is made in changelog.d/788-pin-required-checks.md, byte for byte as before; CHANGELOG.md is #788's exactly, and this PR adds no fragment of its own (it adds no note line of its own). Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
The combined check of 2026-10-20 (main f9422c4, clean dist with build:web) measured the queue in loop order: #881, #788, #803, #789, #839, #844, #834 and #878 merged give a 1,931,687-byte tarball, under 1,950,000; adding #769 gives 1,951,238, over by 1,238 (the full queue is 1,960,374). Per the owner's practice the raise goes on the PR that crosses, at the new size plus 5%, rounded up to 10,000: 1,951,238 * 1.05 = 2,048,800, so 2,050,000. The unpacked budget stays at 6,400,000. Part of #726. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…gh child-process.ts (#883) #834 (src/auto-merge-resume.ts, spawn of git ls-remote) and #803 (src/check-pin-block.ts, execFileSync of git and gh) landed on main with direct node:child_process imports, which #883 requires to go through src/os/child-process.ts like every other module. The guard's import tree is now 60 modules with main's src/ack-reason.ts (#834) and this PR's src/os/child-process.ts, so #883's note says 60, not 59. Test (fails without): test/child-process.test.ts "#846 backstop" fails on the merge commit with exactly src/auto-merge-resume.ts:14 and src/check-pin-block.ts:13. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
Intent:
doctorprints a paste-readyrequired_check_sourcesblock for the required checks with no pin, and a new project's drafted config starts pinned. With #788 (option c), a required check nothing pins is judged by name; this makes pinning it one paste. Split out of #788.Risk class: protected.
src/**is engine, andsrc/doctor.ts,src/auto-config.tsandsrc/cli.tsare touched;docs/design.mdis engine-guards. The owner merges.Part of #727. Merge order: after #788. The base is #788's branch, which is merged in (no rewrite).
How it works (
src/check-pin-block.ts)workflowsAt(repo, rev)reads the workflow files committed at one commit, with git.ghTipRunsreads it, ororigin/<default>when the tip can't be read. A new project uses its base commit..github/workflows/*.y(a)mlregular blobs only (mode 100644 or 100755), each at most 256 KiB.workflowCheckNamesturns the jobs into check names as GitHub names them: plain names or keys, plain matrices, and${{ matrix.x }}substitutions.include/exclude, a non-stringname:, a reusable workflow, an expression name or matrix, a file that doesn't parse.pinPlantakes the names, deduplicated:app_idpin: only when no file posts the name, the tip was read in full, and exactly one App posts it there that it can prove: its id isn't the Actions App's (15368), and its slug is given and isn'tgithub-actions. The GitHub Actions App is never proposed: any workflow posts as it. An App it can't prove is a manual choice;__proto__, over 1000 characters), or a pin the engine's schema refuses.pinBlockandvalidPins: each comment is one line, with no control or format characters (no terminal escape, no bidi override). doctor offers a block only when it parses as the engine parses config.yaml and passesProjectConfig.pinNewConfigappends the block to a config.yaml that auto-config (init, add-project) has just scaffolded. It writes nothing unless the whole config still validates, so the block can't make init or add-project fail. Auto-config leaves a repo that already has a config.yaml alone.ghTipRunsresolves main's tip to its sha once, then reads every page of that commit's check runs and workflow runs (up to 10 pages each, 64 MiB buffer), with read-onlygh api.total_countmeans it keeps reading.doctor; tests use fixtures.How it was verified
{}instead of pins;check-pin-block.jswas missing.# tests 12 # pass 10 # fail 2:init and add-project can't fail because of the block: the old code proposed a config the engine refuses (config.yaml: The : indicator must be at most 1024 chars … at line 95);check-pin-block.test.js: the old module has none of the new functions (does not provide an export named 'NAME_MAX').# tests 7 # pass 5 # fail 2:15368/something-elsetip run was pinned ([]expected);repos/o/r/commits/main/check-runs, not the resolved sha.CHANGELOG.md,docs/design.md, the header ofsrc/check-source.tsand one import intest/required-check-source.test.ts; each was resolved to Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's new text plus this PR's doctor text. There were no code changes here. The pin tests (required-check-source,check-pin-block,pin-engine,required-check-source-coordinator,main-watch) gave# tests 87 # pass 87 # fail 0.CHANGELOG.md,docs/design.md, thesrc/check-source.tsheader, and one import intest/required-check-source.test.ts(which no longer nameswithTestRuns). Each was resolved to Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's text plus this PR's doctor text;checkSourceDoctorstays incheck-pin-block.ts. There were no code changes here. The pin tests,ci-noneincluded, gave# tests 96 # pass 96 # fail 0.mcp-*andplugin-fetch, with a scratch HOME, gave# tests 3559 # pass 3502 # fail 0 # skipped 57. Typecheck is clean,denylist: 1285 files clean, andDCO: all commits signed off.app_idpin this block proposes is judged the same in readiness and the main watch.CHANGELOG.mdwas the only conflict. I took Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's side (-X theirs), soCHANGELOG.mdis Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's (and main's) exactly.CHANGELOG.mdhas the samegit patch-idbefore and after (47215dda).changelog.d/788-pin-required-checks.md(41f2de5), and the line is byte for byte this PR's old one (grep -Fx).changelog,changelog-fragments,changelog-fragments-selfcheckandcheck-pin-blockgave 46 of 46, and typecheck is clean.## v0.5.4 (unreleased)), as 939477a, a signed-off merge with no rewrite. Its one conflict wasCHANGELOG.md: this PR's version of the Upgrading bullet (with the doctor text) sat in v0.5.3. It now replaces Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's version in v0.5.4, and v0.5.3 is main's text exactly.git diff origin/main -- CHANGELOG.mdshows only lines added under v0.5.4.changelog,package-files,check-pin-blockandpin-pagesgave 35 of 35.PACKAGE_BUDGETline, resolved to main's file exactly (below). The pin tests withpackage-filesgave# tests 98 # pass 98 # fail 0.package-files,pin-pages,check-pin-block,main-watch,required-check-source,required-check-source-coordinator) gave# tests 105 # pass 105 # fail 0; typecheck is clean anddenylist: 1315 files clean.CHANGELOG.md, with nothing dropped. The pin tests and main's lints gave 162 of 162.CHANGELOG.mdonly, resolved to Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's text (main's v0.5.3 layout) plus this PR's doctor text. No v0.5.3 bullet was dropped (scripts/lib/changelog.mjssectionFor, in memory).src/: nothing patches a built-in prototype) reportedusable's two string comparisons with the prototype's key.usablenow asks the schema itself whether a name can be arequired_check_sourceskey, so the same names are a manual choice as before.check-pin-block,slot-pathandauto-configgave 51 of 51.CHANGELOG.mdanddocs/design.mdonly, resolved to Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's text plus this PR's doctor text.app_idpin it proposes is now judged by that App's latest run of the name, in any suite. So a later pipeline of the App supersedes its earlier ones. This replaces the round-7 line above.ci-none,pin-pagesandauto-config) gave# tests 114 # pass 114 # fail 0. Typecheck is clean,denylist: 1286 files clean, andDCO: all commits signed off.CHANGELOG.mdanddocs/design.mdonly, each resolved to Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's text plus this PR's doctor text. There were no code changes here.ci-noneandpin-pages) gave# tests 101 # pass 101 # fail 0. Typecheck is clean,denylist: 1286 files clean, andDCO: all commits signed off. Overlap (git merge-tree): none with Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788, Engine updater counts a required check only from its pinned workflow or App [PROTECTED: owner merges] #789, Pin this repo's required checks to the workflow files that post them (after #788 and the engine upgrade) [PROTECTED: owner merges] #794, Review levels: a renamed file counts under its old and its new path, in changeFiles and every path reader [PROTECTED: owner merges] #688, Lock scope follow-ups: old cli.js hook installs flagged and migrated, one githubRepoOf, updates.json read as root's file, readLockScope takes only the instance and its repo [PROTECTED: owner merges] #765, Slot dir: one writeSlotFile helper for every slot-dir write; the lint fails on any other [PROTECTED: owner merges] #766, Tests: no result depends on today's date; five intake tests go red on main ~2026-11-02 (fix before 2026-11-01) #687 or main.Package size (the owner's practice, 2026-10-19, as revised late that day)
This PR doesn't change
PACKAGE_BUDGET. It fits main's{ size: 1_950_000, unpackedSize: 6_060_000 }(#823), sotest/package-files.test.tsis main's file exactly.npm pack --dry-run --jsonafter a cleannpm ci,npm run buildandnpm run build:web, in throwaway worktrees under.worktrees/, since removed): this branch at ad0022d (with Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788 and main d5674d0) is 1,918,697 packed and 5,990,181 unpacked, against main d5674d0's 1,902,158 / 5,934,411. That is +16,539 / +55,770, leaving 31,303 / 69,819 under the budget.check-source.jsnew,backlog/github.js+7,602,main-watch.js+7,104,github/main-watch-api.js+3,840,config/schema.js+1,514,coordinator/merge-flow.js+1,460,pr-watch.js+334,canary.js+333,doctor.js+285,hook-imports.js+27,coordinator/fix-forward.js+21,ci-none.js−460, and the template +439;dist/src/check-pin-block.js, new, +17,307;doctor.js+612 more,auto-config.js+109,cli.js+70, the template +19 more, andcheck-source.js−907 (checkSourceDoctormoved intocheck-pin-block.js; it is +15,154 here against Required checks count only from the App or workflow pinned for their name [PROTECTED: owner merges] #788's +16,061).Adversarial pass (decision guide)
"new-deps": { workflow: ".github/workflows/untracked-local.yml" }); now never readSECRET-FROM-OUTSIDE-THE-REPO); now listeda symlink: never followedover 256 KiB, not read{ app_id: 15368 }; now a manual choiceinclude/excludematrices,name: 123, a reusable job, an expression name or matrix, an unparseable filetest (22),deploypinned by its key); now each is listed and nothing is pinned for it__proto__; a name over 1000 characters; a workflow file name the schema refusesMap keys must be unique,must be at most 1024 chars); now deduplicated or listed, and the block validates (test)__proto__, a 1100-character name and a broken filesomething-elsebecame{ app_id: 15368 }; now each is a manual choice ("an App it can't prove")total_countcommits/<branch>/check-runs, so the branch could move between pagescommits/main/check-runs; now the tip is resolved to its sha once, and every page reads that commitpinNewConfigwrites only a config auto-config just scaffoldedAdversarial pass, round 5: could a proposed pin let another source pass?
This covers every way a source other than the pinned workflow file's own latest run could pass a check this block pins. Judging is #788's, merged in at bdfcc29; this PR only proposes pins.
app_idpin is proposed only when no file posts the name, the tip was read in full, and exactly one provable App posts it;fromPinnedSource). The pages read are the resolved tip sha's, never the branch's (round 2).What this can never do
ghcalls are GETs.Compromised component
app_id, and only when the tip was read in full and it is the only App. The block says "from main's tip only", and it applies only once the owner pastes it.New dependencies
None.