Files
ConfigKit/docs/release.md

10 KiB

ConfigKit Release Procedure

This is the maintainer runbook for ConfigKit's release policy. 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, as recorded in go.mod; see the consumer minimum policy
  • Release-gated targets: Linux amd64 and arm64
  • Best-effort targets: macOS amd64 and arm64, under the platform support policy

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. 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.

Put the selected series' latest locally available compatible patch first on PATH, ensuring it meets go.mod's minimum. The same go executable must build ReleaseKit and run its child validation commands. Set local toolchain selection and verify it:

export GOTOOLCHAIN=local
go version
go env GOVERSION
command -v go

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:

selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
selected_series=${selected_go#go}
selected_series=${selected_series%.*}
release_minimum="$selected_series.0"
go mod edit -go="$release_minimum"

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. For example, validating with Go 1.26.8 leaves go 1.26.0 unchanged. There is no separate requirement to validate with Go 1.26.0.

Prepare dependencies whether or not the minimum changes:

go mod download

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:

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.4.0. Use the built executable for publication to preserve its exit status and publication-state diagnostics. 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.

Verify access to the authoritative remote without displaying credentials:

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. Explain any consumer-minimum increase and required consumer action under the consumer minimum policy.

Create docs/releases/<version>.md. Its first line must be exactly # ConfigKit <version>, and it must end with the license and attribution footer:

[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. Any 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:

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:

"$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:

"$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. Copy the captured endpoint into the variable below using normal shell quoting. Check that Git still resolves it to itself, then query the authoritative refs once:

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"

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.

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:

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:

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.