From 68079e9402db63c569b8f549c339e98510d08ba6 Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Fri, 21 Aug 2026 15:52:25 +0200 Subject: [PATCH] Restore the absence entry that carries its own reason and the guard over it (#155) This does not finish #155. Five more of the paths that commit removed are still missing, and the question of whether a merge removing a tracked path should be refused is not touched here. What was wrong. `d3edfc95b8526033c79cb26afe48282c2c090e32` took its tree from an older state of the default branch and landed on top of a newer one, under a message describing a change to how one workflow pin is commented. Among the seven paths it replaced were both files in `internal/contexts`. The `zizmor` entry went back to giving the shared reason and naming #26 as what ends it, and `TestAPermanentAbsenceDoesNotRestOnAReasonThatEnds` went with it: git log --diff-filter=M --format='%H %s' origin/main -- internal/contexts/contexts_test.go d3edfc95b8526033c79cb26afe48282c2c090e32 Name the version the pinned commit actually is (#151) Both blobs are the bytes that were there rather than a retyping of them: git rev-parse 90656ba:internal/contexts/contexts.go 9608b17ec822791856dd866411c1c1835079e1d9 git hash-object internal/contexts/contexts.go 9608b17ec822791856dd866411c1c1835079e1d9 git rev-parse 90656ba:internal/contexts/contexts_test.go 9bdd8745837bdbaf21d41915d537fbafaab61cff git hash-object internal/contexts/contexts_test.go 9bdd8745837bdbaf21d41915d537fbafaab61cff What failure this prevents is an absence written as permanent whose reason is the one that stops being true the day the required set is assembled. The guard reads `Until` and `Why` together and refuses an entry carrying both, and the near-miss it is written for is one field: emptying `Until` on an entry whose reason is still the shared one. I proved it bites by making that near-miss. With `Until` emptied on the `DCO sign-off` entry and the guard back, one test reddens and nothing else does: --- FAIL: TestAPermanentAbsenceDoesNotRestOnAReasonThatEnds (0.00s) contexts_test.go:297: the absence "DCO sign-off" is written as permanent and its reason is that the required set is empty, which is the condition that ends when the set is assembled, so the entry says the absence outlives the only thing it rests on What the absence cost is the same near-miss against what the default branch holds today, where all eleven packages run green, so nothing separated an entry resting on a debt from one that does not. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- internal/contexts/contexts.go | 7 ++++++- internal/contexts/contexts_test.go | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/internal/contexts/contexts.go b/internal/contexts/contexts.go index 8ec6353..9608b17 100644 --- a/internal/contexts/contexts.go +++ b/internal/contexts/contexts.go @@ -140,7 +140,12 @@ var Absences = []Absence{ {Name: "format", Why: theSetIsEmpty, Until: "#26"}, {Name: "CodeQL (go)", Why: theSetIsEmpty, Until: "#26"}, {Name: "CodeQL", Why: theSetIsEmpty, Until: "#26"}, - {Name: "zizmor", Why: theSetIsEmpty, Until: "#26"}, + { + Name: "zizmor", + Why: "this name is created by the code-scanning upload in zizmor.yml rather than by the job, and that step is skipped wherever the token cannot write security events, so requiring it would hold open every pull request the condition excludes with nothing on the pull request saying why. " + + "The job reports under Audit workflows (zizmor), which is a different string, arrives on those pull requests and carries the step that fails on findings", + Until: "", + }, {Name: "DCO sign-off", Why: theSetIsEmpty, Until: "#26"}, {Name: "dependency-review", Why: theSetIsEmpty, Until: "#26"}, {Name: "headless and unelevated", Why: theSetIsEmpty, Until: "#26"}, diff --git a/internal/contexts/contexts_test.go b/internal/contexts/contexts_test.go index aaeb1ec..9bdd874 100644 --- a/internal/contexts/contexts_test.go +++ b/internal/contexts/contexts_test.go @@ -279,6 +279,26 @@ func TestAnAbsenceCarriesAReason(t *testing.T) { } } +// TestAPermanentAbsenceDoesNotRestOnAReasonThatEnds refuses an entry that says +// its absence is permanent and gives the reason that stops being true first. +// +// The two fields are read together. An empty Until says the absence survives the +// day the required set is assembled, and theSetIsEmpty says the name is outside +// the set because the set has no members, which is exactly the condition that day +// ends. An entry carrying both is a decision resting on a debt, and it reads as +// settled to whoever assembles the set. +// +// The near-miss this is written for is one field: an absence whose reason turns +// out to be its own rather than the shared one is made permanent by emptying +// Until, and the reason above it is left as it was. +func TestAPermanentAbsenceDoesNotRestOnAReasonThatEnds(t *testing.T) { + for _, absence := range Absences { + if absence.Until == "" && absence.Why == theSetIsEmpty { + t.Errorf("the absence %q is written as permanent and its reason is that the required set is empty, which is the condition that ends when the set is assembled, so the entry says the absence outlives the only thing it rests on", absence.Name) + } + } +} + // TestAJobWithNoNameOfItsOwnIsNotedRatherThanRefused pins the one place this // package answers with a note. The platform reports the job id where a job // carries no name, so such a job is legal and its name is still a gate string, and