fix(#676): update tests/docs referencing deleted hotfix.yml (#680)

hotfix.yml was deleted (folded into release.yml). Remove the now-broken
release-coverage-scope and policy-release-no-npm-self-upgrade assertions that
readFileSync'd hotfix.yml (release.yml equivalents retained), drop the dead
install-smoke.yml path trigger, and update VERSIONING.md / docs/branching.md
prose to describe hotfixes via the Release workflow with a patch version.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-04 14:57:18 -04:00
committed by GitHub
parent e14218a424
commit e3571b2e1b
5 changed files with 18 additions and 55 deletions

View File

@@ -30,7 +30,6 @@ on:
- 'tests/release-tarball-smoke.install.test.cjs'
- '.github/workflows/install-smoke.yml'
- '.github/workflows/release.yml'
- '.github/workflows/hotfix.yml'
push:
branches:
- main

View File

@@ -71,35 +71,24 @@ For fixes that need to ship without waiting for the next minor.
A hotfix `vX.YY.Z` cumulatively includes everything in `vX.YY.{Z-1}` plus every `fix:`/`chore:` commit landed on `main` since that base. The base tag is the anchor — `git cherry $BASE_TAG main` reveals exactly which commits are still unshipped, and the new `vX.YY.Z` tag becomes the next hotfix's base, so the cycle is self-documenting.
#### Two paths
#### How to dispatch a hotfix
**Path A — `hotfix.yml` (canonical, two-step):**
Hotfixes are dispatched via the **Release workflow (`release.yml`)** with a patch version (X.Y.Z). There is no separate hotfix workflow.
1. Trigger `hotfix.yml` with `action=create`, `version=1.27.1`, `auto_cherry_pick=true` (default).
1. Trigger `release.yml` with `action=create`, `version=1.27.1`, `auto_cherry_pick=true` (default).
- Workflow detects `BASE_TAG` = highest `v1.27.*` < `v1.27.1` (so `1.27.1` branches from `v1.27.0`; `1.27.2` would branch from `v1.27.1`).
- Branches `hotfix/1.27.1` from `BASE_TAG`.
- Auto-cherry-picks every `fix:`/`chore:` commit on `origin/main` not already in the base, oldest-first. Patch-equivalents are skipped via `git cherry`. `feat:`/`refactor:` are **never** auto-included.
- On conflict the workflow halts with the offending SHA. Resolve manually on the branch, then re-run finalize with `auto_cherry_pick=false`.
- Bumps `package.json` (and `sdk/package.json`), pushes the branch, and lists every included SHA in the run summary.
2. (Optional) push additional manual commits to `hotfix/1.27.1`.
3. Trigger `hotfix.yml` with `action=finalize`. The workflow:
3. Trigger `release.yml` with `action=finalize`. The workflow:
- Runs `install-smoke` cross-platform gate.
- Runs full test suite + coverage.
- Builds SDK, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball (parity with `release-sdk.yml`).
- Builds SDK, bundles `sdk-bundle/gsd-sdk.tgz` inside the CC tarball.
- Tags `v1.27.1`, publishes to `@latest`, re-points `@next → v1.27.1`.
- Opens merge-back PR against `main`.
**Path B — `release-sdk.yml` (stopgap, one-shot):**
Active while the `@opengsd/gsd-sdk` npm token is unavailable; bundles the SDK inside the CC tarball.
1. Trigger `release-sdk.yml` with `action=hotfix`, `version=1.27.1`, `auto_cherry_pick=true`.
- The `prepare` job creates the branch and cherry-picks (same logic as Path A).
- `install-smoke` runs against the new branch.
- The `release` job tags, publishes to `@latest`, re-points `@next`, opens merge-back PR.
- Idempotent: if `hotfix/1.27.1` already exists (e.g. you ran `hotfix.yml create` first), the prepare job checks it out and re-runs cherry-pick as a no-op.
2. `dry_run=true` exercises the full pipeline without pushing the branch or publishing.
### Minor Release (Standard Cycle)
For accumulated fixes and enhancements.

View File

@@ -74,7 +74,7 @@ These are work branches. Open one, push commits, PR it, merge it, let it auto-de
| `perf/` | Performance work, no behavior change | `next` | `perf/3300-skill-index` |
| `ci/` | CI/workflow changes only | `next` | `ci/3801-add-node-26-matrix` |
| `revert/` | Reverting a previously-merged change | `next` (or `main` if urgent) | `revert/3919-bad-merge` |
| `hotfix/X.Y.Z` | Patch release branch (created by `hotfix.yml`) | `main` | `hotfix/1.27.1` |
| `hotfix/X.Y.Z` | Patch release branch (created by `release.yml` with a patch version X.Y.Z) | `main` | `hotfix/1.27.1` |
| `release/X.Y.0` | Minor/major release branch (created by `release.yml`) | `main` | `release/1.28.0` |
> **The branch name rule is enforced** by `.github/workflows/branch-naming.yml`.
@@ -130,10 +130,10 @@ git pull --ff-only
git checkout -b fix/3919-critical-crash
# ... commit, push, PR to next, merge.
# 2. Trigger the hotfix workflow from the Actions tab:
# workflow: Hotfix Release
# 2. Trigger the Release workflow (release.yml) from the Actions tab:
# workflow: Release
# action: create
# version: 1.27.1 (next patch number)
# version: 1.27.1 (next patch number, X.Y.Z)
# auto_cherry_pick: true (default)
```
@@ -148,7 +148,7 @@ The workflow:
6. `finalize` publishes to npm `@latest`, tags `v1.27.1`, opens
merge-back PRs to **both** `main` and `next`.
> See `.github/workflows/hotfix.yml` and `VERSIONING.md` for the deep dive.
> See `.github/workflows/release.yml` and `VERSIONING.md` for the deep dive.
### Flow 3 — Minor or major release
@@ -212,7 +212,7 @@ resolve anyway. The treadmill is gone.
## Cheat sheet: "where does my PR go?"
```
Is it a hotfix release branch? → main (cut by hotfix.yml)
Is it a hotfix release branch? → main (cut by release.yml with a patch version X.Y.Z)
Is it a stable release branch? → main (cut by release.yml)
Is it an RC-blocker fix? → release/X.Y.0 (and also next, or rely on back-merge)
Is it everything else? → next
@@ -248,10 +248,10 @@ the base branch — no need to recreate the PR.
```
- **Phasing in:** see the migration notes in
`docs/adr/230-introduce-next-integration-branch.md`.
- **Updating release.yml / hotfix.yml:** these workflows currently branch
from `main` and cherry-pick from `main`. After phase-2 of the migration
they should branch from `next` (release) and cherry-pick from `next`
(hotfix). The patches are inlined in the ADR.
- **Updating release.yml:** this workflow currently branches from `main` and
cherry-picks from `main`. After phase-2 of the migration it should branch
from `next` (release) and cherry-pick from `next` (hotfix). The patches
are inlined in the ADR.
---
@@ -262,7 +262,7 @@ A few exceptions where the rules above bend:
- **True production-down emergency.** Push directly to a `fix/critical-*`
branch, PR to `main`. The auto-back-merge workflow will replay it onto
`next` within minutes. Use sparingly — most "urgent" things are fine to go
through `next` and ship same day via the hotfix workflow.
through `next` and ship same day via the Release workflow (patch version).
- **Documentation-only typo on a published page.** If the only change is a
doc fix that's visible right now and shouldn't wait for the next release,
PR it to `main`. The auto-back-merge will sync `next`. Most doc changes

View File

@@ -18,7 +18,6 @@ const NPM_SELF_UPGRADE_RE = /\bnpm\s+(install|i)\s+(-g|--global)\b[^\n]*\bnpm(@|
describe('policy: no runtime npm self-upgrade in release lanes (#318)', () => {
const releaseFile = path.join(WORKFLOWS_DIR, 'release.yml');
const hotfixFile = path.join(WORKFLOWS_DIR, 'hotfix.yml');
test('release.yml must not contain a runtime global npm self-upgrade step', () => {
const content = fs.readFileSync(releaseFile, 'utf8');
@@ -36,21 +35,6 @@ describe('policy: no runtime npm self-upgrade in release lanes (#318)', () => {
);
});
test('hotfix.yml must not contain a runtime global npm self-upgrade step', () => {
const content = fs.readFileSync(hotfixFile, 'utf8');
const lines = content.split('\n');
const violations = lines
.map((line, idx) => ({ line, lineNo: idx + 1 }))
.filter(({ line }) => NPM_SELF_UPGRADE_RE.test(line));
assert.strictEqual(
violations.length,
0,
`hotfix.yml contains ${violations.length} runtime npm self-upgrade line(s) — ` +
`Violations: ${violations.map(({ lineNo, line }) => `line ${lineNo}: ${line.trim()}`).join('; ')}`
);
});
test('NPM_SELF_UPGRADE_RE correctly matches the antipattern', () => {
// Positive cases — must match
assert.ok(NPM_SELF_UPGRADE_RE.test('npm install -g npm@latest'), 'should match npm install -g npm@latest');

View File

@@ -1,5 +1,5 @@
// allow-test-rule: source-text-is-the-product
// .github/workflows/{release,hotfix}.yml are the deployed CI contract; asserting
// .github/workflows/release.yml is the deployed CI contract; asserting
// the release-gate test command is only expressible against the workflow text.
'use strict';
@@ -10,7 +10,6 @@ const fs = require('node:fs');
const path = require('node:path');
const RELEASE_WORKFLOW = path.join(__dirname, '..', '.github', 'workflows', 'release.yml');
const HOTFIX_WORKFLOW = path.join(__dirname, '..', '.github', 'workflows', 'hotfix.yml');
describe('release-coverage-scope', () => {
test('release.yml uses test:coverage:unit (not full suite) in both rc and finalize gates', () => {
@@ -23,13 +22,5 @@ describe('release-coverage-scope', () => {
`release.yml has ${unitCount} 'npm run test:coverage:unit' line(s); expected 2`);
});
test('hotfix.yml uses test:coverage:unit (not full suite) in the finalize gate', () => {
const lines = fs.readFileSync(HOTFIX_WORKFLOW, 'utf8').split('\n').map(l => l.trim());
const bareCount = lines.filter(l => l === 'npm run test:coverage').length;
const unitCount = lines.filter(l => l === 'npm run test:coverage:unit').length;
assert.strictEqual(bareCount, 0,
`hotfix.yml still has ${bareCount} bare 'npm run test:coverage' line(s); expected 0`);
assert.strictEqual(unitCount, 1,
`hotfix.yml has ${unitCount} 'npm run test:coverage:unit' line(s); expected 1`);
});
});