Commit Graph
3 Commits
Author SHA1 Message Date
timothyandClaude Opus 5 a4df8f7958 fix(887): the gate must be REAL, not merely present — two cold reviews' findings
Both independent reviews (Codex GPT-5.6 cross-family, and a cold Opus agent in an
isolated worktree) returned BLOCKED. Both independently confirmed the CI path itself is
sound — neither found a route that publishes an image on which the suite never ran — so
every finding is about the guard's reach, plus one factual error in the prose.

THE STRUCTURAL ONE. The guard asserted a `needs:` edge EXISTS, never that it is load
bearing. Since this change deletes the in-image run, that edge is the only remaining
layer, so `continue-on-error: true`, `if: false`, a job-level `if:`, `npm test … || true`,
a pipe into `tee`, and `set +e` each certified a publish over a red suite with every
assertion green. `test_the_gating_suite_run_is_NOT_ADVISORY` closes all six.

A filter written into `web/package.json`'s script body was invisible at the call site:
`"test": "vitest --exclude x"` with a workflow saying `npm test -- --run` is a filtered
gating run reading as clean — the removed defect, one level down. `vitest_scripts()` now
derives each script's own narrowing arguments and `suite_args` prepends them.

PARSER REACH, every case measured rather than argued. `shlex.split` yields `lint&&npm` as
one token, so unspaced `&&` and `;` re-adds were invisible; `shlex` in punctuation_chars
mode splits them. Added: `sh -c` payload expansion, `npm --prefix`/`npx -p` flag skipping,
`xargs`, heredoc bodies as DATA (a `cat > f <<'EOF' … npm test … EOF` block counted as a
real run), `ADD`/JSON-form/no-trailing-slash `COPY` in `carries_spa_source`, and
redirections no longer read as spec filters. `--root` and `--config` moved to the
narrowing set: both change which specs vitest collects.

A FACTUAL ERROR, in five places including the mutation `expect`: "the build context is
`web/` + `design-system/`, so there is no `.git`". The context is the repository root
(`context: .`) and `.dockerignore` does not exclude `.git`. The true statement is about
the STAGE, which copies only those two directories. The conclusion survives — bookworm
slim has no git binary either — but a reader who checked would have found `.git` in the
context and concluded the note was stale.

ONE FINDING WAS MINE, from the mutant battery rather than from either review, and it is
the reason the battery exists: `failure_suppressions` tokenised the whole multi-line
`run:` body at once. A newline is not a shell separator, so a realistic two-line step —
the `ci-step-ran.sh` marker line, then the suite — merged into ONE segment whose head was
the marker script, and three suppression mutants passed while my single-line unit test
was green. It now works per logical line, and the regression test uses the two-line shape.

17 mutants, 0 missed, each caught by the intended assertion; baseline green. The
`docs/guard-inventory.md` residual list is rewritten as MEASURED reach — the previous one
was wrong rather than merely short, which cold review rightly called worse than silence.

Refs: #887
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T79beF1Ufid3dXju4yqkF
2026-08-30 13:37:58 +02:00
timothyandClaude Opus 5 febaad77d7 fix(887): the image build builds the SPA and does not test it
`docker/Dockerfile`'s web-build stage is gitless twice over — the build context is
`web/` + `design-system/` so there is no `.git`, and `node:22-bookworm-slim` ships no
git binary. Members of the SPA suite need one or the other, so running the suite there
required naming the ones that cannot run. That list was a population nothing derived:
#883 added a third member without updating the hand-written pair of `--exclude`s, and
because `Build & push image (amd64)` is `if: github.event_name != 'pull_request'` the
resulting red was unreachable on a PR. It landed on `main` and on the `v*` tag path
instead — every image build failed, `:latest` stopped being republished, and a release
cut would have failed at the image build.

Adding a third `--exclude` re-arms the trap, so the list is removed rather than
extended: the stage now lints, typechecks and BUILDS the SPA, and the suite runs once,
unfiltered, in `docker-build.yml`'s `test` job on a real checkout. `build` carries
`needs: [test, migrations, scan]`, so no image is published past a red suite.

`scripts/tests/test_image_build_delegates_the_spa_suite.py` holds both halves — the
negative one alone would be satisfied by deleting the `needs:` edge. Three populations,
all derived: tracked Dockerfiles and workflows from the git index, and which npm scripts
ARE the suite from `web/package.json` (so `test` is in and the Playwright `test:ui-e2e`
is out, with no exemption list). Publishing jobs come from the `docker/build-push-action`
step and the Dockerfile each builds from that step's own `file:` input, which is why
`ci-image.yml` is out of scope by derivation rather than by an entry that would outlive
its reason.

Four mutants witnessed red, each by the intended test: a filtered suite run put back
into the Dockerfile, the `needs:` edge deleted, and the gating run narrowed in both the
block and the single-line `run:` step forms.

Refs: #887
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T79beF1Ufid3dXju4yqkF
2026-08-30 13:37:58 +02:00
timothyandtimothy 8aeacd534a fix(819): derive the SPA page-size guard population from the git index (#875)
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 9s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 21s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 8m52s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m23s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m50s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m5s
The guard asserted EXACT completeness over a population enumerated by a directory
walk, so an untracked .ts/.tsx under web/src/ entered it and failed as unregistered
on that developer's checkout while CI — which only ever checks out tracked files —
stayed green.

The glob still supplies file CONTENT; the POPULATION is now the git index, read by
web/vite-plugins/trackedSourceFiles.ts in Vite's own Node context and handed to the
app project as a virtual module. That reaches the index without admitting
@types/node to tsconfig.app.json, the obstacle that deferred this in #818.

Three mechanisms carry the proof, each added because the previous was measured
insufficient: a closed-form restatement of the shared scope predicate (sharing no
helper at any depth with what it checks); a second independent `ls-files --others`
query cross-checking the population; and real-git tests that execute the derivation
against a temp repository.

Six residuals are stated with their MEASURED fail-directions, and
testing.guard-derives-population-from-source gains a bounded exception plus the
closed-form criterion.

fixes #819

Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-29 09:14:15 +00:00