diff --git a/internal/guard/attestation_test.go b/internal/guard/attestation_test.go index e427ba0..f12b917 100644 --- a/internal/guard/attestation_test.go +++ b/internal/guard/attestation_test.go @@ -245,6 +245,26 @@ func TestTheReleaseNotesSayHowToCheckWhatWasDownloaded(t *testing.T) { t.Errorf("the release notes never mention %q", want) } } + + // BOTH commands, because they answer different questions and the notes say + // so themselves. One asks what is inside a file and needs the predicate + // type spelled out, since gh asks for build provenance unless told + // otherwise. The other asks where a Linux archive came from and must not + // carry that flag. + // + // Asking only whether the words appear was not enough, and the full + // mutation run of 2026-09-01 said so: replacing one of the two left the + // guard green, because the other still carried the phrase. That is the + // third guard in this tree to fail the same way in one run - "is this text + // in the file" stops meaning anything the day the text appears twice. + if n := strings.Count(notes, "gh attestation verify"); n < 2 { + t.Errorf("the release notes give %d attestation command(s) and there are two things to check - "+ + "what is inside a file, and where a Linux archive came from", n) + } + if !strings.Contains(notes, "--predicate-type https://spdx.dev/Document/v2.3") { + t.Error("the release notes never name the predicate type, so somebody following them asks " + + "for build provenance and is told the bill of materials is not there") + } // The binaries are still unsigned, and the notes have said so since the // first release. Provenance is a different question and must not be read // as an answer to that one. diff --git a/internal/guard/jxlladder_test.go b/internal/guard/jxlladder_test.go index cf31035..b851fb0 100644 --- a/internal/guard/jxlladder_test.go +++ b/internal/guard/jxlladder_test.go @@ -216,8 +216,18 @@ func TestJxlStillFitsWhenTheQualityIsNotTheOneTheCeilingsWereMeasuredAt(t *testi t.Fatal(err) } + // The sizes matter as much as the qualities, and the first version of this + // guard had too few of them. It asked 2000, 20000 and 200000, and the full + // mutation run of 2026-09-01 called it a HOLE: turning the slow road off + // left it green. Measured afterwards by sweeping, the break shows at + // quality 100 and 5000 B, where a 160x120 picture codes to 6047 B and the + // file was to be 5000 - so the rung the table picked does not fit at all. + // + // The guard was right about what it watches and short on where it looked, + // which is the same shape as O163 one file over: a measurement proves what + // its sample covers and says nothing about the gap beside it. for _, quality := range []string{"1", "10", "70", "90", "100"} { - for _, want := range []int64{2000, 20000, 200000} { + for _, want := range []int64{2000, 5000, 20000, 60000, 200000} { p, err := d.Generator.Plan(format.Request{ Bytes: want, Seed: 7741, Label: true, Properties: map[string]string{"quality": quality}, diff --git a/internal/guard/smallfixes_test.go b/internal/guard/smallfixes_test.go index 87f4ce4..e1c5f09 100644 --- a/internal/guard/smallfixes_test.go +++ b/internal/guard/smallfixes_test.go @@ -208,9 +208,49 @@ func TestTheWindowBinaryIsBuiltSomewhere(t *testing.T) { if err != nil { t.Skipf("the workflow is not here: %v", err) } - if !strings.Contains(string(raw), "./cmd/tfg-gui") { - t.Error("nothing in CI builds ./cmd/tfg-gui, so it can stop compiling without a red run") + // Asked of the job that runs on every operating system, not of the file. + // + // It used to ask the file, and the full mutation run of 2026-09-01 called + // that a HOLE. Removing both builds from the platform matrix left the guard + // green, because a third build had appeared since - in the sbom job, which + // runs on Linux alone. So the substring was still there and the protection + // was gone: a window binary that stops compiling on Windows or macOS would + // have passed CI in silence, which is the whole thing this watches for. + // + // That is the failure a guard asking "is this text in the file" always has, + // and it arrives the day the text appears a second time. Nothing about the + // text changed - the tree grew a second writer of it. + const matrixJob = "\n test:\n" + at := strings.Index(string(raw), matrixJob) + if at < 0 { + t.Fatal("ci.yml has no job called test, so this guard is reading a file it does not understand") + } + // The job ends where the next one begins, at the next key on its own + // indentation. + block := string(raw)[at+1:] + if end := nextJobAfter(block); end > 0 { + block = block[:end] + } + if !strings.Contains(block, "./cmd/tfg-gui") { + t.Error("the job that runs on every operating system does not build ./cmd/tfg-gui, " + + "so the window can stop compiling on one of them without a red run.\n" + + " A build elsewhere is not the same promise - the sbom job runs on Linux alone.") + } +} + +// nextJobAfter is where the job starting at the top of block ends, which is the +// next key at the same indentation. Zero when it runs to the end of the file. +func nextJobAfter(block string) int { + for i := 1; i < len(block); i++ { + if block[i-1] != '\n' { + continue + } + rest := block[i:] + if len(rest) > 2 && rest[0] == ' ' && rest[1] == ' ' && rest[2] != ' ' && rest[2] != '#' { + return i + } } + return 0 } // The formatter leaves nothing beside the file it settled. It writes through a