Repository navigation
279 lines (257 loc) · 14.7 KB
/
Copy pathrefresh-coderabbit-schema.yml
File metadata and controls
279 lines (257 loc) · 14.7 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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
name: Refresh CodeRabbit schema
# Keep the vendored CodeRabbit config schema from going stale silently.
#
# `coderabbit-config-validate.yml` validates against a VENDORED copy of
# CodeRabbit's schema (.github/coderabbit-config/schema.v2.json) rather than
# fetching it live: a live fetch would make every consumer's CI depend on a
# third-party endpoint, and an upstream tightening would turn CI red across the
# fleet with no change on our side. The cost of vendoring is that the copy rots —
# nothing moves it, so the check would keep grading configs against an
# ever-older schema until someone noticed. This job notices instead: weekly it
# fetches the published schema, compares it SEMANTICALLY with the vendored copy,
# and when they differ opens a PR moving the vendored file with a summary of what
# changed. Nothing auto-merges — a human reads the summary, which is the point.
#
# Merging that PR touches .github/coderabbit-config/**, which is exactly the path
# filter bump-coderabbit-config-callers.yml watches, so the SHA-pinned caller
# fleet rolls forward on its own. No new fleet plumbing here.
#
# Modelled on bump-cursor-cli-pin.yml: same main-only job guard, same
# non-cancelling concurrency around a stable shared branch, same App-token
# credential pattern (vars.APP_ID + secrets.CLOUD_CODE_BOT_PRIVATE_KEY, already
# provisioned in this repo).
#
# The fetch uses `curl -fsSL`, and the -L is load-bearing: the schema URL
# 301-redirects, and a bare `curl` writes a small HTML redirect stub instead. That
# stub is neither JSON nor a schema — `schema_drift.py` refuses it with exit 2
# rather than reporting "no drift", because a check that silently freezes is worse
# than one that fails.
on:
schedule:
# Mondays 15:00 UTC — an hour after bump-cursor-cli-pin so the two weekly
# PR-opening jobs don't collide on the same runner minute, and early enough
# in the week that a drift PR gets read before the weekend.
- cron: '0 15 * * 1'
workflow_dispatch: {}
permissions:
contents: read
# Serialize runs: the refresh lands on a STABLE branch
# (ci/refresh-coderabbit-schema) that is force-reset each run, so two overlapping
# runs (a manual dispatch racing the cron) would race that reset and the PR
# update, and the older run finishing last could leave the PR carrying a stale
# schema. cancel-in-progress: false lets the running refresh finish; the newest
# pending run then wins. Same reasoning as the bump fleets (BE-3882).
concurrency:
group: refresh-coderabbit-schema
cancel-in-progress: false
jobs:
refresh:
# Guard against a workflow_dispatch run from a non-main ref: this job
# force-resets a shared branch and mints an App token, so it must only ever
# run from reviewed main. The schedule trigger is already main-only, so this
# only skips stray manual runs.
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 15
env:
SCHEMA_URL: https://coderabbit.ai/integrations/schema.v2.json
VENDORED: .github/coderabbit-config/schema.v2.json
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# `ref: main` resolves at run time, so the refresh is always built on
# the LIVE main tip. Without it a re-run of an older run would check out
# that run's (now stale) github.sha, and the force-push below would
# publish a branch that reverts everything merged since. The `if:` guard
# above already restricts this job to main.
ref: main
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Fetch the published schema
run: |
set -euo pipefail
# -L is load-bearing: the URL 301-redirects, and without it curl writes
# a ~167-byte HTML redirect stub that parses as neither JSON nor a
# schema. -f turns an HTTP error into a non-zero exit rather than a body
# we would happily vendor. The remaining flags mirror the hardening in
# bump-cursor-cli-pin.yml: https only (including across the redirect),
# bounded time, bounded size, bounded retries.
curl -fsSL \
--proto '=https' --proto-redir '=https' \
--connect-timeout 10 --max-time 60 \
--retry 3 --retry-all-errors --retry-delay 2 --retry-max-time 120 \
--max-filesize 8388608 \
"$SCHEMA_URL" -o "${RUNNER_TEMP}/schema.v2.json"
- name: Compare against the vendored copy
id: drift
run: |
set -uo pipefail
summary="${RUNNER_TEMP}/drift.md"
set +e
python3 .github/coderabbit-config/schema_drift.py \
--vendored "$VENDORED" \
--fetched "${RUNNER_TEMP}/schema.v2.json" \
--summary-out "$summary"
code=$?
set -e
case "$code" in
0)
echo "drifted=false" >> "$GITHUB_OUTPUT"
echo "Vendored CodeRabbit schema is current." >> "$GITHUB_STEP_SUMMARY"
;;
1)
echo "drifted=true" >> "$GITHUB_OUTPUT"
;;
*)
# Exit 2 is "I could not compare" — an unreadable or non-schema
# fetch. Fail the run: a comparison we could not perform is not
# evidence that nothing changed, and swallowing it here is how a
# vendored file freezes forever behind a green weekly run.
echo "::error::Could not compare the fetched schema against the vendored copy (schema_drift.py exit ${code})."
exit 1
;;
esac
- name: Vendor the new schema and re-run the checker against it
id: invariants
if: steps.drift.outputs.drifted == 'true'
run: |
set -euo pipefail
cp "${RUNNER_TEMP}/schema.v2.json" "$VENDORED"
python3 -m pip install --disable-pip-version-check --no-input \
--require-hashes --only-binary=:all: \
-r .github/coderabbit-config/requirements.txt
# The proposed schema has to still work as the thing the fleet is graded
# against. The unit suite asserts the facts the checker's messages rest
# on (root closed, tone_instructions capped at 250, `tools` living under
# `reviews`, the 14-cap set), so a refresh that would quietly redefine
# what the check MEANS must not merge green. Plus a self-check: this repo
# has no .coderabbit.yaml, so validate a smoke fixture rather than nothing.
#
# CAPTURED, not fatal — and that distinction is the whole point. Those
# assertions are exact (`len(caps) == 14`, `tone_instructions == 250`), so
# ANY legitimate upstream cap add, removal or change fails them. Aborting
# this step would take every later step with it, since their
# `if: steps.drift.outputs.drifted == 'true'` carries an implicit
# success() — so no PR would be opened, and the drift class this workflow
# exists to catch first would be the one it silently never proposes.
# Instead the verdict rides in the PR body and reddens the run at the end,
# once there is a PR to explain it.
log="${RUNNER_TEMP}/invariants.log"
set +e
(
set -e
python3 -m unittest discover -s .github/coderabbit-config/tests -p 'test_*.py'
root="$(mktemp -d)"
printf 'language: en-US\nreviews:\n profile: chill\n' > "$root/.coderabbit.yaml"
python3 .github/coderabbit-config/check_coderabbit_config.py --root "$root"
) > "$log" 2>&1
code=$?
set -e
cat "$log"
if [ "$code" -eq 0 ]; then
echo "invariants=pass" >> "$GITHUB_OUTPUT"
else
echo "invariants=fail" >> "$GITHUB_OUTPUT"
echo "::warning::The invariant suite fails against the proposed schema (exit ${code}). Opening the refresh PR anyway so the change is reviewable; this run ends red."
fi
- name: Generate Cloud Code Bot token
id: token
if: steps.drift.outputs.drifted == 'true'
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 — pinned: see dependabot.yml ignore (v2+ breaks cross-repo `owner:` token scoping)
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.CLOUD_CODE_BOT_PRIVATE_KEY }}
# No `owner:` — this only ever writes to THIS repo, so the default
# repo-scoped installation token is both sufficient and narrower.
permission-contents: write
permission-pull-requests: write
- name: Open or update the refresh PR
if: steps.drift.outputs.drifted == 'true'
env:
GH_TOKEN: ${{ steps.token.outputs.token }}
BRANCH: ci/refresh-coderabbit-schema
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
INVARIANTS: ${{ steps.invariants.outputs.invariants }}
run: |
set -euo pipefail
# Verify the body's ingredients BEFORE the force-push. The push moves a
# shared branch; if drift.md were missing, the `cat` below would abort
# under `set -e` with the branch already reset and no PR to explain it.
if [ ! -s "${RUNNER_TEMP}/drift.md" ]; then
echo "::error::Drift was reported but ${RUNNER_TEMP}/drift.md is missing or empty — refusing to reset the shared branch with nothing to explain it."
exit 1
fi
# Committer identity is cosmetic — the PR AUTHOR (what reviewers and
# filters key off) comes from the App token used below. Same convention
# as bump-cursor-cli-pin.yml and groom.yml's auto-builder.
git config user.name 'coderabbit-schema refresher'
git config user.email 'coderabbit-schema-refresher@users.noreply.github.com'
git checkout -B "$BRANCH"
git add "$VENDORED"
git commit -m "ci(coderabbit-config): refresh the vendored CodeRabbit schema"
# Force-reset the stable branch so its diff is rebuilt from the current
# main tip rather than stacked on a previous week's refresh. The token is
# masked in the run log by the create-github-app-token action.
git push --force \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
"HEAD:refs/heads/${BRANCH}"
# Built with printf rather than a heredoc: a heredoc terminator has to
# sit at column 0, which would end this YAML block scalar early.
body="$(mktemp "${RUNNER_TEMP}/pr-body.XXXXXX")"
# SC2016: the single-quoted literals hold Markdown backticks, not
# command substitutions — that is exactly why they are single-quoted.
# shellcheck disable=SC2016
{
printf '%s\n\n' 'Upstream'"'"'s CodeRabbit config schema has drifted from the copy vendored here, which is what `coderabbit-config-validate.yml` grades every enrolled repo against.'
cat "${RUNNER_TEMP}/drift.md"
printf '\n'
printf '%s\n\n' '**Before merging**, check the tightened-cap section above if there is one: a lowered `maxLength` retroactively invalidates configs nobody has touched, and merging this makes their next PR red. Landing the cleanup alongside is usually the right order.'
if [ "${INVARIANTS:-}" = "fail" ]; then
# No blank line between the marker and the body: GitHub only
# renders the alert when the blockquote is contiguous.
printf '%s\n' '> [!WARNING]'
printf '%s\n\n' '> **The invariant unit suite FAILS against the proposed schema** ([this run]('"$RUN_URL"')). That suite asserts the exact facts the checker'"'"'s messages rest on, so this is the signal to read closely, not a flake: either the schema change is one we must adapt the checker and its tests to, or the fetch is not what we think it is. Update those assertions in this PR, deliberately, before merging.'
printf '%s\n' '<details><summary>Invariant suite output</summary>'
printf '\n```\n'
tail -n 60 "${RUNNER_TEMP}/invariants.log" || echo '(invariant log unavailable)'
printf '```\n\n</details>\n\n'
else
printf '%s\n\n' 'The unit suite ran green against the proposed schema in [this run]('"$RUN_URL"') — it asserts the facts the checker'"'"'s messages rest on, so a refresh that would silently redefine the check cannot slip through unnoticed.'
fi
printf '%s\n\n' 'Merging this touches `.github/coderabbit-config/**`, which triggers `bump-coderabbit-config-callers.yml` and rolls the SHA-pinned caller fleet forward.'
printf '%s\n' '_Opened by the `refresh-coderabbit-schema` workflow._'
} > "$body"
# Reuse the one open PR for this branch if there is one — the force-push
# already refreshed its diff, so only the title/body need updating. The
# stable head branch guarantees at most one. Exclude cross-repository
# PRs: `--head` matches by branch NAME across forks, and this branch name
# is predictable, so a fork PR opened on it must not be mistaken for ours
# and stamped with the official text.
existing=$(gh pr list --repo "${GITHUB_REPOSITORY}" --head "$BRANCH" --state open \
--json number,isCrossRepository \
--jq 'map(select(.isCrossRepository == false)) | .[0].number // empty')
title='ci(coderabbit-config): refresh the vendored CodeRabbit schema'
if [ -n "$existing" ]; then
gh pr edit "$existing" --repo "${GITHUB_REPOSITORY}" --title "$title" --body-file "$body"
echo "Updated PR #${existing}"
else
gh pr create --repo "${GITHUB_REPOSITORY}" \
--head "$BRANCH" --base main \
--title "$title" --body-file "$body"
fi
{
echo "Vendored CodeRabbit schema drift proposed:"
echo
cat "${RUNNER_TEMP}/drift.md"
} >> "$GITHUB_STEP_SUMMARY"
- name: Fail the run when the proposed schema breaks the invariants
# Deliberately last: the PR is already open and carries the failure in its
# body, so the red run points at something reviewable instead of vanishing.
if: steps.invariants.outputs.invariants == 'fail'
run: |
echo "::error::The proposed schema fails the checker's invariant suite. The refresh PR is open with the output in its body — adapt the checker and its assertions there, deliberately."
exit 1