Skip to content

ci: prove a PR's tests fail without its fix (bug-fix-gate) - #442

Merged
xerhab merged 2 commits into
mainfrom
feat/bug-fix-gate
Aug 12, 2026
Merged

ci: prove a PR's tests fail without its fix (bug-fix-gate)#442
xerhab merged 2 commits into
mainfrom
feat/bug-fix-gate

Conversation

@xerhab

@xerhab xerhab commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What

Adds .github/workflows/bug-fix-gate.yml: an advisory PR check that, for every test file a PR adds or changes, runs that test against the merge-base with only the test files overlaid — the fix absent — and requires it to FAIL there and pass on the head.

A test that passes on the base does not pin the behaviour the PR changed.

Why

This is the prerequisite for letting a Bug ticket's PR merge without a human reading the diff. "CI is green" cannot distinguish a fix that works from a fix whose test never exercised it, and QA-filed Bug tickets are headed for exactly those unattended merges. A local gh pr merge gate (not in this PR) will refuse a Bug PR whose proof is not green.

Advisory, not required. A human merging by hand is the judgement this stands in for, so a red result never blocks a person — a refactor with no behavioural delta legitimately has no regression test to prove. Automation is the only consumer that must obey it.

Not path-filtered, on purpose: it must report on every PR so the merge gate always reads a conclusion rather than an absent check. A PR touching no test files passes with "no proof available".

Also gives the two image-scan jobs explicit names. Both workflows called their job scan with no name:, so both reported the same status-check context and neither could ever be required by branch protection.

How it was verified

Dry-run against XERK-268's real merge (05fa50d) before pushing:

run result
base + only turma/tests/server.test.js overlaid 19 assertion failures
head passes
verdict proven

The extracted prove/classify logic was executed end-to-end against that case and produced | node | failed — assertion (then hung) | passed | proven |. Every run: block passes bash -n; the YAML parses.

That dry run also found two defects in the first draft, both fixed here:

  1. The overlay step copied every changed file, carrying the fix into the base tree and voiding the whole proof.
  2. An unbounded run can hang rather than fail — server.test.js produced 19 real failures on the base and then never exited, on an open handle. Each run is now wrapped in timeout, and a hang is reported (assertion (then hung) / weak (timed out)) rather than silently counted as a good failure.

Known limit, documented in the workflow header and release.md

On android/glasses/veiller a base failure is usually a build error — the test references a symbol the fix introduces — not a failed assertion. That proves the test reaches new code but not that its assertion discriminates. The summary labels those weak (build error) so a person knows to look.

Not verified

The qa agent was not run: this session is configured not to spawn subagents. The android/glasses/veiller paths of the gate have not been exercised against a real PR in those components — only the node path was dry-run. First live PRs touching those will be the real test.

xerhab added 2 commits August 12, 2026 16:39
Adds an advisory PR check that runs every test file a PR adds or changes
against the merge-base with ONLY the test files overlaid — the fix absent
— and requires it to fail there and pass on the head. A test that passes
on the base does not pin the behaviour the PR changed.

This is the evidence an automated merge needs. "CI is green" cannot tell
a fix that works from a fix whose test never exercised it, and Bug
tickets filed by a QA pass are headed for merges no human reads. The
local `gh pr merge` gate will refuse a Bug PR whose proof is not green.

Deliberately advisory, not a required check: a human merging by hand is
the judgement this stands in for, and a refactor with no behavioural
delta legitimately has no regression test to prove. It is also not
path-filtered, so the merge gate always reads a conclusion rather than an
absent check; a PR touching no tests passes as "no proof available".

Also names the two image-scan jobs. Both workflows called their job
`scan` with no `name:`, so both reported the same status-check context
and neither could be required by branch protection.

Verified against XERK-268's real merge (05fa50d): overlaying only
turma/tests/server.test.js onto its merge-base reproduces 19 assertion
failures, and the same file passes on the head — "proven". That run also
exposed two defects in the first draft, both fixed here: the overlay step
copied every changed file (carrying the fix across and voiding the
proof), and an unbounded run can hang rather than fail, so each run is
now wrapped in `timeout` and a hang is reported rather than counted as a
clean failure.
Semgrep's run-shell-injection rule flagged `${{ github.base_ref }}`
spliced into the detect step's script. code-scan runs that rule over
every workflow in this repo, so the fix is the general one: every `${{ }}`
now reaches these scripts through `env:` and is referenced as a shell
variable.
@xerhab
xerhab merged commit 0aff8a2 into main Aug 12, 2026
10 of 11 checks passed
@xerhab
xerhab deleted the feat/bug-fix-gate branch August 12, 2026 22:12
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.

1 participant