10 KiB
ConfigKit Release Procedure
This is the maintainer runbook for ConfigKit's release policy. ConfigKit publishes one source-only Go module: the immutable source tag and the matching checked-in release note are the deliverables. There is no application 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, expected to fetch fromhttps://gitea.maximumdirect.net/eric/ConfigKit.git - Tags: stable Semantic Versions such as
v0.1.0; prerelease tags are not supported - Minimum Go language/toolchain line: Go 1.25, tested with a supported patched Go 1.25 toolchain and one newer supported toolchain
- Build targets: Linux and macOS on amd64 and arm64
The automated checker cross-builds all four targets. Before release, the maintainer must also obtain native test, race, symlink, and permission-fixture evidence on Linux and macOS; cross-compilation is not native execution evidence.
1. Prepare access and tools
The release maintainer owns this procedure. Work in a dedicated clone with Git, Bash 3.2 or newer, and two supported Go toolchains available. The checker supports the stock macOS Bash; its filename enumeration is null-delimited, including names with whitespace. Configure private-host Git and Go module access through the normal credential manager before starting. Keep tokens, passwords, SSH material, and environment dumps outside the repository and command transcript. Verify access without displaying credentials:
git remote get-url origin
git ls-remote origin HEAD
The remote URL must equal the authoritative URL above. Select the latest supported patch in the Go 1.25 line and a newer supported toolchain. The values below are the toolchains used while this runbook was introduced; update them to supported patch releases during candidate preparation and record the actual versions in the release note or validation record.
export CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5
export CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go version
GOTOOLCHAIN="$CONFIGKIT_NEWER_TOOLCHAIN" go version
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go mod download
GOTOOLCHAIN="$CONFIGKIT_NEWER_TOOLCHAIN" go mod download
Those commands explicitly acquire toolchains and dependencies. The checker itself
sets GOPROXY=off and fails if its required cache is incomplete.
2. Select the version and review compatibility
Choose an unused stable version and compare the candidate with the previous tag. For the first release, review the whole public surface and current contracts.
release_version=v0.1.0
printf '%s\n' "$release_version" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
git show-ref --verify --quiet "refs/tags/$release_version" && exit 1 || :
if git ls-remote --exit-code --tags origin "refs/tags/$release_version" >/dev/null; then
printf 'remote tag already exists: %s\n' "$release_version" >&2
exit 1
else
test $? -eq 2
fi
git log --oneline --decorate --no-merges
Review exported Go signatures and ownership, YAML and scalar behavior, precedence,
diagnostic codes, minimum Go version, and platform support. Before v1.0.0, any
intentional incompatible minor-release change must be explained with consumer action;
patch releases must remain compatible within their minor line. Review the maintained
consumer examples and the migration guide even when signatures did not change.
Reassess go.yaml.in/yaml/v4 rather than using @latest implicitly:
go list -m -versions go.yaml.in/yaml/v4
go list -m -json go.yaml.in/yaml/v4@latest
If changing the explicit pin, make that a reviewed dependency change and rerun the complete checker, especially parser policy, limits, diagnostics, and rendering tests. If retaining a prerelease pin, record that decision in Compatibility.
3. Write the release note
Create docs/releases/$release_version.md before selecting the candidate. It must
begin with # ConfigKit $release_version and contain Summary, Compatibility,
Upgrade, and Changes sections. Use “No action required” where appropriate. Link
to current canonical contracts instead of copying their definitions. Do not create a
release note until there is a real candidate and reviewed content.
4. Validate the source candidate
Run the complete checker from the repository root after dependencies have been acquired. It validates module manifests in a disposable copy, both supported Go toolchains, offline default tests, the native race detector, vet, native builds, CGO-disabled cross-builds, formatting and whitespace (including untracked files), examples, package isolation, and local documentation links.
./scripts/release-check.sh
go vet is the repository's static-analysis baseline. No additional linter is
claimed. Links to current ConfigKit files and headings are strict. Recorded links to
sibling consumer source are checked when those checkouts exist and are otherwise
inapplicable in an isolated clone; their immutable revisions remain recorded in the
extraction inventory.
Complete these manual gates and record their evidence with the candidate:
- The maintainer reviews compatibility and downstream migration impact against
docs/config.md, Go API comments, and the consumer guide. - The maintainer inspects
git status --short,git diff, and the candidate file list for credentials, private data, generated output, unintended files, and unrelated changes. - The maintainer records successful native checker runs on Linux and macOS, including filesystem capability tests. If either host is unavailable, release remains blocked unless platform support is formally narrowed in current documentation.
- The maintainer removes completed, obsolete documents under
docs/roadmap/, repairs inbound links, and reruns the checker. ADR rationale remains historical. - The maintainer reviews the exact version-matched release note for accuracy and confirms it describes only deliverables that will actually be published.
5. Commit, push, and record the candidate
Commit all candidate source, contract, example, roadmap-retirement, and release-note
changes, rerun the checker against that clean commit, then push only main and record
the exact commit in Git metadata local to the clone:
git add .
git commit --message "Prepare ConfigKit $release_version"
./scripts/release-check.sh
test -z "$(git status --porcelain=v1 --untracked-files=all)"
git push origin HEAD:refs/heads/main
git rev-parse HEAD | tee .git/configkit-release-candidate
candidate_commit=$(cat .git/configkit-release-candidate)
If review changes the source after this point, create and validate a new candidate; do not reuse the recorded commit.
6. Run publication guards
Immediately before tagging, fetch the authoritative branch and tags, restore the recorded variables if this is a new shell, and execute every guard:
release_version=v0.1.0
candidate_commit=$(cat .git/configkit-release-candidate)
git fetch --prune origin main
git fetch --tags origin
test "$(git branch --show-current)" = main
test -z "$(git status --porcelain=v1 --untracked-files=all)"
test "$(git rev-parse HEAD)" = "$candidate_commit"
test "$(git rev-parse refs/remotes/origin/main)" = "$candidate_commit"
test "$(git remote get-url origin)" = 'https://gitea.maximumdirect.net/eric/ConfigKit.git'
printf '%s\n' "$release_version" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
test -f "docs/releases/$release_version.md"
test "$(sed -n '1p' "docs/releases/$release_version.md")" = "# ConfigKit $release_version"
for heading in Summary Compatibility Upgrade Changes; do
grep -Fxq "## $heading" "docs/releases/$release_version.md"
done
git show-ref --verify --quiet "refs/tags/$release_version" && exit 1 || :
if git ls-remote --exit-code --tags origin "refs/tags/$release_version" >/dev/null; then
printf 'remote tag already exists: %s\n' "$release_version" >&2
exit 1
else
test $? -eq 2
fi
Confirm the recorded automated and manual approvals apply to exactly
$candidate_commit. A successful older run is not evidence for a changed commit.
7. Tag and publish the exact ref
Create one annotated tag at the guarded commit, verify its peeled target, and push
only that ref. Do not use git push --tags.
git tag --annotate "$release_version" --message "ConfigKit $release_version" "$candidate_commit"
test "$(git rev-list -n 1 "$release_version")" = "$candidate_commit"
git push origin "refs/tags/$release_version:refs/tags/$release_version"
The pushed tag completes the declared source-only publication. No Gitea release object or binary upload is required.
8. Verify publication and clean consumer retrieval
Verify the authoritative tag and then retrieve that exact version into a new module
without a local replace directive. Private-host credentials must already be
configured. Use direct origin retrieval unless an explicitly approved internal Go
proxy is the publication channel.
remote_tag_commit=$(git ls-remote origin "refs/tags/$release_version^{}" | awk '{print $1}')
test "$remote_tag_commit" = "$candidate_commit"
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"
cat > main.go <<'EOF'
package main
import configkit "gitea.maximumdirect.net/eric/ConfigKit"
func main() {
_ = configkit.NoSource()
}
EOF
gofmt -w main.go
go test ./...
go build ./...
go list -m -f '{{.Path}} {{.Version}}' gitea.maximumdirect.net/eric/ConfigKit
)
rm -rf -- "$consumer_dir"
The final go list output must show the exact module path and $release_version.
Record the result with the release evidence.
9. Handle failures without replacement
Before publication, fix the candidate, rerun every gate, commit, push, and record a new exact candidate. An unpublished local tag may be deleted only after verifying it was never pushed or used by another release channel.
After publication, never move, delete, or recreate the tag and never replace its
release note. Correct the defect on main, choose a new semantically appropriate
version, and repeat this procedure. A supported advisory or deprecation mechanism
may warn consumers about the old version but cannot change its contents.