429 lines
19 KiB
Markdown
429 lines
19 KiB
Markdown
# 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 <resolved-version>\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/<tag>.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@<tag>` 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.
|