From 1dc50d8cdf9bc5088de5ecdd685c03016def6e62 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 8 Oct 2026 16:42:56 +0000 Subject: [PATCH] Replace the release procedure with ReleaseKit --- docs/release.md | 292 ++++++++++++++++++--------------- docs/roadmap/implementation.md | 2 +- go.mod | 2 +- 3 files changed, 164 insertions(+), 132 deletions(-) diff --git a/docs/release.md b/docs/release.md index 1ab276e..3ea6ffc 100644 --- a/docs/release.md +++ b/docs/release.md @@ -1,7 +1,7 @@ # 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 annotated tag and +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. @@ -9,183 +9,215 @@ container image, generated package, or separate registry publication. - Module: `gitea.maximumdirect.net/eric/ConfigKit` - Primary branch: `main` -- Authoritative remote: `origin`, with fetch and push destination - `https://gitea.maximumdirect.net/eric/ConfigKit.git` +- 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 line: Go 1.25, validated with one maintained patch +- 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) -The checker runs one offline race suite, one full vet pass, and CGO-disabled -builds for both Linux targets. It reports skipped filesystem cases. macOS checks -and newer-toolchain tests are optional. CI is never a readiness or completion -gate; do not wait for or poll it. +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. Prepare access and tools +## 1. Select Go and prepare the tool -Use a clean checkout with Git, Bash 3.2 or newer, a maintained Go 1.25 patch, -and a native compiler for race tests. Configure private-host access through the -normal credential manager. Keep credentials and environment dumps outside the -repository and command transcript. - -Verify access without displaying credentials: +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 -git remote get-url origin -git ls-remote origin HEAD +export GOTOOLCHAIN=local +go version +go env GOVERSION +command -v go ``` -The fetch URL and all configured push destinations must resolve to the -authoritative URL above. The publication command checks this again. Acquire the -selected toolchain and dependencies explicitly before offline validation: +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 -export CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.14 -GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go version -GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go mod download +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 ``` -A maintained patch may replace the example pin. No per-release latest-version -survey is required. Dependencies must be cached; the checker sets `GOPROXY=off`. -Publication requires user authorization. Standing authorization for local -commits does not authorize a push or release. +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. -## 2. Select the version and review compatibility +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: -Choose an unused stable version. Compare the candidate with the previous tag. -Review exported Go signatures and ownership, YAML and scalar semantics, -precedence, diagnostic codes, minimum Go version, and platform support. A -signature-preserving change may still affect consumers. Before `v1.0.0`, -intentional incompatible minor-release changes need a note explaining impact -and consumer action; patch releases remain compatible within their minor line. -Review the maintained consumer examples and migration guide. +```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 +``` -When dependencies change, review parser behavior and migration impact as part -of that change. Module tidiness belongs to dependency-change review, not the -release checker. Do not repeat a dependency-version survey for every release. +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. -## 3. Write and commit the release note +Verify access to the authoritative remote without displaying credentials: -Create `docs/releases/.md` for the selected version. Its first line -must be `# ConfigKit `. Give a meaningful summary and include -compatibility impact or upgrade instructions when applicable. Link to current -contracts for exact behavior. Review the note and affected canonical documents -as ordinary preparation, then commit the complete candidate. +```sh +git ls-remote https://gitea.maximumdirect.net/eric/ConfigKit.git HEAD refs/heads/main +``` -End every new note with these links, replacing `VERSION` with the complete tag: +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/.md`. Its first line must be exactly +`# ConfigKit `, 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) ``` -Confirm the three target files exist in the candidate and review the version in -the URLs. The future tag need not resolve remotely yet; this review adds no -network gate. ConfigKit uses checked-in notes without hosted release objects. -If a hosted page is used later, reuse the note and preserve its footer. Do not -rewrite historical notes or add downloadable license assets. +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 -Before publication, review compatibility, release-note accuracy, and the file -list for unintended content or credentials. Retire completed roadmaps as part of -release preparation, preserving unfinished and deferred work and repairing -links. These are ordinary preparation tasks, not separate human sign-offs. - -With the release explicitly authorized, run the one publication command. -The script resolves the repository root from its own path, so it may be invoked -from another directory by using its path there. Replace the sample version with -the selected version: +With the authorized candidate already on authoritative `main`, invoke the +module-selected native executable exactly once: ```sh -./scripts/release.sh v0.5.0 +"$releasekit_bin" publish vMAJOR.MINOR.PATCH ``` -The command requires `main`, a clean index/worktree, an unused version locally -and on the authoritative remote, and a matching note in the captured commit. -Ignored local files do not block it. It checks remote ancestry, runs -[`release-check.sh`](../scripts/release-check.sh) once against that commit, -rechecks candidate and version state, pushes only that commit to `main`, then -creates and pushes one annotated tag. The tag message is the checked-in note. -It never commits or repairs the checkout. - -A successful intended tag push completes publication. The command reports the -version and commit immediately. No CI, module retrieval, remote read-back, or -hosted release object is required. The checker may be run separately for -diagnosis, but it is not a prerequisite: a fresh publication invocation -validates its candidate once. - -## 5. Recover from a failed or uncertain push - -The command performs one bounded authoritative-state check after a failed push. -If the main push failed but authoritative main equals the candidate, it proceeds. -If the tag push failed but its exact tag object reached the remote, it reports -success. Otherwise it reports branch-only, conflicting, or unknown state and -retains any local tag. - -Inspect exact refs before any retry. Set `release_version` to the attempted -version and compare main with the candidate from the command's report: +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 -release_version=v0.5.0 authoritative=https://gitea.maximumdirect.net/eric/ConfigKit.git -if remote_main=$(git ls-remote --refs --exit-code "$authoritative" refs/heads/main); then - printf 'remote main: %s\n' "$remote_main" -else - result=$? - if [ "$result" -eq 2 ]; then - printf 'authoritative main confirmed absent\n' >&2 - else - printf 'remote main state unknown; inspect before retrying\n' >&2 - fi -fi -ref="refs/tags/$release_version" -if remote_tag=$(git ls-remote --refs --exit-code "$authoritative" "$ref"); then - printf 'remote tag: %s\n' "$remote_tag" -else - result=$? - if [ "$result" -eq 2 ]; then - printf 'remote tag confirmed absent\n' - else - printf 'remote tag state unknown; do not reuse the version\n' >&2 - fi -fi -if local_tag=$(git rev-parse --verify "refs/tags/$release_version" 2>/dev/null); then - printf 'local tag object: %s\n' "$local_tag" -else - printf 'no local tag object\n' -fi +release_version=vMAJOR.MINOR.PATCH +git ls-remote --refs "$authoritative" refs/heads/main "refs/tags/$release_version" ``` -A query failure is not proof that the tag is absent. Compare the remote tag -object ID with the local annotated tag object ID; its peeled commit alone is -insufficient to establish identical publication. A conflicting published tag -must not be moved or replaced. If remote main advanced but the tag was not -published, report that partial progress. Only after confirming the local tag -was never published or used by another channel may a maintainer remove it: +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 subsequent invocation revalidates the candidate; a correctly published tag -needs no rerun. Do not roll back main or automatically delete a tag. Transport -or authentication failure alone does not require a new version. A defect in -published source requires a corrected commit, a new semantically appropriate -version and note, and the release procedure again. +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 -Only 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. -Private-host credentials must already be configured. For direct-origin -verification, set the version to the release just published (replace the sample -below), then use a temporary consumer module and check the exact module path -and version reported by `go list -m`: +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=v0.5.0 +release_version=vMAJOR.MINOR.PATCH consumer_dir=$(mktemp -d) ( cd "$consumer_dir" diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index a899e92..8c3e7d2 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -93,7 +93,7 @@ Completion evidence: policy and roadmap agree on order, gates, compatibility, and completion; no policy passage still describes the retired script or its stronger automated guards as current behavior. -## Stage 4: Replace the maintainer release runbook +## Stage 4: Replace the maintainer release runbook — Complete 1. Revise [the runbook](../release.md) into copyable steps for the adopted process. During this cutover, move `go.mod` from its current minimum to the diff --git a/go.mod b/go.mod index 13bda5a..dbe7d0f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gitea.maximumdirect.net/eric/ConfigKit -go 1.25.0 +go 1.26.0 require go.yaml.in/yaml/v4 v4.0.0-rc.6