Document the ReleaseKit release workflow

This commit is contained in:
2026-10-07 14:09:00 +00:00
parent 5af6ba7d89
commit 5ac704c4c4
9 changed files with 136 additions and 115 deletions

View File

@@ -1,144 +1,120 @@
# Releasing Audita
The [release policy](policy/release.md) defines versioning, supported platforms,
immutability, and success. This runbook implements it with
[scripts/release.sh](../scripts/release.sh). Publication ends after a successful
source/tag push; binary delivery runs asynchronously.
The [release policy](policy/release.md) owns release requirements. This runbook
uses the native commands in [scripts/release.sh](../scripts/release.sh), which
builds and invokes ReleaseKit. A confirmed source tag completes the release;
Woodpecker reports binary delivery separately.
## Prerequisites
Use Bash, Git, and Go 1.25.5 or newer (CI uses Go 1.25.5), with dependencies
available locally or downloadable. Tests themselves are offline. Work from a
checkout of `main` with permission to push that branch and tags to `origin`.
For this repository, `origin` is `https://gitea.maximumdirect.net/eric/audita.git`.
The script uses the checkout's configured `origin`; set it correctly before use.
The remote must support atomic pushes. No CI credentials or Mac are needed locally.
Use Bash, Git, and Go 1.25.5 or newer. The authoritative remote is `origin`,
which must point to `https://gitea.maximumdirect.net/eric/audita.git`; the
primary branch is `main`. Source publication needs no delivery credential.
Woodpecker uses the existing `GITEA_RELEASE_TOKEN` secret for delivery.
CI uses the existing `GITEA_RELEASE_TOKEN` secret for asynchronous asset delivery.
macOS arm64 is a best-effort development environment; no macOS binary is published.
Prepare dependencies before offline candidate validation:
## Prepare the Candidate
```sh
GOWORK=off GOTOOLCHAIN=local go mod download
```
Choose an unused version following the policy. Write
`docs/releases/<version>.md`, beginning with `# Audita <version>`, followed by a
short summary and any compatibility or upgrade instructions relevant to the
release. There are no mandatory boilerplate sections. Update affected canonical
documentation with behavior changes.
If dependency preparation changes `go.mod` or `go.sum`, include those changes in
the committed candidate. The release wrapper separately bootstraps the pinned
ReleaseKit command into ignored `bin/releasekit`; that build is tool preparation,
not candidate validation.
End each new note with these links, substituting the selected release version for
`<version>`:
## Prepare and publish the candidate
Choose an unused version under the [release policy](policy/release.md), write
`docs/releases/<version>.md`, and begin it with `# Audita <version>`. Include a
short summary, relevant compatibility or upgrade instructions, and the required
`COPYRIGHT | LICENSE | ATTRIBUTION` footer with links for the selected version.
Update affected canonical documentation with behavior changes.
End each note with this footer, substituting its complete tag for `<version>`:
```markdown
[COPYRIGHT](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/audita/src/tag/<version>/docs/oss/attribution.md)
```
Check that all three paths exist in the candidate source. Retire completed
roadmaps when useful during preparation, preserving planned and deferred work
and repairing affected links. This is ordinary preparation, not a script gate.
Commit the intended changes, including the note, on `main`. The release script
requires a clean index and worktree and does not create commits. There is no
separate approval round. Published notes describe their tagged release; existing
release history does not need to be reconstructed.
## Validate and Publish
From the repository root, substitute the chosen version:
Commit the complete candidate on `main`, including the release note, then push
the branch first:
```sh
bash scripts/release.sh --publish v0.2.0
git push origin main
```
This single invocation:
Record the candidate commit. Before publication, remote `main` must identify
that exact commit. ReleaseKit checks this along with the clean candidate, note,
version, and remote tag state.
1. Checks the version, branch, clean candidate, matching note title, and tag absence.
2. Runs the default offline test suite once, then builds both required Linux targets
in a temporary directory using the same build script as CI.
3. Checks that the candidate and worktree are unchanged and that it includes the
current `origin/main` history.
4. Creates the version tag at that commit and atomically pushes the exact source
commit to `main` and the exact tag to `origin`.
5. Reports release success and exits without polling CI or checking asset availability.
To validate a prepared candidate without remote access, creating a tag, or pushing:
Publish from the repository root, substituting the selected version:
```sh
bash scripts/release.sh --check v0.2.0
bash scripts/release.sh publish VERSION
```
This is an optional diagnostic command, not a required step before `--publish`.
Publication validates within its own invocation; it does not use persistent cached
results. Both modes require the candidate note and a clean `main` checkout.
The command runs `go test -mod=readonly -count=1 ./...` once with module and
toolchain network access disabled, builds the profile's Linux amd64 and arm64
targets, and checks the native version output. It creates an annotated tag whose
message comes from the committed note, then pushes the exact tag object. A
successful publication completes the source release without waiting for CI or
checking artifact availability.
## Artifact Delivery
`bash scripts/release.sh check VERSION` is an optional local diagnostic. It
does not inspect remote availability or publish a tag, and it is not a required
step before `publish`, which performs its own candidate validation.
[Woodpecker](../.woodpecker/release.yml) runs on tag events. It invokes
[scripts/build-release.sh](../scripts/build-release.sh), then publishes:
The wrapper preserves ReleaseKit's exit status: `0` means confirmed publication;
`1` means a known validation or publication failure/conflict; `2` means usage,
profile, or version input error; and `3` means the push outcome remains unknown.
## Binary delivery
Woodpecker runs the pinned ReleaseKit `ci-deliver` command for tags under
`refs/tags/v*`. It verifies the triggering commit against the tag, prepares the
tagged source, and uses `releasekit.json` to build and deliver:
- `audita-<version>-linux-amd64`
- `audita-<version>-linux-arm64`
- `SHA256SUMS`
Builds disable CGO and use the shared script's release flags. The version is in the
asset name and source tag; Audita has no CLI version command. Cross-compilation
checks compilation, not native execution on each architecture.
The CI job status and logs show delivery progress or failure. Assets appear on
the matching Gitea release page when delivery succeeds. A successful tag push
must not be reported as confirmation that these files are available.
For a new hosted release, the release plugin uses the checked-in note at the
tag as its body, including the footer. On delivery retries it preserves
existing release information and skips files that already exist.
Consumers can download the binary for their Linux architecture and `SHA256SUMS`
from that page, check its listed SHA-256 digest with `sha256sum`, and make it
executable. Optional consumer checks are not maintainer release gates. Digests
detect file mismatches; they are not an independent signature of authenticity.
## Failure and Retry
**Local validation fails:** correct the issue, commit any changes, and rerun the
publication command. A failure before tag creation publishes nothing. Temporary
local build files are removed on exit.
**Push fails or the connection is interrupted:** the script retains the local tag
and reports failure rather than guessing whether publication succeeded. Inspect
remote state with the actual version:
The workflow invokes:
```sh
git rev-parse 'v0.2.0^{commit}'
git ls-remote origin refs/heads/main refs/tags/v0.2.0
go run gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit@${RELEASEKIT_VERSION} ci-deliver \
--repo "$${CI_WORKSPACE}" \
--commit "$${CI_COMMIT_SHA}" \
"$${CI_COMMIT_TAG}"
```
If the remote tag identifies the intended candidate and its source is on remote
`main`, the release succeeded; no CI waiting is needed. An atomic push normally
updates both refs or neither. If `main` has advanced since publication, fetch it
and check candidate ancestry rather than requiring tip equality.
Woodpecker sets `RELEASEKIT_VERSION` to `v0.1.0` for this workflow.
If the tag is absent remotely and was never published or used by a release
channel, remove only that unpublished local tag (`git tag -d v0.2.0`), resolve
any rejected branch update, and rerun the command. If the remote tag has a
different target, do not overwrite it; choose a fresh version. Never force a
branch or tag update to bypass the guards.
The complete checked-in release note, including its licensing footer, supplies
the hosted release body. The workflow maps the existing `GITEA_RELEASE_TOKEN`
secret to `RELEASEKIT_GITEA_TOKEN`. Delivery runs independently after source
publication; its status and logs report delivery progress or failure.
**CI delivery fails:** the release remains successful. Inspect the failed tag
job's logs and retry that same tagged job for a transient failure. Publication is
configured to skip existing filenames and not overwrite assets. Keep the same
source and build environment when retrying; inspect any existing assets/checksum
manifest before filling gaps. Do not regenerate and replace a published manifest
or binary. If an existing manifest cannot describe the missing rebuilt assets,
use a new version instead. A pipeline or source defect also requires a corrected
commit and new version. An outage does not require moving the tag.
ReleaseKit handles content-aware retries and retained-artifact recovery. See
the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md)
and [delivery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/delivery.md)
for those mechanics. A delivery failure does not undo a confirmed source
release. Retry the same immutable tag after resolving the reported state; do not
replace published files or a checksum manifest.
**A released source defect is discovered:** fix it on `main` and publish a new
appropriate version with a note explaining the correction. Leave the old tag and
published files intact.
## Failure and recovery
## Maintaining the Release Workflow
Fix validation failures in a new committed candidate, then rerun `publish`.
After a failed push, ReleaseKit retains the local annotated tag and reports the
captured authoritative endpoint and publication state. Resolve the result
against that endpoint before retrying. An exact remote tag-object match, with
its source available on remote `main`, can confirm publication. If the remote
state cannot be read, the result remains unknown; do not remove or replace the
tag while uncertain. Follow the pinned [publication recovery guide](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/publish.md)
for object-level recovery details.
The shared build script keeps local and CI build settings aligned. Tests in
[scripts/release_test.go](../scripts/release_test.go) exercise publication against
temporary local Git remotes, including guard failures and rejected pushes, with
Go execution stubbed to avoid recursive builds/tests. They run in `go test ./...`.
Changes to CI delivery should preserve explicit asset names, checksums, and
non-overwriting publication. CI delivery is separate from the local success path.
A source-release success and artifact-delivery status are separate. For a
partial or uncertain delivery, inspect the job output and follow the pinned
[delivery recovery guidance](https://gitea.maximumdirect.net/eric/ReleaseKit/src/tag/v0.1.0/docs/consumers/delivery.md).
A source or published-content correction requires a new appropriate version;
leave the original tag and its contents intact.