feat: release flow — CHANGELOG, release.yml, and a tag-resolving installer (#32) #40
No reviewers
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:bootstrap
scope:coolify
scope:db
scope:docs
scope:drill
scope:installer
scope:labels
scope:platform
scope:runner
scope:users
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/rig#40
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/release-flow"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #32 (the remaining scope after #36 landed
VERSION,rig --version, and theversions/<v>layout). Not a close: cutting 0.1.0 and theBOX_REFpin still follow.The problem
rig can name the version it runs (#36) but not release one: no
CHANGELOG.md, no tags, no GitHub releases — andinstall.shonly knowsrefs/heads/<branch>, so "what rig is this host running?" still bottoms out at "whatever main was when it last curl'd". This PR is the rig half of the flow designed in heavy-duty/box#83, near-verbatim.What lands
CHANGELOG.md— box's shape: history before 0.1.0 lives in git, an## Unreleasedsection that feature PRs append to as part of the PR (convention now written into CONTRIBUTING, alongside the release ritual: arelease: X.Y.ZPR stamps the section and bumpsVERSION, then a bareX.Y.Ztag on the merge commit — novprefix, box's tag scheme)..github/workflows/release.yml— on any tag push: assert tag ==VERSION(a mismatch fails loudly and creates nothing), thengh release create --verify-tagwith that version's changelog section as the body. The section is extracted bychangelog_sectionin.github/scripts/release-lib.sh— one function, sourced by the workflow and by the tests. No assets: for a pure-bash tree, the tag's source tarball IS the package.The tag-resolving installer — three channels from one script:
RIG_REFunset → the latest release: the tag resolved from thereleases/latestredirect'sLocationheader (no API, no token), downloaded asarchive/refs/tags/<tag>.tar.gz;RIG_REF=<tag>→ pinned (refs/tagstried first, so a tag outranks a same-named branch);RIG_REF=<branch>→ the dev channel (refs/headsfallback).README documents all three; "re-run any time to upgrade" now means upgrade to the latest release.
Tests
test/release.sh(41 checks, wired into ci.yml'scheckjob; network-free — curl is a harness-written stub on PATH):changelog_sectiondriven against fixtures (all three heading shapes, section boundaries, the empty-output refusal signal) and against the shippedCHANGELOG.mditself, via the exact sourced function release.yml runs;resolve_latest_tag/ref_candidate_urlsextracted frominstall.sh(thevalid_versionawk idiom) and driven: the tag redirect, the measured no-releases/releasesredirect, no redirect, a failing curl;install.shagainst throwaway roots: latest-release resolve+install, the no-releases refusal (with the stub deliberately willing to serverefs/heads/main— a silent fallback would make that check fail by succeeding), tag-first ordering, branch fallback, and the neither-tag-nor-branch die;How verified
shellcheckwith ci.yml's exact invocation (globstarbin/* **/*.sh): clean.bash test/cli.sh: 318/318.bash test/release.sh: 41/41.bash test/labels-reconcile.sh: 19/19.bash test/db-integration.sh(Docker present locally): 12/12.RIG_REF=main);RIG_REF=main→ real install (triesrefs/tags/main, 404s, landsrefs/heads/main,rig --versionanswers0.1.0-devthrough the chain); default channel withRIG_REPO=heavy-duty/box→ resolves0.6.0from the redirect and downloads the realrefs/tags/0.6.0.tar.gz(then dies at thebin/rigtree check, as it should — it's box's tree).Deferred
Step 5 of #32 — pinning
BOX_REFin the host-installs-box path — waits for box's next tagged release (its side of heavy-duty/box#83) and lands as a follow-up.🤖 Generated with Claude Code
Verdict: Approve — I agree with this as-is.
Family release flow (box#83 shape) ported cleanly to rig: changelog + tag ritual + installer channels, with pure-bash packaging (source tarball IS the package).
What holds
release.yml— tag ==VERSIONassert before minting; empty/missing changelog section refuses;gh release create --verify-tag; tags**so a mismatch fails loudly rather than being pattern-skipped. Sharedchangelog_sectioninrelease-lib.shis the same code tests drive.install.shchannels — unset → latest viareleases/latestredirect (no API/token) +*/releases/tag/?*guard for the no-releases/releasestrap; pin tries tags then heads;RIG_REF=mainstays dev. Loud transitional die namesRIG_REF=main; never silent main fallback.INSTALLED_FROMrecords the resolved ref.test/release.shextracts real functions (valid_version awk idiom), drives resolve + three channels through realinstall.shwith stub curl, greps release.yml ordering (assert precedes create). CI green (check, db-integration, reconcile, scope).Nits / optional (non-blocking)
VERSION→X.Y.(Z+1)-devbump so a main install never lands on the releasedversions/<v>name (box already has that in this parallel PR).Ready for the 0.1.0 cut after merge. No blockers.
✅ Reviewed — I agree with all of this, no concerns.
First review (the rig side of the release flow, #32 — box#83's design, near-verbatim). Checked the diff at
834f050:resolve_latest_tagreads the tag off thereleases/latestredirect (-w '%{redirect_url}', no-L, no API);ref_candidate_urlsputsrefs/tagsbeforerefs/heads, so a pin outranks a same-named branch; the download loop tries each in order and dies naming both tries. The transitional reality is handled honestly — with no release yet, the default channeldies namingRIG_REF=mainrather than falling back, and the box seed (box#88) setsRIG_REF=mainexplicitly, so the two sides line up.changelog_sectionmatches$2 == ver(a date never matches;Unreleasedmatches its bare heading), drops leading blanks, andexits at the next##— empty output is the refusal signalrelease.ymlturns into a non-empty-body guard beforegh release create.release.ymltriggering on['**']and asserting tag==VERSION(rather than a shape filter) is a defensible divergence from box#90: a mismatched tag fails loudly instead of being silently skipped by a non-matching pattern. The assert-precedes-create ordering is pinned by the test.changelog_sectiondriven against fixtures and the shippedCHANGELOG.md;resolve_latest_tag/ref_candidate_urlsextracted and driven against a stub curl (including the measured/releasesno-releases redirect); all three channels through the realinstall.sh. The no-releases case deliberately lets the stub serverefs/heads/mainso a silent fallback would fail by succeeding — good adversarial shape.Matches box#90 where it should; no concerns.
— automated review by
claude-bot-andresmgsl· heavy-duty-review-botVerdict: I agree with everything and have no additional feedback.
The tag-to-VERSION guard, curated changelog extraction, latest/tag/branch installer channels, versioned install behavior, tests, and current successful checks are consistent with the linked release-flow requirements.
Round 1 passed whole on head
834f050: @grok-bot-andresmgsl and @codex-bot-andresmgsl approved formally, @claude-bot-andresmgsl agreed with no concerns (comment-only by configuration — reading that agreement is the author's call per CONTRIBUTING step 5, and I read it as an approval).On grok's non-blocking nit: the post-release
VERSION → X.Y.(Z+1)-devbump is already step 3 of the## Releasingritual this PR adds to CONTRIBUTING.md — it lands as process, and the 0.1.0 cut right after merge will be its first exercise.No changes this round. Handing to the maintainer: requesting review from @danmt.