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
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/<version>.md` for the selected version. Its first line
must be `# ConfigKit <version>`. 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/<version>.md`. Its first line must be exactly
`# ConfigKit <version>`, 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"

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

2
go.mod
View File

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