Repository navigation
test(docs): the post-gate hooks and the npm publishers keep only the tests that guard a relation - #216
Conversation
…tests that guard a relation; test/root.ts's comment sits on its export
File size check0 over a hard cap (fails), 14 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 4 managed file(s) skipped; repo-platform owns them. |
There was a problem hiding this comment.
🟡 Changes recommended
Several replacement tests allow security, permission, queueing, and publishing-contract regressions to pass unnoticed.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Replaces brittle workflow snapshots with tests of cross-file release and publishing invariants.
Changes:
- Adds post-gate reachability, permissions, SHA, and probe-gating checks.
- Adds relational npm publisher checks while retaining Bash behavior tests.
- Removes an unused helper and fixes JSDoc placement.
File summaries
| File | Description |
|---|---|
test/root.ts |
Attaches documentation to ROOT. |
test/docs/workflow-loader.ts |
Removes unused SETUP_USES. |
test/docs/post-green-workflow.test.ts |
Reworks post-gate workflow tests. |
test/docs/npm-publish-workflows.test.ts |
Reworks npm publishing tests. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 6
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… publishing claims are relations; a fixed fence token and a token in a job's env fail
There was a problem hiding this comment.
🟡 Changes recommended
Several new assertions allow security-sensitive workflow regressions to pass undetected.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Balanced
…ery call, npm is not configured through the env
…the steps that read SOURCE_SHA; the post-fence error carries none of git's words
There was a problem hiding this comment.
🟡 Changes recommended
Several new relations still permit publishing schedule, output-gate, token, and source-SHA drift to pass unnoticed.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
test/docs/npm-publish-workflows.test.ts:44
- Workflow-level environment variables bypass the token check. Adding
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" }at the top of either publisher workflow exposes that token tonpm publish, butrunnerInputs()only visits the job and its steps, so this test still passes. Include top-levelenvfrom both workflows in the scanned inputs.
test/docs/post-green-workflow.test.ts:245 - The documentation schedule is reduced to "contains release cut", so unrelated schedules are treated as green pushes. For example, changing the
nextrow toEvery pull requeststill selects post-green.yml and passes, even though that workflow is not called for pull requests; changing the post-green caller to a pull-request condition also passes. Reject unknown schedule text and match the green-push claim to a caller condition that requires a push torefs/heads/main.
This issue also appears on line 257 of the same file.
test/docs/post-green-workflow.test.ts:263
- A syntactically shaped verdict gate is accepted even when no step can produce its output. Setting the stable publish step to
if: steps.missing.outputs.go == 'true'passesVERDICT_GATE, so the documentation test stays green while every release skips publishing. Resolve a verdict gate through an earlier probe/output writer before counting the subcommand as runnable.
const runsSubcommand = (workflow: Workflow, subcommand: string): boolean =>
Object.values(workflow.jobs).some((job) =>
(job.steps ?? []).some(
(step) =>
new RegExp(`release-pipeline\\.ts ${subcommand}(?![\\w-])`).test(step.run ?? "") &&
(step.if === undefined || VERDICT_GATE.test(condition(step.if))),
),
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
…be is conditioned, the library is built before it is published
There was a problem hiding this comment.
🟡 Changes recommended
Publisher checks discard stable-step conditions, allowing required build steps to be silently skipped.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
The new checks allow post-gate callers and stable publishing prerequisites to be incorrectly gated.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
test/docs/npm-publish-workflows.test.ts:146
- Stripping
iflets the stable publisher skip required shared steps without failing this relation. Addingif: ${{ false }}to itsBuild the librarystep still leaves the compared bodies and ordering unchanged, butnpm publishthen runs without producinglib/pkg. Require the stable publisher's shared prerequisites to be unconditional; the next publisher's conditions remain covered by the probe-wiring check.
for (const [label, a, b] of sharedSteps(next, stable)) {
if (JSON.stringify(body(a)) !== JSON.stringify(body(b)))
problems.push(`"${label}" diverged between the publishers`);
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Balanced
…probed job allows only a verdict gate
There was a problem hiding this comment.
🔵 Needs a closer look
The tests allow a post-gate hook to run after a failed all-green gate.
Review details
Suppressed comments (1)
test/docs/post-green-workflow.test.ts:102
- The downstream check does not prevent a hook from bypassing a failed gate. For example, changing
ci.yml'spost-green.iftoalways()keepsneeds: [all-green], somisplacedremains empty and this suite passes, but the hook can package and publish a red commit. Assert that direct post-gate callers requireneeds.all-green.result == 'success', and preserve equivalent success gating along indirect paths.
expect(misplaced, "hook callers that all-green does not gate").toEqual([]);
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…hook is the one caller on a push to main behind the gate
There was a problem hiding this comment.
🔵 Needs a closer look
Documentation drift can escape detection, while equivalent concurrency mappings can fail due solely to key order.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
test/docs/npm-publish-workflows.test.ts:142
- The lane comparison treats YAML key order as behavior. Reordering the same
group,queue, andcancel-in-progressentries in one workflow makesJSON.stringifyreport different lanes even though GitHub receives the same concurrency configuration. Normalize the entries before comparing them.
test/docs/post-green-workflow.test.ts:242 - Unknown documentation text is silently treated as a green-push claim. For example, changing
next'sPublishes oncell toEvery pull requeststill selectspost-green.yml, soclaimProblems(page)returns no problem even though the page is wrong. Reject claim text that names neither supported event.
test/docs/post-green-workflow.test.ts:283 - Two matching rows do not guarantee that both channels are documented. Replacing the
latestrow with a copy of thenextrow leaves two matches, and both validate against the green-push hook, so deleting the stable-channel documentation passes. Check the captured tag set, not only its length.
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…utcomes; expressions normalized before comparison
There was a problem hiding this comment.
🔵 Needs a closer look
Container-level environment variables can bypass the new no-registry-token invariant.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
test/docs/npm-publish-workflows.test.ts:156
-
Container-level environment variables bypass this scan even though they reach every publisher step. For example:
container:
image: node:24
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" }
publisherProblems() reports no token problem for that job, so the documented no-registry-token invariant can regress silently. Add container.env to the workflow model and scan its keys and values here too.
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Several new relation tests have demonstrated blind spots that can silently permit incorrect or unsafe workflow drift.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
test/docs/npm-publish-workflows.test.ts:186
- A later
setup-nodestep can redirect the stable publish without failing this relation. Ifpublish-npmgains a secondactions/setup-nodebeforenpm publishwith anotherregistry-url, bothsharedSteps()andsetupNode(stable)inspect the first action, sopublisherProblems()still returns no problem while npm uses the second registry. Check every setup-node occurrence, or require exactly one in each publisher.
test/docs/npm-publish-workflows.test.ts:439 - A valid hyphenated outcome is omitted from this extracted union. Adding
{ outcome: "timed-out"; ... }while leaving the workflow case block unchanged still leavesoutcomesas the original three values, so all confirmation assertions pass despite the missing arm. Capture the complete quoted literal rather than letters only.
test/docs/post-green-workflow.test.ts:242 - An unrecognized documentation schedule is silently treated as a green push. For example, changing the
nextrow toEvery pull requestleavesreleasefalse, sohookFor()still selectspost-green.ymlandclaimProblems()returns no problem. Reject claims that do not name exactly one supported schedule before selecting a hook.
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
One deleted test beside what covers it now:
The second pass of the same rule, on the two files PR #203 rewrote. The line count ends above where it began (4302 -> 4420): the two byte-exact workflow pins and their 50 mutation controls are gone, and what stands in their place is the set of relations six codex rounds, three Copilot passes, and the lead gate each showed a silent break for, so the file is no longer a copy of the workflows but is not smaller. The whole-workflow pins of
post-green.ymland of the two npm publishers are gone, the properties those pins happened to guard are stated as relations between artifacts (ci.yml's callers and the hooks, a job's grant and its steps, the two publishers and package.json's slug, the library page's claim and the jobs' env), and the bash runs of the probe, the floor guard, and the publish blocks stay.test/root.ts's floating JSDoc now sits on the export it describes.workflow_callalone with their ci.yml callers downstream ofall-green;withkeys == declared inputs; each job's effective grant covers its pushes and OIDC publishes; the judged sha is every checkout's ref and everySOURCE_SHA; every step after a probe runs on its verdict; the push probe's fence and remedies under bashowner/name; one lane; the stable one downstream of every other job; no registry token, as library.md promises; the shared steps identical; one registry; probe, floor guard, and publish blocks under bashSETUP_USEShad no user left;Job.envandWorkflow.envadded for the token scanROOTProof:
bun run checkexit 0 on every commit (70fab5f, this head: 3371 pass, 0 fail, build:check 12 files match; all-green success, CI run 34779825625); codex rounds 1 to 5 each closed with their fold committed, rounds 6 and 7 returned 0 blocking (7 on the lead gate fold); Copilot's twelve threads fixed or answered and resolved.Technical details
Line accounting: test files -761 / +879 (net +118, all under
test/docs);test/root.ts-2 / +1; branch numstat +880 / -763. Both files end above their start: post-green-workflow.test.ts (718 -> 720) and npm-publish-workflows.test.ts (455 -> 571), because the two-publisher contract pin became nine relations (fork guard from package.json, one literal lane, stable last, no token or npm_config in any env, shared steps identical, build before publish, no step under its own condition, one registry) plus their controls, each of which a reviewer showed a silent break for; post-green-workflow.test.ts went 718 -> 374 -> 720 as five deleted pins came back as relations a reviewer showed a silent break for.test/library,test/sections, and every other test directory are untouched.Per-test classification
RESTATES = pins one file's text or shape, no second artifact. COVERED = the same drift fails another gate or is loud at runtime (named). INVARIANT = relates two artifacts or a property a later edit breaks with no other check noticing.
post-green-workflow.test.ts
CALLER_EXPECTEDpin of the whole workflow) -> RESTATES, deleted with its six script-text constants (PUSH_PROBE,FENCED_STDERR,OIDC_PROBE,NPM_FLOOR,PUBLISH_NEXT,CONFIRM_NEXT)proceed=to GITHUB_OUTPUT) carriessteps.<id>.outputs.<x> == 'true'where<id>is an earlier step that is the probe or is gated the same way (3 negative controls)contents: writefor a step that pushes or writes a release andid-token: writefor a step that publishes through OIDC; a block below the ceiling makes the probe warn and skip quietly (1 negative control)github.shaas post-green's one input, and that input is every checkout'srefand everySOURCE_SHAall-greencalls hason: [workflow_call]and nothing else; checks.yml (called before the gate) is the control (2 negative controls)withkeys == the hook's declared inputs" is kept for every hookpackage-commitandprerelease-versionrefuse a shallow checkout loudlybehindas a warning -> INVARIANT, restored under bash: the confirm block runs with a stubbedbunprinting each outcome literal of the script'sConfirmVerdictunion plus an unknown line;behindand the unknown exit 1 with::error::,unsettledwarns,settlednotices, and the table of expected verdicts must name every outcome of the union. Publish under the default dist-tag / registry token / probe warning naming one remedy / fenced stderr / fixed fence token -> the other bash runs observe each; the verdict semantics themselves aretest/scripts/release-pipeline.test.ts'ssecrets.Xthe checkout'ssecrets.X || github.tokenfalls back from, assecrets.X != '', and the script must branch on that env name::warning::namingcontents: writeandREPO_PLATFORM_TOKEN", the static error to "one::error::after the fence"npm-publish-workflows.test.ts
EXPECTEDcontract pin) and its 20 controls -> the pin is gone; the fields became relations:repositoryGuards-> INVARIANT: bothif==github.repository == '<package.json repository slug>'lanes-> INVARIANT: the twoconcurrencyblocks are equal, with a literal group (queue/cancel literals dropped: GitHub rejects the bad pairing loudly)stableNeeds-> INVARIANT: publish-npm needs exactly every other job of update-release.ymltokenInputs,setupNodeEnvs-> INVARIANT: library.md says "no registry token exists anywhere", and no env/with/if of either publisher names a secret or a tokensameFloor,sameBuild,registries-> INVARIANT: every step the two publishers share (by name, or by action other than the checkout) is the same text, gate and id aside; the registry-url is one string in bothstablePermissions-> COVERED by the grant relation in post-green-workflow.test.ts;nextPermissions: undefined-> RESTATES (a block equal to the ceiling is legal);ungatedNextSteps-> COVERED by the probe-gate relation;publishCommands-> the bash runs observe them::warning::namingid-token: write"floor=lineCodex rounds
permissionsignored; a deleted packaging step or a job-levelifpassing; the confirmation step or itspublishedoutput gone; the probe'sPAT_SETenv unboundifis the fork guard or nothing; every written output is read and every read is written; the probe's env reads the same secret the checkout falls back from; assertions and controls read one problem listqueue: maxdropped (out of scope, answered); token in a job's env or a with key; floor derived from the script alone; a release hook moved ahead of the gate; pipeline subcommands not counted as pushesgh releasecount as contents consumersifskips the whole job quietly; a publish or packaging step underif: github.event_name == 'release'still satisfies the channel claim;NPM_CONFIG_TAG: nextin a job env moves the stable dist-tagnpm_config_*name in env or with fails; the anchor control goes throughgrantProblems; the header's overclaim removedenvtoken unseen; source steps selected by the asserted env, so a dropped SOURCE_SHA passed; the post-fence error not proven static$SOURCE_SHA; the error line carries none of git's wordsifleft the probe judging an empty workspace; the library build moved after the publishif: github.event_name == 'release'skips on the push that calls it and publishes a source-only tarballif: github.event_name == 'release'skipped on the push that calls it, unchecked in a probe-less jobalways()stays downstream (out of scope, answered: ci.yml is managed, the platform's skeleton-gate holds the contract); the green-push lookup selected by the absence ofrelease_createdbehindarm downgraded to a warning passed every check while the body claimed the bash runs observed it; the judged-sha relation compared raw expression strings a managed sync could respell; a stale case namebehinddowngrade, a deletedbehindarm,exit 1dropped from the unknown arm, and a union outcome without an arm each fail a named assertion; nits: bareinputs.shapassescondition()but fails loudly at checkout (recorded), two comments restated the table and its stub (trimmed)hookFor(recorded)Recorded, not built
The confirm hold (
CONFIRM_READSx the read interval) against the publish-next job'stimeout-minutes: raising the reads past the timeout is never covered.Both setup-node
registry-urlvalues against the script'sDEFAULT_REGISTRY.publish-npm's checkout ref (the resolved source sha) against a ref of
main: the verdict holds the built manifest toTAG, so this is uncovered by design.Job ids and step names are pinned in the test helpers (
publish-next,publish-npm,Build the library,Require an npm ...): renaming one fails four controls by name; acceptable, and stated here.The lane relation compares the two
concurrencyblocks throughJSON.stringify, so a key reorder reads as a different lane.condition()accepts a bareinputs.shaorgithub.shawhere GitHub needs the${{ }}wrapper; such a value fails loudly at checkout or at the pipeline's exact-sha check.The shared lane's
queue: max: one file's setting with no second artifact (the lane relation holds both publishers to one literal lane).A hook's job-level
permissionsblock above the caller's ceiling (GitHub fails the call loudly).The green-push negative control in post-green-workflow.test.ts filters the callers with its own copy of the selector instead of calling
hookFor; sharing the selector would let the control fail through the real path.A hook caller rewritten to
always()or to a pull_request event: a deliberate change of the managed ci.yml's gate contract; validate-managed-files fails on the drift and the platform's skeleton-gate rule holds the contract.A second publisher spelled without
npm publish, or a token reaching npm through a file the workflow writes: deliberate evasion, outside the stated accidental-drift scope.Out-of-territory findings
update-release.yml'sverify-releasejob carriescontents: write"read-only in spirit"; the grant relation reads no consumer there and says nothing about it.post-green,update-release, andupdate-release-prcallers, so a sync that renames a caller or drops awithkey fails here first, naming the platform as the place to fix.