10 KiB
ReleaseKit release procedure
ReleaseKit's deliverables are its source tag and a hosted Gitea page containing
the complete checked-in release note. Deliver the page locally after publication
or asynchronously through the prepared
Woodpecker workflow.
Hosted-page delivery has a separate status from source-release completion and
carries no binary assets. The authoritative repository is origin at
ssh://git@forge.maximumdirect.net:22222/base-layer/releasekit.git, and the release
branch is main. The hosted Gitea destination is
https://forge.maximumdirect.net/base-layer/releasekit. A release completes
when the intended annotated tag is successfully
pushed, or when one exact-object recovery query confirms that the push
succeeded. CI and consumer installation are not completion checks.
Prepare a candidate
For local hosted-page delivery, save a Gitea personal access token in
.releasekit/gitea-token with owner-only file permissions. The file contains
only the token. ReleaseKit's profile selects this file as a fallback to
RELEASEKIT_GITEA_TOKEN; .releasekit/ is already ignored. Its optional
validation.forbidden_files selection checks that this path is absent from the
committed candidate. Source publication requires no delivery token. The
configuration reference owns credential selection and validator
semantics.
Use a clean clone on main. The chosen build toolchain is Go 1.26.10. Make that
local installation the active go command for preparation, building the release
executable, and candidate validation:
export GOTOOLCHAIN=local
go version
Record the reported version with the validation results. The Go support policy owns series selection, consumer minima, patch exceptions, and validation scope. The maintained source-delivery image and consumer template image follow the chosen build toolchain; update them together when that selection changes.
The current consumer minimum is Go 1.26.0, declared in go.mod.
Keep it for a release unless release preparation deliberately selects a newer
series under the Go support policy.
When increasing it, update go.mod to the first stable version of the selected
series, or to a higher patch when a dependency or specific fix requires one.
Make module changes before committing the candidate. Minimum increases must fit the
compatibility policy; before
v1.0.0, an increase belongs in a minor release.
Choose an unused stable vMAJOR.MINOR.PATCH version, and add
docs/releases/<version>.md with a first heading containing ReleaseKit and
the exact version. Summarize the change and any compatibility or upgrade impact.
End the note with the three license links at the immutable source tag:
[COPYRIGHT](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/vX.Y.Z/COPYRIGHT) |
[LICENSE](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/vX.Y.Z/LICENSE) |
[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/vX.Y.Z/docs/oss/attribution.md)
Replace every vX.Y.Z with the selected version. These are links to the same
release note's immutable source tree and also work when the note is used as a
hosted release body.
Commit the note and all candidate changes on main, then publish that commit
to origin/main:
git push origin main
ReleaseKit checks that the worktree is clean, the candidate is on main, the
authoritative branch identifies that commit, and the version is unused locally
and on origin.
Prepare dependencies, then publish
Dependency preparation is separate from the offline candidate validation. Run it from the captured candidate source before the release command:
GOTOOLCHAIN=local go mod download
Then run the single publication command from the repository root:
./scripts/release vX.Y.Z
The command always runs the offline default test suite,
go test -mod=readonly -count=1 ./..., with toolchain and module network
access disabled. Any profile checks run afterward as additional product
checks; they should not repeat the standard suite. The command checks the
authoritative remote state, creates a local annotated tag from the captured
candidate and checked-in note, rechecks the tag and remote guards, and pushes
only that tag object. It prints the published version, commit, and tag object
on success. Exit status 0 means publication is confirmed; status 1 means a
known failure or conflict; status 2 means invalid input or profile; status 3
means the push outcome remains unknown and requires resolution.
The wrapper builds the native CLI into ignored .releasekit/bin, then invokes
it directly to preserve its exit status. The CLI comes from the source tree
being published, so this command also works
for ReleaseKit's first release before any released ReleaseKit executable
exists. It uses the local Go toolchain. Preparation must finish before the
offline validation begins.
Deliver the hosted release page
After source publication succeeds, deliver the hosted page locally when
Woodpecker automation is inactive. From the same repository root, keep the
worktree clean and HEAD at the published tag's commit. Reuse the executable
built by the publication command:
.releasekit/bin/releasekit deliver vX.Y.Z
The command uses the configured Gitea destination and token source to deliver the complete checked-in release note. Report its result separately from the completed source release. For failed or uncertain delivery, follow the recovery instructions below using the same immutable version.
Resolve publication failures
Each publication invocation attempts to save its captured identity before the tag push. The CLI reports the saved record path separately from publication success or failure and, after a push or immediate-reconciliation error, prints a copyable command that selects that exact record. Run that command, or use the source-built executable prepared above, to make a separate later observation:
./.releasekit/bin/releasekit reconcile --record '/absolute/path/to/attempt-record.json'
The saved evidence fixes the original endpoint and tag identity. Later recovery
uses current supported transport settings for the recorded remote name, but it
does not substitute the current remote URL, inspect the local tag or current
HEAD, load a profile, rerun checks, or change repository state. A completed
observation reports published, not_published, or conflict; an incomplete
observation reports unknown with diagnostic context. Resolve an unknown result
by correcting the identified endpoint rewrite, transport configuration, or
missing evidence. For incomplete saved identity, select another record with the
required fields or use complete typed evidence through the Go API.
If command output was lost, list local attempts and explicitly select the matching record by its module, full tag, and saved identity:
./.releasekit/bin/releasekit publication-attempts --repo "$PWD"
./.releasekit/bin/releasekit reconcile --record '/absolute/path/from-the-list/attempt-record.json'
Discovery only reads the local store. It neither queries remotes nor chooses an attempt automatically. If the original recovery entry point is unavailable, select another supported ReleaseKit CLI and pass the same record path explicitly. An older record may lack the endpoint, tag-object ID, candidate commit, or another field required for a conclusive result.
A later observation that the tag is absent describes the remote at that time; it does not prove the version was never published. Never reuse a version known to have been published, and preserve published tags and contents as immutable. Keep removal of an unpublished local tag, candidate correction, and retry as explicit operator decisions after reviewing the record and authoritative remote state. A conflicting object requires resolving the conflict and selecting an unused version when appropriate; do not move, overwrite, or delete a published tag.
If record storage fails, ReleaseKit reports the recording warning separately;
the warning does not change the actual publication outcome. The saved handoff is
then unavailable. A Go caller that retained PublicationResult.Evidence can
still pass that typed evidence directly to ReconcilePublication. Process
termination before the record is written, removed storage, and older incomplete
records can leave no sufficient saved evidence for later recovery.
Later reconciliation adds no successful-push read-back, polling, CI, consumer installation, or hosted-delivery gate. Hosted page delivery remains separate.
If validation or a guard fails before this invocation pushes, correct and
commit the candidate, publish main, and run the command again. A retained
local tag still requires reviewing the selected record and authoritative state
before deciding whether to remove it. The prepared
Woodpecker workflow builds the CLI from the tagged checkout, then uses
ci-deliver for page-only delivery. To activate it after configuring the
destination and secret, copy it to .woodpecker/releasekit-source-delivery.yml
and add the Woodpecker repository secret releasekit_gitea_token. If page
delivery fails or is uncertain, inspect the hosted release and retry the same
immutable version from its tag:
git fetch origin "refs/tags/vX.Y.Z:refs/tags/vX.Y.Z"
git checkout --detach "vX.Y.Z"
GOTOOLCHAIN=local go mod download
mkdir -p .releasekit/bin
GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off go build -o .releasekit/bin/releasekit ./cmd/releasekit
.releasekit/bin/releasekit deliver "vX.Y.Z"
For an older tag whose CLI predates token_file, supply the token through its
environment variable when retrying that tagged tool:
RELEASEKIT_GITEA_TOKEN="$(cat .releasekit/gitea-token)" \
.releasekit/bin/releasekit deliver "vX.Y.Z"
The root ignore file excludes .releasekit/. Delivery uses the committed note
and does not build consumer binaries, create a checksum manifest, or require a
previously released ReleaseKit executable. Keep source publication and hosted
page delivery status separate; retry only after resolving an uncertain remote
write. The workflow secret is named releasekit_gitea_token and is mapped to
RELEASEKIT_GITEA_TOKEN only in the delivery step.