2026-09-06 22:47:12 +00:00
# ConfigKit Release Procedure
This is the maintainer runbook for ConfigKit's [release policy ](policy/release.md ).
2026-11-01 19:04:48 +00:00
A release publishes the Go source under a new Git tag, with a matching release
note committed in the repository. The tag is annotated: it includes the release
note text and points to the released commit. Published tags and contents are
immutable. There is no binary, container image, generated package, or separate
registry publication.
A **candidate ** is the committed revision you intend to release. The
**authoritative remote** is the repository where ConfigKit publishes releases;
its exact push URL is listed below. The **consumer minimum ** is the oldest Go
version supported for applications using ConfigKit.
2026-09-06 22:47:12 +00:00
## Release identity and support
2026-11-01 14:19:37 +00:00
- Module: `go.maximumdirect.net/configkit`
2026-09-06 22:47:12 +00:00
- Primary branch: `main`
2026-10-08 16:42:56 +00:00
- Authoritative remote: `origin`
2026-11-01 14:19:37 +00:00
- Authoritative push URL: `ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git`
2026-09-28 01:07:30 +00:00
- Tags: stable Semantic Versions such as `v0.5.0` ; no prereleases
2026-10-08 16:42:56 +00:00
- Minimum Go version: the first stable patch of the selected Go series, or a
2026-10-09 04:23:15 +00:00
higher patch when required by a dependency or specific correctness fix, as
recorded in `go.mod` ; see the
[consumer minimum policy ](policy/release.md#consumer-minimum-and-release-toolchain )
2026-09-28 01:07:30 +00:00
- Release-gated targets: Linux amd64 and arm64
- Best-effort targets: macOS amd64 and arm64, under the
2026-09-12 18:30:59 +00:00
[platform support policy ](policy/release.md#platform-support )
2026-09-06 22:47:12 +00:00
2026-11-01 19:04:48 +00:00
Release validation runs the offline Go test suite once with the race detector,
then a full vet pass and package compilation for both Linux targets with cgo
disabled (`CGO_ENABLED=0` ). macOS and CI are
2026-10-08 16:42:56 +00:00
not release gates; do not wait for or poll CI.
2026-09-06 22:47:12 +00:00
2026-11-01 19:04:48 +00:00
The vanity module path is the name applications put in Go imports. Source/tag
browser links and public Go retrieval use the Forge HTTPS repository
independently of SSH publication.
2026-11-01 14:19:37 +00:00
2026-10-08 16:42:56 +00:00
## 1. Select Go and prepare the tool
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
Use a clean checkout with Git, Bash 3.2 or newer, and a native compiler for race
2026-10-09 04:23:15 +00:00
tests. Choose the release type before selecting Go. For a minor or major release,
select the newest stable Go series available in the development environment.
For a patch release, retain the series recorded in `go.mod` and leave its consumer
minimum unchanged. Follow the
[toolchain selection policy ](policy/release.md#configkit-toolchain-selection ).
2026-11-01 19:04:48 +00:00
Choose the latest compatible patch installed locally in that Go series. Put
its `go` executable first on `PATH` and ensure it meets the minimum in
`go.mod` . The same `go` executable must build ReleaseKit and run its child
validation commands. Set local toolchain selection and verify it:
2026-09-28 01:07:30 +00:00
2026-10-08 16:42:56 +00:00
```sh
export GOTOOLCHAIN=local
go version
go env GOVERSION
command -v go
```
2026-10-09 04:23:15 +00:00
Only when a minor or major release advances the consumer minimum to a newer Go
series, derive that series' initial stable release from the selected executable:
2026-09-06 22:47:12 +00:00
```sh
2026-10-08 16:42:56 +00:00
selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
selected_series=${selected_go#go }
selected_series=${selected_series%.*}
2026-10-09 04:23:15 +00:00
release_minimum="$selected_series.0"
2026-10-08 16:42:56 +00:00
go mod edit -go="$release_minimum"
2026-09-06 22:47:12 +00:00
```
2026-10-09 04:23:15 +00:00
If a dependency or identified correctness fix requires a higher patch minimum,
use that required version instead and document the reason. When the series is
unchanged, leave the minimum unchanged unless a minor or major release
deliberately changes it under the
[consumer minimum policy ](policy/release.md#consumer-minimum-and-release-toolchain ).
2026-11-01 19:04:48 +00:00
For example:
| Release situation | Go used for validation | Consumer minimum |
| --- | --- | --- |
| Patch release in the 1.26 series | Latest compatible patch installed locally, such as 1.26.8 | Keep the existing `go.mod` minimum unchanged. |
| Minor/major release staying in 1.26 | Latest compatible patch installed locally | Normally retain the minimum; any deliberate change follows the linked policy. |
| Minor/major release adopting a newer installed stable series | Latest compatible patch installed locally in that series | Normally declare that series' initial stable patch, or the higher patch required by a dependency or identified fix. |
Using Go 1.26.8 for validation can therefore leave `go 1.26.0` unchanged. Users
need the declared minimum, while maintainers validate with their selected patch.
There is no separate requirement to validate with Go 1.26.0.
2026-10-09 04:23:15 +00:00
Prepare dependencies whether or not the minimum changes:
```sh
go mod download
```
2026-10-08 16:42:56 +00:00
2026-11-01 19:04:48 +00:00
Build the ReleaseKit version recorded in `go.mod` into temporary storage outside
the checkout.
2026-10-08 16:42:56 +00:00
Ensure `$TMPDIR` names a system temporary location, not the repository. These
commands never install another ReleaseKit version:
2026-09-06 22:47:12 +00:00
```sh
2026-10-08 16:42:56 +00:00
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
2026-09-06 22:47:12 +00:00
```
2026-11-01 19:04:48 +00:00
This repository selects ReleaseKit v0.4.2. Use the compiled program above for
publication so its exit status and publication report reach you unchanged.
2026-10-08 17:08:35 +00:00
`go run` maps a nonzero child exit status to 1. ReleaseKit's child Go commands
use the `go` found on `PATH` with `GOTOOLCHAIN=local` ; keep the selected
executable first on `PATH` through the publication command.
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
Verify access to the authoritative remote without displaying credentials:
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
```sh
2026-11-01 14:19:37 +00:00
git ls-remote ssh://git@forge .maximumdirect.net:22222/base-layer/configkit.git HEAD refs/heads/main
2026-10-08 16:42:56 +00:00
```
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
Publication requires explicit authorization. Local implementation commits do
not authorize a push or release.
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
## 2. Prepare and commit the candidate
2026-09-06 22:47:12 +00:00
2026-11-01 19:04:48 +00:00
Choose a stable version that has not been used locally or on the publication
remote. Compare the candidate with the previous tag. Review public Go APIs and
value ownership, YAML and resolution rules, diagnostic contracts, the minimum
Go version, and platform support. Review maintained consumer examples and the
2026-10-09 04:23:15 +00:00
migration guidance. Explain any consumer-minimum increase and required consumer
action under the
[consumer minimum policy ](policy/release.md#consumer-minimum-and-release-toolchain ).
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
Create `docs/releases/<version>.md` . Its first line must be exactly
`# ConfigKit <version>` , and it must end with the license and attribution footer:
2026-09-26 21:15:13 +00:00
```markdown
2026-11-01 14:19:37 +00:00
[COPYRIGHT ](https://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/COPYRIGHT ) | [LICENSE ](https://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/LICENSE ) | [ATTRIBUTION ](https://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/docs/oss/attribution.md )
2026-09-26 21:15:13 +00:00
```
2026-10-08 16:42:56 +00:00
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
2026-10-09 04:23:15 +00:00
tasks, not separate approval gates. Any `go.mod` change belongs in this
2026-10-08 16:42:56 +00:00
candidate.
## 3. Push the candidate to `main`
Push only that committed candidate to the literal authoritative URL. This does
not follow local tags:
```sh
2026-11-01 14:19:37 +00:00
git push --no-follow-tags ssh://git@forge .maximumdirect.net:22222/base-layer/configkit.git HEAD:refs/heads/main
2026-10-08 16:42:56 +00:00
```
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.
2026-09-26 21:15:13 +00:00
2026-09-28 01:07:30 +00:00
## 4. Publish the committed candidate
2026-09-06 22:47:12 +00:00
2026-11-01 19:04:48 +00:00
Once publication is authorized and the candidate is on the publication remote's
`main` , run the compiled ReleaseKit program once to validate and publish it:
2026-10-08 16:42:56 +00:00
```sh
"$releasekit_bin" publish vMAJOR.MINOR.PATCH
```
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
Replace the placeholder with the selected version. ReleaseKit checks the
2026-11-01 19:04:48 +00:00
push URL Git will actually use, after URL rewrites, against `expected_push_url` .
It verifies the candidate on
2026-10-08 16:42:56 +00:00
`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.
2026-11-01 19:04:48 +00:00
Use the optional `check` command for diagnosis when useful. It validates without
publishing and is not required before `publish` , which performs its own checks:
2026-09-06 22:47:12 +00:00
```sh
2026-10-08 16:42:56 +00:00
"$releasekit_bin" check vMAJOR.MINOR.PATCH
2026-09-06 22:47:12 +00:00
```
2026-11-01 19:04:48 +00:00
For `publish` , interpret the program's exit status as follows:
| Status | Meaning | Next action |
| --- | --- | --- |
| `0` | Publication succeeded. | The release is complete. Optional verification can follow. |
| `1` | A known publication failure or conflict was reported. | Read the report and correct the stated problem. Preserve any published tag. |
| `2` | The command, profile, or version is invalid. | Correct the command or configuration before trying again. |
| `3` | The tag push outcome is still unknown. | Follow the recovery procedure below before retrying or declaring success. |
Keep both the program's exit status and its diagnostic report. Using `go run`
would lose the distinction between its nonzero exit statuses.
2026-09-23 18:10:37 +00:00
2026-10-08 16:42:56 +00:00
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.
2026-09-23 18:10:37 +00:00
2026-10-08 16:42:56 +00:00
## 5. Recover from a failed or uncertain tag push
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
The executable reports the publication state, phase, version, candidate commit,
2026-11-01 19:04:48 +00:00
tag-object ID, effective endpoint, and error on standard error. After a failed
tag push, it makes one bounded check of the exact publication remote to find out
whether the tag arrived. Exit status 3
2026-10-08 16:42:56 +00:00
means the tag outcome remains unknown; do not blindly retry or move a tag.
2026-09-06 22:47:12 +00:00
2026-11-01 19:04:48 +00:00
An annotated tag is a Git object containing the release note and a pointer to
the source commit. Its tag-object ID identifies that complete object. Checking
only the commit ID would not establish that the intended tag object arrived.
For an uncertain result, use the exact endpoint (remote URL), candidate commit,
version, and tag-object ID from the report. Copy the captured endpoint into the variable
2026-10-08 17:08:00 +00:00
below using normal shell quoting. Check that Git still resolves it to itself,
then query the authoritative refs once:
2026-09-06 22:47:12 +00:00
```sh
2026-10-08 17:08:00 +00:00
authoritative='COPY_CAPTURED_ENDPOINT'
release_version='vMAJOR.MINOR.PATCH'
test "$(git ls-remote --get-url -- "$authoritative")" = "$authoritative" &&
git ls-remote --refs -- "$authoritative" refs/heads/main "refs/tags/$release_version"
2026-09-06 22:47:12 +00:00
```
2026-10-08 17:08:00 +00:00
A failed URL-equality check or remote query leaves the outcome unresolved.
Correct transport configuration before continuing; a rewritten destination
cannot establish absence at the captured endpoint.
2026-10-08 16:42:56 +00:00
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
2026-11-01 19:04:48 +00:00
object itself. A peeled commit is the source commit reached by following that
tag; it is a different identity. If the tag object matches, the
2026-10-08 16:42:56 +00:00
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:
2026-09-06 22:47:12 +00:00
```sh
2026-09-28 01:07:30 +00:00
git tag -d "$release_version"
2026-10-08 16:42:56 +00:00
"$releasekit_bin" publish "$release_version"
2026-09-06 22:47:12 +00:00
```
2026-10-08 16:42:56 +00:00
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.
2026-09-06 22:47:12 +00:00
2026-09-28 01:07:30 +00:00
## 6. Optional consumer verification
2026-09-06 22:47:12 +00:00
2026-10-08 16:42:56 +00:00
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:
2026-09-28 01:13:09 +00:00
```sh
2026-10-08 16:42:56 +00:00
release_version=vMAJOR.MINOR.PATCH
2026-09-28 01:13:09 +00:00
consumer_dir=$(mktemp -d)
(
cd "$consumer_dir"
go mod init example.invalid/configkit-release-check
2026-11-01 14:19:37 +00:00
GOWORK=off GOPROXY=direct go get "go.maximumdirect.net/configkit@$release_version"
GOWORK=off go list -m -f '{{.Path}} {{.Version}}' go.maximumdirect.net/configkit
2026-09-28 01:13:09 +00:00
)
```
The output should show the exact module path and selected version. Report this
optional check separately from publication success.