Files
configkit/docs/release.md

371 lines
18 KiB
Markdown
Raw Normal View History

2026-09-06 22:47:12 +00:00
# ConfigKit Release Procedure
This is the maintainer runbook for ConfigKit's [release policy](policy/release.md).
A release publishes the Go source under a new Git tag, with a matching release
note committed in the repository. The tag is annotated: it includes the release
note text and points to the released commit. Published tags and contents are
immutable. There is no binary, container image, generated package, or separate
registry publication. Beginning with v1.0.0, maintainers also deliver a Gitea
release page containing the complete checked-in note after source publication.
Source publication and hosted delivery have separate outcomes.
A **candidate** is the committed revision you intend to release. The
**authoritative remote** is the repository where ConfigKit publishes releases;
its exact push URL is listed below. The **consumer minimum** is the oldest Go
version supported for applications using ConfigKit.
2026-09-06 22:47:12 +00:00
## Release identity and support
- Module: `go.maximumdirect.net/configkit`
2026-09-06 22:47:12 +00:00
- Primary branch: `main`
- Authoritative remote: `origin`
- Authoritative push URL: `ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git`
- Tags: stable Semantic Versions such as `v0.5.0`; no prereleases
- Minimum Go version: the first stable patch of the selected Go series, or a
2026-10-09 04:23:15 +00:00
higher patch when required by a dependency or specific correctness fix, as
recorded in `go.mod`; see the
[consumer minimum policy](policy/release.md#consumer-minimum-and-release-toolchain)
- Release-gated targets: Linux amd64 and arm64
- Best-effort targets: macOS amd64 and arm64, under the
2026-09-12 18:30:59 +00:00
[platform support policy](policy/release.md#platform-support)
2026-09-06 22:47:12 +00:00
Release validation runs the offline Go test suite once with the race detector,
then a full vet pass and package compilation for both Linux targets with cgo
disabled (`CGO_ENABLED=0`), followed by the selected committed-file checks
described in [publication](#4-publish-the-committed-candidate). macOS and CI are
not release gates; do not wait for or poll CI.
2026-09-06 22:47:12 +00:00
The vanity module path is the name applications put in Go imports. Source/tag
browser links and public Go retrieval use the Forge HTTPS repository
independently of SSH publication.
## 1. Select Go and prepare the tool
2026-09-06 22:47:12 +00:00
Use a clean checkout with Git, Bash 3.2 or newer, and a native compiler for race
2026-10-09 04:23:15 +00:00
tests. Choose the release type before selecting Go. For a minor or major release,
select the newest stable Go series available in the development environment.
For a patch release, retain the series recorded in `go.mod` and leave its consumer
minimum unchanged. Follow the
[toolchain selection policy](policy/release.md#configkit-toolchain-selection).
Choose the latest compatible patch installed locally in that Go series. Put
its `go` executable first on `PATH` and ensure it meets the minimum in
`go.mod`. The same `go` executable must build ReleaseKit and run its child
validation commands. Set local toolchain selection and verify it:
```sh
export GOTOOLCHAIN=local
go version
go env GOVERSION
command -v go
```
2026-10-09 04:23:15 +00:00
Only when a minor or major release advances the consumer minimum to a newer Go
series, derive that series' initial stable release from the selected executable:
2026-09-06 22:47:12 +00:00
```sh
selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
selected_series=${selected_go#go}
selected_series=${selected_series%.*}
2026-10-09 04:23:15 +00:00
release_minimum="$selected_series.0"
go mod edit -go="$release_minimum"
2026-09-06 22:47:12 +00:00
```
2026-10-09 04:23:15 +00:00
If a dependency or identified correctness fix requires a higher patch minimum,
use that required version instead and document the reason. When the series is
unchanged, leave the minimum unchanged unless a minor or major release
deliberately changes it under the
[consumer minimum policy](policy/release.md#consumer-minimum-and-release-toolchain).
For example:
| Release situation | Go used for validation | Consumer minimum |
| --- | --- | --- |
| Patch release in the 1.26 series | Latest compatible patch installed locally, such as 1.26.8 | Keep the existing `go.mod` minimum unchanged. |
| Minor/major release staying in 1.26 | Latest compatible patch installed locally | Normally retain the minimum; any deliberate change follows the linked policy. |
| Minor/major release adopting a newer installed stable series | Latest compatible patch installed locally in that series | Normally declare that series' initial stable patch, or the higher patch required by a dependency or identified fix. |
Using Go 1.26.8 for validation can therefore leave `go 1.26.0` unchanged. Users
need the declared minimum, while maintainers validate with their selected patch.
There is no separate requirement to validate with Go 1.26.0.
2026-10-09 04:23:15 +00:00
Prepare dependencies whether or not the minimum changes:
```sh
go mod download
```
Run the module-selected ReleaseKit tool from the repository root. Go builds
and caches the executable; no temporary executable or launcher is needed.
These commands use the version recorded in `go.mod`:
2026-09-06 22:47:12 +00:00
```sh
go list -m go.maximumdirect.net/releasekit
go tool releasekit version
2026-09-06 22:47:12 +00:00
```
This repository selects ReleaseKit v0.8.0. `go tool releasekit` preserves the
tool's exit status and publication report; `go run` maps a nonzero child exit
status to 1. ReleaseKit's child Go commands
2026-10-08 17:08:35 +00:00
use the `go` found on `PATH` with `GOTOOLCHAIN=local`; keep the selected
executable first on `PATH` through the publication command.
2026-09-06 22:47:12 +00:00
For hosted delivery, save the Gitea API token in the ignored local file
`.releasekit/gitea-token` with owner-only permissions. The profile uses it when
`RELEASEKIT_GITEA_TOKEN` is unset or blank; a nonblank environment value takes
precedence. The file path resolves from the repository root, and ReleaseKit
trims surrounding whitespace from its contents. Keep credentials out of release
notes, diagnostic labels, command arguments, and commits. The selected
`forbidden_files` check also rejects a committed token at that exact path;
it does not scan other files or history. Source publication uses Git
authentication and does not read or require this token.
Verify access to the authoritative remote without displaying credentials:
2026-09-06 22:47:12 +00:00
```sh
git ls-remote ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git HEAD refs/heads/main
```
2026-09-06 22:47:12 +00:00
Publication requires explicit authorization. Local implementation commits do
not authorize a push or release.
2026-09-06 22:47:12 +00:00
## 2. Prepare and commit the candidate
2026-09-06 22:47:12 +00:00
Choose a stable version that has not been used locally or on the publication
remote. Compare the candidate with the previous tag. Review public Go APIs and
value ownership, YAML and resolution rules, diagnostic contracts, the minimum
Go version, and platform support. Review maintained consumer examples and the
2026-10-09 04:23:15 +00:00
migration guidance. Explain any consumer-minimum increase and required consumer
action under the
[consumer minimum policy](policy/release.md#consumer-minimum-and-release-toolchain).
2026-09-06 22:47:12 +00:00
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://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/COPYRIGHT) | [LICENSE](https://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/LICENSE) | [ATTRIBUTION](https://forge.maximumdirect.net/base-layer/configkit/src/tag/VERSION/docs/oss/attribution.md)
```
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
2026-10-09 04:23:15 +00:00
tasks, not separate approval gates. Any `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 ssh://git@forge.maximumdirect.net:22222/base-layer/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
2026-09-06 22:47:12 +00:00
Once publication is authorized and the candidate is on the publication remote's
`main`, run the module-selected ReleaseKit tool once to validate and publish it:
```sh
go tool releasekit publish vMAJOR.MINOR.PATCH
```
2026-09-06 22:47:12 +00:00
Replace the placeholder with the selected version. ReleaseKit checks the
push URL Git will actually use, after URL rewrites, against `expected_push_url`.
It 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 also checks the committed candidate for the forbidden token path,
filesystem replacements in module declarations, and local Markdown destinations
and supported heading fragments. Markdown links outside the checkout are
reported as out of scope without failing validation. These file checks inspect
the committed candidate, not uncommitted or ignored working-tree content.
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.
Use the optional `check` command for diagnosis when useful. It validates without
publishing and is not required before `publish`, which performs its own checks:
2026-09-06 22:47:12 +00:00
```sh
go tool releasekit check vMAJOR.MINOR.PATCH
2026-09-06 22:47:12 +00:00
```
For `publish`, interpret the program's exit status as follows:
| Status | Meaning | Next action |
| --- | --- | --- |
| `0` | Publication succeeded. | The source release is complete. Continue with hosted delivery. |
| `1` | A known publication failure or conflict was reported. | Read the report and correct the stated problem. Preserve any published tag. |
| `2` | The command, profile, or version is invalid. | Correct the command or configuration before trying again. |
| `3` | The tag push outcome is still unknown. | Follow the recovery procedure below before retrying or declaring success. |
Keep both the program's exit status and its diagnostic report. Using `go run`
would lose the distinction between its nonzero exit statuses.
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. Deliver the hosted release page
After source publication succeeds, run local hosted delivery from the repository
root. Keep the worktree clean and `HEAD` at the published tag's commit. Use the
module-selected tool as in step 1. For the first release using this workflow:
```sh
go tool releasekit deliver v1.0.0
```
For subsequent releases, substitute the selected version. The profile selects
Gitea at `https://forge.maximumdirect.net`, owner `base-layer`, repository
`configkit`. With no build targets, delivery creates or accepts a stable,
non-draft release page containing the complete committed note, including its
licensing footer. It creates no binary assets, receipts, or checksum manifest.
An existing page with matching content is accepted; conflicting content is not
overwritten.
Delivery automatically performs an authenticated, read-only repository preflight
before hosted writes. A separate preflight before source publication is not a
release gate. Delivery does not rerun candidate validation, publish a tag, or
wait for CI. Hosted-service availability and delivery credentials do not gate
source publication. Report source-publication success and hosted-delivery status
separately.
| Delivery status | Meaning and next action |
| --- | --- |
| `0` | Delivery is complete; report the hosted release URL. |
| `1` | Preflight was unavailable, or delivery failed, conflicted, or made partial progress. Read the reported state and correct the cause; investigate conflicts before retrying. |
| `2` | Usage, profile, credential loading, or preflight configuration/access failed. Correct the reported problem before retrying delivery. |
| `3` | A hosted write has an unresolved outcome. Inspect the hosted release and resolve that state before retrying. |
A delivery problem leaves the successfully published source tag intact. Retry
delivery with the same immutable version after correcting the cause and
resolving any uncertain write. For a later retry, use a clean checkout of the
original tag, fetching that tag if needed. Use that checkout's module-selected
CLI, prepare its dependencies and tool as in step 1 without changing the tagged
module files, and make the token available there. For example, with the tag
available locally:
```sh
release_version=v1.0.0
git switch --detach "$release_version"
```
After preparing the tool in that tagged checkout:
```sh
go tool releasekit deliver "$release_version"
```
Do not rerun `publish`, move a tag, or replace a hosted page to repair delivery.
If published source or content needs correction, prepare a new appropriate
version through the normal release procedure.
## 6. Recover from a failed or uncertain tag push
2026-09-06 22:47:12 +00:00
ReleaseKit attempts to save publication evidence before pushing a tag and
reports the absolute record path, or a recording warning, separately from the
publication outcome. A recording failure does not undo a successful publication
or change its exit status. Confirmed publication still completes the source
release; hosted delivery remains a separate operation.
2026-09-06 22:47:12 +00:00
Records default to a private store under the repository's Git common directory,
shared by linked worktrees. `publish --record-dir DIR` can select another
location; relative paths resolve from the source repository root. Records can
contain exact endpoints, including credentials. Keep them private and remove
them when their recovery value expires. There is no new tracked storage,
credential-preflight gate, or required recording-success check.
After a failed push, ReleaseKit makes one bounded observation of the original
publication endpoint. If the outcome remains unknown, use the explicitly saved
record rather than rerunning publication. Copy the recovery command printed by
ReleaseKit, or use the module-selected tool:
2026-09-06 22:47:12 +00:00
```sh
publication_record='/absolute/path/from/the/publication/report.json'
go tool releasekit reconcile --record "$publication_record"
```
If the path was lost, discover local attempts without contacting a remote:
```sh
go tool releasekit publication-attempts
2026-09-06 22:47:12 +00:00
```
Select the record for the intended attempt from its reported version, candidate,
and tag evidence; discovery does not automatically choose the latest attempt.
For an alternate store, supply `publication-attempts --record-dir DIR`.
Both commands support optional `--json` output. Treat saved records as private
regardless of the CLI's credential-redacted diagnostic output.
Reconciliation observes the captured endpoint and exact tag object using current
supported Git transport settings. It does not load the current profile, require
the original `HEAD`, rerun candidate validation, change tags, or contact Gitea
hosted delivery. It needs no clean candidate checkout. Use ReleaseKit v0.8.0 or
another CLI supporting the saved record format, including format 2 where exact
non-UTF-8 endpoint/path bytes were retained.
| Reconciliation status | Meaning and next action |
| --- | --- |
| `0` | The intended tag object is published. The source release is complete; continue with hosted delivery from its clean tagged checkout. |
| `1` | The tag is currently absent or conflicting. Read the reported state. Investigate conflicts; absence alone does not prove that the version was never published. |
| `2` | The record cannot be read/decoded, or the invocation is invalid. Correct the path, access, CLI compatibility, or command. |
| `3` | Observation remains unresolved, including insufficient evidence or cancellation. Correct the transport or other reported cause before another explicit observation; do not blindly republish. |
These are recovery actions after failure, not read-back requirements after a
successful push. A failed observation leaves the result unresolved. Do not poll
CI or move a tag to settle uncertainty.
When no usable record exists (for example, storage failed or an older CLI made
the attempt), use the captured endpoint and exact tag-object ID from the original
report for a manual authoritative observation. Checking only the peeled commit
would not establish that the intended annotated tag object arrived. Do not
substitute current profile or local tag state for missing original evidence.
Only after establishing that a retained local tag was never published or used
by another release channel may it be removed before retrying publication:
2026-09-06 22:47:12 +00:00
```sh
release_version='vMAJOR.MINOR.PATCH'
git tag -d "$release_version"
go tool releasekit publish "$release_version"
2026-09-06 22:47:12 +00:00
```
Replace the placeholder with the original attempt's version.
The next `publish` revalidates the candidate and publication guards. A conflicting
published tag must never be moved or reused. A defect in published source
requires a corrected commit, a new appropriate version, and a matching note.
A validation failure after the candidate reached `main` leaves an untagged
candidate there; correct it in a new commit rather than rewriting `main`.
2026-09-06 22:47:12 +00:00
## 7. Optional consumer verification
2026-09-06 22:47:12 +00:00
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=vMAJOR.MINOR.PATCH
consumer_dir=$(mktemp -d)
(
cd "$consumer_dir"
go mod init example.invalid/configkit-release-check
GOWORK=off GOPROXY=direct go get "go.maximumdirect.net/configkit@$release_version"
GOWORK=off go list -m -f '{{.Path}} {{.Version}}' go.maximumdirect.net/configkit
)
```
The output should show the exact module path and selected version. Report this
optional check separately from publication success.