Prepare source release validation

This commit is contained in:
2026-09-06 22:47:12 +00:00
parent 2836f825b6
commit f42ecc70c3
21 changed files with 721 additions and 1205 deletions

240
docs/release.md Normal file
View File

@@ -0,0 +1,240 @@
# 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 source tag and the
matching checked-in release note are the deliverables. There is no application
binary, container image, generated package, or separate registry publication.
## Release identity and support
- Module: `gitea.maximumdirect.net/eric/ConfigKit`
- Primary branch: `main`
- Authoritative remote: `origin`, expected to fetch from
`https://gitea.maximumdirect.net/eric/ConfigKit.git`
- Tags: stable Semantic Versions such as `v0.1.0`; prerelease tags are not supported
- Minimum Go language/toolchain line: Go 1.25, tested with a supported patched
Go 1.25 toolchain and one newer supported toolchain
- Build targets: Linux and macOS on amd64 and arm64
The automated checker cross-builds all four targets. Before release, the maintainer
must also obtain native test, race, symlink, and permission-fixture evidence on Linux
and macOS; cross-compilation is not native execution evidence.
## 1. Prepare access and tools
The release maintainer owns this procedure. Work in a dedicated clone with Git, Bash,
and two supported Go toolchains available. Configure private-host Git and Go module
access through the normal credential manager before starting. Keep tokens, passwords,
SSH material, and environment dumps outside the repository and command transcript.
Verify access without displaying credentials:
```sh
git remote get-url origin
git ls-remote origin HEAD
```
The remote URL must equal the authoritative URL above. Select the latest supported
patch in the Go 1.25 line and a newer supported toolchain. The values below are the
toolchains used while this runbook was introduced; update them to supported patch
releases during candidate preparation and record the actual versions in the release
note or validation record.
```sh
export CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5
export CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go version
GOTOOLCHAIN="$CONFIGKIT_NEWER_TOOLCHAIN" go version
GOTOOLCHAIN="$CONFIGKIT_BASELINE_TOOLCHAIN" go mod download
GOTOOLCHAIN="$CONFIGKIT_NEWER_TOOLCHAIN" go mod download
```
Those commands explicitly acquire toolchains and dependencies. The checker itself
sets `GOPROXY=off` and fails if its required cache is incomplete.
## 2. Select the version and review compatibility
Choose an unused stable version and compare the candidate with the previous tag.
For the first release, review the whole public surface and current contracts.
```sh
release_version=v0.1.0
printf '%s\n' "$release_version" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
git show-ref --verify --quiet "refs/tags/$release_version" && exit 1 || :
if git ls-remote --exit-code --tags origin "refs/tags/$release_version" >/dev/null; then
printf 'remote tag already exists: %s\n' "$release_version" >&2
exit 1
else
test $? -eq 2
fi
git log --oneline --decorate --no-merges
```
Review exported Go signatures and ownership, YAML and scalar behavior, precedence,
diagnostic codes, minimum Go version, and platform support. Before `v1.0.0`, any
intentional incompatible minor-release change must be explained with consumer action;
patch releases must remain compatible within their minor line. Review the maintained
consumer examples and the migration guide even when signatures did not change.
Reassess `go.yaml.in/yaml/v4` rather than using `@latest` implicitly:
```sh
go list -m -versions go.yaml.in/yaml/v4
go list -m -json go.yaml.in/yaml/v4@latest
```
If changing the explicit pin, make that a reviewed dependency change and rerun the
complete checker, especially parser policy, limits, diagnostics, and rendering tests.
If retaining a prerelease pin, record that decision in Compatibility.
## 3. Write the release note
Create `docs/releases/$release_version.md` before selecting the candidate. It must
begin with `# ConfigKit $release_version` and contain `Summary`, `Compatibility`,
`Upgrade`, and `Changes` sections. Use “No action required” where appropriate. Link
to current canonical contracts instead of copying their definitions. Do not create a
release note until there is a real candidate and reviewed content.
## 4. Validate the source candidate
Run the complete checker from the repository root after dependencies have been
acquired. It validates module manifests in a disposable copy, both supported Go
toolchains, offline default tests, the native race detector, vet, native builds,
CGO-disabled cross-builds, formatting and whitespace (including untracked files),
examples, package isolation, and local documentation links.
```sh
./scripts/release-check.sh
```
`go vet` is the repository's static-analysis baseline. No additional linter is
claimed. Links to current ConfigKit files and headings are strict. Recorded links to
sibling consumer source are checked when those checkouts exist and are otherwise
inapplicable in an isolated clone; their immutable revisions remain recorded in the
extraction inventory.
Complete these manual gates and record their evidence with the candidate:
- The maintainer reviews compatibility and downstream migration impact against
`docs/config.md`, Go API comments, and the consumer guide.
- The maintainer inspects `git status --short`, `git diff`, and the candidate file
list for credentials, private data, generated output, unintended files, and
unrelated changes.
- The maintainer records successful native checker runs on Linux and macOS, including
filesystem capability tests. If either host is unavailable, release remains
blocked unless platform support is formally narrowed in current documentation.
- The maintainer removes completed, obsolete documents under `docs/roadmap/`, repairs
inbound links, and reruns the checker. ADR rationale remains historical.
- The maintainer reviews the exact version-matched release note for accuracy and
confirms it describes only deliverables that will actually be published.
## 5. Commit, push, and record the candidate
Commit all candidate source, contract, example, roadmap-retirement, and release-note
changes, rerun the checker against that clean commit, then push only `main` and record
the exact commit in Git metadata local to the clone:
```sh
git add .
git commit --message "Prepare ConfigKit $release_version"
./scripts/release-check.sh
test -z "$(git status --porcelain=v1 --untracked-files=all)"
git push origin HEAD:refs/heads/main
git rev-parse HEAD | tee .git/configkit-release-candidate
candidate_commit=$(cat .git/configkit-release-candidate)
```
If review changes the source after this point, create and validate a new candidate;
do not reuse the recorded commit.
## 6. Run publication guards
Immediately before tagging, fetch the authoritative branch and tags, restore the
recorded variables if this is a new shell, and execute every guard:
```sh
release_version=v0.1.0
candidate_commit=$(cat .git/configkit-release-candidate)
git fetch --prune origin main
git fetch --tags origin
test "$(git branch --show-current)" = main
test -z "$(git status --porcelain=v1 --untracked-files=all)"
test "$(git rev-parse HEAD)" = "$candidate_commit"
test "$(git rev-parse refs/remotes/origin/main)" = "$candidate_commit"
test "$(git remote get-url origin)" = 'https://gitea.maximumdirect.net/eric/ConfigKit.git'
printf '%s\n' "$release_version" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
test -f "docs/releases/$release_version.md"
test "$(sed -n '1p' "docs/releases/$release_version.md")" = "# ConfigKit $release_version"
for heading in Summary Compatibility Upgrade Changes; do
grep -Fxq "## $heading" "docs/releases/$release_version.md"
done
git show-ref --verify --quiet "refs/tags/$release_version" && exit 1 || :
if git ls-remote --exit-code --tags origin "refs/tags/$release_version" >/dev/null; then
printf 'remote tag already exists: %s\n' "$release_version" >&2
exit 1
else
test $? -eq 2
fi
```
Confirm the recorded automated and manual approvals apply to exactly
`$candidate_commit`. A successful older run is not evidence for a changed commit.
## 7. Tag and publish the exact ref
Create one annotated tag at the guarded commit, verify its peeled target, and push
only that ref. Do not use `git push --tags`.
```sh
git tag --annotate "$release_version" --message "ConfigKit $release_version" "$candidate_commit"
test "$(git rev-list -n 1 "$release_version")" = "$candidate_commit"
git push origin "refs/tags/$release_version:refs/tags/$release_version"
```
The pushed tag completes the declared source-only publication. No Gitea release
object or binary upload is required.
## 8. Verify publication and clean consumer retrieval
Verify the authoritative tag and then retrieve that exact version into a new module
without a local `replace` directive. Private-host credentials must already be
configured. Use direct origin retrieval unless an explicitly approved internal Go
proxy is the publication channel.
```sh
remote_tag_commit=$(git ls-remote origin "refs/tags/$release_version^{}" | awk '{print $1}')
test "$remote_tag_commit" = "$candidate_commit"
consumer_dir=$(mktemp -d)
(
cd "$consumer_dir"
go mod init example.invalid/configkit-release-check
GOPRIVATE=gitea.maximumdirect.net GOPROXY=direct go get "gitea.maximumdirect.net/eric/ConfigKit@$release_version"
cat > main.go <<'EOF'
package main
import configkit "gitea.maximumdirect.net/eric/ConfigKit"
func main() {
_ = configkit.NoSource()
}
EOF
gofmt -w main.go
go test ./...
go build ./...
go list -m -f '{{.Path}} {{.Version}}' gitea.maximumdirect.net/eric/ConfigKit
)
rm -rf -- "$consumer_dir"
```
The final `go list` output must show the exact module path and `$release_version`.
Record the result with the release evidence.
## 9. Handle failures without replacement
Before publication, fix the candidate, rerun every gate, commit, push, and record a
new exact candidate. An unpublished local tag may be deleted only after verifying it
was never pushed or used by another release channel.
After publication, never move, delete, or recreate the tag and never replace its
release note. Correct the defect on `main`, choose a new semantically appropriate
version, and repeat this procedure. A supported advisory or deprecation mechanism
may warn consumers about the old version but cannot change its contents.