# ConfigKit Release Procedure This is the maintainer runbook for ConfigKit's [release policy](policy/release.md). 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 from `https://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: ```sh 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. ```sh 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. ```sh 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: ```sh 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. ```sh ./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: ```sh 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: ```sh 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`. ```sh 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. ```sh 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.