# Release Procedure Upgrade ## Status Implemented. This document records the accepted release-process scope, maintainer policy, and delivered end state. ## 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/.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/.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: ```markdown # 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: ```text 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 ` 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: ```text 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: ```text refs/tags/:refs/tags/ ``` 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 `; - use `docs/releases/.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: ```text 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.