Skip to content

Commit f62f550

Browse files
authored
ci(safe-cosmetic): HMAC-authorized, label-gated auto-merge workflow (#73)
* ci: add SAFE-COSMETIC label-gated auto-merge workflow Operator-sanctioned exception (2026-06-06) to per-push approval for comment/whitespace/doc-only PRs. Merges a PR labeled SAFE-COSMETIC into master once all six required Linux contexts pass: Linux x86_64, Linux x86_64 (AsAN+UBSan), and the ltc/doge/dash/btc coin smokes. Gate matches BARE check-run names (the REST API form), not the CI / ... UI form, verified against live PR #68 check data. Guarded by a fail-closed SAFE_COSMETIC_ENABLED repo-variable kill-switch, label presence, base==master, and non-draft/open state. * ci(safe-cosmetic): add head-SHA-bound HMAC authorization gate Require a valid integrator HMAC authorization comment, bound to the PR head SHA, before the SAFE-COSMETIC auto-merge fires. The gate recomputes HMAC-SHA256(INTEGRATOR_LABEL_KEY, "<repo>:<pr>:<head-sha>") and merges only when a comment of the exact shape SAFE-COSMETIC-AUTH: <hmac> head=<head-sha> matches the current head. A new push changes the head SHA and silently invalidates any prior signature, so authorizations cannot be replayed across PRs or SHAs. Enforced only once a PR is otherwise green; on a green PR lacking valid authorization the gate fails closed: strips the label, posts a refusal, and fails the job. SAFE_COSMETIC_ENABLED kill-switch retained as an AND gate; bare check-run names and the (AsAN+UBSan) literal unchanged.
1 parent 33b854e commit f62f550

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
name: SAFE-COSMETIC auto-merge
2+
3+
# Operator-sanctioned exception to the per-push approval rule
4+
# (operator pick 2026-06-06 13:52 +04, relayed by integrator). Auto-merges a PR
5+
# carrying the `SAFE-COSMETIC` label once ALL required Linux green contexts pass
6+
# AND a head-SHA-bound HMAC authorization from the integrator is present.
7+
# Intended scope: comment / whitespace / doc-only cosmetic changes.
8+
#
9+
# SAFETY CONTROLS (all must hold to merge):
10+
# 1. Repo variable SAFE_COSMETIC_ENABLED == "true" (operator kill-switch; fail-closed)
11+
# 2. PR carries the `SAFE-COSMETIC` label (applied by integrator only)
12+
# 3. PR base branch == master (stated merge target)
13+
# 4. PR open, not draft
14+
# 5. Every context in REQUIRED below == success
15+
# 6. A valid HMAC-SHA256 authorization comment bound to the CURRENT head SHA
16+
# (operator-ratified 2026-06-06; see HMAC GATE below). Fail-closed.
17+
#
18+
# CHECK-RUN NAME MATCHING — DO NOT "FIX" TO THE UI FORM:
19+
# The check-runs REST API returns the BARE job `name:` ("Linux x86_64"), NOT
20+
# the "Workflow / Job" form shown in the PR UI ("CI / Linux x86_64"). Matching
21+
# the prefixed strings makes the gate silently never fire. The ASan job name is
22+
# literally "(AsAN+UBSan)" (sic). Both verified against live check-run data on
23+
# PR #68 on 2026-06-06. The legacy "BTC embedded smoke (Linux x86_64)" job is
24+
# deliberately NOT required here (coin-matrix "btc smoke" is the live gate).
25+
#
26+
# HMAC GATE — why head-SHA binding matters:
27+
# The integrator posts a PR comment of the EXACT shape:
28+
# SAFE-COSMETIC-AUTH: <hmac> head=<head-sha>
29+
# where <hmac> = HMAC-SHA256(INTEGRATOR_LABEL_KEY, "<repo>:<pr>:<head-sha>")
30+
# as lowercase hex. The workflow recomputes the expected value over the PR's
31+
# CURRENT head SHA and only merges if a matching comment exists. Any new push
32+
# changes the head SHA and silently invalidates every prior signature, so an
33+
# authorization can never be replayed across PRs or across SHAs. The signing
34+
# key lives only as the INTEGRATOR_LABEL_KEY Actions secret; this workflow
35+
# never emits it.
36+
37+
on:
38+
check_suite:
39+
types: [completed]
40+
pull_request_target:
41+
types: [labeled, synchronize, reopened]
42+
43+
permissions:
44+
contents: write
45+
pull-requests: write
46+
checks: read
47+
48+
concurrency:
49+
group: safe-cosmetic-automerge-${{ github.event.pull_request.number || github.event.check_suite.head_sha }}
50+
cancel-in-progress: false
51+
52+
jobs:
53+
evaluate:
54+
runs-on: ubuntu-24.04
55+
steps:
56+
- name: Evaluate gate and merge if green and authorized
57+
env:
58+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+
REPO: ${{ github.repository }}
60+
ENABLED: ${{ vars.SAFE_COSMETIC_ENABLED }}
61+
INTEGRATOR_LABEL_KEY: ${{ secrets.INTEGRATOR_LABEL_KEY }}
62+
run: |
63+
set -euo pipefail
64+
65+
if [ "${ENABLED:-}" != "true" ]; then
66+
echo "SAFE_COSMETIC_ENABLED != true (kill-switch off) — auto-merge disabled. Exiting."
67+
exit 0
68+
fi
69+
70+
LABEL="SAFE-COSMETIC"
71+
BASE_REQUIRED="master"
72+
REQUIRED=(
73+
"Linux x86_64"
74+
"Linux x86_64 (AsAN+UBSan)"
75+
"ltc smoke (Linux x86_64)"
76+
"doge smoke (Linux x86_64)"
77+
"dash smoke (Linux x86_64)"
78+
"btc smoke (Linux x86_64)"
79+
)
80+
81+
# Candidate PR numbers from whichever event fired.
82+
PRS=$(jq -r '[ .pull_request.number, (.check_suite.pull_requests[]?.number) ] | map(select(. != null)) | unique | .[]' "$GITHUB_EVENT_PATH")
83+
if [ -z "${PRS:-}" ]; then
84+
echo "No candidate PRs in event payload — nothing to do."
85+
exit 0
86+
fi
87+
88+
for PR in $PRS; do
89+
echo "::group::Evaluating PR #$PR"
90+
META=$(gh api "repos/$REPO/pulls/$PR" --jq '{state:.state, draft:.draft, base:.base.ref, sha:.head.sha, labels:[.labels[].name]}')
91+
STATE=$(jq -r '.state' <<<"$META")
92+
DRAFT=$(jq -r '.draft' <<<"$META")
93+
BASE=$(jq -r '.base' <<<"$META")
94+
SHA=$(jq -r '.sha' <<<"$META")
95+
HAS_LABEL=$(jq -r --arg L "$LABEL" 'any(.labels[]; . == $L)' <<<"$META")
96+
97+
[ "$STATE" = "open" ] || { echo "PR not open — skip."; echo "::endgroup::"; continue; }
98+
[ "$DRAFT" = "false" ] || { echo "PR is draft — skip."; echo "::endgroup::"; continue; }
99+
[ "$HAS_LABEL" = "true" ] || { echo "No $LABEL label — skip."; echo "::endgroup::"; continue; }
100+
[ "$BASE" = "$BASE_REQUIRED" ] || { echo "Base $BASE != $BASE_REQUIRED — out of scope, skip."; echo "::endgroup::"; continue; }
101+
102+
RUNS=$(gh api "repos/$REPO/commits/$SHA/check-runs" --paginate \
103+
--jq '.check_runs[] | "\(.name)\t\(.conclusion)"')
104+
105+
ok=1
106+
for ctx in "${REQUIRED[@]}"; do
107+
concl=$(awk -F'\t' -v c="$ctx" '$1==c {print $2; exit}' <<<"$RUNS" || true)
108+
if [ -z "$concl" ]; then echo " MISSING required context: $ctx"; ok=0; continue; fi
109+
echo " $ctx => $concl"
110+
[ "$concl" = "success" ] || ok=0
111+
done
112+
113+
if [ "$ok" != "1" ]; then
114+
echo "Required contexts not all green for PR #$PR — not merging (waiting)."
115+
echo "::endgroup::"
116+
continue
117+
fi
118+
119+
# --- HMAC authorization gate (operator-ratified 2026-06-06) ---
120+
# Only enforced once the PR is otherwise mergeable, so an in-flight
121+
# (not-yet-green) PR is never punished for a missing signature. When a
122+
# PR IS green but lacks valid authorization bound to the current head,
123+
# fail closed: strip the label, post a refusal, and fail the job.
124+
if [ -z "${INTEGRATOR_LABEL_KEY:-}" ]; then
125+
echo "INTEGRATOR_LABEL_KEY secret unavailable — cannot verify authorization. Fail-closed."
126+
exit 1
127+
fi
128+
129+
MSG="$REPO:$PR:$SHA"
130+
EXPECTED=$(printf '%s' "$MSG" | openssl dgst -sha256 -hmac "$INTEGRATOR_LABEL_KEY" | awk '{print $NF}')
131+
WANT="SAFE-COSMETIC-AUTH: $EXPECTED head=$SHA"
132+
echo " Authorization required for head $SHA (HMAC over \"$MSG\")."
133+
134+
if gh api "repos/$REPO/issues/$PR/comments" --paginate --jq '.[].body' \
135+
| tr -d '\r' | grep -Fqx "$WANT"; then
136+
echo " Valid SAFE-COSMETIC authorization found for head $SHA — proceeding."
137+
else
138+
echo " NO valid authorization comment bound to head $SHA — fail-closed."
139+
gh api -X DELETE "repos/$REPO/issues/$PR/labels/$LABEL" >/dev/null 2>&1 || true
140+
gh pr comment "$PR" --repo "$REPO" --body \
141+
"SAFE-COSMETIC auto-merge refused: no valid integrator authorization bound to head \`$SHA\`. The \`$LABEL\` label has been removed. Re-apply it after the integrator posts a fresh \`SAFE-COSMETIC-AUTH:\` signature for this exact head SHA." || true
142+
exit 1
143+
fi
144+
# --- end HMAC gate ---
145+
146+
echo "All required contexts green AND authorization valid on PR #$PR — merging into $BASE_REQUIRED."
147+
gh pr merge "$PR" --repo "$REPO" --merge --delete-branch=false
148+
echo "::endgroup::"
149+
done

0 commit comments

Comments
 (0)