Files
ConfigKit/docs/release.md

232 lines
10 KiB
Markdown

# ConfigKit Release Procedure
This is the maintainer runbook for ConfigKit's [release policy](policy/release.md).
ConfigKit publishes a source-only Go module. The immutable annotated tag and
matching checked-in release note are the deliverables; there is no binary,
container image, generated package, or separate registry publication.
## Release identity and support
- Module: `gitea.maximumdirect.net/eric/ConfigKit`
- Primary branch: `main`
- Authoritative remote: `origin`
- Authoritative push URL: `https://gitea.maximumdirect.net/eric/ConfigKit.git`
- Tags: stable Semantic Versions such as `v0.5.0`; no prereleases
- Minimum Go version: the first stable patch of the selected Go series, or a
higher patch when required by a dependency or specific correctness fix
- Release-gated targets: Linux amd64 and arm64
- Best-effort targets: macOS amd64 and arm64, under the
[platform support policy](policy/release.md#platform-support)
Release validation runs one offline race-enabled Go suite, one full vet pass,
and CGO-disabled package compilation for both Linux targets. macOS and CI are
not release gates; do not wait for or poll CI.
## 1. Select Go and prepare the tool
Use a clean checkout with Git, Bash 3.2 or newer, and a native compiler for race
tests. Select the newest stable Go series available in the development
environment and put its latest locally available compatible patch first on
`PATH`. The same `go` executable must build ReleaseKit and run its child
validation commands. Set local toolchain selection and verify it:
```sh
export GOTOOLCHAIN=local
go version
go env GOVERSION
command -v go
```
At release preparation, record the selected series' initial stable release in
`go.mod`. These commands derive that minimum from the selected executable. The
calculation preserves a higher patch minimum already recorded within that same
series; retain it only when dependency or correctness review confirms it is
still required. Do not keep a higher patch solely because it was previously
recorded:
```sh
selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
selected_series=${selected_go#go}
selected_series=${selected_series%.*}
current_minimum=$(go list -m -f '{{.GoVersion}}')
release_minimum=$(awk -F. -v current="$current_minimum" -v series="$selected_series" 'BEGIN {
split(current, current_parts, "[.]")
split(series, selected_parts, "[.]")
if (current_parts[1] == selected_parts[1] && current_parts[2] == selected_parts[2] && current_parts[3] + 0 > 0) {
print current
} else {
print series ".0"
}
}')
go mod edit -go="$release_minimum"
go mod download
```
For example, Go 1.26.8 records `go 1.26.0` unless a higher minimum is required.
The `go` directive is ConfigKit's consumer minimum, not the toolchain pin. A
minimum increase requires consumers to use the newer Go version; before v1.0.0,
an intentional compatibility increase requires a minor release under the
[versioning policy](policy/release.md#versioning-and-compatibility). Review any
required higher patch minimum as part of compatibility preparation.
Build the tool selected by `go.mod` into temporary storage outside the checkout.
Ensure `$TMPDIR` names a system temporary location, not the repository. These
commands never install another ReleaseKit version:
```sh
releasekit_tmp=$(mktemp -d "${TMPDIR:-/tmp}/configkit-releasekit.XXXXXX")
releasekit_bin="$releasekit_tmp/releasekit"
go build -o "$releasekit_bin" gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
go list -m gitea.maximumdirect.net/eric/ReleaseKit
"$releasekit_bin" version
```
The module-selected executable is ReleaseKit v0.2.0. Do not use `go run` or
`go tool` for publication: the native executable preserves its exit status and
the publication-state diagnostics. ReleaseKit's child Go commands use the
`go` found on `PATH` with `GOTOOLCHAIN=local`; the existing
`CONFIGKIT_BASELINE_TOOLCHAIN` selector for ConfigKit's local release scripts
does not select their compiler.
Verify access to the authoritative remote without displaying credentials:
```sh
git ls-remote https://gitea.maximumdirect.net/eric/ConfigKit.git HEAD refs/heads/main
```
Publication requires explicit authorization. Local implementation commits do
not authorize a push or release.
## 2. Prepare and commit the candidate
Choose an unused stable version locally and on the authoritative remote. Compare
the candidate with the previous tag and review exported Go signatures and
ownership, YAML and resolution semantics, diagnostic contracts, minimum Go
version, and platform support. Review maintained consumer examples and the
migration guidance. Before v1.0.0, a minimum-Go increase that changes the
compatibility contract requires a minor release and an explanation of consumer
impact in the release note; patch releases remain compatible within their minor
line.
Create `docs/releases/<version>.md`. Its first line must be exactly
`# ConfigKit <version>`, and it must end with the license and attribution footer:
```markdown
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/VERSION/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/VERSION/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/VERSION/docs/oss/attribution.md)
```
Replace `VERSION` with the full tag and confirm the three target files exist in
the candidate. Retire completed roadmaps, preserve unfinished and deferred
work, and repair affected links. Review the complete candidate for unintended
files and credentials, then commit it on `main`. These are ordinary preparation
tasks, not separate approval gates. The `go.mod` change belongs in this
candidate.
## 3. Push the candidate to `main`
Push only that committed candidate to the literal authoritative URL. This does
not follow local tags:
```sh
git push --no-follow-tags https://gitea.maximumdirect.net/eric/ConfigKit.git HEAD:refs/heads/main
```
ReleaseKit requires authoritative `main` to equal this candidate before it
validates. A failure before this push means ReleaseKit publication has not
started. If the push result is uncertain, inspect authoritative `main` and
continue only when it is confirmed at the candidate. If validation later fails,
no release tag is published; correct the issue in a new commit, push the new
candidate to `main`, and invoke validation again.
## 4. Publish the committed candidate
With the authorized candidate already on authoritative `main`, invoke the
module-selected native executable exactly once:
```sh
"$releasekit_bin" publish vMAJOR.MINOR.PATCH
```
Replace the placeholder with the selected version. ReleaseKit checks the
effective push URL against `expected_push_url`, verifies the candidate on
`main`, and checks the release-note H1 for the product and version. It then
runs the selected race-enabled standard suite once with test-time vet disabled,
one full vet pass, and CGO-disabled package compilation for Linux amd64 and
arm64. It publishes only the annotated tag for that candidate. Confirmed
publication of the intended tag completes the release. The tag points to the
captured commit and uses the complete checked-in note as its annotation message.
The optional `check`
command is available for diagnosis but is not a separate required pass:
```sh
"$releasekit_bin" check vMAJOR.MINOR.PATCH
```
Status 0 confirms publication, status 1 reports a known publication failure or
conflict, status 2 reports usage, profile, or version errors, and status 3 means
the push outcome remains unknown. Preserve both the native exit status and its
diagnostics.
The command does not push or repair `main`, wait for CI, or publish binaries or
a hosted release page. A successful intended-tag publication does not require
remote read-back or consumer retrieval; those checks remain optional.
## 5. Recover from a failed or uncertain tag push
The executable reports the publication state, phase, version, candidate commit,
tag-object ID, effective endpoint, and error on standard error. It performs one
bounded authoritative reconciliation after a failed tag push. Exit status 3
means the tag outcome remains unknown; do not blindly retry or move a tag.
For an uncertain result, use the exact endpoint, candidate commit, version, and
tag-object ID from the report. Query the authoritative refs once:
```sh
authoritative=https://gitea.maximumdirect.net/eric/ConfigKit.git
release_version=vMAJOR.MINOR.PATCH
git ls-remote --refs "$authoritative" refs/heads/main "refs/tags/$release_version"
```
Compare the `main` object ID with the reported candidate commit and the tag
object ID with the reported tag-object ID. `--refs` returns the annotated tag
object itself, not merely its peeled commit. If the tag object matches, the
release is published. If the query fails, the result is conflicting, or the
endpoint or candidate differs, stop and resolve the state before retrying. If
the endpoint is reachable, the tag is confirmed absent, and `main` still equals
the reported candidate, remove a retained local tag only after confirming it
was not published or used by another channel; then rerun `publish`, which
revalidates the candidate:
```sh
git tag -d "$release_version"
"$releasekit_bin" publish "$release_version"
```
A validation failure after the candidate reached `main` leaves an untagged
candidate there. Correct it with a new commit rather than rewriting or rolling
back `main`. A conflicting published tag must never be moved or reused. A
defect in published source requires a corrected commit, a new semantically
appropriate version, and a matching release note.
## 6. Optional consumer verification
When useful or requested, read back the remote tag or retrieve the exact
version into a new module without a local `replace` directive. These checks are
optional and non-gating. Do not poll CI or wait for module propagation. For
direct-origin verification, use the exact published version in a temporary
consumer module:
```sh
release_version=vMAJOR.MINOR.PATCH
consumer_dir=$(mktemp -d)
(
cd "$consumer_dir"
go mod init example.invalid/configkit-release-check
GOPRIVATE=gitea.maximumdirect.net GOPROXY=direct go get "gitea.maximumdirect.net/eric/ConfigKit@$release_version"
go list -m -f '{{.Path}} {{.Version}}' gitea.maximumdirect.net/eric/ConfigKit
)
```
The output should show the exact module path and selected version. Report this
optional check separately from publication success.