Replace the release procedure with ReleaseKit

This commit is contained in:
2026-10-08 16:42:56 +00:00
parent 0e92085002
commit 1dc50d8cdf
3 changed files with 164 additions and 132 deletions

View File

@@ -1,7 +1,7 @@
# ConfigKit Release Procedure # ConfigKit Release Procedure
This is the maintainer runbook for ConfigKit's [release policy](policy/release.md). 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, matching checked-in release note are the deliverables; there is no binary,
container image, generated package, or separate registry publication. 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` - Module: `gitea.maximumdirect.net/eric/ConfigKit`
- Primary branch: `main` - Primary branch: `main`
- Authoritative remote: `origin`, with fetch and push destination - Authoritative remote: `origin`
`https://gitea.maximumdirect.net/eric/ConfigKit.git` - Authoritative push URL: `https://gitea.maximumdirect.net/eric/ConfigKit.git`
- Tags: stable Semantic Versions such as `v0.5.0`; no prereleases - 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 - Release-gated targets: Linux amd64 and arm64
- Best-effort targets: macOS amd64 and arm64, under the - Best-effort targets: macOS amd64 and arm64, under the
[platform support policy](policy/release.md#platform-support) [platform support policy](policy/release.md#platform-support)
The checker runs one offline race suite, one full vet pass, and CGO-disabled Release validation runs one offline race-enabled Go suite, one full vet pass,
builds for both Linux targets. It reports skipped filesystem cases. macOS checks and CGO-disabled package compilation for both Linux targets. macOS and CI are
and newer-toolchain tests are optional. CI is never a readiness or completion not release gates; do not wait for or poll CI.
gate; do not wait for or poll it.
## 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, Use a clean checkout with Git, Bash 3.2 or newer, and a native compiler for race
and a native compiler for race tests. Configure private-host access through the tests. Select the newest stable Go series available in the development
normal credential manager. Keep credentials and environment dumps outside the environment and put its latest locally available compatible patch first on
repository and command transcript. `PATH`. The same `go` executable must build ReleaseKit and run its child
validation commands. Set local toolchain selection and verify it:
Verify access without displaying credentials:
```sh ```sh
git remote get-url origin export GOTOOLCHAIN=local
git ls-remote origin HEAD go version
go env GOVERSION
command -v go
``` ```
The fetch URL and all configured push destinations must resolve to the At release preparation, record the selected series' initial stable release in
authoritative URL above. The publication command checks this again. Acquire the `go.mod`. These commands derive that minimum from the selected executable. The
selected toolchain and dependencies explicitly before offline validation: 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 ```sh
export CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.14 selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go version selected_series=${selected_go#go}
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go mod download 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 For example, Go 1.26.8 records `go 1.26.0` unless a higher minimum is required.
survey is required. Dependencies must be cached; the checker sets `GOPROXY=off`. The `go` directive is ConfigKit's consumer minimum, not the toolchain pin. A
Publication requires user authorization. Standing authorization for local minimum increase requires consumers to use the newer Go version; before v1.0.0,
commits does not authorize a push or release. 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. ```sh
Review exported Go signatures and ownership, YAML and scalar semantics, releasekit_tmp=$(mktemp -d "${TMPDIR:-/tmp}/configkit-releasekit.XXXXXX")
precedence, diagnostic codes, minimum Go version, and platform support. A releasekit_bin="$releasekit_tmp/releasekit"
signature-preserving change may still affect consumers. Before `v1.0.0`, go build -o "$releasekit_bin" gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
intentional incompatible minor-release changes need a note explaining impact go list -m gitea.maximumdirect.net/eric/ReleaseKit
and consumer action; patch releases remain compatible within their minor line. "$releasekit_bin" version
Review the maintained consumer examples and migration guide. ```
When dependencies change, review parser behavior and migration impact as part The module-selected executable is ReleaseKit v0.2.0. Do not use `go run` or
of that change. Module tidiness belongs to dependency-change review, not the `go tool` for publication: the native executable preserves its exit status and
release checker. Do not repeat a dependency-version survey for every release. 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/<version>.md` for the selected version. Its first line ```sh
must be `# ConfigKit <version>`. Give a meaningful summary and include git ls-remote https://gitea.maximumdirect.net/eric/ConfigKit.git HEAD refs/heads/main
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.
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/<version>.md`. Its first line must be exactly
`# ConfigKit <version>`, and it must end with the license and attribution footer:
```markdown ```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) [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 Replace `VERSION` with the full tag and confirm the three target files exist in
the URLs. The future tag need not resolve remotely yet; this review adds no the candidate. Retire completed roadmaps, preserve unfinished and deferred
network gate. ConfigKit uses checked-in notes without hosted release objects. work, and repair affected links. Review the complete candidate for unintended
If a hosted page is used later, reuse the note and preserve its footer. Do not files and credentials, then commit it on `main`. These are ordinary preparation
rewrite historical notes or add downloadable license assets. 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 ## 4. Publish the committed candidate
Before publication, review compatibility, release-note accuracy, and the file With the authorized candidate already on authoritative `main`, invoke the
list for unintended content or credentials. Retire completed roadmaps as part of module-selected native executable exactly once:
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:
```sh ```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 Replace the placeholder with the selected version. ReleaseKit checks the
and on the authoritative remote, and a matching note in the captured commit. effective push URL against `expected_push_url`, verifies the candidate on
Ignored local files do not block it. It checks remote ancestry, runs `main`, and checks the release-note H1 for the product and version. It then
[`release-check.sh`](../scripts/release-check.sh) once against that commit, runs the selected race-enabled standard suite once with test-time vet disabled,
rechecks candidate and version state, pushes only that commit to `main`, then one full vet pass, and CGO-disabled package compilation for Linux amd64 and
creates and pushes one annotated tag. The tag message is the checked-in note. arm64. It publishes only the annotated tag for that candidate. Confirmed
It never commits or repairs the checkout. 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.
A successful intended tag push completes publication. The command reports the The optional `check`
version and commit immediately. No CI, module retrieval, remote read-back, or command is available for diagnosis but is not a separate required pass:
hosted release object is required. The checker may be run separately for
diagnosis, but it is not a prerequisite: a fresh publication invocation ```sh
validates its candidate once. "$releasekit_bin" check vMAJOR.MINOR.PATCH
```
## 5. Recover from a failed or uncertain push
Status 0 confirms publication, status 1 reports a known publication failure or
The command performs one bounded authoritative-state check after a failed push. conflict, status 2 reports usage, profile, or version errors, and status 3 means
If the main push failed but authoritative main equals the candidate, it proceeds. the push outcome remains unknown. Preserve both the native exit status and its
If the tag push failed but its exact tag object reached the remote, it reports diagnostics.
success. Otherwise it reports branch-only, conflicting, or unknown state and
retains any local tag. 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
Inspect exact refs before any retry. Set `release_version` to the attempted remote read-back or consumer retrieval; those checks remain optional.
version and compare main with the candidate from the command's report:
## 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 ```sh
release_version=v0.5.0
authoritative=https://gitea.maximumdirect.net/eric/ConfigKit.git authoritative=https://gitea.maximumdirect.net/eric/ConfigKit.git
if remote_main=$(git ls-remote --refs --exit-code "$authoritative" refs/heads/main); then release_version=vMAJOR.MINOR.PATCH
printf 'remote main: %s\n' "$remote_main" git ls-remote --refs "$authoritative" refs/heads/main "refs/tags/$release_version"
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
``` ```
A query failure is not proof that the tag is absent. Compare the remote tag Compare the `main` object ID with the reported candidate commit and the tag
object ID with the local annotated tag object ID; its peeled commit alone is object ID with the reported tag-object ID. `--refs` returns the annotated tag
insufficient to establish identical publication. A conflicting published tag object itself, not merely its peeled commit. If the tag object matches, the
must not be moved or replaced. If remote main advanced but the tag was not release is published. If the query fails, the result is conflicting, or the
published, report that partial progress. Only after confirming the local tag endpoint or candidate differs, stop and resolve the state before retrying. If
was never published or used by another channel may a maintainer remove it: 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 ```sh
git tag -d "$release_version" git tag -d "$release_version"
"$releasekit_bin" publish "$release_version"
``` ```
A subsequent invocation revalidates the candidate; a correctly published tag A validation failure after the candidate reached `main` leaves an untagged
needs no rerun. Do not roll back main or automatically delete a tag. Transport candidate there. Correct it with a new commit rather than rewriting or rolling
or authentication failure alone does not require a new version. A defect in back `main`. A conflicting published tag must never be moved or reused. A
published source requires a corrected commit, a new semantically appropriate defect in published source requires a corrected commit, a new semantically
version and note, and the release procedure again. appropriate version, and a matching release note.
## 6. Optional consumer verification ## 6. Optional consumer verification
Only when useful or requested, read back the remote tag or retrieve the exact 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 version into a new module without a local `replace` directive. These checks are
are optional and non-gating. Do not poll CI or wait for module propagation. optional and non-gating. Do not poll CI or wait for module propagation. For
Private-host credentials must already be configured. For direct-origin direct-origin verification, use the exact published version in a temporary
verification, set the version to the release just published (replace the sample consumer module:
below), then use a temporary consumer module and check the exact module path
and version reported by `go list -m`:
```sh ```sh
release_version=v0.5.0 release_version=vMAJOR.MINOR.PATCH
consumer_dir=$(mktemp -d) consumer_dir=$(mktemp -d)
( (
cd "$consumer_dir" cd "$consumer_dir"

View File

@@ -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 and completion; no policy passage still describes the retired script or its
stronger automated guards as current behavior. 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 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 process. During this cutover, move `go.mod` from its current minimum to the

2
go.mod
View File

@@ -1,6 +1,6 @@
module gitea.maximumdirect.net/eric/ConfigKit 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 require go.yaml.in/yaml/v4 v4.0.0-rc.6