Skip to content

fix(test): bypass pnpm dependency checks in mutations - #1326

Merged
davidfarah2003 merged 1 commit into
mainfrom
fix/1325-mutation-launcher
Sep 6, 2026
Merged

fix(test): bypass pnpm dependency checks in mutations#1326
davidfarah2003 merged 1 commit into
mainfrom
fix/1325-mutation-launcher

Conversation

@davidfarah2003

Copy link
Copy Markdown
Contributor

Summary

  • disable pnpm's pre-run dependency verification only for commands launched by mutation-proof
  • keep mutation fixture commands unchanged, so dependency-metadata mutants reach their real suites under CI
  • pin the child-scoped override in the mutation-proof self-test

Cause

pnpm 11 defaults verifyDepsBeforeRun to install for pnpm run and pnpm exec. A mutation that changes dependency metadata intentionally desynchronizes package.json from pnpm-lock.yaml. Under CI's frozen lockfile, pnpm aborts before the configured suite starts, so mutation-proof sees zero progress marks instead of the guard's verdict.

Without CI set, the same launcher silently rewrites pnpm-lock.yaml before running the suite. That side effect hid the failure locally and is recorded here as a tooling footgun. This change prevents it only inside mutation-proof child runs.

There are 9 mutation cells across 5 fixtures that target a package.json. Four manager-runtime-deps cells are demonstrably broken by dependency-metadata desynchronization. The remaining five are latent exposure, not known failures, because script-only manifest mutations can leave the lockfile consistent.

Validation

  • node scripts/mutation-proof.selftest.mjs: 33 checks passed
  • CI=1 node scripts/mutation-proof.mjs --config bin/smoke/mutations/manager-runtime-deps.json: baseline 54 marks, all 4 mutations KILLED on their named dependency checks
  • CI=1 node scripts/mutation-proof.mjs --config bin/smoke/mutations/package-test-contract.json: baseline 79 marks, all 4 mutations KILLED; the comparison cells remained at 76 and 77 marks

Closes #1325

@davidfarah2003

Copy link
Copy Markdown
Contributor Author

APPROVE

Exact head: 90cebf04dac756cf0e52c2a59d8dbe40d3546007 (matches git rev-parse HEAD and gh pr view 1326 --json headRefOid).

Negative-controlled the fix: yes. Misspelling pnpm_config_verify_deps_before_run as pnpm_config_verify_deps_before_runn in scripts/mutation-proof.mjs run() restored the original defect. Restoring the spelling restored the kills. The env var is load-bearing, not cargo.

Did pnpm install --frozen-lockfile in this worktree.

1. Central probe

RAN (not read):

  • Self-test green at HEAD: node scripts/mutation-proof.selftest.mjs → 33 checks, parent env still unset after.
  • Negative control (misspelled key, CI=1, --allow-dirty):
  • Positive control after restore (clean tree): all four KILLED — M1 51 marks, M2/M3/M4 53 marks, each naming its cell. Baseline 54.

Independent of the author's summary: pnpm 11.1.2 (~/.cache/node/corepack/v1/pnpm/11.1.2/dist/pnpm.mjs) actually reads this spelling.

  • Default is "verify-deps-before-run": "install".
  • addSettingsFromWorkspaceManifestToConfig assigns process.env.pnpm_config_verify_deps_before_run onto verifyDepsBeforeRun when the var is non-null.
  • pnpm run / pnpm exec call runDepsStatusCheck when that setting is truthy; "install" then runs install (or, under CI, frozen-lockfile abort) before the script.
  • pnpm itself injects extraEnv.pnpm_config_verify_deps_before_run = "false" for nested script env, so the string "false" is pnpm's own bypass token. The PR uses that token. Casing of this special-case is lowercase pnpm_config_…; that is what the PR sets.

"false" is a non-empty string, so the if (opts.verifyDepsBeforeRun) gate still enters runDepsStatusCheck, but the switch only acts on install|prompt|error|warn, so a stale tree is neither installed nor failed. That is the intended no-op.

2. Scope of the environment change

  • Leak: none to the parent. Set only as spawnSync env: { ...process.env, pnpm_config_verify_deps_before_run: "false" } inside run(). process.env of mutation-proof is not mutated. Confirmed parent unset before and after the self-test. Applies to every command that run() launches (baseline and each mutant), which is the intended child tree, not the operator shell.
  • What it hides: a genuinely stale node_modules vs lockfile during a reproof run is no longer caught by pnpm's pre-run check. Acceptable: CI already pnpm install --frozen-lockfile before this tool; the mutation is a deliberate desync, so the check is always the wrong signal on mutant cells. The cost is that mutation-proof will not additionally notice an unrelated stale install. That is a real trade, not a free win.
  • Self-test can fail: proven. Break the spawn key and it reddens immediately on the new assertion. Ambient install in runTool plus the child !== 'false' check is the writer.

