Files
gstack/.github/workflows/weekly-release.yml
T
Garry Tanandcapy-ai[bot] 219f7488bb v1.91.292.0 Stable channel: a regression exception beside the security one, and the stale weekly wording (#3533)
* feat(release): regression cuts pass the 72-hour floor like security cuts; weekly wording follows the twice-weekly cadence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a regression anchor needs a published release; an unmoved exception tag reruns only with its recorded reason; a version may end the sentence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* RESTAMP: 1.91.291.0 -> 1.91.292.0 (stamped; queue_mode=stamp-at-merge base=origin/main@6f27f0c269cb policy=base origin/main@6f27f0c269cb:.gstack/ship-policy.json)
STAMP_VERSION: 1.91.292.0
stage version: done (wrote 1.91.292.0)
stage release_tool: done (bun scripts/gen-agents-digest.ts)
stage changelog: done (Unreleased -> 1.91.292.0)
stage stamp_paths: skipped (no tentative mention newer than the fork version 1.91.291.0 and none of base 1.91.291.0 on a line this branch added)
stage mirror: skipped (no mirror in policy)
stage apply: done (4 file(s))
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=40a8df86de0bda12cedea7dbdc744b90f4b1d5eb
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack/.gstack/tmp/restamp-journal.json

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 09:59:40 +00:00

148 lines
6.8 KiB
YAML

name: Stable release
# Tags the newest green commit on main as v<VERSION>, publishes the GitHub
# Release, and fast-forwards refs/heads/stable to it (docs/releases.md). Team
# installs follow stable, so this is the only thing that moves them. Runs
# twice a week, Monday and Thursday at 14:00 UTC (the file keeps its
# weekly-release.yml name so its run history and every `gh workflow run`
# reference stay intact). stable moves at most once per 72 hours: a cut inside
# that floor is refused with REFUSED_FLOOR and NEXT_ALLOWED; a scheduled run
# gets 3 hours of grace for cron start jitter, a hand-run cut none. Two
# exceptions pass it, one per cut and each with a ref: a security cut (the
# security input, naming a GHSA, CVE or #<n> the script ties to the released
# commits) and a regression cut (the regression input, naming a
# regression-labelled #<n> and the earlier stable release v<X> the behavior
# worked in). Run it by hand with a
# ref for a hotfix, or with dry_run to see the plan and change nothing. A pull request that touches
# the release tooling runs the dry run against main with a read-only token,
# which is the only pre-merge exercise of the real workflow. The candidate's
# gate must already be green (free-tests and main-backstop check runs
# succeeded, every check run finished and none red, no active freeze): this
# workflow never runs tests or paid evals. Dispatch it from main: the cut job
# runs only there, with main's tooling.
on:
schedule:
# Monday and Thursday 14:00 UTC (07:00 PDT, 06:00 PST): exactly 72 h, then
# 96 h, apart; the floor's scheduled grace covers a late Monday start.
- cron: '0 14 * * 1,4'
workflow_dispatch:
inputs:
dry_run:
description: 'Print the plan (candidate, tag, stable move, notes) and change nothing'
type: boolean
default: true
ref:
description: 'Hotfix: release this commit of main instead of the newest green one (full 40-hex SHA)'
type: string
default: ''
security:
description: 'Security cut (needs ref): the reason, naming the GHSA-…, CVE-… or #<n> it fixes; passes the 72-hour floor once verified and lands in the release notes'
type: string
default: ''
regression:
description: 'Regression cut (needs ref): the reason, naming the regression-labelled #<n> it fixes and the earlier stable release v<X> it worked in; passes the 72-hour floor once verified and lands in the release notes'
type: string
default: ''
pull_request:
paths:
- '.github/workflows/weekly-release.yml'
- 'scripts/release-cut.ts'
# One cut at a time; a second dispatch waits for the first. A pull request's
# dry run is its own group.
concurrency:
group: weekly-release-${{ github.event.pull_request.number || 'cut' }}
cancel-in-progress: false
permissions: {}
jobs:
cut:
name: Cut the release
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
# The tag, the stable branch and the release.
contents: write
# Reads the candidate's check runs.
checks: read
# A security or regression cut reads the label on the #<n> its reason names.
issues: read
pull-requests: read
steps:
# The dispatched ref supplies the tooling; the release candidate always
# comes from origin/main, which fetch-depth 0 brings along with every
# branch.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- name: Fetch stable and the tags
run: |
set -euo pipefail
git fetch --quiet origin '+refs/heads/stable:refs/remotes/origin/stable' 2>/dev/null || echo "origin has no stable yet: this cut creates it"
git fetch --quiet --tags origin
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Plan and cut
env:
GH_TOKEN: ${{ github.token }}
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && 'true' || 'false' }}
RELEASE_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
SECURITY_REASON: ${{ github.event_name == 'workflow_dispatch' && inputs.security || '' }}
REGRESSION_REASON: ${{ github.event_name == 'workflow_dispatch' && inputs.regression || '' }}
run: |
set -euo pipefail
if [ -n "$RELEASE_REF" ] && ! [[ "$RELEASE_REF" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::ref must be the full 40-hex SHA of a commit on main"
exit 1
fi
if [ -n "$SECURITY_REASON" ] && [ -z "$RELEASE_REF" ]; then
echo "::error::a security cut needs ref: the full SHA of the fix commit on main"
exit 1
fi
if [ -n "$REGRESSION_REASON" ] && [ -z "$RELEASE_REF" ]; then
echo "::error::a regression cut needs ref: the full SHA of the fix commit on main"
exit 1
fi
args=(--repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY")
[ "$DRY_RUN" = true ] && args+=(--dry-run)
[ -n "$RELEASE_REF" ] && args+=(--ref "$RELEASE_REF")
[ -n "$SECURITY_REASON" ] && args+=(--security "$SECURITY_REASON")
[ -n "$REGRESSION_REASON" ] && args+=(--regression "$REGRESSION_REASON")
echo "## Stable release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut'), $GITHUB_EVENT_NAME)" >> "$GITHUB_STEP_SUMMARY"
bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts "${args[@]}"
dry-run:
name: Dry run against main
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
checks: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.2
- name: Plan only
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
git fetch --quiet origin '+refs/heads/stable:refs/remotes/origin/stable' 2>/dev/null || echo "origin has no stable yet"
git fetch --quiet --tags origin
echo "## Stable release (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY"
bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run