Skip to content

Commit 622cbb1

Browse files
authored
[codex] harden release workflow automation (#134)
1 parent 23d1cd4 commit 622cbb1

3 files changed

Lines changed: 72 additions & 8 deletions

File tree

.github/workflows/release-notes.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ on:
88
tag:
99
description: 'Release tag (e.g., v0.3.3)'
1010
required: true
11+
target_ref:
12+
description: 'Branch to check out and update when writing changelog changes'
13+
default: main
14+
required: true
1115

1216
permissions:
1317
contents: write
@@ -18,15 +22,20 @@ jobs:
1822
steps:
1923
- uses: actions/checkout@v6
2024
with:
25+
ref: ${{ github.event_name == 'release' && github.event.release.target_commitish || inputs.target_ref }}
2126
fetch-depth: 0 # full history for tag detection and diff stats
2227

2328
- name: Resolve tag
2429
id: tag
2530
run: |
2631
if [[ "${{ github.event_name }}" == "release" ]]; then
2732
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
33+
echo "target_ref=${{ github.event.release.target_commitish }}" >> "$GITHUB_OUTPUT"
34+
echo "markdown_post_to=step-summary" >> "$GITHUB_OUTPUT"
2835
else
2936
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
37+
echo "target_ref=${{ inputs.target_ref }}" >> "$GITHUB_OUTPUT"
38+
echo "markdown_post_to=release,step-summary" >> "$GITHUB_OUTPUT"
3039
fi
3140
3241
# ── Surface 1: GitHub Release body + step summary ──────────
@@ -39,7 +48,7 @@ jobs:
3948
data-format: github-prs
4049
head-ref: ${{ steps.tag.outputs.tag }}
4150
release-tag: ${{ steps.tag.outputs.tag }}
42-
post-to: release,step-summary
51+
post-to: ${{ steps.tag.outputs.markdown_post_to }}
4352
token: ${{ secrets.GITHUB_TOKEN }}
4453

4554
# ── Surface 2: Terminal-formatted release asset ────────────
@@ -59,6 +68,7 @@ jobs:
5968

6069
# ── Surface 3: Compact notes for changelog ─────────────────
6170
- name: Generate release notes (compact)
71+
if: github.event_name == 'workflow_dispatch'
6272
uses: ./
6373
id: compact
6474
with:
@@ -72,7 +82,7 @@ jobs:
7282
token: ${{ secrets.GITHUB_TOKEN }}
7383

7484
- name: Commit changelog updates
75-
if: steps.compact.outcome == 'success'
85+
if: github.event_name == 'workflow_dispatch' && steps.compact.outcome == 'success'
7686
run: |
7787
if git diff --quiet CHANGELOG.md 2>/dev/null; then
7888
echo "No changelog changes to commit."
@@ -81,5 +91,5 @@ jobs:
8191
git config user.email "github-actions[bot]@users.noreply.github.com"
8292
git add CHANGELOG.md
8393
git commit -m "docs: update CHANGELOG for ${{ steps.tag.outputs.tag }}"
84-
git push
94+
git push origin "HEAD:${{ steps.tag.outputs.target_ref }}"
8595
fi

Makefile

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,34 @@ release: build publish
144144
gh-release:
145145
@VERSION=$$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/'); \
146146
PROJECT=$$(grep -m1 '^name = ' pyproject.toml | sed 's/name = "\(.*\)"/\1/'); \
147+
TAG="v$$VERSION"; \
147148
NOTES="site/content/releases/$$VERSION.md"; \
149+
if [ -n "$$(git status --porcelain)" ]; then echo "Error: working tree is not clean."; exit 1; fi; \
148150
if [ ! -f "$$NOTES" ]; then echo "Error: $$NOTES not found"; exit 1; fi; \
149-
echo "Creating release v$$VERSION for $$PROJECT..."; \
150-
git push origin main 2>/dev/null || true; \
151-
git push origin v$$VERSION 2>/dev/null || true; \
152-
awk '/^---$$/{c++;next}c>=2' "$$NOTES" | gh release create v$$VERSION \
151+
if gh release view "$$TAG" >/dev/null 2>&1; then echo "Error: release $$TAG already exists"; exit 1; fi; \
152+
git fetch origin main --tags; \
153+
LOCAL=$$(git rev-parse HEAD); \
154+
REMOTE=$$(git rev-parse origin/main); \
155+
if [ "$$LOCAL" != "$$REMOTE" ]; then \
156+
echo "Error: HEAD must match origin/main before releasing."; \
157+
echo "HEAD=$$LOCAL"; \
158+
echo "origin/main=$$REMOTE"; \
159+
exit 1; \
160+
fi; \
161+
REMOTE_TAG=$$(git ls-remote origin "refs/tags/$$TAG" | awk '{print $$1}'); \
162+
if [ -n "$$REMOTE_TAG" ] && [ "$$REMOTE_TAG" != "$$LOCAL" ]; then \
163+
echo "Error: remote $$TAG points at $$REMOTE_TAG, not $$LOCAL"; \
164+
exit 1; \
165+
fi; \
166+
git tag -f "$$TAG" HEAD; \
167+
if [ -z "$$REMOTE_TAG" ]; then git push origin "$$TAG"; fi; \
168+
echo "Creating release $$TAG for $$PROJECT..."; \
169+
awk '/^---$$/{c++;next}c>=2' "$$NOTES" | gh release create "$$TAG" \
170+
--verify-tag \
171+
--target main \
153172
--title "$$PROJECT $$VERSION" \
154173
-F -; \
155-
echo "✓ GitHub release v$$VERSION created (PyPI publish will run via workflow)"; \
174+
echo "✓ GitHub release $$TAG created (PyPI publish will run via workflow)"; \
156175
$(MAKE) action-tag
157176

158177
# Move the floating major action tag so `uses: lbliii/kida@v0` tracks the latest release.

docs/stability-gate.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,41 @@ The package smoke test verifies:
2828
- component metadata
2929
- sandbox denial for blocked reflection attributes
3030

31+
## Release Automation Gate
32+
33+
Before running the release target, merge the release-prep PR and work from a
34+
clean checkout whose `HEAD` matches `origin/main`. The release target expects
35+
the version in `pyproject.toml` to match a source page at
36+
`site/content/releases/<version>.md` and creates `v<version>` from the merged
37+
main commit:
38+
39+
```bash
40+
make gh-release
41+
```
42+
43+
The target fails if the worktree is dirty, if `HEAD` differs from `origin/main`,
44+
if the GitHub release already exists, or if an existing remote version tag points
45+
at a different commit. It pushes the version tag, creates the GitHub release from
46+
the curated site release notes, and then moves the floating major action tag
47+
with `make action-tag`.
48+
49+
After `make gh-release`, verify:
50+
51+
- `refs/heads/main`, `refs/tags/v<version>`, and `refs/tags/v<major>` point at
52+
the same release commit
53+
- the GitHub release body still contains the curated release notes
54+
- the `Upload Python Package` release workflow succeeded
55+
- `https://pypi.org/pypi/kida-templates/<version>/json` returns the released
56+
wheel and sdist metadata
57+
- the docs release page is reachable under
58+
`https://lbliii.github.io/kida/releases/<version>/`
59+
60+
The `Release Notes` workflow dogfoods Kida's release-note templates on release
61+
events, but release events do not rewrite the curated GitHub release body or
62+
commit changelog changes. Use its manual `workflow_dispatch` mode when a
63+
maintainer explicitly wants to regenerate release-note/changelog output for a
64+
tag and target branch.
65+
3166
## Benchmark Evidence
3267

3368
Linux 3.14t benchmark baselines are the performance comparison baseline. Darwin

0 commit comments

Comments
 (0)