371 lines
18 KiB
Markdown
371 lines
18 KiB
Markdown
# 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.
|
|
|
|
## Release identity and support
|
|
|
|
- Module: `go.maximumdirect.net/configkit`
|
|
- 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
|
|
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
|
|
[platform support policy](policy/release.md#platform-support)
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
Use a clean checkout with Git, Bash 3.2 or newer, and a native compiler for race
|
|
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
|
|
```
|
|
|
|
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:
|
|
|
|
```sh
|
|
selected_go=$(GOTOOLCHAIN=local go env GOVERSION)
|
|
selected_series=${selected_go#go}
|
|
selected_series=${selected_series%.*}
|
|
release_minimum="$selected_series.0"
|
|
go mod edit -go="$release_minimum"
|
|
```
|
|
|
|
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.
|
|
|
|
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`:
|
|
|
|
```sh
|
|
go list -m go.maximumdirect.net/releasekit
|
|
go tool releasekit version
|
|
```
|
|
|
|
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
|
|
use the `go` found on `PATH` with `GOTOOLCHAIN=local`; keep the selected
|
|
executable first on `PATH` through the publication command.
|
|
|
|
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:
|
|
|
|
```sh
|
|
git ls-remote ssh://git@forge.maximumdirect.net:22222/base-layer/configkit.git HEAD refs/heads/main
|
|
```
|
|
|
|
Publication requires explicit authorization. Local implementation commits do
|
|
not authorize a push or release.
|
|
|
|
## 2. Prepare and commit the candidate
|
|
|
|
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
|
|
migration guidance. Explain any consumer-minimum increase and required consumer
|
|
action under the
|
|
[consumer minimum policy](policy/release.md#consumer-minimum-and-release-toolchain).
|
|
|
|
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
|
|
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
|
|
|
|
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
|
|
```
|
|
|
|
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:
|
|
|
|
```sh
|
|
go tool releasekit check vMAJOR.MINOR.PATCH
|
|
```
|
|
|
|
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
|
|
|
|
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.
|
|
|
|
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:
|
|
|
|
```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
|
|
```
|
|
|
|
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:
|
|
|
|
```sh
|
|
release_version='vMAJOR.MINOR.PATCH'
|
|
git tag -d "$release_version"
|
|
go tool releasekit publish "$release_version"
|
|
```
|
|
|
|
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`.
|
|
|
|
## 7. Optional consumer verification
|
|
|
|
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.
|