fix(887): a pin assumes it is pinning the thing that still DECIDES
Round 6 found three more false greens and named the class they share, which is worth
more than any of the three fixes:
* `web/vitest.config.ts` OUTRANKS the pinned `vite.config.ts` — closed in the previous
commit, found by probing vitest rather than reading about it.
* A DECOY first `test: {` block. The comparison took `text.index("test: {")`, so a copy
of the pin placed above `defineConfig` satisfied it while the real block was narrowed.
Exactly one is now required — the same assertion this file already made about the
gating step's NAME, for the same reason, not carried across.
* A `needs:` edge matched by bare job id. `needs:` resolves within its own workflow, so
a SECOND workflow publishing this Dockerfile while needing its own unrelated job
called `test` satisfied it. Now bound to `GATING_WORKFLOW`. (The reviewer downgraded
this to MEDIUM on measuring that `test_remote_state_inventory.py` forces a human to
classify any new workflow — so the hole is "the guard is blind", not "silent". The
forced review asks about remote state, not about whether the image is gated, so the
one-line fix stands.)
* A vite PLUGIN can shell out to the suite from `buildStart()`. The plugin ARRAY is
pinned; the plugin BODIES are a stated residual, mitigated because
`trackedSourceFilesPlugin` is deliberately lazy — a fact its own comment now marks as
LOAD-BEARING for the image build rather than leaving as an optimisation note.
THE CLASS: **a pin assumes it is pinning the artifact that still decides.** Every route
found so far is authority moving where the pin is not looking — to another FILE, another
OCCURRENCE in the same file, another WORKFLOW, or a HOOK the pinned command invokes. That
question is now written down for the next person adding a pin, because a list of four
instances is not what generalises.
Prose, all refuted by execution: the residual naming the uncovered COPY shapes was wrong a
THIRD time at the same site (`/source/web /elsewhere` IS recognised — only the destination
is renamed — and the file's own test 700 lines below said so); "only an `ENV` is
unmodelled" was an absolute and is now a list; "Reach: N mutants, 0 missed" is restated as
a DEVELOPMENT BATTERY, since it is not in the repo, nothing re-derives it, and an
independent battery found misses against an earlier head; and `PUBLISH_ACTION` was claimed
covered by anti-vacuity, which proves the selector is non-empty and cannot prove it
complete.
Battery 61 -> 64, 0 missed.
Refs: #887
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T79beF1Ufid3dXju4yqkF
This commit is contained in:
@@ -66,6 +66,14 @@ So: the pin removes the SPELLING problem outright and reduces the MEANING proble
|
||||
be worked through — a much better place than a predicate that was wrong nine times. It is not
|
||||
"cannot be wrong", and the list's own history says so.
|
||||
|
||||
THE SHAPE THOSE ROUTES SHARE, which is the thing to check when adding a pin rather than a list to
|
||||
memorise: **a pin assumes it is pinning the artifact that still DECIDES.** Every instance found so
|
||||
far is authority moving somewhere the pin is not looking — to another FILE (`vitest.config.*`
|
||||
outranks `vite.config.ts`), to another OCCURRENCE in the same file (a decoy first `test: {`), to
|
||||
another WORKFLOW (a `needs:` edge naming a job called `test` that is not this one), or to a HOOK the
|
||||
pinned command invokes (a vite plugin, an npm lifecycle script). Ask of any new pin: what else could
|
||||
decide this, and would the pin still match?
|
||||
|
||||
The cost is stated rather than hidden: a legitimate edit to those command lines reddens this guard
|
||||
and its author must update the pin. That is the same deliberate review trigger `KNOWN_GATE_*` uses,
|
||||
applied to the two places where a silent change publishes an untested image.
|
||||
@@ -93,9 +101,12 @@ WHAT IS NOT COVERED, stated rather than implied:
|
||||
copy whose source names `web` — and NOT the wider "has the suite available to run", which is
|
||||
what an earlier version of this sentence claimed. A stage copy that RENAMES the tree on the way
|
||||
in (`COPY --from=web-build /source/web /elsewhere`) has no `web` segment in its source and is
|
||||
not recognised — nor is a source that is an ANCESTOR of it (`COPY --from=web-build /source /src`
|
||||
brings `/source/web` along without naming it). Both were MEASURED; the previous two attempts at
|
||||
this sentence each named a shape that was in fact covered while omitting these. The
|
||||
not recognised. THAT CLAUSE WAS WRONG THREE TIMES and is now stated from a measurement rather
|
||||
than from memory: `COPY --from=web-build /source/web /elsewhere` IS recognised (only the
|
||||
DESTINATION is renamed, and only sources are read). What is not: a source that is an ANCESTOR of
|
||||
the tree (`COPY --from=web-build /source /src` brings `/source/web` along without naming it) or
|
||||
names it only as `/source/.`. Both measured, and both backstopped by the substring sweep — an
|
||||
ancestor-copy stage running `npm t`, `npx vitest` or a new npm script is caught. The
|
||||
false-NEGATIVE direction is what drops a stage out of the check, so the predicate is pinned in
|
||||
both directions by its own test, and the substring sweep below is the backstop for whatever it
|
||||
still misses.
|
||||
@@ -213,6 +224,12 @@ VITE_CONFIG = "web/vite.config.ts"
|
||||
# image published. Pinning one file is worthless while a second can outrank it, so the absence of
|
||||
# every higher-precedence spelling is asserted rather than assumed.
|
||||
VITEST_CONFIG_PREFIXES = ("vitest.config.", "vitest.workspace.", "vitest.projects.")
|
||||
# A vite plugin runs inside the same process as the suite and can shell out from a build hook, so a
|
||||
# `buildStart()` calling `npm test` re-arms ersatztv#887 from the plugin side with every other pin
|
||||
# matching (measured). The ARRAY is pinned so no plugin joins silently; the plugin BODIES are not,
|
||||
# and that is a stated residual — `trackedSourceFiles.ts` is deliberately LAZY for this exact
|
||||
# reason, which its own comment now marks as load-bearing for the image build.
|
||||
PINNED_VITE_PLUGINS = "plugins: [react(), trackedSourceFilesPlugin()],"
|
||||
PINNED_VITE_TEST_BLOCK = """test: {
|
||||
environment: 'jsdom',
|
||||
environmentOptions: {
|
||||
@@ -245,6 +262,16 @@ def _vite_test_block() -> list[str]:
|
||||
explained without the explanation being pinned.
|
||||
"""
|
||||
text = _read(VITE_CONFIG)
|
||||
occurrences = text.count("test: {")
|
||||
# A decoy `const reviewed = { test: {…} }` copied byte-for-byte from the pin, placed ABOVE
|
||||
# `defineConfig`, satisfies a comparison that takes the FIRST match while the real block is
|
||||
# narrowed — measured green. The file already requires exactly ONE step named `Test SPA` for the
|
||||
# same reason; the reasoning had not been applied here.
|
||||
assert occurrences == 1, (
|
||||
f"{VITE_CONFIG} contains {occurrences} `test: {{` blocks, not one. The pin compares the "
|
||||
f"FIRST, so a second is either a decoy that satisfies it while another block decides, or a "
|
||||
f"real second config nobody is checking."
|
||||
)
|
||||
start = text.index("test: {")
|
||||
depth = 0
|
||||
for index in range(start, len(text)):
|
||||
@@ -560,13 +587,20 @@ def test_every_IMAGE_PUBLISHING_job_is_gated_on_the_job_that_runs_the_suite() ->
|
||||
if not any(stage_rel == dockerfile for stage_rel, _, _ in spa_carrying_stages()):
|
||||
continue
|
||||
gates = _transitive_needs(job_id, jobs)
|
||||
if GATING_JOB not in gates:
|
||||
# `needs:` names a job in the SAME workflow, so the edge only reaches the pinned step when
|
||||
# the publishing job lives in `GATING_WORKFLOW`. Matching a bare job id anywhere was a false
|
||||
# green: a second workflow publishing this Dockerfile while `needs:`-ing its OWN unrelated
|
||||
# job called `test` (`run: echo ok`) satisfied it — measured, and it escaped every
|
||||
# workflow-population guard in the repo, 588 tests green.
|
||||
if rel != GATING_WORKFLOW or GATING_JOB not in gates:
|
||||
faults.append(
|
||||
f"{rel}: job `{job_id}` publishes an image built from `{dockerfile}`, which carries "
|
||||
f"the SPA source, but does not transitively `needs:` `{GATING_JOB}` — the job "
|
||||
f"holding the pinned suite step. It needs: {sorted(gates)}. Since ersatztv#887 the "
|
||||
f"image build deliberately does not run the suite itself, so this `needs:` edge is "
|
||||
f"the ONLY thing standing between a red suite and a published image."
|
||||
f"the SPA source, but does not transitively `needs:` `{GATING_JOB}` IN "
|
||||
f"`{GATING_WORKFLOW}` — the job holding the pinned suite step. It needs: "
|
||||
f"{sorted(gates)}. A `needs:` edge resolves within its own workflow, so a job named "
|
||||
f"`{GATING_JOB}` in a DIFFERENT workflow is a different job and gates nothing. Since "
|
||||
f"ersatztv#887 the image build does not run the suite itself, so this edge is the "
|
||||
f"ONLY thing between a red suite and a published image."
|
||||
)
|
||||
assert not faults, "\n".join(faults)
|
||||
|
||||
@@ -698,6 +732,23 @@ def test_the_npm_SCRIPT_MAP_is_exactly_what_was_reviewed() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_the_VITE_PLUGIN_LIST_is_exactly_what_was_reviewed() -> None:
|
||||
"""A plugin can run the suite from a build hook, so the list of them is pinned.
|
||||
|
||||
Vite plugins execute in the same process as `vite build` and `vitest`, and a `buildStart()` that
|
||||
shells out to `npm test` puts the suite back into the gitless stage with every command pin
|
||||
intact — measured, both as an inline plugin and inside an existing one. The list is pinned so a
|
||||
new plugin cannot arrive unnoticed; what each plugin DOES is a residual, and the mitigation
|
||||
there is that `trackedSourceFilesPlugin` is deliberately lazy.
|
||||
"""
|
||||
text = _read(VITE_CONFIG)
|
||||
assert text.count(PINNED_VITE_PLUGINS) == 1, (
|
||||
f"`{VITE_CONFIG}`'s plugin list is not the pinned one ({PINNED_VITE_PLUGINS!r}). A vite "
|
||||
f"plugin runs in the same process as the suite and can shell out from a build hook, so a "
|
||||
f"new one is a decision to state. Update the pin deliberately."
|
||||
)
|
||||
|
||||
|
||||
def test_NO_OTHER_vitest_CONFIG_outranks_the_pinned_one() -> None:
|
||||
"""A pinned config is worthless if a second file can take precedence over it.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user