-
Notifications
You must be signed in to change notification settings - Fork 0
201 lines (192 loc) · 11 KB
/
Copy pathbugbot-gate.yml
File metadata and controls
201 lines (192 loc) · 11 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
# GENERATED FILE -- DO NOT EDIT BY HAND.
#
# Self-contained copy of the reusable workflow `.github/workflows/bugbot-gate.yml`
# (blob e9a4e91e501f24f5f2f07d455eaef81f91e3423f) folded with the private source repo's own caller of it,
# `.github/workflows/bugbot-gate-caller.yml` (blob f87fa0ed0dfd714ced846d5db9487f2bf9622cbe), for a PUBLIC repo: GitHub lets a
# public repo `uses:` a reusable workflow only from a public repo, and the
# reusables live in the private source repo. So this file carries the
# reusable's jobs inline -- the triggers are the caller's; every `inputs.*` is
# resolved to a literal (repo-inventory.yml's `caller_inputs` floor, else the
# reusable's default -- the caller's own toolchain choices are NOT copied); the
# App-token secrets are named directly; a job whose condition resolves to a
# constant false is omitted; and each job's display name keeps the
# `bugbot / ` prefix so the check contexts branch protection requires do
# not change. Scripts are still fetched from the private source repo at run time with a
# read-only App installation token; on a fork PR that token cannot be minted
# (no secrets), so the checkout fails closed rather than running untrusted input.
#
# PUBLIC SURFACE. This file is delivered to a public repo. Exactly what was
# scrubbed, and what was not:
#
# * COMMENT TEXT -- a whole-line YAML `#` comment, a `#`-first line of a `run:`
# script that is not inside a heredoc, and this header -- was scrubbed: every
# private-repo issue reference, org-qualified private repo name, RFC id and
# tenant name became `(internal ref)`, and the private repo this file was
# generated from became `the private source repo`.
# * CODE -- everything else: commands, heredoc bodies, strings a workflow posts,
# `repository:` values, URLs, step names -- was NOT edited. An internal
# reference there refuses the render (it is a defect in the source), with one
# exception: the name of the private repo this file was generated from, which
# the code states verbatim because the workflow checks that repo out and
# fetches its scripts from it at run time. Its bare, unqualified name is not
# scrubbed anywhere.
#
# Regenerate: python3 scripts/render-self-contained.py (in the private source repo)
# Verify: python3 scripts/render-self-contained.py --check (make selftest-self-contained)
#
# The comments below are the reusable's own and describe the reusable; where
# they mention `inputs.*` or callers, read them as history.
name: Bugbot review gate
# Reusable workflow. Makes Cursor Bugbot's review a GATE instead of advice
# ((internal ref)). `scripts/bugbot-gate.py` carries the measurements and
# the reasoning; this file is the wiring, and only says what the script cannot.
#
# THE SHORT VERSION OF WHY THIS IS NOT "ADD `Cursor Bugbot` TO THE REQUIRED
# CONTEXTS". Measured 2026-08-22: Bugbot emits `success` when clean and `neutral`
# when it has findings, and never `failure`. That makes requiring the context a
# dichotomy with two bad horns:
#
# * if `neutral` SATISFIES a required context -- GitHub's documented behaviour
# -- requiring it gates nothing, because `failure` never occurs;
# * if it does NOT, requiring it permanently bricks every PR that ever received
# a finding, because Bugbot re-runs only on a push or an explicit
# `bugbot run`, so resolving a finding can never turn the check green again
# -- and a promotion PR may not be pushed to at all. client#786 and
# (internal ref) both merged to `main` on 2026-08-21 carrying exactly that
# state (`neutral` on the merged head, one resolved Medium beneath).
#
# So the verdict is REPORTED here and the decision is derived from the threads.
#
# ARMING THIS IS THREE STEPS, IN THIS ORDER, AND THE ORDER IS THE POINT.
#
# 1. this file reaches `main`. Every caller in this org pins
# `tracebloc/.github/...@main` ((internal ref) Q3), so a caller added
# before the reusable is ON main references something main does not have and
# dies with a `startup_failure` -- a red check on the very PR introducing a
# gate. code-quality-caller.yml records the identical sequencing for its
# `action-pins` input: the job went to develop in #159 and was armed only
# once `main` carried it. That is why the PR adding THIS file adds no caller.
# 2. a caller is added, starting with tracebloc/.github itself. The gate is
# ADVICE at this point, and saying so plainly is the whole of (internal ref).
# 3. the `bugbot / review` context is added to branch protection, once step 2
# has been observed green on real PRs.
#
# Never the reverse. (internal ref)'s lesson is that a contract claiming a context
# reality lacks is itself a finding; and a required context no workflow reports
# leaves every PR waiting forever (client#665, pii-gate/pii-check). Arm while
# green, then let the contract claim it.
#
# The TEST tier needs none of that and is armed from the start:
# `scripts/tests/bugbot-gate-selftest.py` and `-mutations.py` both run inside
# `selftests`, ALREADY a required context on develop/staging/main -- the same
# trick selftests.yml uses to arm a guard with no branch-protection edit.
#
# NO `paths:` FILTER, and the caller must not add one. A required check that is
# path-filtered never reports on a PR the filter skips, so the PR waits at
# "Expected -- waiting for status" forever. This org has hit that twice
# (client#665, pii-gate/pii-check); code-quality.yml's and selftests.yml's
# headers both warn about it.
#
# WHAT A CALLER MUST TRIGGER ON: `pull_request` with at least
# `[opened, reopened, synchronize, ready_for_review]`. `ready_for_review` is
# load-bearing, not boilerplate -- this gate deliberately PASSES a draft (a draft
# cannot merge, and Bugbot's behaviour on drafts is not this gate's business), so
# leaving that type out means the exemption is never lifted and the gate is
# permanently green on a PR that was opened as a draft.
#
# THE TRIGGER THIS WANTS AND CANNOT HAVE YET. Resolving a Bugbot thread is not a
# `pull_request` event, so `pull_request_review_thread: [resolved]` is the right
# trigger for the severity half of this gate. actionlint 1.7.12 -- a REQUIRED
# check in tracebloc/.github, run with no config file and no `-ignore` on purpose
# -- does not know that event name and rejects the workflow, so no caller can
# carry it without landing a red required check. Measured against the pinned
# binary: zero occurrences of `review_thread`.
#
# The consequence is written into the failure message rather than left to be
# discovered: resolve the thread, then RE-RUN this check. Not "push a commit",
# which is what a stale gate otherwise teaches. Adding the trigger when
# actionlint learns the event is a follow-up on (internal ref).
#
# THIS JOB WAITS, on purpose. Its central claim is that Bugbot reviewed the
# CURRENT head, which is false for the first minutes after every push -- so it
# polls. Measured over 40 Bugbot runs: p50 164s, p90 332s, max 635s. The wait
# runs concurrently with Bugbot's own work, so it adds latency only when Bugbot
# is slower than usual. `timeout-minutes` is deliberately above the script's own
# budget so the script reports WHY it gave up instead of the runner killing it
# with no message.
on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]
concurrency:
group: bugbot-gate-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# The CALLER must grant at least these three, or the run fails at startup with
# no jobs -- a called workflow cannot hold more than its caller. `checks: read`
# and `pull-requests: read` are both load-bearing: the first reads the head's
# check runs, the second reads the review threads. `contents: read` is for the
# checkout of the checker.
permissions:
contents: read
checks: read
pull-requests: read
jobs:
review:
name: bugbot / review
# `ubuntu-latest` and not `ubuntu-slim` FOR NOW, on purpose: every short
# reusable in this repo moved to the slim tier together, and this one is
# sequenced behind an observed slim run of those because its job holds the
# runner longest (it polls) -- a scheduling pathology on the new label would
# cost the most here and be hardest to tell from a slow review.
# `scripts/runner-tier.py` holds it by name and says when to flip it.
runs-on: ubuntu-latest
# Above the script's own 660s budget on purpose: the script must be the thing
# that reports a timeout, with the measured latencies in the message. A
# runner-level kill produces no explanation at all.
timeout-minutes: 30
steps:
# Same shape as code-quality.yml and blocked-gate.yml: the checker lives
# here, not in the calling repo, so the reusable fetches it. This repo is
# PRIVATE (org-config replaced the public `.github` as the host on
# 2026-09-10) and the caller's `github.token` reads only the caller's own
# repo, so the checkout needs a token that can read org-config: a
# read-only App installation token, minted from the two secrets the
# caller passes by name (`on.workflow_call.secrets` above, Q5).
- name: Mint a read-only token for org-config (private; the caller's own token cannot read it)
id: org-config-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.RELEASE_TRAIN_APP_ID }}
private-key: ${{ secrets.RELEASE_TRAIN_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: org-config
permission-contents: read
- name: Check out the shared checker
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: tracebloc/org-config
token: ${{ steps.org-config-token.outputs.token }}
ref: ${{ 'main' }}
path: .bugbot-gate-tools
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
# Nothing is pip-installed: the checker imports only the standard library
# and shells out to `gh`, which is preinstalled on the runner. Asserted
# rather than assumed -- the selftest imports the module and would fail on
# a missing import, and it runs with no pip step in `selftests.yml`.
- name: Bugbot review gate
env:
# `github.token`, not `secrets.inherit`: this needs only the scopes
# declared above, and inheriting would hand it every secret the caller
# holds for no gain ((internal ref) Q5).
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
MIN_SEVERITY: ${{ 'high' }}
WAIT_SECONDS: ${{ 660 }}
POLL_SECONDS: ${{ 30 }}
# No interpolation of any PR-controlled string into this shell: the
# checker reads everything it needs from the API itself, so a finding
# title or a branch name never transits a command line.
run: python3 .bugbot-gate-tools/scripts/bugbot-gate.py