# 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](../examples/woodpecker/releasekit-source-delivery.yml). 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](config.md) 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: ```sh export GOTOOLCHAIN=local go version ``` Record the reported version with the validation results. The [Go support policy](policy/release.md#consumer-minimum-and-release-toolchain) owns series selection, consumer minima, patch exceptions, and validation scope. The maintained [source-delivery image](../examples/woodpecker/releasekit-source-delivery.yml) and [consumer template image](../examples/woodpecker/tag-delivery.yml) 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](../go.mod). Keep it for a release unless release preparation deliberately selects a newer series under the [Go support policy](policy/release.md#consumer-minimum-and-release-toolchain). 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](policy/release.md#versioning-and-compatibility); before v1.0.0, an increase belongs in a minor release. Choose an unused stable `vMAJOR.MINOR.PATCH` version, and add `docs/releases/.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: ```markdown [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`: ```sh 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: ```sh GOTOOLCHAIN=local go mod download ``` Then run the single publication command from the repository root: ```sh ./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: ```sh .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: ```sh '/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: ```sh 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: ```sh 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: ```sh 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.