19 KiB
Source-Only Release Implementation Plan
Purpose
Implement the target state defined by Source-Only Releases: 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.PATCHsource tags onmain; 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
amd64andarm64withCGO_ENABLED=0. - macOS is best-effort. Release checks compile Darwin
amd64andarm64withCGO_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 --versionis 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
- Create
internal/buildinfowith an exported link-time string variable namedOverrideand an exported resolver such asVersion() (string, error). Keep this package independent of CLI and application packages. - Resolve the displayed value using this precedence:
- a nonempty
Override; - the main-module version returned by
runtime/debug.ReadBuildInfo; then - the literal
development.
- a nonempty
- 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 todevelopment. - Add
--versionto the root dispatch ininternal/cli. It is valid only as the sole argument, writes exactlynotarius <resolved-version>\nto 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. - Add the flag to root usage without changing the existing behavior of empty arguments, help spellings, or subcommands.
- Update
docs/cli.mdas the canonical public contract anddocs/internal/cli.mdas the implementation owner. State that taggedgo installbuilds can obtain the main-module tag from Go build information, controlled builds may injectgitea.maximumdirect.net/eric/notarius/internal/buildinfo.Override, and an ordinary unversioned checkout reportsdevelopment.
Tests
- Add table-driven
internal/buildinfotests 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
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:
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
- 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. - 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. - Keep release-note, branch, remote, clean-worktree, and tag-existence guards
out of this script. Those publication-specific checks belong in
docs/release.mdand tag CI. This script owns the substantive source checks that both workflows share. - Implement these checks in a clear fail-fast order:
- require the expected Notarius module path in
go.mod; - reject tracked
go.workorgo.work.sum, an existingvendordirectory, and anyreplacedirective ingo.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 -lfor tracked Go files; git diff --checkandgit diff --cached --check;- validate
examples/dnd-minimal.config.ymlandexamples/dnd-complete.config.ymlfor pipelinednd-sessionusing the built orgo runNotarius command; - build
./cmd/notariuswithCGO_ENABLED=0for Linuxamd64andarm64and Darwinamd64andarm64; and - inject the supplied tag through
internal/buildinfo.Overridein every cross-build.
- require the expected Notarius module path in
- Execute the built command and verify exact
--versionoutput when the current host GOOS/GOARCH matches one of the four targets. Do not attempt to execute a foreign target. - 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.0as 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.
./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
- Add
.woodpecker/release.ymltriggered only by tag events. - 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. - 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## Changesheadings; and - invoke
./scripts/check-release-source.sh "$CI_COMMIT_TAG".
- read the candidate version only from
- 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.
- 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
- Create
docs/release.md, adapted to Notarius's source-only model. It must define:- stable SemVer selection and the pre-
v1compatibility policy; - the exact
docs/releases/<tag>.mdtemplate 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_COMMITfromHEAD^{commit}; - a copyable guard that requires
main, a clean worktree/index, disabled Go workspace use, an exact match betweenRELEASE_COMMITandorigin/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--versionoutput; and - immutable-tag failure and correction policy.
- stable SemVer selection and the pre-
- 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.
- Document private-module installation through standard
GOPRIVATEand Git authentication mechanisms without including credentials or private environment dumps. Do not make one maintainer's credential setup part of the release contract. - Update
docs/policy/documentation.md:- add canonical-owner rows for
docs/release.mdanddocs/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.
- add canonical-owner rows for
- Update
docs/development.mdwith a release-preparation/tagging/verification routing row pointing todocs/release.mdand the relevant policies. - 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.mdis 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
- Update
README.mdwith a concise source-installation section. Showgo install gitea.maximumdirect.net/eric/notarius/cmd/notarius@<tag>as a version-pinned pattern and retain the existing minimal product quickstart. Link release maintainers todocs/release.mdrather than reproducing its guards. - Update
docs/operations.mdwith operator-facing source deployment facts: Linux support, the Go version declared bygo.mod, version pinning, exact tag builds, andnotarius --versionas a diagnostic. Link command semantics todocs/cli.mdand maintainer publication mechanics todocs/release.md. - Update
docs/policy/architecture.mdwith 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. - Review
docs/internal/overview.mdnavigation after addinginternal/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. - Confirm
docs/roadmap/future.mdno 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. - 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:
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
- Inspect all commits associated with Stages 1–5 and compare the result with
docs/roadmap/source-releases.mdand this plan. - 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. - Independently run the repository-wide baseline checks if any are not already performed by the shared checker:
go test ./...
go vet ./...
go build ./cmd/notarius
- 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.
- ordinary checkout:
- Confirm both maintained D&D configurations validate offline.
- Verify all added or changed local Markdown links and run
git diff --check. - Audit
.woodpecker/release.ymlanddocs/release.mdfor agreement on tag form, note location/headings, Go image/version expectations, and shared checker use. - Confirm the repository contains no generated release binary, distribution directory, checksum file, release credential, Windows target, release plugin, synthetic release note, or new tag.
- 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.
- 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.