The drift detector merged in #11 was inert and would have stayed inert silently.
WHAT HAPPENED. infra/gitea/set-ci-token.sh minted a token and PUT it to the repo Actions
secret GITEA_SETTINGS_TOKEN. Gitea RESERVES the `GITEA_` prefix for secret names -- exactly
as GitHub reserves `GITHUB_` -- and rejects that PUT with:
HTTP 400 {"message":"invalid secret name"}
The script sent the PUT to /dev/null and printed "==> stored." unconditionally, and
`curl -sS` exits 0 on an HTTP error, so it reported success for a store that never happened.
Measured afterwards: the token existed in the user's token list, and the repo's secret list
was EMPTY. ci/gitea-settings.sh would have skipped on every CI run forever, and with
REQUIRE_GITEA_SETTINGS=true it would instead have BLOCKED EVERY PR -- including the one
fixing it.
Three changes:
* the secret is SETTINGS_API_TOKEN; ci/gitea-settings.sh reads it, keeping
GITEA_SETTINGS_TOKEN as a local-run fallback;
* set-ci-token.sh checks the PUT's HTTP status AND reads the secret back, refusing to claim
success otherwise. That missing verification is the actual defect -- the wrong name was
just what it hid;
* gate.yml maps the secret and ENABLES REQUIRE_GITEA_SETTINGS.
WHY ENABLING IT HERE IS SAFE: this PR's own CI run is the proof. If the stage were still
skipping, REQUIRE turns that into a failure and this PR goes RED rather than merging. Green
means the token resolved and live Gitea matched infra/gitea/expected-branch-protection.json.
Verified locally before pushing:
REQUIRE=true, token present -> MERGE GATE: PASS
REQUIRE=true, no token -> gitea-settings SKIPPED -> BLOCKED (not a silent skip)
The secret is already stored and read back as present, so CI has what it needs.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>