Skip to content

ci: the foreign judge had never run in CI, and nothing could have said so - #45

Merged
tannevaled merged 2 commits into
mainfrom
ci/the-foreign-judge-never-ran-in-ci
Nov 14, 2026
Merged

tannevaled merged 2 commits into
mainfrom
ci/the-foreign-judge-never-ran-in-ci

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Found while reviewing the negative control merged yesterday: the foreign judge it added has never run in CI. Nor has any of the rest of the agreement with qpdf.

Measured

Run 43595391186: the string qpdf appears zero times in the entire log.

And the time is the witness — the only thing that could have been noticed:

ok  github.com/go-pdfkit/reader  0.197s   CI
ok  github.com/go-pdfkit/reader  0.758s   a workstation that has qpdf

The sweep generates 42 files by invoking qpdf. It cannot do that in 0.197 s. So the agreement across 1, 2, 3, 5, 25 and 100 pages in five shapes — and the negative control added yesterday, that an ordinary file is refused by the foreign judge — have never been checked by anything.

Why nothing reported it

  • go test without -v prints nothing about a skip. The log was not hiding this badly; it could not report it at all.
  • Coverage stayed at 100.0% throughout. The same statements are reached by the tests that need no qpdf, so the coverage gate cannot see this and never could.

Two gates were green about a third thing neither of them measures.

This repository already holds the opposite standard, one job down

Of a missing qemu emulator, its own workflow says:

⛔ A missing emulator FAILS this lane. A lane that reports success for an architecture it never ran is the one outcome this matrix exists to rule out.

A missing foreign judge is that outcome for a different reason. go-pdfkit/ops already does this correctly for pdftoppm — its render tests t.Fatal when CI is set.

⛔ REQUIRE_QPDF, not CI

Not every lane can run it, and a rule that demands the impossible gets deleted:

  • under qemu-user the test binary is emulated, and an execve of the host's x86-64 qpdf is not something the emulator can carry out;
  • the Windows lane has no apt.

So one lane installs qpdf and sets REQUIRE_QPDF, and on that lane a missing judge fails. The others skip — which is now a stated design rather than an accident nobody could see.

Both directions measured rather than predicted:

qpdf off PATH
REQUIRE_QPDF=1 FAIL, naming the reason
unset skip, exit 0

aptneed for the install

A mirror outage must not arrive looking like a missing judge — go-pdfkit/ops#90 was four red runs in half an hour on a third party's DNS. --need qpdf asks for the binary, because apt saying yes is not qpdf being on PATH. qpdf --version is printed, so which judge agreed is answerable afterwards instead of assumed.

⚠ One thing deliberately not fixed

actionlint reports SC2086 on go test $ex ./... in the qemu job. It is pre-existing — main carries it at line 141 — and shellcheck is wrong here: ex is empty on the 386 lane, which runs natively on purpose, so quoting it would hand go test an empty package pattern and break the one lane that must not be emulated.

Reader's CI never ran actionlint, which is why it went unnoticed. Silencing it for the right reason is its own change, and I would rather not bury it in this one.

gofmt clean, go vet clean, 0 uncovered blocks of 1806.

Refs #40, #44.

🤖 Generated with Claude Code

tannevaled and others added 2 commits November 14, 2026 13:40
…d so

⛔⛔ EVERY TEST THAT AGREES WITH qpdf HAS BEEN SKIPPING ON EVERY LANE. Measured
on run 43595391186: the string "qpdf" appears ZERO times in the whole log.

⛔ AND THE TIME IS THE WITNESS, which is the only thing that could have been
noticed: this job reports

    ok  github.com/go-pdfkit/reader  0.197s   (CI)
    ok  github.com/go-pdfkit/reader  0.758s   (a workstation with qpdf)

The sweep generates 42 files by invoking qpdf; it cannot do that in 0.197s. So
the agreement across 1, 2, 3, 5, 25 and 100 pages in five shapes, AND the
negative control that an ordinary file is REFUSED by the foreign judge — the
one #44 added yesterday — have never been checked by anything.

⛔ `go test` WITHOUT -v PRINTS NOTHING ABOUT A SKIP, so the log was not
hiding this badly, it could not report it at all. And coverage stayed at
100.0% throughout: the same statements are reached by the tests that need no
qpdf, so the coverage gate cannot see this and never could. Two gates were
green about a third thing neither measures.

⛔ THIS REPOSITORY ALREADY HOLDS THE OPPOSITE STANDARD, one job down, about a
missing qemu emulator: "A missing emulator FAILS this lane. A lane that
reports success for an architecture it never ran is the one outcome this
matrix exists to rule out." A missing foreign judge is that outcome for a
different reason. go-pdfkit/ops already does it right for pdftoppm.

⛔ REQUIRE_QPDF, NOT CI, because not every lane CAN run it and a rule that
demands the impossible gets deleted. Under qemu-user the test binary is
emulated and an execve of the host's x86-64 qpdf is not something the
emulator can carry out; the Windows lane has no apt. So ONE lane installs
qpdf and sets REQUIRE_QPDF, and there a missing judge FAILS. The others skip,
which is now a stated design instead of an accident nobody could see.