3. Regression control

RAN CI=1 node scripts/mutation-proof.mjs --config bin/smoke/mutations/package-test-contract.json at this head.

  • baseline 79 marks
  • all 4 KILLED
  • comparison cells 76 and 77 (against baseline 79), matching the pre-change chore(release): version packages #1305 log the author cited
  • remaining two cells 160 and 78 (the assertion-counter mutant is supposed to emit extra marks)

Did not sample a third fixture family. PTC is a different family (script-field package.json mutant plus suite-source mutants) and is the corroboration the review asked for.

Independent census of bin/smoke/mutations/*.json: 9 package.json cells across 5 fixtures. Matches the body.

4. Claims

  • Census wording: body says 9 cells / 5 fixtures at risk, 4 demonstrably broken, 5 latent. That distinction is correct. package-test-contract mutates a test script, not dependency metadata, and graded here. The other latent cells (package-json-duplicate-keys, sandbox-guard M5/M6, smoke-ci-build-first) also edit scripts, not dep blocks.
  • Footgun: stated. "Without CI set, the same launcher silently rewrites pnpm-lock.yaml."
  • Closes: only Closes #1325. No other #NNNN in the body.
  • Changeset: none in the PR. No package should bump (test/CI machinery only). The house pattern for a CHANGELOG line with no bump is empty frontmatter (---\n---). That file is absent. Non-blocking: the gate this unblocks is the reproof, not a publish bump. Worth adding if the release notes should mention the launcher fix.

CI at 90cebf04dac756cf0e52c2a59d8dbe40d3546007

Enumerated via the runs API at the full 40-character sha.

Workflow Present by name Conclusion
Mutation reproof PRESENT success
CI PRESENT UNMEASURED (in_progress)
Windows PRESENT UNMEASURED (queued)
Code Quality: PR #1326 PRESENT success
Docs correctly absent path filter is website/**, docs/**, spec/**, SPEC.md, assets/**, .github/workflows/docs.yml; this PR touches only scripts/mutation-proof.mjs and scripts/mutation-proof.selftest.mjs
Installer correctly absent path filter is install.sh, install/**, .github/workflows/installer.yml

Do not treat CI or Windows as green.

Could not check

  • Full 342-fixture sweep (cost; sampled the broken fixture and one other family).
  • CI / Windows conclusions at this sha (still queued / in progress).
  • Live lockfile rewrite without CI (would dirty pnpm-lock.yaml; body claim is consistent with pnpm 11.1.2 case "install": install2()).

Ran vs read

Ran: pnpm install --frozen-lockfile; self-test; self-test with misspelled env; manager-runtime-deps mutation-proof with misspelled env; same after restore; package-test-contract mutation-proof; pnpm 11.1.2 source for the env mapping; issue-ref scan of the PR body.

Read: PR body, the two-file diff, fixture JSON, docs.yml / installer.yml path filters, existing empty-frontmatter changeset example.

Worktree source is clean (git status --porcelain empty). Temporary misspell of the env key was restored with git checkout HEAD -- scripts/mutation-proof.mjs before the positive control. No leftover mutation-proof processes.

davidfarah2003 added a commit that referenced this pull request Sep 6, 2026
mutation-proof consumed each run's output for counting and filtering
(`progressCount`, the `expectRed` filter, `completionMarker`) and never printed
it. A WRONG-RED therefore reported that the expected string was absent and never
what was there instead.

That is what made #1325 cost a day. `manager-runtime-deps` sat red on main, on
every PR selecting it, and on the 0.46.0 release PR, with all four cells at
`0 marks (baseline 54)` and no way to tell from the log why. The cause was a
pnpm frozen-lockfile abort printing to the child's output, which the runner had
captured (`output = stdout + stderr`) and then discarded.

Non-KILL verdicts now echo that transcript, bounded to the last 20 lines, with a
distinct line when the run produced nothing at all. A KILL prints none: its
verdict already names the assertion that reddened.

Controlled both ways rather than assumed. A deliberately bogus `expectRed`
produces WRONG-RED and prints the suite's own output beneath it, ending in the
`✗ FAIL:` line and `SUITE COMPLETE: 53 passed, 1 failed`; the four real cells
come back KILLED and print nothing extra.

Scope note: this deliberately does NOT fix the launcher defect behind #1325.
That is #1326's, which sets `pnpm_config_verify_deps_before_run=false` in the
spawned children and carries a negative control plus a self-test. I had a
narrower fixture-level fix and dropped it: two mechanisms for one defect leaves
the next reader unsure which is load-bearing, and it would have made
`manager-runtime-deps` the only fixture invoking its suite differently from the
other 341.
@davidfarah2003
davidfarah2003 merged commit 1c94c7f into main Sep 6, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mutation: manager-runtime-deps cells exit 1 with 0 marks on main, blocking the 0.46.0 release reproof

1 participant