Files
ersatztv/scripts
timothy bcc3669005 fix(748): declare permissions: on all six workflows, and prove it binds
Rebased onto #857 (#744/#835), which landed while this was in review and
changed the answer rather than just the context.

  review-verdict.yml (job-level)  code: write, issues: read, pull-requests: read
  the other five (top-level)      code: read

`code: write` because Gitea has no `statuses` scope — status-write IS Code-write
(`reqRepoWriter(unit.TypeCode)`). The two read units are not spare: the gate job
also GETs `/issues/{n}/timeline` (the #706 retarget fence) and, via
`scripts/pr-changed-files.sh`, `/pulls/{n}` and `/pulls/{n}/files` (the #698
exemption path). Enumerated against every authenticated call reachable from
`set-verdict-status`; the set covers it exactly, with nothing over. No
`packages:` unit anywhere: all five `container:` blocks carry explicit
`credentials:`.

THE BLOCKER WAS NEVER THE DIFF. `ci.actions-credential-scoping` had carried
"whether `permissions:` is honored here is UNPROBED" since 2026-08-05, and
`review-verdict.yml` is base-resolved so its own PR's CI proves nothing about
it. Settled with a matched scratch-base probe pair on 1.27.1, the block carried
at JOB level on `set-verdict-status` — the shipped shape:

  code: write -> job success, `Posted review-verdict/h10-PROBE=success`,
                 status creator: null
  code: read  -> job failure at the POST, curl exit 22, NO status written

The negative control is the load-bearing half: a green positive alone is equally
consistent with `permissions:` being a no-op beside a permissive default.
`creator: null` also confirms approach A preserves the provenance signal
approach B would destroy.

NOT established, and the docs say so: both controls ran under a PERMISSIVE owner
default, so nothing here shows what a declared `code: write` receives under
Restricted. If Gitea caps at the default the way GitHub does, the flip makes the
gate unwritable. server-management#714 stays open for that probe.

WHAT #857 CHANGED. This PR originally excluded `ci-image.yml` — editing it
re-pointed `ci-image-pin`'s `expected` at the editing commit and reddened a
BLOCKING job (which CI demonstrated on the first push), and its own `paths:`
made the edit publish an image. #744 removed this file from both. So the
one-name exemption this branch briefly carried is DELETED rather than
documented, and the guard asserts over the whole derived population with nothing
carved out — the form #835 asked for.

Corrects the issue body: `docker-build.yml` does NOT write a status with the
injected token (no `GITEA_TOKEN` reference; its `ETV_STATUS_AUTH` is the scoped
REGISTRY_* PAT doing read-only GETs). Only review-verdict.yml needs write.

Guard: two mutation-proved tests in `test_pr_changed_files.py` — the gate job's
units by EQUALITY (an extra unit is a finding too; this is the repo's only CI
repo-write), and every tracked workflow declaring a block. Needed because that
workflow's base resolution means the PR removing `code: write` structurally
cannot catch itself.

Reviewed cold in an isolated worktree across 7 rounds; the reviewer re-ran the
mutation battery itself rather than accepting the claims.

Decisions-Edit: yes
fixes #748
2026-08-27 23:08:00 +02:00
..
2024-01-15 10:29:07 -06:00
2024-01-16 13:28:46 -06:00