-
Notifications
You must be signed in to change notification settings - Fork 65
190 lines (176 loc) · 8.16 KB
/
Copy pathkoth-gate.yml
File metadata and controls
190 lines (176 loc) · 8.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# koth ladder gate - scores kit-only PRs against the reigning kit and
# enables auto-merge on a dethrone. the ditto mining loop, rebuilt on
# github primitives: the bench is the validator, branch protection is the
# chain, auto-merge is the emission.
#
# security model (do not weaken):
# - pull_request_target: this definition and every line of executed code
# come from the BASE branch. PR code is never checked out or executed.
# - the only thing read from the PR is competition/kits/current/kit.yaml,
# fetched over the api as data and schema-validated (closed allowlist)
# before it is passed to the bench as extra_config.
# - a PR qualifies for the ladder only if the kit file is the ONLY file
# it touches. anything else is a normal PR: the gate passes without
# scoring and a human reviews as usual.
# - untrusted strings (branch names, repo names, titles) reach scripts
# via env, never by interpolation into run bodies.
name: koth-gate
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] no PR code ever executes: the kit is fetched as data, validated against a closed-world allowlist, and scored by base-branch code with a read-only checkout
types: [opened, synchronize, reopened, ready_for_review]
concurrency:
group: koth-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
jobs:
gate:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # auto-merge (squash) needs it
pull-requests: write # enable auto-merge + scorecard comment
steps:
- name: checkout base branch (trusted code only)
uses: actions/checkout@v4
# pull_request_target defaults to the DEFAULT branch, not the PR
# base - pin the base ref explicitly. still trusted code: a branch
# of this repo, never the PR head. the base carries the scripts,
# the engine, and the reigning kit.
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: classify the PR
id: classify
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" \
--paginate --jq '.[].filename' > /tmp/changed.txt
count=$(wc -l < /tmp/changed.txt)
if [ "$count" = "1" ] && \
grep -qx 'competition/kits/current/kit.yaml' /tmp/changed.txt; then
echo "mode=ladder" >> "$GITHUB_OUTPUT"
else
echo "mode=normal" >> "$GITHUB_OUTPUT"
fi
- name: pass through (not a ladder PR)
if: steps.classify.outputs.mode == 'normal'
run: echo "not a kit-only PR - koth gate does not apply, humans review."
- name: fetch challenger kit from the PR (data only)
if: steps.classify.outputs.mode == 'ladder'
env:
GH_TOKEN: ${{ github.token }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
KIT_PATH: competition/kits/current/kit.yaml
run: |
# the contents api inlines base64 only for files under its size
# limit (~1MB); a larger file returns empty content and a download
# url instead. read size + content together and refuse anything
# that is not a small inlined blob, so an oversized kit can never
# decode to "" and be scored as champion defaults.
gh api "repos/${HEAD_REPO}/contents/${KIT_PATH}?ref=${HEAD_SHA}" \
> /tmp/kit-meta.json
size=$(jq -r '.size // 0' /tmp/kit-meta.json)
encoding=$(jq -r '.encoding // ""' /tmp/kit-meta.json)
if [ "$encoding" != "base64" ]; then
echo "kit not returned as an inlined base64 blob (encoding=$encoding, size=$size)" >&2
exit 1
fi
jq -r '.content' /tmp/kit-meta.json | base64 -d > /tmp/kit-challenger.yaml
if [ ! -s /tmp/kit-challenger.yaml ]; then
echo "fetched kit is empty" >&2
exit 1
fi
- name: set up python
if: steps.classify.outputs.mode == 'ladder'
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: install vouch (base branch code)
if: steps.classify.outputs.mode == 'ladder'
run: python -m pip install -e .
- name: validate challenger kit against the allowlist
if: steps.classify.outputs.mode == 'ladder'
run: python .github/scripts/validate_kit.py /tmp/kit-challenger.yaml
- name: paired scoring - challenger vs reigning kit
if: steps.classify.outputs.mode == 'ladder'
id: score
run: |
set +e
# seed identity = the tree actually scored (the checked-out base
# tip), not github.sha, which is the default branch under
# pull_request_target
BASE_SHA="$(git rev-parse HEAD)"
python .github/scripts/koth_score.py \
--champion competition/kits/current/kit.yaml \
--challenger /tmp/kit-challenger.yaml \
--base-sha "$BASE_SHA" \
--out /tmp/koth-report.json
code=$?
set -e
if [ "$code" = "0" ]; then
echo "verdict=dethroned" >> "$GITHUB_OUTPUT"
elif [ "$code" = "3" ]; then
echo "verdict=held" >> "$GITHUB_OUTPUT"
else
exit "$code"
fi
- name: post the scorecard
if: steps.classify.outputs.mode == 'ladder'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
VERDICT: ${{ steps.score.outputs.verdict }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
LADDER_BASE: ${{ vars.KOTH_LADDER_BASE }}
run: |
if [ "$BASE_REF" = "$LADDER_BASE" ] && [ -n "$LADDER_BASE" ]; then
gate_note="auto-merge on dethrone (provisional ladder branch)"
else
gate_note="scored only - auto-merge is disabled for base '${BASE_REF}'; a maintainer promotes proven champions to shipped defaults by hand"
fi
# the posted markdown carries literal backticks
# shellcheck disable=SC2016
{
echo "koth ladder - ${VERDICT}"
echo
echo '```json'
cat /tmp/koth-report.json
echo '```'
echo
echo "${gate_note}."
echo
echo "seeds derive from base sha + utc date; rerun locally with"
echo '`python .github/scripts/koth_score.py --date <utc-date>` to reproduce.'
echo "the daily result is provisional and overfittable - payout rank is"
echo "settled monthly on sealed commit-reveal seeds (docs/vouchbench-seasons.md)."
} > /tmp/comment.md
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--body-file /tmp/comment.md
# auto-merge fires ONLY on a dedicated, non-shipped ladder branch
# (repo variable KOTH_LADDER_BASE). the trunk is never auto-written:
# a kit-only PR against main is scored and commented, then a human
# merges. this keeps the review gate load-bearing for everything that
# ships - a beatable benchmark must never be the sole writer to code
# users install. when KOTH_LADDER_BASE is unset, nothing auto-merges.
- name: enable auto-merge on dethrone (ladder branch only)
if: >-
steps.classify.outputs.mode == 'ladder' &&
steps.score.outputs.verdict == 'dethroned' &&
vars.KOTH_LADDER_BASE != '' &&
github.event.pull_request.base.ref == vars.KOTH_LADDER_BASE
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
gh pr merge "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--auto --squash
- name: hold the throne (challenger did not clear the band)
if: >-
steps.classify.outputs.mode == 'ladder' &&
steps.score.outputs.verdict == 'held'
run: |
echo "champion holds - challenger did not clear max(0.007, 1.96 x paired SE)."
exit 1