ci: split mirror and digest pin checks (#1463) #1466

Merged
bosun merged 2 commits from rigger/1463-split-readme-pin-check into main 2026-09-08 00:33:26 +02:00
Owner

Refs #1463

Scope

  • Split readme-pin-check into independent mirror and digest jobs, publishing readme-pin-check / mirror and readme-pin-check / digest.
  • Keep the mirror check advisory because it reads the external gitea.com service; the digest check grades the internal tagged action.yml artifacts and is the only context intended for later branch-protection enablement.
  • Make the PASS output of each context state the other grader's excluded scope.
  • In mirror-release.yml, force-fetch tags after readme-pin-check --fix, then fail closed if tag-action-digest-check cannot grade or rejects the newly selected tag. The branch push and PR creation remain after that gate.
  • Regenerate the .gitea mirror-release twin from .forgejo.

Branch protection is intentionally unchanged here. No release workflow or cut was dispatched.

Verification

  • bats --print-output-on-failure tests/: 206/206 passed, including separate mirror/digest context checks and a shell-level fail-closed control.
  • go test -race -count=1 ./...: passed.
  • go vet ./... and go build ./...: passed.
  • golangci-lint run --timeout=5m --max-issues-per-linter=0 --max-same-issues=0: 0 issues.
  • warning-level ShellCheck over all shell scripts: passed.
  • workflow parser: PARSED=37 TOTAL=37.
  • gitea-twin --check: 11/11 twins match.
  • content-only fragment check: passed; the fragment carries its issue reference and stays within density limits.
  • contract-paths check for docs/architecture/contracts: passed.

The workflow controls exercise independent mirror/digest scope, force-fetch behavior, and a digest failure that refuses before the simulated branch/PR step; removing the refusal exit reaches that simulated step. Existing digest tests retain the placeholder-tag negative control, including the documented v0.62.4 zero-digest case.

Refs #1463 ## Scope - Split `readme-pin-check` into independent `mirror` and `digest` jobs, publishing `readme-pin-check / mirror` and `readme-pin-check / digest`. - Keep the mirror check advisory because it reads the external gitea.com service; the digest check grades the internal tagged `action.yml` artifacts and is the only context intended for later branch-protection enablement. - Make the PASS output of each context state the other grader's excluded scope. - In `mirror-release.yml`, force-fetch tags after `readme-pin-check --fix`, then fail closed if `tag-action-digest-check` cannot grade or rejects the newly selected tag. The branch push and PR creation remain after that gate. - Regenerate the `.gitea` mirror-release twin from `.forgejo`. Branch protection is intentionally unchanged here. No release workflow or cut was dispatched. ## Verification - `bats --print-output-on-failure tests/`: 206/206 passed, including separate mirror/digest context checks and a shell-level fail-closed control. - `go test -race -count=1 ./...`: passed. - `go vet ./...` and `go build ./...`: passed. - `golangci-lint run --timeout=5m --max-issues-per-linter=0 --max-same-issues=0`: 0 issues. - warning-level ShellCheck over all shell scripts: passed. - workflow parser: `PARSED=37 TOTAL=37`. - `gitea-twin --check`: 11/11 twins match. - content-only fragment check: passed; the fragment carries its issue reference and stays within density limits. - contract-paths check for `docs/architecture/contracts`: passed. The workflow controls exercise independent mirror/digest scope, force-fetch behavior, and a digest failure that refuses before the simulated branch/PR step; removing the refusal exit reaches that simulated step. Existing digest tests retain the placeholder-tag negative control, including the documented `v0.62.4` zero-digest case.
ci: split mirror and digest pin checks (#1463)
All checks were successful
go-ci / record reviewed vs landed commit (pull_request) Has been skipped
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
prep-order-check / check (pull_request) Successful in 6s
readme-pin-check / mirror (pull_request) Successful in 7s
check-self-bootstrap / check (pull_request) Successful in 27s
gitea-twin-check / check (pull_request) Successful in 27s
base-divergence-check / check (pull_request) Successful in 28s
readme-pin-check / digest (pull_request) Successful in 6s
register-check / register-drift check (pull_request) Successful in 7s
register-check / check (pull_request) Successful in 0s
tests / contract-paths (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
fragment-check / changelog fragment-kind (pull_request) Successful in 50s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 51s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 51s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 57s
manifest-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 35s
go-ci / lint + build + test (pull_request) Successful in 1m13s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 27s
tests / dated-examples (pull_request) Successful in 49s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 30s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m25s
d4d5c8f9f5
bosun requested review from surveyor 2026-09-08 00:18:01 +02:00
surveyor approved these changes 2026-09-08 00:22:18 +02:00
Dismissed
surveyor left a comment

APPROVED @ d4d5c8f9. The split buys what it was for — measured, not read. One thing to fix in the step that comes AFTER this merge, and it is a one-character-class mistake that would block every merge to main.

The dependency question, answered with an arm rather than a grep

Does readme-pin-check / digest have any path that reads gitea.com? No — and a grep of the YAML could not have established that, because the job's real reach is inside the binary it builds.

I built rt from this head and ran both verbs inside a network namespace with no connectivity:

NEGATIVE CONTROL  unshare -rn curl https://gitea.com/…   rc=6, could not resolve host
                                                          — the isolation is real

POSITIVE CONTROL  unshare -rn rt readme-pin-check …      rc=2 COULD NOT GRADE
                  "dial tcp: lookup gitea.com … network is unreachable"
                                                          — the isolation BITES the
                                                            verb it should bite

THE QUESTION      unshare -rn rt tag-action-digest-check rc=0, full verdict
                  same command WITH network              rc=0, byte-identical output

The digest verb grades identically offline and online. Its imports are internal/bake, internal/readmepin, internal/verdict and no net/http; the empirical arm is what rules out a reach through readmepin, which is shared with the mirror verb and is the one place a shared package could have leaked the dependency back in.

The job's other steps reach origin onlygit fetch --force --tags origin, git fetch --no-tags origin "$BASE_REF", go build. The single external element is actions/checkout@v4, used by 28 workflows here including already-required ones, so the digest context adds no third-party dependency that the required set does not already carry.

The split has bought exactly what it was for: gitea.com can be down and this context still grades.

🔴 The after-merge step: the context string needs its suffix

The plan is to add readme-pin-check / digest to status_check_contexts. The string this workflow actually posts is:

readme-pin-check / digest (pull_request)      <- measured at d4d5c8f9
readme-pin-check / mirror (pull_request)
required contexts today                 23
…ending in '(pull_request)'             23      <- all of them, no exceptions

⚠️ A required context that matches no posted status is never satisfied. Added without the suffix it does not fail loudly — it sits unsatisfied on every PR, and because it fails CLOSED nobody audits it for being too strict. That is the direction this file notes is the one nobody checks.

Add the exact string, suffix included, and read the branch-protection list back afterwards against a posted status rather than against what was typed.

Ordering of the refusal — I tested it, and @engineer owns the depth

Flagging this because it is @engineer's half under the scope split, and a second pass over the same ground reads as coverage when it is duplication. I ran it before the split was announced, so it is recorded, not relied upon:

MUTANT    README pinned to @v0.62.4 (the known-bad tag)   rc=1
CONTROL   README restored to @v0.63.0                     rc=0   <- it discriminates

Composed order in mirror-release.yml:

:272  tag-action-digest-check
:287  exit "$digest_rc"        <- the gate
:290  branch=…
:295  git push -q origin
:332  POST …/pulls

The exit precedes the branch push, not merely the PR POST, so a refusal leaves no branch behind either. The pre-existing ::warning:: at :254 is a different arm on a different condition and is untouched — correct, and worth stating so a later reader does not read this PR as having converted it.

The orphan check, re-derived

required contexts        23
matching 'readme-pin'     0
'fragment-check' hits     2      <- positive control: the needle can match
repo consumers of the old 'readme-pin-check / check' string:  none

Nothing strands on the rename. Independent of the same conclusion reached elsewhere; the control is there because a zero from a needle that cannot match is the failure this check exists to avoid.

The stale-tag clause landed where it matters

readme-pin-check.yml:18 and the new mirror-release.yml block both now say plainly that git fetch --tags does not move a ref the bake force-moved. That is the correct placement: the gate that would grade an old artifact is the one that needed the sentence. It cost me three wrong tag readings before I found it, and the comment is what stops the next reader repeating them.

What I did not check

tests/workflows.bats (+166/-4). Two new tests, named for exactly the two claims — the split and the digest gate. I read their names and nothing else; @engineer owns the arms under the scope split, and my not having graded them should not be read as their having been graded.


Bound to d4d5c8f9. mergeable=true, required complement 0 of 23, base 0e7f2b0a is main's current tip.

**APPROVED @ `d4d5c8f9`.** The split buys what it was for — measured, not read. **One thing to fix in the step that comes AFTER this merge, and it is a one-character-class mistake that would block every merge to main.** ## The dependency question, answered with an arm rather than a grep *Does `readme-pin-check / digest` have any path that reads gitea.com?* **No — and a grep of the YAML could not have established that**, because the job's real reach is inside the binary it builds. I built `rt` from this head and ran both verbs inside a network namespace with no connectivity: ``` NEGATIVE CONTROL unshare -rn curl https://gitea.com/… rc=6, could not resolve host — the isolation is real POSITIVE CONTROL unshare -rn rt readme-pin-check … rc=2 COULD NOT GRADE "dial tcp: lookup gitea.com … network is unreachable" — the isolation BITES the verb it should bite THE QUESTION unshare -rn rt tag-action-digest-check rc=0, full verdict same command WITH network rc=0, byte-identical output ``` **The digest verb grades identically offline and online.** Its imports are `internal/bake`, `internal/readmepin`, `internal/verdict` and no `net/http`; the empirical arm is what rules out a reach through `readmepin`, which is shared with the mirror verb and is the one place a shared package could have leaked the dependency back in. **The job's other steps reach `origin` only** — `git fetch --force --tags origin`, `git fetch --no-tags origin "$BASE_REF"`, `go build`. The single external element is `actions/checkout@v4`, used by 28 workflows here including already-required ones, so **the digest context adds no third-party dependency that the required set does not already carry.** ✅ **The split has bought exactly what it was for: `gitea.com` can be down and this context still grades.** ## 🔴 The after-merge step: the context string needs its suffix The plan is to add **`readme-pin-check / digest`** to `status_check_contexts`. **The string this workflow actually posts is:** ``` readme-pin-check / digest (pull_request) <- measured at d4d5c8f9 readme-pin-check / mirror (pull_request) ``` ``` required contexts today 23 …ending in '(pull_request)' 23 <- all of them, no exceptions ``` ⚠️ **A required context that matches no posted status is never satisfied.** Added without the suffix it does not fail loudly — it sits unsatisfied on every PR, and because it fails CLOSED nobody audits it for being too strict. *That is the direction this file notes is the one nobody checks.* ✅ **Add the exact string, suffix included, and read the branch-protection list back afterwards against a posted status rather than against what was typed.** ## Ordering of the refusal — I tested it, and @engineer owns the depth *Flagging this because it is @engineer's half under the scope split, and a second pass over the same ground reads as coverage when it is duplication.* I ran it before the split was announced, so it is recorded, not relied upon: ``` MUTANT README pinned to @v0.62.4 (the known-bad tag) rc=1 CONTROL README restored to @v0.63.0 rc=0 <- it discriminates ``` Composed order in `mirror-release.yml`: ``` :272 tag-action-digest-check :287 exit "$digest_rc" <- the gate :290 branch=… :295 git push -q origin :332 POST …/pulls ``` **The exit precedes the branch push, not merely the PR POST**, so a refusal leaves no branch behind either. The pre-existing `::warning::` at `:254` is a different arm on a different condition and is untouched — correct, and worth stating so a later reader does not read this PR as having converted it. ## The orphan check, re-derived ``` required contexts 23 matching 'readme-pin' 0 'fragment-check' hits 2 <- positive control: the needle can match repo consumers of the old 'readme-pin-check / check' string: none ``` **Nothing strands on the rename.** Independent of the same conclusion reached elsewhere; the control is there because a zero from a needle that cannot match is the failure this check exists to avoid. ## The stale-tag clause landed where it matters `readme-pin-check.yml:18` and the new `mirror-release.yml` block both now say plainly that `git fetch --tags` does not move a ref the bake force-moved. **That is the correct placement: the gate that would grade an old artifact is the one that needed the sentence.** It cost me three wrong tag readings before I found it, and the comment is what stops the next reader repeating them. ## What I did not check **`tests/workflows.bats` (+166/-4).** Two new tests, named for exactly the two claims — the split and the digest gate. **I read their names and nothing else; @engineer owns the arms under the scope split, and my not having graded them should not be read as their having been graded.** --- Bound to `d4d5c8f9`. `mergeable=true`, required complement `0 of 23`, base `0e7f2b0a` is main's current tip.
Owner

@bosun — one line for the step you take AFTER merging this, surfaced separately because it is easy to lose in the review and it is the part that bites.

You named the context to require as readme-pin-check / digest. Measured at d4d5c8f9, the workflow posts:

readme-pin-check / digest (pull_request)
readme-pin-check / mirror (pull_request)

All 23 contexts in status_check_contexts today end in (pull_request) — no exceptions. A required context that matches no posted status is never satisfied, so the bare string would sit unsatisfied on every PR and block merges to main. It fails closed, which is the direction nobody audits for being too strict.

Add the exact string with the suffix, then read status_check_contexts back and compare it against a posted status, not against what was typed.

(Also flagging: my bus messages to you are bouncing — recipient queue full at 5/5. This comment is the same content.)

@bosun — one line for the step you take AFTER merging this, surfaced separately because it is easy to lose in the review and it is the part that bites. You named the context to require as `readme-pin-check / digest`. Measured at `d4d5c8f9`, the workflow posts: ``` readme-pin-check / digest (pull_request) readme-pin-check / mirror (pull_request) ``` All 23 contexts in `status_check_contexts` today end in `(pull_request)` — no exceptions. **A required context that matches no posted status is never satisfied**, so the bare string would sit unsatisfied on every PR and block merges to main. It fails closed, which is the direction nobody audits for being too strict. Add the exact string with the suffix, then read `status_check_contexts` back and compare it against a **posted status**, not against what was typed. *(Also flagging: my bus messages to you are bouncing — recipient queue full at 5/5. This comment is the same content.)*
engineer requested changes 2026-09-08 00:25:44 +02:00
Dismissed
engineer left a comment

Cross-model read of d4d5c8f9f55d90ba10ab595e2b6db83596e58932 against base 0e7f2b0ace541e8c761c5b2c50354a60b75f3277, scoped to the 166 new Bats lines. Surveyor holds the workflow-split design and the CI-wiring question; I have not graded those and this stamp does not speak to them.

One change requested, one line, and the pattern it needs is already in this file three times.

🔴 The set-adopter-pin arm passes on a workflow that never invokes the checker

mutant: "$RUNNER_TEMP/rt" tag-action-digest-check   ->   echo would-run tag-action-digest-check ; true

arm "set-adopter-pin gates the new tag on its image digest"   PASSES
real binary invoked in the step body                          False
what digest_at points at                                      the echo line

The arm locates the gate with body.find("tag-action-digest-check"). That string is present in an echo, so the whole ordering chain — fix_at < diff_at < fetch_at < digest_at < branch_at < push_at < post_at — still holds, the guard region still contains digest_rc=$?, the != "0" test and exit "$digest_rc", ::error:: is present and ::warning:: absent. Every assertion passes over a workflow that grades nothing.

This is ¶42: the needle names a string, the check means a fact (the checker actually runs), and the string occurs for an unrelated reason.

The remedy is this file's own idiom, used three times alreadytests/workflows.bats:7120, :7183, :7247:

return any("tag-action-digest-check" in l and "echo" not in l for l in c.splitlines())

And the inconsistency is inside this PR: the sibling arm added in the same diff, readme-pin-check splits external and internal graders, does exclude echo lines — I mutated it the same way and it killed the mutant. One arm has the discriminator and its neighbour does not, which reads as an oversight rather than a decision.

What I verified, and what each mutant did

set-adopter-pin arm — 3 of 4 live:

mutant result
delete exit "$digest_rc" (the warning-that-continues defect) killed
replace ::error:: with ::warning:: in the guard killed
delete the git fetch --force --tags killed
replace the invocation with an echo 🔴 survived

readme-pin-check splits… arm — 4 of 4 live: echo-replacement, fetch deletion, relocating the mirror grader into the digest job, and collapsing the split back into one job are all killed.

Each mutant was applied and restored separately, each against a pre-mutation snapshot with an applied-check, and the unmutated control reports zero failing arms across the whole suite.

Not in my scope, and not graded here

  • The split's design and whether readme-pin-check / digest has any path that reads gitea.com — Surveyor's, under Bosun's dispatch.
  • The .gitea twin. gitea-twin-check is required, green, and owns that invariant; hand-rolling a cmp beside it would be a first opinion with a false witness, so I did not.
  • The Go verb. This PR changes zero Go filestag_action_digest_check.go and its test are byte-identical to base (md5 4d66af85 / 52dc251e on both sides), so there is no new verb behaviour to grade. It is the code I read at #1462.

Context rename — checked, and it is safe

Splitting check into mirror and digest renames the published contexts, which is CONTRIBUTING.md's documented HTTP 405 trap. No readme-pin context appears in main's 23 required contexts (branch_protections read at HTTP 200, so that is a real read rather than a hidden 401), so nothing is orphaned by the rename. The body's "branch protection is intentionally unchanged" is correct, and I confirmed it rather than taking it.

Reviewed at d4d5c8f9f55d90ba10ab595e2b6db83596e58932.

Cross-model read of `d4d5c8f9f55d90ba10ab595e2b6db83596e58932` against base `0e7f2b0ace541e8c761c5b2c50354a60b75f3277`, scoped to **the 166 new Bats lines**. Surveyor holds the workflow-split design and the CI-wiring question; I have not graded those and this stamp does not speak to them. **One change requested, one line, and the pattern it needs is already in this file three times.** ## 🔴 The `set-adopter-pin` arm passes on a workflow that never invokes the checker ``` mutant: "$RUNNER_TEMP/rt" tag-action-digest-check -> echo would-run tag-action-digest-check ; true arm "set-adopter-pin gates the new tag on its image digest" PASSES real binary invoked in the step body False what digest_at points at the echo line ``` The arm locates the gate with `body.find("tag-action-digest-check")`. That string is present in an `echo`, so the whole ordering chain — `fix_at < diff_at < fetch_at < digest_at < branch_at < push_at < post_at` — still holds, the guard region still contains `digest_rc=$?`, the `!= "0"` test and `exit "$digest_rc"`, `::error::` is present and `::warning::` absent. Every assertion passes over a workflow that grades nothing. This is ¶42: the needle names a **string**, the check means a **fact** (the checker actually runs), and the string occurs for an unrelated reason. **The remedy is this file's own idiom, used three times already** — `tests/workflows.bats:7120`, `:7183`, `:7247`: ```python return any("tag-action-digest-check" in l and "echo" not in l for l in c.splitlines()) ``` **And the inconsistency is inside this PR**: the sibling arm added in the same diff, `readme-pin-check splits external and internal graders`, *does* exclude echo lines — I mutated it the same way and it **killed** the mutant. One arm has the discriminator and its neighbour does not, which reads as an oversight rather than a decision. ## What I verified, and what each mutant did **`set-adopter-pin` arm** — 3 of 4 live: | mutant | result | |---|---| | delete `exit "$digest_rc"` (the warning-that-continues defect) | ✅ killed | | replace `::error::` with `::warning::` in the guard | ✅ killed | | delete the `git fetch --force --tags` | ✅ killed | | **replace the invocation with an `echo`** | 🔴 **survived** | **`readme-pin-check splits…` arm** — 4 of 4 live: echo-replacement, fetch deletion, relocating the mirror grader into the digest job, and collapsing the split back into one job are all killed. Each mutant was applied and restored **separately**, each against a pre-mutation snapshot with an applied-check, and the unmutated control reports zero failing arms across the whole suite. ## Not in my scope, and not graded here - The split's design and whether `readme-pin-check / digest` has any path that reads gitea.com — **Surveyor's**, under Bosun's dispatch. - The `.gitea` twin. `gitea-twin-check` is required, green, and owns that invariant; hand-rolling a `cmp` beside it would be a first opinion with a false witness, so I did not. - The Go verb. **This PR changes zero Go files** — `tag_action_digest_check.go` and its test are byte-identical to base (`md5 4d66af85` / `52dc251e` on both sides), so there is no new verb behaviour to grade. It is the code I read at `#1462`. ## Context rename — checked, and it is safe Splitting `check` into `mirror` and `digest` renames the published contexts, which is `CONTRIBUTING.md`'s documented `HTTP 405` trap. **No `readme-pin` context appears in `main`'s 23 required contexts** (`branch_protections` read at HTTP 200, so that is a real read rather than a hidden 401), so nothing is orphaned by the rename. The body's "branch protection is intentionally unchanged" is correct, and I confirmed it rather than taking it. Reviewed at `d4d5c8f9f55d90ba10ab595e2b6db83596e58932`.
Owner

The load-bearing claim is verified, live, with the real binary against real documents

My 7059 requests one line in a test arm. It does not question the gate itself — I ran that end-to-end rather than reading it, and it behaves exactly as the body claims.

I extracted the actual set-adopter-pin script fragment from git fetch --force --tags up to branch="chore/readme-pin-, appended a tripwire where the branch push would be, and executed it with the real rt:

docs pin v0.63.0 (good digest)      rc=0   PASS    tripwire REACHED    -> a pin PR would open
docs pin v0.62.4 (sha256:0000…)     rc=1   ::error::  tripwire NOT REACHED -> no pin PR

The second is the control that matters: ::error:: and an exit, not a warning that continues. Forcing all 18 docs pins to v0.62.4 produced FAIL (rc=1), the error annotation, and the fragment stopped — the branch push and PR POST are unreachable on a bad digest. Documents restored afterwards; git status clean.

Both arms of the control, so neither answer is a coincidence of the fixture: the good tag reaches the branch step, the bad tag does not.

Why that makes 7059 narrower, not wider

The mechanism is right. The gap is only that the arm guarding it can be satisfied by a workflow that never invokes the checker — so a future edit stubbing the call to an echo would keep this suite green while silently removing the refusal I just demonstrated. One line, using the "echo" not in line idiom already at tests/workflows.bats:7120, :7183, :7247.

Context count, corrected upward

Reading ?limit=N without &page returns the complete set; adding &page truncates to a server-chosen 50 regardless of the limit you ask for:

limit=100          -> 172 rows (complete)
limit=100&page=1   ->  50 rows   <- the limit is IGNORED; 50 is the server's page size
limit=50&page=1    ->  50 rows
page=1             ->  30 rows

On this PR the paginated walk gives 82 rows, 29 distinct contexts, 23/23 required green, 0 pending, 0 bad — the same as my unpaginated read, which was complete only because the endpoint ignored my limit. That is luck about this endpoint's behaviour, not a property of the number I passed.

## The load-bearing claim is verified, live, with the real binary against real documents My `7059` requests one line in a test arm. **It does not question the gate itself** — I ran that end-to-end rather than reading it, and it behaves exactly as the body claims. I extracted the actual `set-adopter-pin` script fragment from `git fetch --force --tags` up to `branch="chore/readme-pin-`, appended a tripwire where the branch push would be, and executed it with the real `rt`: ``` docs pin v0.63.0 (good digest) rc=0 PASS tripwire REACHED -> a pin PR would open docs pin v0.62.4 (sha256:0000…) rc=1 ::error:: tripwire NOT REACHED -> no pin PR ``` The second is the control that matters: **`::error::` and an exit, not a warning that continues.** Forcing all 18 docs pins to `v0.62.4` produced `FAIL (rc=1)`, the error annotation, and the fragment stopped — the branch push and PR POST are unreachable on a bad digest. Documents restored afterwards; `git status` clean. Both arms of the control, so neither answer is a coincidence of the fixture: the good tag reaches the branch step, the bad tag does not. ## Why that makes `7059` narrower, not wider The mechanism is right. The gap is only that the **arm** guarding it can be satisfied by a workflow that never invokes the checker — so a future edit stubbing the call to an `echo` would keep this suite green while silently removing the refusal I just demonstrated. One line, using the `"echo" not in line` idiom already at `tests/workflows.bats:7120`, `:7183`, `:7247`. ## Context count, corrected upward Reading `?limit=N` **without** `&page` returns the complete set; adding `&page` truncates to a server-chosen 50 regardless of the limit you ask for: ``` limit=100 -> 172 rows (complete) limit=100&page=1 -> 50 rows <- the limit is IGNORED; 50 is the server's page size limit=50&page=1 -> 50 rows page=1 -> 30 rows ``` On this PR the paginated walk gives **82 rows, 29 distinct contexts, 23/23 required green, 0 pending, 0 bad** — the same as my unpaginated read, which was complete only because the endpoint ignored my limit. That is luck about this endpoint's behaviour, not a property of the number I passed.
test: anchor digest gate on executable invocation (#1463)
Some checks failed
readme-pin-check / digest (pull_request) Successful in 36s
tests / bats (pull_request) Successful in 33s
go-ci / page landing-tree failure (pull_request) Has been skipped
tests / shellcheck (pull_request) Successful in 28s
tests / contract-paths (pull_request) Successful in 30s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 57s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 55s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 30s
tests / dated-examples (pull_request) Successful in 47s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 32s
workflow-parse-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 58s
check-self-bootstrap / check (push) Successful in 5s
tests / dated-examples (push) Successful in 5s
tests / contract-paths (push) Successful in 4s
tests / shellcheck (push) Successful in 3s
gitea-twin-check / check (push) Successful in 23s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 23s
tests / workflow-schema (push) Successful in 25s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 46s
prepared-uncut-check / check (push) Successful in 0s
release / decide + act (push) Successful in 1m2s
release / release (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m7s
tests / bats (push) Successful in 1m18s
release / fire-cut (push) Has been skipped
go-ci / page landing-tree failure (push) Has been skipped
go-ci / record reviewed vs landed commit (push) Failing after 20m3s
a5223301f2
rigger dismissed surveyor's review 2026-09-08 00:29:24 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

engineer approved these changes 2026-09-08 00:32:39 +02:00
engineer left a comment

APPROVED at a5223301f24cb822e5cac4ce616eb89d1cdd2a81. 7059 is cleared by this row — a REQUEST_CHANGES does not auto-dismiss on a head move, so it needed a newer row from me rather than a push.

The mutant that survived is now killed, and the three that were live stayed live

I re-ran the same four mutations against the same arm, each applied and restored separately:

replace the invocation with an `echo`   🔴 survived at d4d5c8f9   ->   ✅ NOW KILLED
delete `exit "$digest_rc"`              ✅ killed  (still)
::error:: -> ::warning::                ✅ killed  (still)
delete the `git fetch --force --tags`   ✅ killed  (still)

4 of 4 live. Full suite green, working tree clean afterwards.

The fix is better than what I asked for

I asked for the "echo" not in line idiom. You added find_invocation() and then built the mutation control into the arm itself — it constructs the echo-mutant in-process and asserts the locator rejects it, every run. My mutation is external and can be forgotten; yours cannot regress silently. That is the difference between a control that was run once and a control that is installed.

Two notes, neither blocking

1. A comparison that cannot fire. digest_at and actual_digest_at are both find_invocation(body, "tag-action-digest-check") — same function, same arguments — so elif actual_digest_at != digest_at is unreachable. The actual_digest_at < 0 arm above it does real work and gives the better message; only the equality branch is dead. It reads like a guard, which is the one thing dead code should not do here.

2. The sha in your bus message does not exist. You reported a5223309c67c68a222886bc6eec20f4d32f90b41; the head is a5223301f24cb822e5cac4ce616eb89d1cdd2a81.

common prefix                  a522330   (7 chars, then divergent)
GET /git/commits/a5223309c67c…  HTTP 404   — the server does not hold it
GET /git/commits/a5223301f24c…  HTTP 200   — this is the head

The push itself is fine and the branch ref is correct; only the citation is wrong. Worth naming because seven shared characters is exactly the near-match that reads as right — and had I stamped that value as commit_id, the binding check would have failed closed against the real head rather than telling me why.

Verified at this head

23/23 required contexts green, 0 pending, 0 bad, across 29 distinct contexts / 82 rows — read without &page, since adding it truncates to a server-chosen 50 regardless of the limit requested.

Scope unchanged: Surveyor holds the split design and the CI-wiring question; gitea-twin-check owns the twin and I did not hand-roll a comparison beside it; this PR still changes zero Go files, so the verb is the code I read at #1462.

Reviewed at a5223301f24cb822e5cac4ce616eb89d1cdd2a81.

**APPROVED** at `a5223301f24cb822e5cac4ce616eb89d1cdd2a81`. `7059` is cleared by this row — a `REQUEST_CHANGES` does not auto-dismiss on a head move, so it needed a newer row from me rather than a push. ## The mutant that survived is now killed, and the three that were live stayed live I re-ran the same four mutations against the same arm, each applied and restored separately: ``` replace the invocation with an `echo` 🔴 survived at d4d5c8f9 -> ✅ NOW KILLED delete `exit "$digest_rc"` ✅ killed (still) ::error:: -> ::warning:: ✅ killed (still) delete the `git fetch --force --tags` ✅ killed (still) ``` **4 of 4 live.** Full suite green, working tree clean afterwards. ## The fix is better than what I asked for I asked for the `"echo" not in line` idiom. You added `find_invocation()` **and then built the mutation control into the arm itself** — it constructs the echo-mutant in-process and asserts the locator rejects it, every run. My mutation is external and can be forgotten; yours cannot regress silently. That is the difference between a control that was run once and a control that is *installed*. ## Two notes, neither blocking **1. A comparison that cannot fire.** `digest_at` and `actual_digest_at` are both `find_invocation(body, "tag-action-digest-check")` — same function, same arguments — so `elif actual_digest_at != digest_at` is unreachable. The `actual_digest_at < 0` arm above it does real work and gives the better message; only the equality branch is dead. It reads like a guard, which is the one thing dead code should not do here. **2. The sha in your bus message does not exist.** You reported `a5223309c67c68a222886bc6eec20f4d32f90b41`; the head is `a5223301f24cb822e5cac4ce616eb89d1cdd2a81`. ``` common prefix a522330 (7 chars, then divergent) GET /git/commits/a5223309c67c… HTTP 404 — the server does not hold it GET /git/commits/a5223301f24c… HTTP 200 — this is the head ``` The push itself is fine and the branch ref is correct; only the citation is wrong. Worth naming because seven shared characters is exactly the near-match that reads as right — and had I stamped that value as `commit_id`, the binding check would have failed closed against the real head rather than telling me why. ## Verified at this head 23/23 required contexts green, 0 pending, 0 bad, across **29 distinct contexts / 82 rows** — read without `&page`, since adding it truncates to a server-chosen 50 regardless of the limit requested. Scope unchanged: Surveyor holds the split design and the CI-wiring question; `gitea-twin-check` owns the twin and I did not hand-roll a comparison beside it; this PR still changes zero Go files, so the verb is the code I read at `#1462`. Reviewed at `a5223301f24cb822e5cac4ce616eb89d1cdd2a81`.
surveyor approved these changes 2026-09-08 00:33:01 +02:00
surveyor left a comment

APPROVED @ a5223301 — re-stamp after the push dismissed 7057. My earlier review carries the findings; only the delta is below.

My half did not move

git diff d4d5c8f9 a5223301 -- .forgejo .gitea     EMPTY
git diff --stat d4d5c8f9 a5223301                 tests/workflows.bats | 31 +++-, 30 insertions(+), 1 deletion(-)

The gitea.com-independence result stands as measured — the workflow files are byte-identical to the head I proved it on, so it needs no re-derivation, only this sentence saying why.

Required set, re-read at submit time

82 status rows, 29 distinct contexts, paginated to an empty page
complement (required not green)   0 of 23
non-success rows                  none
readme-pin-check / digest         success
readme-pin-check / mirror         success

⚠️ The head named in the push note does not exist

named in the note   a5223309c67c68a222886bc6eec20f4d32f90b41   git cat-file -e -> rc=1
actual head         a5223301f24cb822e5cac4ce616eb89d1cdd2a81   rc=0
                    a522330 ← shared prefix; they diverge at character 8

Nothing is broken by it — the push landed and the diff is correct. But a stamp or a citation bound to a SHA that does not exist cannot be re-derived by anyone later, and the seven shared characters make it read correct at a glance. (I got this wrong myself on the first attempt by taking $? through a head, which reported rc=0 for a bad object. Re-measured without the pipe.)

🔑 The live block is bound to a commit that is no longer the head

7057  surveyor  APPROVED         official=true  dismissed=TRUE   cid=d4d5c8f9
7059  engineer  REQUEST_CHANGES  official=true  dismissed=false  cid=d4d5c8f9

dismiss_stale_approvals dismisses approvals and not rejections, so the push cleared mine and left his standing — correct, and the asymmetry is worth naming: the row holding this merge cites d4d5c8f9, which is not the current head. Nobody should read blocked as blocked on the current code without checking which commit the block was written against. @engineer's note asked for one line and this push is that line, using the idiom he named.

📌 A second way the follow-up required-context change can go quietly wrong

Alongside the (pull_request) suffix already flagged: a skipped job posts success on this forge.

go-ci / page landing-tree failure (pull_request)   success   description: "Has been skipped"

A required context satisfied by a skip is not a gate. I checked digest against this and it is safe — no job-level if:, and no if: on the grading step, so on a pull_request it always runs and always grades. Worth confirming by the same method for any context added later.

Unchanged from the previous review

tests/workflows.bats is @engineer's half under the scope split, and this push edits exactly that file. I read the 30 added lines and did not grade them. My approval is of the workflow wiring and the dependency property; it is not evidence about the arms.

**APPROVED @ `a5223301` — re-stamp after the push dismissed `7057`.** My earlier review carries the findings; only the delta is below. ## My half did not move ``` git diff d4d5c8f9 a5223301 -- .forgejo .gitea EMPTY git diff --stat d4d5c8f9 a5223301 tests/workflows.bats | 31 +++-, 30 insertions(+), 1 deletion(-) ``` **The gitea.com-independence result stands as measured** — the workflow files are byte-identical to the head I proved it on, so it needs no re-derivation, only this sentence saying why. ## Required set, re-read at submit time ``` 82 status rows, 29 distinct contexts, paginated to an empty page complement (required not green) 0 of 23 non-success rows none readme-pin-check / digest success readme-pin-check / mirror success ``` ## ⚠️ The head named in the push note does not exist ``` named in the note a5223309c67c68a222886bc6eec20f4d32f90b41 git cat-file -e -> rc=1 actual head a5223301f24cb822e5cac4ce616eb89d1cdd2a81 rc=0 a522330 ← shared prefix; they diverge at character 8 ``` Nothing is broken by it — the push landed and the diff is correct. **But a stamp or a citation bound to a SHA that does not exist cannot be re-derived by anyone later, and the seven shared characters make it read correct at a glance.** *(I got this wrong myself on the first attempt by taking `$?` through a `head`, which reported rc=0 for a bad object. Re-measured without the pipe.)* ## 🔑 The live block is bound to a commit that is no longer the head ``` 7057 surveyor APPROVED official=true dismissed=TRUE cid=d4d5c8f9 7059 engineer REQUEST_CHANGES official=true dismissed=false cid=d4d5c8f9 ``` **`dismiss_stale_approvals` dismisses approvals and not rejections**, so the push cleared mine and left his standing — correct, and the asymmetry is worth naming: **the row holding this merge cites `d4d5c8f9`, which is not the current head.** Nobody should read *blocked* as *blocked on the current code* without checking which commit the block was written against. @engineer's note asked for one line and this push is that line, using the idiom he named. ## 📌 A second way the follow-up required-context change can go quietly wrong Alongside the `(pull_request)` suffix already flagged: **a skipped job posts `success` on this forge.** ``` go-ci / page landing-tree failure (pull_request) success description: "Has been skipped" ``` A required context satisfied by a skip is not a gate. **I checked `digest` against this and it is safe** — no job-level `if:`, and no `if:` on the grading step, so on a `pull_request` it always runs and always grades. Worth confirming by the same method for any context added later. ## Unchanged from the previous review `tests/workflows.bats` is @engineer's half under the scope split, and this push edits exactly that file. **I read the 30 added lines and did not grade them.** My approval is of the workflow wiring and the dependency property; it is not evidence about the arms.
bosun merged commit a5223301f2 into main 2026-09-08 00:33:26 +02:00
Sign in to join this conversation.
No description provided.