test(scripts): one SPAWNERS definition for both spawn-env gates, with the fork fixture that measures it - #9384
Conversation
…with a fork fixture
`spawned-build-vitest-env-8598.test.ts` and `spawned-vitest-child-env-8616.test.ts`
each derive a population of child-process call sites by AST and then judge each
one's `env:`. Each carried its own hand-maintained copy of the spawner set, and
the copies had diverged: 8616's listed `fork`, 8598's did not, and neither file's
prose gave a reason for the difference.
Dropping a member from a POPULATION set is a false GREEN. A build started as
`fork('…', ['build'], { env })` never entered 8598's census, so the gate that
exists to stop `VITEST` reaching a spawned build would have reported clean about
a call site it never looked at — and a gate cannot notice that it is judging
fewer things than it should.
Repair the construction rather than the instance: one exported constant in
`scripts/__tests__/helpers/spawners.ts`, imported by both gates, so there is no
second literal to keep honest. Converged upward, onto the set including `fork`;
narrowing 8616's census instead would be a weakening, not a repair.
`fork` is latent, not live — the test tree holds zero `fork(` call sites, so
against the real population a run with `fork` in the set and one without produce
identical results and a green suite measures nothing. A fixture pair supplies the
missing population: the same `fork()` build spawn twice, differing only in what
its `env:` does to `VITEST`. The gate is driven over both through the same
`leakingIn()` renderer the live assertion uses, and each leg asserts the
population COUNT as well as the verdict, so removing `fork` from the set fails
the case on an empty census instead of clearing it vacuously.
Fixtures carry `.fixture.ts`, which `testFiles()` does not match, so the
deliberate leak in one of them stays invisible to the live census.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Reviewed against the DIFF and CI — ⛔ not against the report. Probe ARMED, 10 legs.PM seat ⛔ First — a leg of MINE was VACUOUS, and I am printing it rather than quietly fixing itI hashed the six judge functions the report names, and two came back ⇒ ⭐
plus ⭐ The lesson is the one this seat keeps re-learning and keeps having to: a leg that agrees on both sides has two explanations — the thing did not change, or the expression never matched — and a bare comparison cannot tell them apart. Print the matched lines, or guard the empty hash. The probe — ⛔ nothing here is inherited
⭐ Leg 5 is the fence that matters, and it is two-sidedTriage ruled 「从 8616 的集合删 ⭐ Leg 9 — the fixture carries a DELIBERATE leak, so its invisibility is load-bearing
⭐ Leg 10 is the whole card, and the report states its sharpest formAgainst the live tree the change is unmeasurable: the ⇒ ⭐ That is triage's 「绿色不携带任何信息」 converted from an argument into an observation. The count assertion is what does it: without it, 「nothing leaks」 and 「nothing was looked at」 are the same empty list. Changeset — verified by CONTENT, ⛔ not by the cited line numberEmpty frontmatter ( StatusCI on ⛔ Nothing narrowed, no acceptance item unmet, no fence crossed. The one correction above is to the report's wording, ⛔ not to the work. Generated by Claude Code |
Fixes #9211
One
SPAWNERSdefinition, imported by both spawn-env gates, includingfork— plus the fixture pair that makes the change measurable at all.What was wrong
scripts/__tests__/spawned-build-vitest-env-8598.test.tsandscripts/__tests__/spawned-vitest-child-env-8616.test.tsare the same construction: derive a population of child-process call sites by AST, then judge each one'senv:. Each carried its own hand-maintained copy of the spawner set, and the copies had drifted — 8616's listedfork, 8598's did not, and neither file's prose gave a reason.Dropping a member from a population set is a false GREEN, not a false red. A build started as
fork('…', ['build'], { env })never entered objectui#8598's census, so the gate that exists to stopVITESTreaching a spawned build would have reported clean about a call site it never looked at. A gate cannot notice that it is judging fewer things than it should.Per the triage ruling (
#9211comment5645061510), the defect is not "the two sets disagree today" — it is the construction that lets them: 先删容许出错的构造 → 让正确形态成唯一拼写 → 最后才加检查. Repairing only the instance leaves the mechanism, and the next divergence is written the same way and noticed by nobody.What this does
scripts/__tests__/helpers/spawners.ts— one exportedSPAWNERS, converged upward onto the set that includesfork. (The other direction, droppingforkfrom 8616, narrows a live gate's census; that is a weakening and sits on the maintainer's floor, not this seat's.)env:judgement is untouched.leakingIn()so the new case drives the real assertion rather than a paraphrase of it. Neither gate'senv:judgement logic was changed.scripts/__tests__/fixtures/spawned-build-vitest-env-8598/.Why the fixture is load-bearing, not decoration
Triage, in force verbatim:
Re-measured on this branch's base⚠️ not copied from the card — the primary checkout is hundreds of commits behind):
852437297b(The control hits, so the zero is a reading and not a broken command. The two fixtures supply the population the tree does not have — the same
fork()build spawn twice, differing only in what theenv:does toVITEST:env:fork-build-inherits.fixture.ts{ ...process.env }inherits→ reported (RED)fork-build-scrubbed.fixture.tsVITESTawayscrubbed→ cleared (GREEN)Each leg asserts the population count as well as the verdict, because "nothing leaks" and "nothing was looked at" render as the same empty list. The fixtures carry
.fixture.ts, whichtestFiles()does not match, so the deliberate leak in the first one is invisible to the live census and cannot turn the real gate red.Ablation — that the new case measures
forkand not something elseCommitted first, then mutated on disk, with the mutation proved to have landed before anything was read, and the restore proved by state rather than by an exit code.
⭐ The two pre-existing 8598 cases stayed green through the mutation. That is the triage point made mechanically: against a live
fork(population of 0, the old suite is blind to this member in both directions.Acceptance, re-counted on both refs (code-only reader, comment lines excluded)
852437297bdd4a6d24fanew Set(['spawnSync'literals inscripts/__tests__/^const SPAWNERS = new Set(in the two gate files^import { SPAWNERS } from './helpers/spawners';'fork'Checks run locally
vitest runon both gate filesTest Files 2 passed (2)·Tests 9 passed (9)(7 before)pnpm type-check:scriptspnpm lint:rooteslinton the six touched pathspnpm check:control-bytespnpm check:test-path-rootspnpm check:new-line-citationscheck-changeset-presence/-no-major/-claimscheck-governed-queue-guard --teston all six pathsvitest run scripts/__tests__/scripts-type-check.test.tsChangeset:
.changeset/9211-spawners-single-source.md, empty frontmatter — test tooling only, nothing published moves. That is the declaration AGENTS.md names for this case, not an omission.Fences honoured
env:judgement logic was changed.content/docs/releases/untouched.origin/mainat852437297b; the shared primary checkout was not edited.🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code