Files
narratio/docs/roadmap/release-procedure.md

13 KiB

Release Procedure Upgrade

Status

Accepted target state. This document owns the intended release-process scope, maintainer policy, and target end state until the work is implemented.

Goal

Give Narratio one reproducible, guarded minor/patch release procedure that validates a source candidate before publication, creates the existing binary release inputs consistently, and makes the exact upstream tag push the clear completion boundary.

The procedure should borrow WeatherReporter's binary-publication model and Notarius's centralized source-candidate validation and immutable-tag guards, while retaining Narratio's own repository-wide checks and six supported build targets.

Responsibility Boundary

A Narratio release is complete when the selected tag has been successfully pushed to its exact tag ref in the upstream Git repository. The release command must then return successfully without polling Woodpecker, waiting for a pipeline, querying a Gitea release, downloading assets, or otherwise making its result depend on CI availability.

The tag event may asynchronously cause Woodpecker to validate the tagged tree, build binaries and checksums, and create a Gitea release. That work is outside the synchronous release procedure. CI failure or delay does not change whether the Git tag was successfully published, and release tooling must never wait indefinitely for infrastructure outside the upstream Git push.

An operator may inspect CI or published assets later. Such inspection is an optional asynchronous follow-up, not a release completion condition and not a step that an automated coding agent must perform after pushing the tag.

Release Model

  • Releases use unused stable semantic-version tags in the form vMAJOR.MINOR.PATCH; prereleases are outside this feature.
  • Narratio is past v1.0.0, so a minor release adds backward-compatible functionality, a patch release contains backward-compatible fixes, and an intentional breaking change to a documented public contract requires a new major version.
  • Release tags remain lightweight, matching Narratio's established tag history, and are bound explicitly to a guarded commit on main.
  • Every new release has a nonempty checked-in note at docs/releases/<tag>.md in the tagged tree.
  • Published tags are immutable. They are never moved, reused, overwritten, or deleted, even when asynchronous CI fails to create a complete binary release.
  • The Gitea release, when asynchronous publication succeeds, remains the canonical source for downloadable binaries and checksums. The Git tag and checked-in note remain valid source-release history independently of CI.
  • Existing historical tags and notes are not retroactively rewritten or backfilled to satisfy the new procedure.

Canonical Documentation

Add docs/release.md as the canonical maintainer procedure. Update docs/development.md to route release preparation, tagging, and publication to it, and update the documentation policy so it assigns ownership as follows:

  • docs/release.md owns version selection, candidate preparation, validation, tag guards, tag publication, the responsibility boundary, and failure recovery;
  • docs/releases/<tag>.md owns the immutable historical summary for one release;
  • docs/releases/README.md owns the release-note index and points to the Gitea release collection for downloadable assets; and
  • current CLI, configuration, operation, integration, and internal contracts remain in their existing canonical documents rather than being restated in release notes.

The maintainer procedure must be written as a complete safe workflow suitable for either a human maintainer or a coding agent. Commands must be explicit, non-interactive, and scoped to the selected version and tag ref.

Release Notes

Require each future note to use this minimum structure:

# Narratio vMAJOR.MINOR.PATCH

This release ...

## Summary

## Compatibility

## Upgrade

## Changes

The compatibility section must identify changes to documented CLI, configuration, durable artifact, integration, or operating contracts. The upgrade section must state required operator actions or explicitly say that no special action is required. When the Notarius consumer contract changes, the note should identify the supported/tested contract version and any required Notarius upgrade.

Release notes summarize a historical candidate and link to current canonical documentation for exact behavior. They must not substitute for updating that documentation, include credentials or private infrastructure details, or make claims that have not been validated against the candidate.

Shared Release Scripts

Source-candidate checker

Add the POSIX-shell checker:

scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH

It must be deterministic, non-interactive, safe to run repeatedly, and usable both by maintainers before tagging and by tag CI in a detached checkout. It must not create or push Git tags, modify tracked source, contact CI, or require live WhisperX, LLM, Notarius, Scriptorium, object-storage, or notification services.

The checker must fail clearly unless all of the following hold:

  • the argument is a stable semantic version;
  • go.mod declares Narratio's expected module path;
  • the matching release note exists, has the exact Narratio/version heading, and contains the required sections;
  • Go workspace use is disabled for candidate commands;
  • tracked go.work/go.work.sum, vendoring, and go.mod replacement directives are absent;
  • uncached repository tests, race tests, vet, build, documentation checks, and maintained-example validation succeed;
  • go mod tidy -diff reports no module-file change;
  • every tracked Go source file is gofmt clean;
  • working-tree and cached whitespace checks succeed; and
  • all supported release binaries cross-compile with the selected version embedded, with the host-platform binary reporting exactly narratio <tag> through narratio version.

The checker must preserve the distinction already documented by Narratio: cross-compilation is compilation evidence, not native macOS or Windows runtime evidence.

Asset builder

Add scripts/build-release-assets.sh, used by both the candidate checker and release CI. It must build exactly these CGO-disabled targets:

  • Linux amd64 and arm64;
  • macOS amd64 and arm64; and
  • Windows amd64 and arm64.

