# Source-Only Release Implementation Plan ## Purpose Implement the target state defined by [Source-Only Releases](source-releases.md): immutable source tags with checked-in release notes, a diagnostic version interface, strong shared candidate checks, validation-only tag CI, Linux support, best-effort macOS compilation, and no packaged binaries or Windows support. This plan is ordered. Each numbered stage is one implementation prompt for a gpt-5.6-terra coding agent. Complete and validate one stage before beginning the next. Preserve all unrelated worktree changes, follow every policy under `docs/policy/`, and update current-behavior documentation in the same stage as the behavior it describes. Do not create or push a release tag while implementing this plan. Do not invent a release note for `v0.1.0`, `v0.2.0`, or `v0.3.0`. The first real release under the completed procedure will add its own note in a separate release operation. ## Decisions Fixed For Implementation - Releases are stable `vMAJOR.MINOR.PATCH` source tags on `main`; prereleases are unsupported initially. - Tags are lightweight and immutable after publication. - No release binaries, archives, checksums, signatures, containers, package-manager entries, or Gitea release objects are produced. - Linux is supported. Release checks compile Linux `amd64` and `arm64` with `CGO_ENABLED=0`. - macOS is best-effort. Release checks compile Darwin `amd64` and `arm64` with `CGO_ENABLED=0`, without promising runtime CI or packaged output. - Windows is unsupported and must not be added to build checks. - Release notes begin with the first release made under the new procedure; historical tags are left untouched. - `notarius --version` is informational. Receipt and artifact contracts remain authoritative for downstream compatibility. - One checked-in POSIX shell command owns substantive source-candidate checks. The release procedure and tag CI call it rather than maintaining duplicate test/build matrices. - Tag CI validates only. Pre-publication local guards remain mandatory because tag CI cannot prevent an already-pushed tag. ## Stage 1: Add Build Version Resolution And `--version` ✅ ### Goal Add a small, testable build-information boundary and expose the public root version flag without affecting existing command behavior. ### Implementation 1. Create `internal/buildinfo` with an exported link-time string variable named `Override` and an exported resolver such as `Version() (string, error)`. Keep this package independent of CLI and application packages. 2. Resolve the displayed value using this precedence: 1. a nonempty `Override`; 2. the main-module version returned by `runtime/debug.ReadBuildInfo`; then 3. the literal `development`. 3. Accept a release value only when it matches the complete stable SemVer tag form `^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`. Whitespace, prerelease/build suffixes, pseudo-versions, and arbitrary text are not release versions. A nonempty invalid linker override is an error; an empty, `(devel)`, pseudo-version, or otherwise non-release main-module version falls back to `development`. 4. Add `--version` to the root dispatch in `internal/cli`. It is valid only as the sole argument, writes exactly `notarius \n` to stdout, writes nothing to stderr, and exits zero. Additional arguments are a syntax error using the existing exit-2 and stderr conventions. An invalid linker override is a runtime/build error using exit 1 and stderr. 5. Add the flag to root usage without changing the existing behavior of empty arguments, help spellings, or subcommands. 6. Update `docs/cli.md` as the canonical public contract and `docs/internal/cli.md` as the implementation owner. State that tagged `go install` builds can obtain the main-module tag from Go build information, controlled builds may inject `gitea.maximumdirect.net/eric/notarius/internal/buildinfo.Override`, and an ordinary unversioned checkout reports `development`. ### Tests - Add table-driven `internal/buildinfo` tests for valid stable versions, leading-zero rejection, whitespace, prerelease/build suffixes, pseudo- versions, override precedence, invalid nonempty override, and development fallback. Test the pure resolution decision rather than trying to mutate process build information. - Extend the CLI command-contract tests to cover exact stdout/stderr/exit behavior for `--version`, extra arguments, and unchanged help/unknown-command behavior. - Do not snapshot the whole usage document solely for the new line; assert the stable semantic fragments already owned by the CLI contract tests. ### Validation ```sh go test ./internal/buildinfo ./internal/cli go test ./... go vet ./... go build ./cmd/notarius go run ./cmd/notarius --version ``` Build a temporary Linux host binary with: ```sh go build -trimpath \ -ldflags '-X gitea.maximumdirect.net/eric/notarius/internal/buildinfo.Override=v0.0.0' \ -o /path/to/temp/notarius ./cmd/notarius ``` and verify that its output is exactly `notarius v0.0.0`. ### Acceptance Criteria - The public and internal documentation matches the implemented version behavior. - Ordinary builds print `notarius development`. - A valid linker override prints the exact stable tag. - Invalid linker content cannot masquerade as a release version. - Existing CLI commands, help, streams, and exit classes remain unchanged. ## Stage 2: Add One Reusable Source-Candidate Checker ✅ ### Goal Create one repository-owned, offline validation command used identically by a maintainer and release CI. ### Implementation 1. Create executable POSIX shell script `scripts/check-release-source.sh`. Require exactly one positional argument containing a stable SemVer tag. The script must locate and enter the repository root from its own checked-in path so callers cannot accidentally validate another working directory. 2. Use `set -eu`, quote all expansions, reject invalid versions before using them in paths or linker arguments, and use a freshly created temporary directory for cross-build output. Install a cleanup trap scoped only to that resolved temporary directory. 3. Keep release-note, branch, remote, clean-worktree, and tag-existence guards out of this script. Those publication-specific checks belong in `docs/release.md` and tag CI. This script owns the substantive source checks that both workflows share. 4. Implement these checks in a clear fail-fast order: - require the expected Notarius module path in `go.mod`; - reject tracked `go.work` or `go.work.sum`, an existing `vendor` directory, and any `replace` directive in `go.mod`; - `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`; - require no output from `gofmt -l` for tracked Go files; - `git diff --check` and `git diff --cached --check`; - validate `examples/dnd-minimal.config.yml` and `examples/dnd-complete.config.yml` for pipeline `dnd-session` using the built or `go run` Notarius command; - build `./cmd/notarius` with `CGO_ENABLED=0` for Linux `amd64` and `arm64` and Darwin `amd64` and `arm64`; and - inject the supplied tag through `internal/buildinfo.Override` in every cross-build. 5. Execute the built command and verify exact `--version` output when the current host GOOS/GOARCH matches one of the four targets. Do not attempt to execute a foreign target. 6. Do not compile for Windows, write output beneath the repository, contact an LLM provider, require credentials, or mutate tracked files. ### Tests And Validation - Run the script with `v0.0.0` as a synthetic build version. It does not require or create a corresponding release note or Git tag. - Exercise its cheap argument guards separately with missing, extra, malformed, prerelease, and leading-zero versions. These failures must occur before Go tests or builds begin. - Confirm temporary outputs are removed on success and ordinary command failure. Do not add a large shell-test framework solely for this script; retain focused automated tests only if they protect a realistic failure that is not more clearly covered by executing the checker itself. ```sh ./scripts/check-release-source.sh v0.0.0 git status --short ``` ### Acceptance Criteria - One command runs every substantive source-candidate check required by the feature roadmap. - The command is deterministic, offline, credential-free, POSIX-compatible, fail-fast, and safe with temporary paths. - The Linux and Darwin target matrix succeeds and Windows is absent. - Version injection and host-binary reporting are checked as part of the same matrix. - Successful execution leaves the worktree and index unchanged. ## Stage 3: Add Validation-Only Tag CI ✅ ### Goal Independently validate every newly pushed release tag without publishing or mutating release state. ### Implementation 1. Add `.woodpecker/release.yml` triggered only by tag events. 2. Use the Go 1.25.5 container image to match the version currently declared by `go.mod`. When the declared Go version changes in a future release, the release pipeline image and release documentation must be reviewed in the same change. 3. In one validation step: - read the candidate version only from `CI_COMMIT_TAG`; - require the stable SemVer form fixed above; - require a nonempty `docs/releases/$CI_COMMIT_TAG.md`; - require the exact heading `# Notarius $CI_COMMIT_TAG`; - require exact `## Summary`, `## Compatibility`, `## Upgrade`, and `## Changes` headings; and - invoke `./scripts/check-release-source.sh "$CI_COMMIT_TAG"`. 4. Do not include a release plugin, API token, artifact upload, Gitea release creation, archive/checksum step, Windows target, tag mutation, or retry that could overwrite published state. 5. Keep the CI file thin: note/tag guards belong in it, while the substantive source checks remain in the shared script. ### Validation - Review the YAML trigger and commands against the repository's Woodpecker syntax and the established Weatherreporter tag pipeline structure. - Confirm every invoked path exists and the script is executable. - Run the shared checker locally with `v0.0.0`. - Search the new pipeline for release-plugin configuration, upload commands, secrets, Windows targets, and mutation commands; none may be present. - Run `git diff --check`. Do not push a synthetic tag merely to test this stage. The first real release will exercise the remote trigger; local source validation and review provide the pre-release confidence boundary. ### Acceptance Criteria - Every stable release tag triggers the validation pipeline. - Missing or malformed version-matched release notes fail before the expensive source checks. - CI calls the same substantive checker used locally. - The pipeline cannot publish binaries, releases, checksums, or other assets and requires no release secret. ## Stage 4: Establish The Canonical Release Procedure And Documentation Policy ✅ ### Goal Make the complete source-release workflow executable by a maintainer without undocumented knowledge, and give release documentation an explicit canonical home. ### Implementation 1. Create `docs/release.md`, adapted to Notarius's source-only model. It must define: - stable SemVer selection and the pre-`v1` compatibility policy; - the exact `docs/releases/.md` template and validation guards; - invocation of `./scripts/check-release-source.sh "$RELEASE_VERSION"`; - manual review of changed Markdown links and unintended repository files; - committing and pushing the release note and current documentation before tagging; - recording `RELEASE_COMMIT` from `HEAD^{commit}`; - a copyable guard that requires `main`, a clean worktree/index, disabled Go workspace use, an exact match between `RELEASE_COMMIT` and `origin/main`, a matching note, and an unused local and remote tag; - explicit lightweight-tag creation against `RELEASE_COMMIT`; - pushing only `refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION`; - remote tag and tagged-note verification; - a fresh `go install ...@"$RELEASE_VERSION"` or fresh exact-tag checkout verification, including exact `--version` output; and - immutable-tag failure and correction policy. 2. The procedure must say that the tag and checked-in note are the release and that tag CI is validation-only. It must explicitly exclude binaries, archives, checksums, signatures, containers, package-manager publication, Gitea release objects, Windows, and retrospective notes for existing tags. 3. Document private-module installation through standard `GOPRIVATE` and Git authentication mechanisms without including credentials or private environment dumps. Do not make one maintainer's credential setup part of the release contract. 4. Update `docs/policy/documentation.md`: - add canonical-owner rows for `docs/release.md` and `docs/releases/`; - state that release notes are historical summaries, not current-state contract owners; - state that the checked-in note at the immutable tag is the release record; and - require current canonical docs to change with behavior rather than using release notes as substitutes. 5. Update `docs/development.md` with a release-preparation/tagging/verification routing row pointing to `docs/release.md` and the relevant policies. 6. Do not create an empty placeholder release note or a retrospective note. `docs/releases/` first becomes tracked when the next actual release note is prepared. ### Validation - Follow every local Markdown link added or changed in this stage. - Execute every non-destructive candidate-validation command that does not require an actual new release note, remote tag, or publication. - Compare the procedure line by line with the shared checker and CI so their tag syntax, note headings, target matrix, and validation ownership agree. - Confirm the procedure never uses `git push --tags`, moves a published tag, uploads an asset, or embeds credentials. - Run `git diff --check`. ### Acceptance Criteria - `docs/release.md` is sufficient to prepare, guard, tag, publish, verify, and recover from a source release. - Release procedure and release-note ownership are explicit and nonduplicative. - The procedure calls the shared checker rather than restating its full command matrix. - No historical or placeholder release note is introduced. ## Stage 5: Align Platform, Installation, And Operational Documentation ✅ ### Goal Make the supported-platform and source-installation story discoverable in the canonical current-state documents without duplicating the release procedure. ### Implementation 1. Update `README.md` with a concise source-installation section. Show `go install gitea.maximumdirect.net/eric/notarius/cmd/notarius@` as a version-pinned pattern and retain the existing minimal product quickstart. Link release maintainers to `docs/release.md` rather than reproducing its guards. 2. Update `docs/operations.md` with operator-facing source deployment facts: Linux support, the Go version declared by `go.mod`, version pinning, exact tag builds, and `notarius --version` as a diagnostic. Link command semantics to `docs/cli.md` and maintainer publication mechanics to `docs/release.md`. 3. Update `docs/policy/architecture.md` with the durable platform and distribution invariants: supported Linux deployment, best-effort macOS development, unsupported Windows, and source-only distribution. Keep tag commands and release mechanics out of architecture. 4. Review `docs/internal/overview.md` navigation after adding `internal/buildinfo`. Add only the smallest component entry needed if the current inventory would otherwise omit a meaningful implemented boundary; do not inflate build information into a subsystem. 5. Confirm `docs/roadmap/future.md` no longer lists the active documented release-process work. Retain packaged alpha artifacts as deferred work; the source-only release feature does not permanently reject reconsideration. 6. Review the completed feature roadmap against the implementation and correct only genuine target-state inconsistencies. Do not convert it into a changelog or duplicate `docs/release.md`. ### Validation - Verify all added and changed local Markdown links. - Confirm commands agree with the implemented CLI and declared module path. - Confirm no current-state document claims that Notarius publishes binary assets or supports Windows. - Run: ```sh go run ./cmd/notarius --version go run ./cmd/notarius help git diff --check ``` ### Acceptance Criteria - Users can discover how to install a pinned source release. - Operators can identify the deployed version and understand the support boundary. - Architecture records durable platform/distribution policy without owning maintainer release commands. - Packaged artifacts remain clearly deferred rather than accidentally promised or permanently prohibited. ## Stage 6: Final Release-System Verification ✅ ### Goal Review the implemented feature as one system and prove that code, documentation, shared validation, and CI converge on the same release model. ### Verification Work 1. Inspect all commits associated with Stages 1–5 and compare the result with `docs/roadmap/source-releases.md` and this plan. 2. Run the shared candidate checker with synthetic version `v0.0.0`. This is a build identity only; do not create a note or tag for it. 3. Independently run the repository-wide baseline checks if any are not already performed by the shared checker: ```sh go test ./... go vet ./... go build ./cmd/notarius ``` 4. Verify exact development and injected release output: - ordinary checkout: `notarius development`; - injected `v0.0.0`: `notarius v0.0.0`; - invalid linker override: nonzero exit, no false release identity. 5. Confirm both maintained D&D configurations validate offline. 6. Verify all added or changed local Markdown links and run `git diff --check`. 7. Audit `.woodpecker/release.yml` and `docs/release.md` for agreement on tag form, note location/headings, Go image/version expectations, and shared checker use. 8. Confirm the repository contains no generated release binary, distribution directory, checksum file, release credential, Windows target, release plugin, synthetic release note, or new tag. 9. Review tests under the repository testing policy. Retain behavior-level coverage for the public version contract and important validation guards; remove redundant tests that merely duplicate the shared checker or CI text. 10. Report any remaining divergence as a concrete finding. Fix only in-scope release-feature defects discovered during this verification; do not expand into packaged distribution or unrelated cleanup. ### Acceptance Criteria - All feature-roadmap acceptance criteria are met except creation of the first real post-procedure release, which is intentionally a separate operator action. - Local validation and tag CI share one substantive source checker. - Version reporting, source installation, platform policy, documentation ownership, tag guards, and immutable failure handling are internally consistent. - The full test suite, vet, build, configuration validation, four-target cross- build matrix, Markdown link review, and whitespace checks pass. - The worktree contains no release side effects beyond the intended source, automation, and documentation changes.