Simplify source releases with one guarded publication command

This commit is contained in:
2026-09-25 20:33:19 +00:00
parent cee254651f
commit acec6dc15e
9 changed files with 596 additions and 255 deletions

View File

@@ -1,164 +1,84 @@
# Source Releases
This procedure is for maintainers publishing Notarius source releases. A
release is an immutable lightweight `vMAJOR.MINOR.PATCH` tag on `main` together
with its checked-in `docs/releases/<tag>.md` note. Tag CI validates that source
candidate after publication; it does not publish or repair a release.
This is the executable runbook for [Release Policy](policy/release.md).
Notarius publishes an immutable lightweight tag and its checked-in release note,
not binaries or Gitea release objects. Linux is supported; macOS is best-effort
for development, and Windows is unsupported. `main` on `origin` is authoritative.
Notarius publishes no binaries, archives, checksums, signatures, containers,
package-manager entries, or Gitea release objects. Windows is not supported.
Do not create retrospective notes for the pre-procedure `v0.1.0`, `v0.2.0`, or
`v0.3.0` tags.
## Prepare
## Select And Describe The Release
Choose an unused stable semantic version in the form `vMAJOR.MINOR.PATCH`.
Prereleases are not supported. Before `v1.0.0`, a minor release may change a
documented CLI, configuration, durable artifact, integration, or operating
contract when its note explains the impact and required operator action. A
patch release must not intentionally break those documented contracts within
its minor line.
Create the version-matched note as part of the candidate. Every new note uses
this structure, with concise, truthful content in each section:
```markdown
# Notarius vMAJOR.MINOR.PATCH
This release ...
## Summary
## Compatibility
## Upgrade
## Changes
```
The note is a historical summary. Link to current canonical documentation for
exact behavior, and update that documentation in the candidate rather than
using the note as a substitute.
## Prepare The Candidate
Set the selected release version and disable Go workspace use for every
candidate command:
Use Git, a POSIX shell, and Go 1.25.5 or newer. Configure ordinary Git credentials
for `origin` and private-module access without putting secrets in the repository.
Prepare dependencies before offline validation where necessary:
```sh
RELEASE_VERSION=vMAJOR.MINOR.PATCH
export RELEASE_VERSION GOWORK=off
GOWORK=off GOPRIVATE=gitea.maximumdirect.net go mod download
```
Run the shared source-candidate checks from the repository. They cover module
hygiene, tests, race tests, vet, builds, formatting, whitespace, maintained
configuration validation, and the Linux and Darwin command-build matrix:
Choose an unused stable version under the policy's compatibility rules. Add
`docs/releases/<version>.md`, starting with `# Notarius <version>`, with a concise
summary and relevant compatibility/upgrade information. No fixed section list is
required. Update current documentation with behavior changes; retire only
completed roadmaps and preserve planned/deferred work. Commit the candidate on
`main` after ordinary change review. There is no separate release sign-off.
## Publish
Run one command from the repository with the actual selected version:
```sh
./scripts/check-release-source.sh "$RELEASE_VERSION"
./scripts/release.sh vMAJOR.MINOR.PATCH
```
Before committing, manually follow every changed local Markdown link and
review the candidate for unintended files, generated output, credentials, or
other unrelated changes. Commit the release note and all affected current
documentation, then run the shared checker against that exact candidate. Push
the candidate commit to `main` only after it succeeds. Record the exact commit
only after that push:
The command checks version availability on `origin`, branch and worktree state,
and the matching note. It runs the complete offline default test suite once and
a native CLI build/version check with `GOWORK=off`. The
[gate rationale](policy/release.md#notarius-gate-selection) explains the selected
checks. It then rechecks candidate and publication state, pushes the exact
candidate to `main` without force, creates a lightweight tag, and pushes only
that tag. It neither rewrites history nor publishes other local tags.
A successful tag push completes the source release. The command does not wait
for CI or verify consumer installation/module availability. Tag CI is advisory
validation only; there are no asynchronous deliverables to await.
## Failure And Recovery
Before publication, correct failed checks, commit any changes, and rerun the
command. If the candidate changes during validation, validation must run again
on the new candidate. A failed branch push publishes no tag.
On a failed or ambiguous push, the command queries the affected remote ref. If
a tag push reports failure but the intended lightweight tag is confirmed on the
remote, publication succeeded. Otherwise it stops and reports failure or unknown
status, leaving any local tag intact for inspection. Do not blindly rerun, force
push, or delete a possibly published tag.
Inspect authoritative state before retrying:
```sh
RELEASE_COMMIT=$(git rev-parse 'HEAD^{commit}')
export RELEASE_COMMIT
git ls-remote origin refs/heads/main refs/tags/vMAJOR.MINOR.PATCH
git rev-parse vMAJOR.MINOR.PATCH
```
For private-module installation, configure standard `GOPRIVATE` matching this
module and ordinary Git authentication for the hosting service before running
the verification below. The exact authentication mechanism belongs to the
maintainer environment; never record credentials or environment dumps in a
release note, command history, or repository file.
If the intended tag exists remotely, leave it immutable. If it is absent and
you have confirmed that the local tag was never published or used by a release
channel, remove only that unpublished local tag, then rerun the command. A
failed remote query is not proof of absence. Network/authentication failures
alone do not require a new version. A defect requiring changes to published
source requires a new semantically appropriate version and matching note.
## Guard And Publish The Tag
## Optional Diagnostics
Fetch current remote references, then run this guard without editing the
candidate. It requires `main`, a clean worktree and index, disabled workspace
use, a stable release version, the recorded and pushed commit, a matching note,
and unused local and remote tags:
`./scripts/check-release-source.sh <version>` runs validation without publication.
It is useful for diagnosis, but is not a required preliminary step that must
repeat the publication command's checks.
```sh
git fetch origin main --tags
Race tests, vet, module hygiene, formatting, example checks, and cross-builds may
be run when relevant to a change; they are not additional release gates. The
development guide and testing policy govern change-specific verification.
if ! printf '%s\n' "$RELEASE_VERSION" |
grep -E -x 'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)' >/dev/null
then
printf '%s\n' "invalid release version: $RELEASE_VERSION" >&2
exit 1
fi
test "$GOWORK" = off
test "$(git branch --show-current)" = main
test -z "$(git status --porcelain)"
test "$RELEASE_COMMIT" = "$(git rev-parse 'HEAD^{commit}')"
test "$RELEASE_COMMIT" = "$(git rev-parse 'origin/main^{commit}')"
test -s "docs/releases/$RELEASE_VERSION.md"
grep -F -x "# Notarius $RELEASE_VERSION" "docs/releases/$RELEASE_VERSION.md"
for heading in '## Summary' '## Compatibility' '## Upgrade' '## Changes'; do
grep -F -x "$heading" "docs/releases/$RELEASE_VERSION.md"
done
if git rev-parse -q --verify "refs/tags/$RELEASE_VERSION" >/dev/null; then
printf '%s\n' "local tag already exists: $RELEASE_VERSION" >&2
exit 1
fi
if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_VERSION" >/dev/null 2>&1; then
printf '%s\n' "remote tag already exists: $RELEASE_VERSION" >&2
exit 1
fi
```
Create an explicitly lightweight tag against the guarded commit, verify its
target, and push only that tag ref:
```sh
git -c tag.gpgSign=false tag "$RELEASE_VERSION" "$RELEASE_COMMIT"
test "$(git cat-file -t "$RELEASE_VERSION")" = commit
test "$(git rev-parse "$RELEASE_VERSION^{commit}")" = "$RELEASE_COMMIT"
git push origin "refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION"
```
Never use `git push --tags`, move a published tag, or delete a published tag.
## Verify The Published Release
Confirm that the remote tag still points at the guarded commit and that the
note is available from the tagged tree:
```sh
REMOTE_TAG_COMMIT=$(git ls-remote origin "refs/tags/$RELEASE_VERSION" | awk '{print $1}')
test "$REMOTE_TAG_COMMIT" = "$RELEASE_COMMIT"
git show "$RELEASE_VERSION:docs/releases/$RELEASE_VERSION.md" >/dev/null
```
Verify a fresh source installation and its diagnostic version. The temporary
directory confines the installed command to this check:
```sh
release_verification_dir=$(mktemp -d)
trap 'rm -rf "$release_verification_dir"' 0 HUP INT TERM
mkdir -p "$release_verification_dir/bin"
GOWORK=off GOBIN="$release_verification_dir/bin" go install \
"gitea.maximumdirect.net/eric/notarius/cmd/notarius@$RELEASE_VERSION"
test "$("$release_verification_dir/bin/notarius" --version)" = "notarius $RELEASE_VERSION"
```
An exact fresh checkout and `GOWORK=off go build ./cmd/notarius` is an
equivalent source verification when local installation policy requires it.
`notarius --version` is diagnostic only; downstream compatibility remains
defined by the published receipt and artifact contracts.
## Failure And Correction Policy
If candidate validation fails before publication, fix the candidate on `main`,
rerun the shared checker, and repeat the guards. An unpublished local tag may
be deleted after inspection.
If the remote tag or tag CI reveals a defect, leave the published tag intact.
Fix the defect on `main`, choose a new patch version, write a new matching
note, and repeat this procedure. Do not weaken tag immutability or add release
assets as a workaround.
A fresh source install or exact-tag checkout/build may be used to investigate
consumer availability after release. Such checks are optional and do not delay
or reverse release completion. `notarius --version` is diagnostic; subprocess
compatibility is defined by the receipt and artifact contracts.