Asset names must remain deterministic and include the Narratio version, operating system, architecture, and .exe suffix for Windows. Build output must use a caller-supplied, narrowly validated staging directory and must not perform broad or ambiguous deletion. Official assets use -trimpath and the established release version linker override.

Tag-publication command

Add the narrowly scoped release command:

scripts/release.sh vMAJOR.MINOR.PATCH

The command owns local validation, publication guards, lightweight tag creation, local tag verification, and pushing only the selected tag ref. It must not commit candidate changes or push main; the candidate must already be committed and present on upstream main.

Before creating a tag, the command must:

  • run the shared candidate checker;
  • require the current branch to be main;
  • require a clean worktree and index;
  • fetch upstream main and tags;
  • record the exact candidate commit and require local HEAD to equal origin/main;
  • require the version-matched release note from that commit; and
  • reject an existing local or upstream tag of the selected name.

It must create an explicitly lightweight tag against the recorded commit, verify that the local tag resolves directly to that commit, and push only:

refs/tags/<version>:refs/tags/<version>

It must never use git push --tags. If the tag push succeeds, the command must report the version and commit and exit successfully immediately. In particular, it must not invoke a CI client, poll a build status, wait for a Gitea release, or verify release assets.

If a failure occurs before the upstream tag is published, no release has completed. An unpublished local tag may be removed only after the maintainer has inspected the failure. The script should not silently delete it. A successful upstream push is final even if later asynchronous work fails.

Woodpecker Release Workflow

Retain tag-triggered asynchronous release automation, but make it consume the same checked-in contracts as local preparation:

  • validate the tag syntax and matching release note before asset work;
  • call the shared candidate checker rather than maintaining an independent drifting validation list;
  • use the shared asset builder for the six official binaries;
  • pin the Go builder image to golang:1.25.5, an explicit patch version compatible with the current go.mod language version;
  • pin the release plugin to the reviewed explicit version woodpeckerci/plugin-release:0.3.1 rather than a floating image;
  • set the Gitea release title explicitly to Narratio <tag>;
  • use docs/releases/<tag>.md from the tagged tree as the release body;
  • generate a flattened SHA256SUMS file for all official binaries;
  • keep prerelease publication disabled and overwrite disabled; and
  • keep validation, asset building, and publication dependency-ordered so a failed CI validation cannot publish assets.

The workflow must not require a callback to, acknowledgement from, or status update in the release command. The two sides have intentionally separate responsibilities:

local release command               asynchronous infrastructure
---------------------               ---------------------------
validate candidate
guard main and exact commit
create lightweight tag
push exact upstream tag  ────────►  validate tagged tree
return success immediately          build assets and checksums
                                    create Gitea release

Failure And Correction Policy

Failures before tag publication are corrected on main, followed by complete candidate revalidation and fresh guards.

After a tag has been pushed, neither CI failure nor a defect permits changing that tag. Correct the cause on main, choose a new patch version, write a new release note, and repeat the complete release procedure. Do not move or delete the published tag, manually overwrite generated assets, or make the release command wait for repaired infrastructure.

A maintainer may later inspect the remote tag, Gitea note, assets, checksums, and host binary. Documentation may provide these as optional asynchronous inspection commands, clearly outside the release responsibility boundary. No automated release instruction may require them after a successful tag push.

Security And Operational Constraints

  • Local scripts never read or print the Gitea release token; the token remains a Woodpecker-owned secret supplied only to the release plugin.
  • Candidate validation remains offline with respect to paid APIs, mutable application services, and private user data.
  • Temporary build output is confined to a validated temporary or staging directory and cleaned safely.
  • Scripts avoid environment dumps and never record credentials in release notes, logs, repository files, or command output.
  • The release command does not edit tracked files, create commits, or push branches. Apart from fetching refs for its guards, its only release-state mutations are creating the selected local tag and pushing that exact tag.

Target End State

When this feature is complete:

  1. A maintainer or coding agent prepares and commits a standardized release note plus any final canonical-documentation updates.
  2. The candidate is pushed to main through the ordinary development workflow.
  3. One shared checker proves the exact tagged source is clean, self-contained, formatted, tested, race-tested, statically checked, documented, module tidy, cross-buildable, and version-correct.
  4. One guarded release command refuses ambiguous or unpublished candidates, creates the established lightweight tag, and pushes only that tag.
  5. The command completes successfully at the upstream tag push and performs no CI or Gitea status wait.
  6. Woodpecker independently reuses the shared checks and asset builder and may publish six versioned binaries, release notes, and SHA-256 checksums.
  7. Published tags remain immutable, and failures after tag publication are corrected only through a new version.
  8. The release procedure is documented in one canonical location, linked from contributor guidance, enforced by scripts and CI, and clear about the boundary between guaranteed Git publication and best-effort asynchronous binary publication.

Non-Goals

This feature does not:

  • repair, replace, monitor, or improve Woodpecker reliability;
  • wait for or synchronously verify CI or Gitea release completion;
  • introduce prereleases, signed tags, containers, package-manager publication, installers, auto-updaters, or native macOS/Windows test runners;
  • publish main automatically or create release-note content automatically;
  • run live external adapters or paid model calls during release validation; or
  • rewrite historical tags or manufacture retrospective release notes.