Document the source release workflow

This commit is contained in:
2026-10-08 16:49:01 +00:00
parent d3f0891225
commit 8c5fea0aae
12 changed files with 197 additions and 34 deletions

133
docs/release.md Normal file
View File

@@ -0,0 +1,133 @@
# CommandKit release procedure
## Scope and prerequisites
CommandKit publishes source-only Go module releases: an immutable annotated
version tag for one committed candidate and its matching checked-in release
note. This procedure does not publish a hosted release page, binaries,
checksums, or other delivery assets, and it requires no delivery credentials.
Use branch `main`, remote `origin`, and the expected effective push endpoint
`https://gitea.maximumdirect.net/eric/CommandKit.git`. The profile enforces that
endpoint during publication. The maintainer needs a local Go toolchain
compatible with the module's Go 1.25.5 minimum, Git with push access to `origin`,
and a POSIX shell.
This procedure follows the [release policy](policy/release.md); the release-note
footer follows the [OSS discoverability policy](policy/oss.md#discoverability).
ReleaseKit v0.2.0 owns candidate validation, publication guards, annotated tag
creation, tag publication, and outcome reporting. The repository profile selects
only its default candidate suite. The launcher bootstraps the module-selected
native executable into ignored `.releasekit/` storage and invokes its `publish`
command. The upstream [CLI reference](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e030794969fb7787e647a90bbc4eb81bd0610f07/docs/cli.md),
[profile reference](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e030794969fb7787e647a90bbc4eb81bd0610f07/docs/config.md),
and [publication guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e030794969fb7787e647a90bbc4eb81bd0610f07/docs/consumers/publish.md)
own detailed command, profile, and status contracts.
## Prepare and publish a candidate
Choose a fresh, unused stable Semantic Version tag in the form
`vMAJOR.MINOR.PATCH`, with no leading zeroes. Before `v1.0.0`, a minor release
may change a documented contract if its note explains the impact and consumer
action; a patch release must not intentionally break contracts within its
minor line. Never reuse a version previously published through any release
channel. The first version is selected during release preparation, not embedded
in the tool or profile.
Start from `main` and prepare a note at `docs/releases/<version>.md`. Begin with
a heading containing `CommandKit` and the exact version. Include a useful
summary and any compatibility impact or upgrade instructions. End it with the
license and attribution links at that version's immutable source tag, replacing
`VERSION` with the selected tag:
```markdown
[COPYRIGHT](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/COPYRIGHT) |
[LICENSE](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/LICENSE) |
[ATTRIBUTION](https://gitea.maximumdirect.net/eric/CommandKit/src/tag/VERSION/docs/oss/attribution.md)
```
Review these paths in the candidate; the future tag URLs do not need to resolve
before publication.
Prepare dependencies separately from the offline candidate suite:
Run these commands in the same shell session so the version variable remains
set:
```sh
commandkit_version=vX.Y.Z
GOWORK=off GOTOOLCHAIN=local go mod download
```
If dependency preparation changes `go.mod` or `go.sum`, review and commit those
changes with the candidate. Commit the note and all candidate changes on `main`,
then publish that commit to `origin`:
```sh
git push origin HEAD:refs/heads/main
./scripts/release "$commandkit_version"
```
The launcher builds the ReleaseKit version selected by the module in readonly
mode. After preflight, `publish` runs the offline default Go suite once, checks
the candidate and remote guards, creates an annotated tag from the committed
note, and pushes only that tag object. The launcher does not push `main`; push
the candidate first. Do not require a separate `check` before `publish` or repeat
the same suite. Successful source publication does not wait for CI, module
availability, or consumer installation.
## Completion and statuses
Publication is complete when the intended annotated tag is successfully pushed
to the expected endpoint, or when an exact tag-object query confirms that push
after an error. Status 0 reports confirmed publication. Status 1 reports a
known failure or conflict; status 2 reports invalid input or profile; status 3
means the push outcome remains unresolved. See the upstream [CLI status
contract](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/e030794969fb7787e647a90bbc4eb81bd0610f07/docs/cli.md).
For a failed or ambiguous push, use the exact endpoint, version, and annotated
tag object reported by the invocation. Confirm that Git still resolves the
captured endpoint to itself before querying only that tag ref:
```sh
releasekit_endpoint='COPY THE CAPTURED ENDPOINT EXACTLY'
releasekit_version='vX.Y.Z'
releasekit_tag_object='COPY THE REPORTED TAG OBJECT ID'
releasekit_ref="refs/tags/$releasekit_version"
releasekit_resolved_endpoint=$(git ls-remote --get-url -- "$releasekit_endpoint") || {
printf '%s\n' 'publication state is unresolved: endpoint resolution failed' >&2
exit 1
}
if [ "$releasekit_resolved_endpoint" != "$releasekit_endpoint" ]; then
printf '%s\n' 'publication state is unresolved: endpoint resolution changed' >&2
exit 1
fi
releasekit_remote_tag=$(git ls-remote --refs -- "$releasekit_endpoint" "$releasekit_ref") || {
printf '%s\n' 'publication state is unresolved: remote query failed' >&2
exit 1
}
printf '%s\n' "$releasekit_remote_tag"
```
`--refs` returns the annotated tag object's ID, not its peeled commit. Compare
that ID with `releasekit_tag_object`: an exact match confirms publication; a
different object is a conflict; successful output with no matching ref proves
absence at the captured endpoint. A failed query or changed URL resolution is
unresolved, never evidence of absence. Do not query a different fetch URL or
follow a rewritten endpoint to infer the captured destination's state.
ReleaseKit retains a local tag after a failed push, so the tag blocks an ordinary
retry. After proving the tag absent at the captured endpoint,
confirm that it was never published or used by another release channel before
deliberately removing the unpublished local tag:
```sh
git tag -d "$releasekit_version"
```
Do not move, overwrite, recreate, or delete a published tag. If the exact tag
object is present remotely, keep it. Resolve conflicts with a fresh version;
corrections to published source require a new version. A transport failure
alone does not require a new version.