Files
releasekit/docs/release.md

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 to make a separate later observation:

'/path/to/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 publication-attempts --repo "$PWD"
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. Use a supported ReleaseKit CLI if the executable named in the printed handoff command is no longer available. 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.