forked from lidge-jun/opencodex
-
Notifications
You must be signed in to change notification settings - Fork 0
287 lines (261 loc) · 14.1 KB
/
Copy pathdev-version-bump.yml
File metadata and controls
287 lines (261 loc) · 14.1 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
280
281
282
283
284
285
286
287
name: Dev version bump (dormant fallback)
# Dormant upstream fallback for manually prepared recovery. The fork's live release
# path must not call this workflow: `promote-dev.yml` is the sole post-release writer
# and advances `dev` through an exact-SHA, tag-verified App update. Keeping two callers
# produced both a direct 2.40.1 bump and a conflicting 2.41.0 PR after v2.40.0.
#
# If explicitly restored as the sole authority in a future design, open a pull request
# that moves `dev` past the published
# version. Without this, `dev` keeps carrying a version that is at or behind a released
# one, and `tests/ci-workflows/release-version-line.test.ts` fails on `dev` and on every pull request
# opened against it - inherited red a contributor cannot fix from their own diff.
#
# That has been repaired by hand four times: 32529c2b2, e4a85d134, 076ad3036, befcac3e1.
# The second of those ADDED the detector and two more repairs followed it, so more
# visibility was never the missing piece; a prepared change was.
#
# WHAT THIS DOES NOT DO. It does not push to `dev`. It opens a pull request and a human
# merges it, because ruleset `Protect dev` requires an approving review and code-owner
# sign-off that a bot cannot supply. Until that merge the red persists. This converts a
# forgotten chore into a queued, reviewable change - not into an automatic repair.
#
# WHY THIS IS CALLED, NOT TRIGGERED. It used to listen for `release: published`, and in
# that form it ran ZERO times across v2.37.0, v2.38.0 and v2.39.0 - every one of those
# bumps was still opened by hand (#3045, #3076, #3127). The workflow was not broken; the
# event never existed. `release.yml` creates the GitHub release with
# `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events
# raised by the default `GITHUB_TOKEN`. A `release: published` listener therefore cannot
# observe a release this repository publishes itself, no matter which branch it sits on.
#
# The historical upstream fix kept the credential surface unchanged: no PAT or app
# token. It called this workflow from `release.yml`; the fork deliberately does not,
# because its verified promotion controller already owns that mutation.
#
# A `workflow_call` body resolves from the CALLER's ref. This file would have to exist on
# whichever release branch deliberately restored it as the sole authority before it
# could take effect.
#
# There is deliberately no `workflow_dispatch`: a branch-selected manual run executes
# THAT branch body with `contents: write`. Re-drive a missed run by running
# `bun scripts/bump-dev-version.ts <released> package.json` locally and opening the pull
# request normally.
on:
workflow_call:
inputs:
released-version:
description: "The tag that just published, e.g. v2.39.0"
required: true
type: string
permissions: {}
concurrency:
group: dev-version-bump
cancel-in-progress: false
jobs:
open-bump-pr:
runs-on: ubuntu-latest
permissions:
# Push the new codex/dev-version-* branch. Ruleset `Protect dev` covers only
# refs/heads/dev, so the bump branch is unprotected and this token cannot
# bypass dev review. It is the ruleset that keeps this job off dev, not the
# permission name.
contents: write
# Open the pull request.
pull-requests: write
steps:
- name: Checkout dev
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: dev
# Tags are load-bearing, not decoration: the freeness gate below is a bun
# test that reads the local tag set, and release-version-line.test.ts
# returns EARLY on an empty set. A shallow checkout would make that gate
# silently vacuous instead of failing loudly.
fetch-depth: 0
# Do NOT set persist-credentials: false here as the read-only workflows do.
# This job has to push its bump branch.
# The repository-owned composite action, not a hand-pinned setup-bun SHA: it
# resolves the Bun version from package.json so the runtime SOT stays in one
# place. An independently pinned action here would drift from every other job.
- name: Setup project Bun
uses: ./.github/actions/setup-project-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Decide the version dev should carry
id: decide
env:
RELEASED_VERSION: ${{ inputs.released-version }}
run: |
set -euo pipefail
bun scripts/bump-dev-version.ts "${RELEASED_VERSION}" package.json
- name: Prove the chosen version is unused
if: ${{ steps.decide.outputs.changed == 'true' }}
# The script decides the candidate from the released version SHAPE, which is all
# a pure function can see. Whether that candidate is actually FREE is a property
# of the tag set, so it is settled here by the detector that already owns the
# question. If this fails, no pull request is opened and the job goes red asking
# for a human decision - which is the correct outcome, not a fallback.
run: bun test tests/ci-workflows/release-version-line.test.ts
- name: Open the bump pull request
if: ${{ steps.decide.outputs.changed == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
NEXT_VERSION: ${{ steps.decide.outputs.version }}
RELEASED_VERSION: ${{ inputs.released-version }}
run: |
set -euo pipefail
branch="codex/dev-version-${NEXT_VERSION}"
# Idempotent: a second publish, a re-run, or a manual repair must not turn a
# successful release into a red job.
#
# Check the PULL REQUEST as well as the branch, not just the branch. A security
# review caught that: an open bump pull request whose head branch was deleted
# leaves the branch check passing, so the job would recreate the branch and then
# fail on `gh pr create` with "already exists" — turning a successful release red
# for a repair that was already queued.
open_prs="$(
gh api --method GET "repos/${GITHUB_REPOSITORY}/pulls" \
-f state=open \
-f base=dev \
-f "head=${GITHUB_REPOSITORY_OWNER}:${branch}" \
-F per_page=1 \
--jq 'length'
)"
if [ "${open_prs}" != "0" ]; then
echo "::notice::a bump pull request for ${branch} is already open; nothing to do"
exit 0
fi
# An existing branch is NOT terminal. If a previous run pushed the branch and then
# failed at `gh pr create`, exiting here would leave the repair permanently unqueued.
# Validate that the name still belongs to this automation, then rebuild it from the
# CURRENT dev head. Reusing an old commit would open an immediately stale PR and could
# reintroduce files that moved on dev while the failed run was waiting for a retry.
existing_branch_sha=""
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null 2>&1; then
echo "::notice::${branch} exists without an open pull request; validating it"
git fetch origin "${branch}"
existing_branch_sha="$(git rev-parse "origin/${branch}")"
# Fail closed on unexpected content. The branch carries the bot's own one-line
# bump, so anything else on it means a human or another job is using that name and
# this job must not push to it or open a pull request from it.
changed_files="$(git diff --name-only "origin/dev...origin/${branch}")"
if [ "${changed_files}" != "package.json" ]; then
echo "::error::${branch} touches unexpected files: ${changed_files:-<none>}"
exit 1
fi
branch_version="$(git show "origin/${branch}:package.json" | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).version")"
if [ "${branch_version}" != "${NEXT_VERSION}" ]; then
echo "::error::${branch} carries ${branch_version}, expected ${NEXT_VERSION}"
exit 1
fi
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -B "${branch}" origin/dev
git add package.json
git commit -m "fix(release): move dev to ${NEXT_VERSION} after ${RELEASED_VERSION}"
if [ -n "${existing_branch_sha}" ]; then
# Exact lease: fail if anything moved the validated branch after our fetch.
git push --force-with-lease="refs/heads/${branch}:${existing_branch_sha}" origin "${branch}"
else
git push origin "${branch}"
fi
gh pr create \
--base dev \
--head "${branch}" \
--title "fix(release): move dev to ${NEXT_VERSION} after ${RELEASED_VERSION}" \
--body "$(cat <<BODY
## Summary
\`${RELEASED_VERSION}\` published, so \`dev\` would otherwise keep a version at or
behind a released one and \`tests/ci-workflows/release-version-line.test.ts\` would fail on
\`dev\` and on every pull request opened against it. This moves \`dev\` to
\`${NEXT_VERSION}\`.
Opened automatically by \`.github/workflows/dev-version-bump.yml\`. The same
repair was previously done by hand in 32529c2b2, e4a85d134, 076ad3036, and
befcac3e1.
## Verification
\`bun test tests/ci-workflows/release-version-line.test.ts\` ran against this exact tree
before the pull request was opened; the workflow refuses to open one if the
chosen version collides with a published release.
## Checklist
- [x] Scope stays focused and avoids unrelated cleanup.
- [x] Docs or release notes were updated when needed.
- [x] Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.
BODY
)"
reconcile-failure-alert:
needs: open-bump-pr
if: ${{ always() }}
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Queue repair or record recovery
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
env:
BUMP_RESULT: ${{ needs.open-bump-pr.result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const { owner, repo } = context.repo;
const marker = "<!-- opencodex-dev-version-bump-failure -->";
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner, repo, state: "open", per_page: 100
});
const existing = issues.find(issue => !issue.pull_request && String(issue.body || "").includes(marker));
const runUrl = process.env.RUN_URL;
if (process.env.BUMP_RESULT !== "failure") {
if (!existing) return;
await github.rest.issues.createComment({
owner, repo, issue_number: existing.number,
body: `The dev-version bump automation recovered successfully: ${runUrl}`,
});
await github.rest.issues.update({
owner, repo, issue_number: existing.number, state: "closed", state_reason: "completed",
});
return;
}
if (existing) {
await github.rest.issues.createComment({
owner, repo, issue_number: existing.number,
body: `The dev-version bump automation failed again: ${runUrl}`,
});
return;
}
const labels = {
"agent:jules": ["8250df", "Trusted Jules implementation request"],
"agent:generated": ["0969da", "Trusted generated maintenance issue"],
"agent:queued": ["d4c5f9", "Maintenance task queued"],
};
const known = new Set((await github.paginate(
github.rest.issues.listLabelsForRepo, { owner, repo, per_page: 100 }
)).map(label => label.name));
for (const [name, [color, description]] of Object.entries(labels)) {
if (!known.has(name)) {
await github.rest.issues.createLabel({ owner, repo, name, color, description });
}
}
await github.rest.issues.create({
owner, repo,
title: "[agent:release] Dev version bump automation failed",
labels: Object.keys(labels),
body: [
marker,
"### Area", "", "Installation or packaging", "",
"### What are you trying to accomplish?", "",
"Keep the dev package version strictly ahead of every published release so inherited CI stays green.", "",
"### What prevents this today?", "",
`The trusted dev-version bump workflow failed. Exact run: ${runUrl}`, "",
"### What should OpenCodex do?", "",
"Diagnose the exact failed run, prepare the smallest safe repair, and preserve the human-reviewed pull-request boundary for changes to dev.", "",
"### Example usage or interface", "",
`Inspect ${runUrl}, reproduce the failed gate locally, and open a template-complete PR against dev.`, "",
"### Alternatives or workarounds", "",
"A maintainer can run scripts/bump-dev-version.ts locally and open the bump PR manually while the automation is repaired.", "",
"### Additional context", "",
"Generated by the trusted release-version supervisor. Workflow and release changes remain protected and require explicit security review.", "",
"### Checks", "",
"- [x] I searched existing issues and documentation.",
"- [x] This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.",
"- [x] I removed secrets and personal data.",
].join("\n"),
});