Measured, both directions, rather than predicted:

    REQUIRE_QPDF=1, qpdf off PATH -> FAIL, naming the reason
    unset,          qpdf off PATH -> skip, exit 0

⛔ aptneed FOR THE INSTALL, because a mirror outage must not arrive as a
missing judge: go-pdfkit/ops#90 was four red runs in half an hour on a third
party's DNS. `--need qpdf` asks for the BINARY, since apt saying yes is not
qpdf being on PATH. And `qpdf --version` is printed, so which judge agreed is
answerable afterwards rather than assumed.

⚠ NOT FIXED, and deliberately: actionlint reports SC2086 on `go test $ex
./...` in the qemu job. It is pre-existing — main carries it at line 141 — and
shellcheck is WRONG here. `ex` is empty on the 386 lane, which runs natively
on purpose, so quoting it would hand `go test` an empty package pattern and
break the one lane that must not be emulated. Reader's CI never ran
actionlint, which is why it went unnoticed; silencing it properly is its own
change.

gofmt clean, go vet clean, 0 uncovered blocks of 1806.

Refs #40.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
⛔⛔ SECOND OCCURRENCE IN TWO DAYS, AND I PREDICTED THIS ONE IN WRITING. The
first push of the qpdf lane failed on

    go: …/cmd/aptneed@latest: fleettools@v0.112.0 requires go >= 1.27.4
        (running go 1.27.3; GOTOOLCHAIN=local)

go-pdfkit/ops hit the identical line on 2026-11-13, and after fixing it I
wrote on that pull request: "Worth knowing for other repositories in the fleet
that pin a toolchain and install fleettools commands in CI: the same pin
blocks them the moment they try." Then I walked into it myself, one repository
later, the next day. A note is applied when somebody remembers to apply it,
and at the moment of adding an install step one is thinking about the install.

So it is recorded as a recurrence rather than a third note. The gate that
catches it is a check over `.github/workflows` for a `<module>/cmd/<x>@latest`
install beside a `go-version` pin below that module's `go` directive —
already filed as pending on go-fleettools/fleettools#103, where the `@latest`
half of the same problem lives. `tagscan` cannot see either: it reads
repositories, not workflows.

All three jobs move to 1.27.4, not only the one that installs aptneed. The
other two do not need it, but a matrix measuring on two different toolchains
is a difference nobody chose, and this fleet has already been bitten by
`stable` quietly selecting a minor.

⛔ GOTOOLCHAIN IS STILL LEFT ALONE. That is what made this a clean failure
naming both versions instead of a runner silently fetching 1.27.4 and
measuring a chain nobody picked.

go.mod stays at `go 1.27.1`: that is the language version this module needs,
not the toolchain a runner must have, and nothing here requires it to move.

actionlint reports only the pre-existing SC2086 discussed in the previous
commit.

Refs #40, #44.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled

Copy link
Copy Markdown
Contributor Author

Green at b25130e, and the judge demonstrably ran this time. Two things the log says that are worth having on the record before this merges.

It ran

aptneed: installed qpdf, qpdf on PATH
qpdf version 11.9.0
ok  github.com/go-pdfkit/reader  0.262s  coverage: 100.0% of statements

25 mentions of qpdf in the log, against zero in the run this PR was opened from.

⛔ The timing alone would not settle it — 0.262 s against 0.197 s before is a smaller gap than I expected, and runner speed varies. The argument that does settle it is structural: REQUIRE_QPDF=1 is set on this lane, so a missing qpdf fails rather than skips. The lane is green and qpdf is on PATH, and the only path to a skip is qpdf being absent. So the agreement tests and the negative control executed.

⛔ CI judges with qpdf 11.9.0, and every measurement in my notes used 12.4.2

Ubuntu's apt serves 11.9.0. My workstation has 12.4.2, which is the version named in the README, in linearized.go's comments, and in the "0 disagreements across 42 generated files" claim.

This is the step Say which qpdf will judge earning its place on its first run: without it the version would have been an assumption, and "which qpdf agreed" is exactly the question an unexplained future disagreement would turn on.

⭐ It is good news, not a problem. The checker now agrees with two independent qpdf majors — 11.9.0 on CI and 12.4.2 locally — which is a stronger claim than either alone, and precisely the "a second corpus measures what the first cannot" argument applied to the judge rather than the inputs.

⚠ What it does mean: the README's "qpdf 12.4.2 is the reference implementation of this format in practice" describes my judge, not CI's. I am not editing that here — it is accurate about where it was measured — but a future disagreement should ask which version reported it before anything else. Pinning CI to 12.4.2 would mean building qpdf from source, which is a cost I would not pay for this.

Merging on this.

@tannevaled
tannevaled merged commit bdf88e9 into main Nov 14, 2026
9 checks passed
@tannevaled
tannevaled deleted the ci/the-foreign-judge-never-ran-in-ci branch November 14, 2026 20:11
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