diff --git a/docs/development.md b/docs/development.md index bde29b5..652aeca 100644 --- a/docs/development.md +++ b/docs/development.md @@ -32,6 +32,7 @@ boundaries and invariants. | Workspace paths, metadata, atomic persistence, lookup, inspection, or recovery | [State internals](internal/state.md), [operations guide](operations.md), and [troubleshooting guide](troubleshooting.md) | These separate implementation, operator workflows, and symptom-based recovery. | | Distributor bundles, uploads, notification artifacts, or failures | [Distributor adapter internals](internal/distributor-adapter.md), [Distributor integration contracts](integrations/distributor/), and [operations guide](operations.md) | These separate adapter behavior, external contracts, and operational lifecycle. | | Maintained example configuration | [Configuration reference](config.md) and files under `examples/` | The reference owns field meaning; examples own complete copyable files. | +| Release preparation, tagging, publication, or verification | [Release procedure](release.md) | It owns version selection, release-note preparation, candidate validation, tag publication, CI behavior, and post-publication checks. | | Proposed, deferred, or unimplemented work | Documents under `docs/roadmap/` | Future behavior and implementation status belong only in roadmaps until implemented. | For an existing subsystem, inspect its focused internal document, package-local diff --git a/docs/policy/documentation.md b/docs/policy/documentation.md index 429b968..38f4aed 100644 --- a/docs/policy/documentation.md +++ b/docs/policy/documentation.md @@ -82,6 +82,8 @@ mechanisms, not secret values. | Current application architecture | `docs/policy/architecture.md` | System shape, normative ownership, dependency direction, package boundaries, invariants, safety properties, and non-goals. | Concrete implementation mechanics, contributor procedures, decision history, and future work. | | Documentation organization | `docs/policy/documentation.md` | Documentation ownership, audience boundaries, maintenance rules, and document lifecycle. | Application architecture and runtime behavior. | | Testing policy | `docs/policy/testing.md` | Test philosophy, risk-based sufficiency, stable test boundaries, doubles, coverage guidance, regression policy, and criteria for adding, rewriting, or deleting tests. | Subsystem behavior, application contracts, subsystem-specific test inventories, and implementation plans. | +| Release procedure | `docs/release.md` | Version policy, release preparation, validation, tagging, automated publication, verification, failure handling, and release ordering. | General contributor workflow, product contracts, release-specific change summaries, and implementation history. | +| Release notes | `docs/releases/` | One versioned, changelog-style summary for each release, including compatibility and operator action. The file at the tagged commit supplies the corresponding Gitea release body. | Current CLI, configuration, operations, integration, architecture, and internal contracts; release procedure; implementation plans. | | CLI contract | `docs/cli.md` | Commands, arguments, flags, invocation semantics, stdout and stderr behavior, summaries, and exit behavior. | Configuration field definitions, complete operating procedures, runtime filesystem layout, and command implementation. | | Configuration contract | `docs/config.md` | Discovery and precedence, fields, defaults, secrets, validation rules, and user-selectable values. | Complete example files, CLI syntax, runtime state lifecycle, and loading implementation. | | Operations | `docs/operations.md` | Normal workflows, physical workspace layout, artifacts and metadata, inspection, notification behavior, recovery, cleanup, permissions, and operational caveats. | Complete CLI syntax, configuration field definitions, logical external contracts, and implementation mechanics. | @@ -131,6 +133,25 @@ Internal documents may name a command, field, template value, path, or protocol to identify a dependency, but must link to its canonical documentation for the complete definition. +### Release Procedure And Release Notes + +The release procedure owns how a maintainer prepares, publishes, verifies, and +recovers from a Weatherreporter release. Release notes under `docs/releases/` +own the concise historical summary for one version and are the checked-in +source for its generated Gitea release body. + +Release notes are not current-state reference documents. They may summarize +what changed and link to durable documentation, but they must not become a +second command, configuration, operations, integration, architecture, or +internal reference. Correct the applicable canonical owner in the same change +when a release changes an implemented contract. + +The release note at a published tag and the Gitea release generated from it are +historical records. Later corrections on `main` do not rewrite that published +record. Material release errors require the failure handling defined by the +release procedure rather than moving a published tag or overwriting its +release. + ### Executable Authority CLI parsing and help generation are the executable authority for accepted @@ -195,6 +216,10 @@ durable owners, update incoming links, and archive or remove the roadmap according to repository practice. Do not preserve completed roadmaps as a second current-state reference. +Release notes are durable historical summaries rather than temporary roadmaps. +Keep them concise, retain them after publication, and keep current contracts in +their canonical owners. + Before completing documentation work: - verify affected behavior and examples; diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 0000000..c26b580 --- /dev/null +++ b/docs/release.md @@ -0,0 +1,269 @@ +# Release Procedure + +## Release Model + +Weatherreporter publishes executable binaries through tagged commits on +`main`. Releases use stable semantic-version tags in the form +`vMAJOR.MINOR.PATCH`. The current pipeline does not publish prereleases. + +Every release has one nonempty, version-matched note at +`docs/releases/.md`. After the tag is pushed, the Woodpecker release +pipeline validates the tagged source, builds six binaries, creates SHA-256 +checksums, and creates the corresponding Gitea release. The pipeline uses the +checked-in release note as the Gitea release body and does not overwrite an +existing release. + +Before `v1.0.0`, a minor release may deliberately change user-facing +interfaces when its release note explains the compatibility impact and +required operator action. Patch releases must not intentionally break the +documented CLI, configuration, durable artifact, or integration contracts in +their minor line. + +Published tags and their generated releases are immutable. Never move, reuse, +or delete a published tag, and never manually overwrite the release produced +from it. + +## Select The Version And Write The Release Note + +Choose an unpublished version and export it as `RELEASE_VERSION`. Run the +commands in this procedure from the Weatherreporter repository root in one +POSIX shell: + +```sh +export RELEASE_VERSION=vMAJOR.MINOR.PATCH +``` + +Create `docs/releases/$RELEASE_VERSION.md` with this structure: + +```markdown +# Weatherreporter vMAJOR.MINOR.PATCH + +This release ... + +## Summary + +Summarize the release's purpose and most important outcomes. + +## Compatibility + +State compatibility with the preceding release and identify any changed CLI, +configuration, durable artifact, integration, or operating contract. + +## Upgrade + +State the operator actions required to upgrade, or state that no special +action is required. + +## Changes + +Describe the material user-visible, operational, and maintainer-visible +changes. Link to canonical documentation for exact current contracts. +``` + +The note is a concise changelog and adoption aid, not a replacement for current +documentation. Update every affected canonical document in the same candidate +commit. Do not include credentials, private infrastructure details, or claims +that are not true of the candidate. + +Require the version, path, heading, and minimum sections before continuing: + +```sh +set -eu + +: "${RELEASE_VERSION:?export an unpublished vMAJOR.MINOR.PATCH version}" +if ! printf '%s\n' "$RELEASE_VERSION" | + grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$' +then + printf '%s\n' "invalid release version: $RELEASE_VERSION" >&2 + exit 1 +fi + +RELEASE_NOTE="docs/releases/$RELEASE_VERSION.md" +export RELEASE_NOTE + +test -s "$RELEASE_NOTE" +grep -Fx "# Weatherreporter $RELEASE_VERSION" "$RELEASE_NOTE" +grep -Fx '## Summary' "$RELEASE_NOTE" +grep -Fx '## Compatibility' "$RELEASE_NOTE" +grep -Fx '## Upgrade' "$RELEASE_NOTE" +grep -Fx '## Changes' "$RELEASE_NOTE" +``` + +## Validate The Candidate + +Run the same substantive checks enforced by the tag pipeline before committing +the release note: + +```sh +test -z "$(git ls-files go.work go.work.sum)" +test ! -e vendor +if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod +then + printf '%s\n' 'go.mod contains a replacement' >&2 + exit 1 +fi + +GOWORK=off go test -count=1 ./... +GOWORK=off go test -race -count=1 ./... +GOWORK=off go vet ./... +GOWORK=off go build ./... +GOWORK=off go mod tidy -diff + +unformatted=$( + git ls-files '*.go' | + while IFS= read -r go_file + do + gofmt -l "$go_file" + done +) +test -z "$unformatted" +git diff --check +git diff --cached --check +``` + +Follow every added or changed Markdown link and confirm that its local target +exists. Review the candidate for generated binaries, test output, credentials, +temporary files, workspace files, replacements, vendored dependencies, and +other files that do not belong in source control. + +## Publish The Candidate Commit + +Commit the release note and any final current-state documentation updates, then +push `main` through the ordinary repository workflow: + +```sh +git add "$RELEASE_NOTE" +git commit -m "Document Weatherreporter $RELEASE_VERSION" +git push origin main +``` + +Do not tag an uncommitted or unpushed candidate. Record and export the exact +candidate commit after the push: + +```sh +RELEASE_COMMIT=$(git rev-parse --verify 'HEAD^{commit}') +export RELEASE_COMMIT +``` + +## Guard And Tag The Candidate + +Run this guard immediately before creating the tag. It requires a clean +checkout on synchronized `main`, valid module hygiene, the version-matched +release note, and an unpublished local and remote tag: + +```sh +check_release_candidate() { + test "$(git branch --show-current)" = main + test -z "$(git status --porcelain)" + + gowork_value=$(go env GOWORK) + case "$gowork_value" in + ''|off) ;; + *) + printf '%s\n' "active Go workspace: $gowork_value" >&2 + return 1 + ;; + esac + + test -z "$(git ls-files go.work go.work.sum)" + test ! -e vendor + if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod + then + printf '%s\n' 'go.mod contains a replacement' >&2 + return 1 + fi + + test -s "$RELEASE_NOTE" + grep -Fx "# Weatherreporter $RELEASE_VERSION" "$RELEASE_NOTE" + + git fetch origin main --tags + test "$RELEASE_COMMIT" = \ + "$(git rev-parse --verify 'refs/remotes/origin/main^{commit}')" + + if git show-ref --verify --quiet "refs/tags/$RELEASE_VERSION" + then + printf '%s\n' "local tag already exists: $RELEASE_VERSION" >&2 + return 1 + fi + if test -n "$( + git ls-remote --tags origin \ + "refs/tags/$RELEASE_VERSION" \ + "refs/tags/$RELEASE_VERSION^{}" + )" + then + printf '%s\n' "remote tag already exists: $RELEASE_VERSION" >&2 + return 1 + fi +} + +check_release_candidate +``` + +Create a lightweight tag, matching Weatherreporter's existing release tags, +and bind it explicitly to the guarded commit: + +```sh +git tag "$RELEASE_VERSION" "$RELEASE_COMMIT" +test "$(git cat-file -t "refs/tags/$RELEASE_VERSION")" = commit +test "$(git rev-parse --verify "refs/tags/$RELEASE_VERSION^{commit}")" = \ + "$RELEASE_COMMIT" +git show --no-patch --decorate "refs/tags/$RELEASE_VERSION" +``` + +If inspection finds an error, delete the unpublished local tag, correct the +candidate, and repeat the procedure. Once the tag is pushed, it is immutable. + +## Publish And Verify The Release + +Push only the selected tag ref. Do not use `git push --tags`: + +```sh +git push origin \ + "refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION" +``` + +The tag event starts the release pipeline. Its validation step rejects a +non-stable semantic tag, a missing release note, module or repository hygiene +violations, and any failing test, race test, vet, build, module-tidiness, +formatting, or whitespace check. Its build step also verifies that the host +binary reports `weatherreporter $RELEASE_VERSION`. + +Wait for the pipeline to succeed, then confirm that the Gitea release: + +- targets `RELEASE_COMMIT` through `RELEASE_VERSION`; +- is titled `Weatherreporter $RELEASE_VERSION`; +- uses `RELEASE_NOTE` from the tagged commit as its body; +- contains `SHA256SUMS`; and +- contains Linux, macOS, and Windows binaries for both `amd64` and `arm64`, + named `weatherreporter-$RELEASE_VERSION--` with `.exe` on Windows. + +Compare the remote tag with the guarded commit: + +```sh +remote_commit=$( + git ls-remote --tags origin "refs/tags/$RELEASE_VERSION" | + awk 'NR == 1 { print $1 }' +) +test "$remote_commit" = "$RELEASE_COMMIT" +``` + +Download `SHA256SUMS` and every release binary into a new temporary directory, +run `sha256sum --check SHA256SUMS`, and execute the binary for the maintainer's +host platform with `--version`. It must print exactly: + +```text +weatherreporter vMAJOR.MINOR.PATCH +``` + +## Failed Publication And Corrections + +If the tag pipeline fails after publication, preserve the tag and diagnose the +failure from the pipeline logs. Fix the cause on `main`, select a new patch +version, prepare a new release note, and repeat the complete procedure. Do not +move or recreate the failed published tag. + +Do not manually edit an automatically generated Gitea release or republish its +assets. A wording-only correction may be committed to the historical document +on `main`, with an explicit correction note, but it does not alter the file at +the tag or the generated release. Publish a new patch release when the error is +material to installation, compatibility, security, or operation.