mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
v1.91.234.0 feat(releases): weekly tagged releases and a stable update channel for team mode (GSTA-98) (#3429)
* feat(releases): weekly tagged releases and a stable update channel for team mode (GSTA-98)
Every Monday morning .github/workflows/weekly-release.yml tags the newest green
commit on main as v<VERSION>, publishes a GitHub Release from the CHANGELOG
headlines and fast-forwards refs/heads/stable to it (scripts/release-cut.ts;
workflow_dispatch with dry_run, and ref for a hotfix). Team-mode installs
follow stable through the new update_channel key (auto: stable when
team_mode, else main) in bin/gstack-session-update, bin/gstack-update-check,
bin/gstack-upgrade and /gstack-upgrade. Per-merge stamping and the merge
train are unchanged. docs/releases.md explains it.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(weekly-release): check out the dispatched ref so a dry run exercises that branch's script
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(weekly-release): a PR touching the release tooling runs the dry run against main (read-only); the cut job checks out the dispatched ref
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(gstack-upgrade): no $1 in the rendered skill (Claude Code rewrites $N), trim Step 4 under the size budget, pin the new floor-check literal
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: leave setup untouched (its channel note moves to README and docs/releases.md); the release workflow and script are free-only PR files
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: record the pr-profile free-only classification as non-behavioral (HARNESS_VERSION 6)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(releases): gate the cut on a main-backstop check run, finish half-done cuts, keep team installs on their channel
/ship review fixes (GSTA-98, PR #3429):
- The weekly release read the Main backstop gate run's conclusion, but
scripts/train-backstop.ts exits 0 when main moved past the push before it
was gated (superseded), so a never-gated tree read as green. The backstop
workflow now publishes its verdict on the commit as the `main-backstop`
check run from a code-free checks:write job (success for covered|gated,
neutral for inert|superseded|deferred, failure for frozen|failed), fed by
the new `--outputs` flag; release-cut.ts requires that check run and
free-tests to succeed and holds a commit whose check runs are still going.
- A cut that pushed the tag and stable but failed to create the release can
be finished: stable itself stays a candidate until its release exists, the
notes compare against the release tag behind it, and a dry run with nothing
green exits 0 with the skips named (the real cut still fails).
- weekly-release.yml: the cut job runs only from refs/heads/main, `ref` must
be a full 40-hex SHA, and actions:read is dropped (no workflow-runs call).
- /gstack-upgrade: the destructive fallback requires the carried UPSTREAM_REF
instead of defaulting to origin/main in a fresh shell; a team install past
the release stops with AHEAD_OF_STABLE instead of FF_REFUSED and a reset
backwards. gstack-update-channel.sh --fetch-ref keeps origin/stable on any
fetch failure other than a missing release; dead gstack_update_channel_ref
removed. gstack-update-check follows main before the first release instead
of caching CHECK_FAILED; gstack-upgrade treats a failed ls-remote as a
fetch failure, not an empty answer.
- Tests: release-cut (check-run fixture, partial cut, neutral/pending),
weekly-release-workflow (new), evals-train-workflow and train-backstop
(backstop-check job, --outputs), update-channel (no-release fallback,
offline --fetch-ref), template pins.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(releases): the upgrade skill's ahead-of-stable exit works in depth-1 clones; update-check keeps its cache identity on the pre-release fallback
/ship second-pass review of 73e278d4 (GSTA-98, PR #3429):
- gstack-upgrade/SKILL.md.tmpl: README installs are depth-1 clones, where
origin/stable never connects to HEAD, so the AHEAD_OF_STABLE shortcut now
also fires when HEAD is not behind stable and stable's VERSION is not
higher than the installed one (the order bin/gstack-upgrade uses). The
fence prints the channel helper's warnings instead of discarding them, and
a 'fetching origin/stable failed' line is a STOP. Prose trimmed to stay
under the skill size budget. New behavioral test runs the Step 4 fence on
a depth-1 install past the release (AHEAD_OF_STABLE, nothing moved) and on
one behind it (goes on to the fast-forward).
- bin/gstack-update-check: the no-stable fallback keeps #stable in the
cache identity (so the outcome replays within its TTL instead of two
ls-remote calls per skill start), triggers only when ls-remote answered
with no such branch (a failed ls-remote stays CHECK_FAILED on stable), and
the branch-pinned URL derives from REMOTE_RAW_BASE so tests stay offline.
- scripts/release-cut.ts: a cancelled check run is red; the previous-release
lookups check .ok before using git output. docs/releases.md says which
pushes get no main-backstop check run at all.
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>
This commit is contained in:
1 parent
4216bbb223
commit
75badf306c
27 files changed
+1833
-94
No files matched your search
@@ -7,6 +7,11 @@ name: Main backstop gate
|
||||
# backstop writes the freeze record gstack-ship-queue reads. Inert (no paid
|
||||
# work, nothing written) unless main's .gstack/ship-policy.json declares a
|
||||
# `train` block; without one the per-PR gates cover main as before.
|
||||
#
|
||||
# The verdict is published on the commit as the main-backstop check run
|
||||
# (success when a green train covers the tree or the backstop gated it green,
|
||||
# neutral when the push was superseded or deferred, failure when it froze the
|
||||
# queue or could not finish); the weekly release reads it (docs/releases.md).
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
@@ -25,6 +30,8 @@ jobs:
|
||||
if: github.actor != 'dependabot[bot]'
|
||||
# Settle window (60) + the full gate (270) + margin.
|
||||
timeout-minutes: 345
|
||||
outputs:
|
||||
action: ${{ steps.gate.outputs.action }}
|
||||
permissions:
|
||||
# Pushes the freeze record to refs/notes/gstack-trains.
|
||||
contents: write
|
||||
@@ -56,10 +63,38 @@ jobs:
|
||||
bun-version: 1.4.2
|
||||
|
||||
- name: Gate main unless a green train covers its tree
|
||||
id: gate
|
||||
if: steps.policy.outputs.train == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GSTACK_STATE_ROOT: ${{ runner.temp }}/gstack-state
|
||||
run: |
|
||||
bun --no-env-file --config=/dev/null --no-install run scripts/train-backstop.ts run --repo "$GITHUB_REPOSITORY" --branch "$GITHUB_REF_NAME" --sha "$GITHUB_SHA" \
|
||||
--summary "$GITHUB_STEP_SUMMARY"
|
||||
--summary "$GITHUB_STEP_SUMMARY" --outputs "$GITHUB_OUTPUT"
|
||||
|
||||
# The per-commit verdict the weekly release reads (scripts/release-cut.ts).
|
||||
# Its own job with checks: write and no repository code (the same split as
|
||||
# evals.yml's train-check): only the action word the gate step recorded.
|
||||
backstop-check:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: backstop
|
||||
if: ${{ !cancelled() && needs.backstop.outputs.action != '' }}
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- name: Publish the main-backstop check run on this commit
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ACTION: ${{ needs.backstop.outputs.action }}
|
||||
run: |
|
||||
case "$ACTION" in
|
||||
covered|gated) conclusion=success ;;
|
||||
inert|superseded|deferred) conclusion=neutral ;;
|
||||
frozen|failed) conclusion=failure ;;
|
||||
*) echo "::error::unknown backstop action '$ACTION'"; exit 1 ;;
|
||||
esac
|
||||
gh api "repos/${GITHUB_REPOSITORY}/check-runs" -X POST -f name=main-backstop -f head_sha="$GITHUB_SHA" -f status=completed -f conclusion="$conclusion" \
|
||||
-f details_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
|
||||
-f "output[title]=Main backstop: ${ACTION}" -f "output[summary]=${ACTION}: see the backstop job summary of run ${GITHUB_RUN_ID}" >/dev/null
|
||||
echo "- main-backstop: ${conclusion} (${ACTION}) on ${GITHUB_SHA}" >> "$GITHUB_STEP_SUMMARY"
|
||||
@@ -0,0 +1,114 @@
|
||||
name: Weekly 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
|
||||
# every Monday morning Pacific; 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 14:00 UTC: 07:00 PDT, 06:00 PST.
|
||||
- cron: '0 14 * * 1'
|
||||
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: ''
|
||||
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
|
||||
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 || '' }}
|
||||
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, not '$RELEASE_REF'"
|
||||
exit 1
|
||||
fi
|
||||
args=(--repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY")
|
||||
[ "$DRY_RUN" = true ] && args+=(--dry-run)
|
||||
[ -n "$RELEASE_REF" ] && args+=(--ref "$RELEASE_REF")
|
||||
echo "## Weekly release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut'))" >> "$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 "## Weekly 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
|
||||
Reference in new issue
Block a user