From aa9a5c66ca6b8680a419bd547b583cc370d886fa Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Mon, 17 Aug 2026 06:09:17 +0200 Subject: [PATCH] Say that the zizmor absence outlives the empty required set (#26) The deliberate-absence list gave `zizmor` the shared reason that every name this tree declares is outside the required set because the set has no members, and it named #26 as what retires the entry. `docs/quality-parity.md` decides otherwise. That name is created by the code-scanning upload rather than by the job, the upload step is skipped wherever the token cannot write security events, and requiring it would hold open every pull request the condition excludes with nothing on the pull request saying why. The absence therefore survives the day the set is assembled instead of being paid off by it. The entry now carries its own reason and an empty `Until`, which is the shape `Scorecard analysis` already has for a decision of the same kind. What that prevents is the change assembling the required set reading this entry as a debt and moving the name into the ruleset, which blocks every fork and Dependabot pull request behind a context that cannot arrive on them. Measured on the head of a pull request the condition excludes rather than read off the file: gh api repos/Flowfin/lab/actions/runs/31929377858/jobs \ --jq '.jobs[].steps[] | select(.number >= 4 and .number <= 6) | "step \(.number), \(.name): \(.conclusion)"' step 4, Audit workflows (SARIF for code scanning): success step 5, Upload SARIF: skipped step 6, Fail on actionable findings: failure gh api repos/Flowfin/lab/commits/d18d040/check-runs?per_page=100 \ --jq '.check_runs[] | select(.name | test("zizmor";"i")) | "\(.name): \(.conclusion)"' | sort -u Audit workflows (zizmor): failure A test refuses the pair rather than leaving it to whoever reads the list next. An entry written as permanent while carrying the reason that ends when the set is assembled says its decision outlives the only thing it rests on. That is also the near miss the test is written for, which is one field: emptying `Until` because an absence turned out to have its own reason, and leaving the sentence above it as it was. 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