The override did not work on its first real test, and the reason was invisible from the outside. Run 203 on PR #38: a well-formed `/approve-override` posted by jakman85 in the PR comment box, 29 minutes newer than the head commit, and the stage still blocked with: approval-override: COMMENTS_JSON is not a list SETTINGS_API_TOKEN is minted [read:repository, read:admin], and Gitea puts /issues/{n}/comments behind the *issue* scope — so Gitea answered with an error OBJECT, `jq -e .` accepted it as valid JSON, and the stage fell through to 'no approving review'. A permission failure wearing the costume of a missing override. /pulls/{n}/files and /pulls/{n}/reviews are under read:repository, which is why every other part of the same stage worked. 1. The diagnosis no longer lies. COMMENTS_JSON leaves the must-parse-or-block loop and is checked for being an ARRAY; when it is not, the stage prints Gitea's own .message, names the missing scope and says where to post instead. An unreadable source can only cause a false BLOCK, never a false pass, so a loud warning is the honest behaviour and blocking was not. 2. An override is accepted from a PR REVIEW body as well as a comment, so the hatch is not hostage to one API scope — a review of type 'Comment' works on the token CI has today. The gate names which source it used; PENDING (unsubmitted, author-only) reviews are dropped. 3. set-ci-token.sh requests read:issue, making the ordinary comment box work — on a RE-RUN only, since an existing token's scopes cannot be widened. ci/tests/approval-override.test.sh: 23 -> 38 assertions, covering the reviews source, PENDING reviews, an error object from the comments API, 'an unreadable comments source must not suppress a valid review override', latest-wins across both sources, and that the diagnostic quotes Gitea's message. Also measured and recorded: run 203 shows gitea-settings PASS after the operator ran the live PATCH, with block_admin_merge_override still true — the narrow change held, the approval count moved and the gate's teeth did not. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
71 lines
4.2 KiB
Bash
Executable File
71 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Mint a Gitea API token and store it as the repo Actions secret GITEA_SETTINGS_TOKEN,
|
|
# so ci/gitea-settings.sh can read the live branch-protection rule during the gate.
|
|
#
|
|
# The token value is NEVER printed. It goes from the token API into the secrets API and
|
|
# nowhere else -- not to stdout, not to a file, not into an agent transcript.
|
|
#
|
|
# Scope: `read:repository` + `read:admin` are what ci/gitea-settings.sh needs (the rule object,
|
|
# and the runner list). `read:issue` is for ci/approval-policy.sh, and it was MISSING — see below.
|
|
# Gitea token scopes are coarse; grant the least that works on your version and confirm the check
|
|
# goes green rather than assuming the scope name.
|
|
#
|
|
# WHY read:issue IS HERE (added 2026-10-02, measured on PR #38). ci/approval-policy.sh reads the
|
|
# PR's comments to find an admin `/approve-override`. /issues/{n}/comments sits behind the *issue*
|
|
# scope, which this script did not request — so Gitea answered with an error OBJECT instead of an
|
|
# array, and a perfectly well-formed override posted in the PR comment box was invisible to CI.
|
|
# The failure did not look like a permission problem from the outside; it looked like the override
|
|
# had not been posted. `/pulls/{n}/files` and `/pulls/{n}/reviews` are under read:repository, which
|
|
# is why every other part of the same stage worked and this one did not.
|
|
#
|
|
# The stage now accepts an override from a REVIEW body as well, so it still functions on a token
|
|
# WITHOUT read:issue. This scope is what makes the ordinary comment box work too — the thing
|
|
# anybody would reach for first. **Re-run this script to pick it up; an existing token's scopes
|
|
# cannot be widened in place.**
|
|
set -euo pipefail
|
|
HOST=gitea.home.keithjackson.dev; OWNER=Home-Network; REPO=home-network-k3s-impl
|
|
# NOT GITEA_* — Gitea reserves that prefix for secret names and returns HTTP 400
|
|
# "invalid secret name". The first version of this script used GITEA_SETTINGS_TOKEN, sent the
|
|
# PUT to /dev/null, and printed "stored" regardless; the secret never existed and nobody knew.
|
|
SECRET=SETTINGS_API_TOKEN
|
|
NAME="ci-gitea-settings-$(date +%Y%m%d%H%M%S)"
|
|
creds="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill)"
|
|
U="$(sed -n 's/^username=//p' <<<"$creds")"; P="$(sed -n 's/^password=//p' <<<"$creds")"
|
|
basic() { curl -sS --config <(printf 'user = "%s:%s"\n' "$U" "$P") "$@"; }
|
|
|
|
echo "==> minting token '${NAME}' as ${U}"
|
|
TOKEN="$(basic -X POST -H 'Content-Type: application/json' \
|
|
-d "{\"name\":\"${NAME}\",\"scopes\":[\"read:repository\",\"read:admin\",\"read:issue\"]}" \
|
|
"https://${HOST}/api/v1/users/${U}/tokens" | jq -r '.sha1 // empty')"
|
|
[ -n "$TOKEN" ] || { echo "token creation failed (check scopes for your Gitea version)" >&2; exit 1; }
|
|
echo "==> got a ${#TOKEN}-character token (value not shown)"
|
|
|
|
echo "==> storing it as repo Actions secret ${SECRET}"
|
|
code="$(basic -o /tmp/set-ci-token.out -w '%{http_code}' -X PUT -H 'Content-Type: application/json' \
|
|
-d "$(jq -n --arg d "$TOKEN" '{data:$d}')" \
|
|
"https://${HOST}/api/v1/repos/${OWNER}/${REPO}/actions/secrets/${SECRET}")"
|
|
unset TOKEN
|
|
if [ "$code" != 201 ] && [ "$code" != 204 ]; then
|
|
echo " PUT failed: HTTP ${code} -- $(head -c 200 /tmp/set-ci-token.out)" >&2
|
|
rm -f /tmp/set-ci-token.out; exit 1
|
|
fi
|
|
rm -f /tmp/set-ci-token.out
|
|
|
|
# VERIFY BY READING IT BACK. curl -sS exits 0 on an HTTP error, so without this the script
|
|
# reports success for a store that never happened -- which is exactly what it did.
|
|
names="$(basic "https://${HOST}/api/v1/repos/${OWNER}/${REPO}/actions/secrets" \
|
|
| jq -rc '[.. | objects | select(has("name")) | .name] | unique')"
|
|
grep -q "$SECRET" <<<"$names" \
|
|
|| { echo " stored HTTP ${code} but ${SECRET} is NOT in ${names} -- refusing to claim success" >&2; exit 1; }
|
|
echo "==> stored and VERIFIED present: ${names}"
|
|
|
|
cat <<'NOTE'
|
|
|
|
Next:
|
|
1. .gitea/workflows/gate.yml already passes this secret to the gate step.
|
|
2. Open any PR and confirm the gate prints "gitea-settings: green" -- it SKIPS silently
|
|
without the token, so a green run is the only proof the wiring works.
|
|
3. THEN set REQUIRE_GITEA_SETTINGS=true in gate.yml, so a future missing token BLOCKS
|
|
instead of skipping. Do that only after you have seen it green, or you lock the repo.
|
|
NOTE
|