Align release publication policy and runbook

This commit is contained in:
2026-09-28 01:50:26 +00:00
parent 65502ce5b4
commit ce91f04974
9 changed files with 291 additions and 325 deletions

View File

@@ -1,167 +1,114 @@
# Releasing Narratio
This is the maintainer runbook for the [Release Policy](policy/release.md).
Its [explicit exceptions](policy/release.md#policy-exceptions) preserve the
successful tag push as the automated responsibility boundary. Binary publication
is asynchronous; later consumer-facing inspection is optional.
The source release completes when the intended tag is successfully pushed to
`origin`; binary delivery is a separate asynchronous status.
## Prerequisites and publication scope
## Prepare the candidate
Use a checkout on `main` with Git authentication permitting fetches and pushes
to `origin`, the authoritative Git remote. Install Git, a POSIX shell, standard
Unix build tools, and the Go toolchain below. Make required modules available
before offline validation. No model/provider credentials are needed. Gitea
publication credentials belong to CI's secret store, not the local command.
Use `main` and the authoritative Git remote `origin`. Install Git, a POSIX
shell, standard Unix build tools, and Go 1.26. Have the selected Go modules
available before offline validation. Git access must permit fetch and tag push.
Application-provider credentials are not required; Gitea delivery credentials
belong to CI.
Synchronous guards inspect local tags and `origin`, not every configured remote
or Gitea release entry. Do not select a version known to have been published in
any channel, even if these guards cannot see it.
Choose an unused stable `vMAJOR.MINOR.PATCH` under the
[compatibility rules](policy/release.md#versioning-and-compatibility). Narratio
is past v1.0.0, so intentional CLI or configuration breaks require a major
release. Update affected canonical documentation with the change. Write the
matching `docs/releases/vMAJOR.MINOR.PATCH.md` with a short summary and any
relevant compatibility impact or upgrade action; choose sections to suit the
release.
## Go prerequisite
End that checked-in note with these three links, replacing the version in
each URL with the intended tag. The tag need not exist remotely yet. Check that
`COPYRIGHT`, `LICENSE`, and `docs/oss/attribution.md` are present in the
candidate. The tag CI reuses the checked-in note as the hosted release body.
Release preparation requires the maintained Go 1.26 baseline. Tag CI currently
uses the explicitly pinned `golang:1.26.7` image for validation and asset builds.
That asynchronous work does not change the tag-push completion boundary.
```markdown
[COPYRIGHT](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/docs/oss/attribution.md)
```
## Choose a version and write its note
Select an unused stable version under the
[versioning rules](policy/release.md#versioning-and-compatibility). Narratio is
past v1.0.0; intentional CLI/configuration breaks require a major release.
Create `docs/releases/vMAJOR.MINOR.PATCH.md` using the
[required note structure](policy/release.md#release-documentation).
Describe CLI, configuration, artifact, integration, and operating-contract
changes, and the required operator action. Link to current canonical
documentation for exact behavior.
## Prepare and record the candidate
Before committing, perform these manual reviews in addition to automated checks:
- Confirm compatibility/version selection and update affected canonical
documentation and maintained examples.
- Remove completed roadmap documents covering work included in this release;
retain pending work. Their history remains in Git. Do not delete historical
release notes.
- Review the diff, staged changes, and untracked files for credentials, private
data, generated output, unintended files, and unrelated changes.
- Review changed documentation links and behavior-bearing commands. Automated
link checks supplement rather than replace review.
Commit the note and candidate changes, then push that commit to `main` through
the ordinary development workflow. Record the exact candidate:
Retire completed roadmaps included in the release while preserving planned or
deferred work and repairing affected links. Review the change through the
ordinary development workflow for unintended files, credentials, private data,
and documentation accuracy. Commit and push the candidate to `origin/main`
before tagging; record its exact commit if needed:
```sh
git rev-parse HEAD
```
Do not create a tag until `origin/main` points to that same commit. If the
candidate changes, repeat the reviews and validation.
## Publish
## Validate the candidate
From the repository root, run:
From a clean `main` checkout at the pushed candidate, run one command:
```sh
scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH
scripts/release.sh vMAJOR.MINOR.PATCH
```
The script also works when invoked by its absolute path from another directory.
It validates the version and matching note, module hygiene, formatting,
whitespace, uncached tests, race tests, static checks, documentation, examples,
and three cross-build assets. It uses `GOWORK=off`, does not contact application
services, CI, or Gitea, and does not create tags or modify tracked source.
Fix failures on `main`, commit, push normally, and rerun validation.
It fetches `origin/main` and tags; requires local `HEAD` to equal
`origin/main`; checks the committed note and unused version; runs the
candidate checker **once**; then repeats cheap mutable-state guards before
creating an unsigned lightweight tag at the recorded commit. It verifies the
tag target and pushes only
`refs/tags/vMAJOR.MINOR.PATCH:refs/tags/vMAJOR.MINOR.PATCH`. It does not
commit, push `main`, force updates, or publish all tags.
The macOS cross-build proves compilation, not native runtime behavior.
The checker runs `go mod tidy -diff`, tracked Go formatting and diff whitespace
checks, one `go test -mod=readonly -race -count=1 ./...` full suite, and
`go vet -mod=readonly ./...`. Its asset builder cross-builds Linux amd64,
Linux arm64, and best-effort macOS arm64, then checks the embedded version
on a host binary. The macOS cross-build proves compilation, not native
runtime behavior. Owned Go commands run with `GOWORK=off`; tests, vet, and
builds use readonly module mode. The deterministic suite includes documentation
and maintained-example checks. The command does not contact application
services or Gitea and does not wait for CI.
## Declared deliverables and automation
A successful tag push is the source-release completion point. The command
prints the tag and commit and exits without a separate network read-back.
For optional diagnosis before publication, run
`scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH`; publication will
validate the candidate again, so this is not a required preparation step.
The source release is the exact version tag at the validated commit.
The tag-triggered `.woodpecker/release.yml` validates the tagged source,
runs `scripts/build-release-assets.sh`, and publishes these Gitea assets:
## Artifact delivery
Tag-triggered `.woodpecker/release.yml` independently validates the tagged
source, builds the same three targets, and publishes these Gitea downloads:
- `narratio-vMAJOR.MINOR.PATCH-linux-amd64`
- `narratio-vMAJOR.MINOR.PATCH-linux-arm64`
- `narratio-vMAJOR.MINOR.PATCH-darwin-arm64` (best effort)
- `SHA256SUMS`
The builder embeds the version, disables ambient Go workspaces, and checks a
host binary's `version` output. The release publisher uses the matching
checked-in note and generates SHA-256 checksums. Existing files are skipped and
overwrite is disabled. No image or package-registry deliverable is declared.
CI does not choose versions, modify source candidates, or move tags.
The builder embeds the tag version and checks host `version` output. CI
generates checksums and uses non-overwriting publication settings. It does
not select versions, change the source, or move tags. No image or package
registry output is declared. Tag publication is not proof that these
asynchronous downloads are available.
## Publish the tag
## Failure and recovery
From a clean checkout on `main` whose `HEAD` equals `origin/main`, run:
If validation fails before tagging, correct and commit the candidate, push
it to `main`, and rerun the publication command. If the tag push reports
failure, its outcome may be ambiguous: keep the local tag and inspect the
exact `refs/tags/vMAJOR.MINOR.PATCH` at `origin` before declaring success
or retrying. A reachable matching remote tag at the recorded commit establishes
publication; a confirmed absent tag permits investigation of the transport
failure. If `origin` cannot be inspected, publication status is unknown.
Do not move, overwrite, or automatically retry the tag. Remove a local tag
only after confirming it was never published or used by a release channel.
```sh
scripts/release.sh vMAJOR.MINOR.PATCH
```
A transient CI or delivery failure can be retried from the same immutable
tag. Missing assets may be added later if they match the tagged source and
any published checksum manifest; do not overwrite existing binaries,
checksums, or release-note contents. A source/content defect, or delivery
that cannot be corrected without replacement, requires a new suitable
version and matching note. Leave the original release intact.
The command fetches and checks `origin/main`, re-runs candidate validation,
then fetches and checks again before creating an explicitly unsigned lightweight
tag for the originally recorded commit. It refuses dirty, divergent, changed,
or already-tagged candidates. It verifies the local tag target and pushes only:
```text
refs/tags/vMAJOR.MINOR.PATCH:refs/tags/vMAJOR.MINOR.PATCH
```
It never commits changes, pushes `main`, force-pushes, moves tags, or pushes all
tags. Successful publication of that ref completes the command; it prints the
tag and commit and returns without waiting for CI, querying Gitea, downloading
assets, or checking checksums.
## Failure handling
Before tag creation, correct the candidate on `main` and repeat validation.
If a push fails after local tag creation, the local tag is retained for inspection.
Do not retry blindly or assume the remote received nothing: a connection failure
can leave publication uncertain. Inspect the exact remote ref before taking any
local-tag cleanup action. This is recovery from a failed command, not a
post-success publication gate.
After publication, follow the
[immutability and correction rules](policy/release.md#immutability-and-correction).
Correct defects or failed asynchronous publication with a new semantically
appropriate version, matching note, and the complete procedure. Never replace
existing tagged assets in place.
## Optional asynchronous inspection
A human may later inspect the tag-triggered Woodpecker job and Gitea release.
This is optional follow-up, never part of the automated releaser's completion
criteria. Do not wait for or poll CI/Gitea from the release command.
For optional source verification, substitute the released version below and
compare the remote target to the recorded candidate hash:
```sh
git ls-remote --tags origin refs/tags/vMAJOR.MINOR.PATCH
git rev-parse 'refs/tags/vMAJOR.MINOR.PATCH^{commit}'
```
Download the three named binaries and `SHA256SUMS` from the matching Gitea release
into an empty inspection directory. In that directory, verify on Linux:
```sh
sha256sum --check SHA256SUMS
```
On macOS, use `shasum -a 256 --check SHA256SUMS`. After successful verification,
make the matching host binary executable and run its version command, for example
on Linux amd64 (replace the version in both commands):
```sh
chmod +x ./narratio-vMAJOR.MINOR.PATCH-linux-amd64
./narratio-vMAJOR.MINOR.PATCH-linux-amd64 version
```
Expected output is `narratio vMAJOR.MINOR.PATCH`. Use only the matching architecture
on other supported hosts. Checksums establish consistency with the downloaded
checksum file, not a new signature or independent authenticity guarantee.
Missing assets are an asynchronous publication problem, not grounds to move or
reuse the successfully published source tag.
Optional consumer inspection may compare the tag's remote commit with the
recorded candidate, check the Woodpecker delivery job and hosted Gitea note,
download the assets into an empty directory, run `sha256sum --check
SHA256SUMS` (or `shasum -a 256 --check SHA256SUMS` on macOS), and run
the matching host binary's `version` command. This is separate delivery
observation, never a source-release gate.