201 lines
11 KiB
Markdown
201 lines
11 KiB
Markdown
# ReleaseKit: shared release process
|
||
|
||
This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius,
|
||
Narratio, and Scriptorium on 2026-09-29. Product capabilities remain proposed.
|
||
The public facade architecture is accepted in
|
||
[ADR 0001](docs/adr/0001-public-facade.md), with implementation pending.
|
||
|
||
## Opportunity
|
||
|
||
All six repositories follow the same base release policy. Their release scripts
|
||
repeat stable-version parsing, candidate and release-note checks, local and
|
||
remote tag checks, validation of one captured commit, explicit Git publication,
|
||
and recovery guidance for uncertain pushes. They differ in publication approach,
|
||
tag type, validation commands, and artifact delivery. The mechanics of embedding
|
||
a release version and cross-building an application are further candidates for
|
||
standardization.
|
||
|
||
If the projects deliberately standardize publication approach and tag type,
|
||
ReleaseKit can own most of the repeated release-process code. The current six
|
||
repositories contain 942 lines of release shell scripts and 742 lines of
|
||
dedicated release tests, or 1,684 lines total. A directional estimate is that
|
||
roughly 1,000–1,250 lines could leave those repositories: about 450–500 lines
|
||
of publication scripts, 150–250 lines of checks and builders, and 400–500 lines
|
||
of repeated publication tests. ReleaseKit would gain its own implementation and
|
||
tests, so this is a reduction in local code, not a net line-count saving.
|
||
|
||
## Proposed publication standard
|
||
|
||
1. Prepare and commit a version-matched note at `docs/releases/<version>.md`.
|
||
2. Publish the candidate commit to authoritative `main` before invoking the
|
||
release command.
|
||
3. Run one command that checks the candidate and unused stable version, runs
|
||
that repository's offline candidate validation once, and rechecks mutable
|
||
state.
|
||
4. Create an annotated tag at the validated commit with a message derived from
|
||
the checked-in note. Push only that exact tag object to the authoritative
|
||
endpoint. A successful tag push completes the source release.
|
||
5. Treat hosted release pages and binary assets as asynchronous delivery with
|
||
their own status. Do not poll CI or require artifact availability to declare
|
||
the source release complete.
|
||
|
||
This would replace Audita's atomic branch-and-tag push, ConfigKit's and
|
||
Notarius's branch push during the release command, and Audita's, Notarius's,
|
||
and Narratio's lightweight tags. Those changes require updates to each
|
||
repository's adopted release policy and procedure; they are not merely code
|
||
cleanup.
|
||
|
||
## Architecture
|
||
|
||
[ADR 0001](docs/adr/0001-public-facade.md) owns the accepted facade decision.
|
||
The root `releasekit` package will provide the public facade for publication,
|
||
builds, and delivery. A separate public `buildinfo` package will provide the
|
||
lightweight facade for runtime version information.
|
||
|
||
Each facade owns its public named types and documented contracts. Public
|
||
operations, inputs, outcomes, errors, and callbacks express deliberately
|
||
selected consumer needs. Handles retain private implementation state; explicit
|
||
forwarding and conversions connect the public contracts to internal
|
||
representations.
|
||
|
||
Git operations, Go toolchain execution, Gitea integration, and orchestration
|
||
bookkeeping belong under Go's `internal/` boundary. Dependency direction runs
|
||
from the facades into internal implementation, with internal collaborators using
|
||
contracts defined inside that boundary. Implementation packages can evolve
|
||
while the supported public contracts retain their compatibility obligations.
|
||
|
||
The `buildinfo` facade keeps runtime version access lightweight. Applications
|
||
read their embedded version through its supported accessor. ReleaseKit's builder
|
||
and runtime implementation own the linker symbol and injection mechanism
|
||
together.
|
||
|
||
The `buildinfo` facade follows three constraints:
|
||
|
||
- Its public API exposes only runtime information justified by consumer needs.
|
||
- Its implementation uses the standard library and a small internal dependency
|
||
graph independent of release orchestration.
|
||
- Dependency checks protect that boundary, and build-and-run integration tests
|
||
verify that the builder injects the selected version and the public accessor
|
||
reports it correctly.
|
||
|
||
The release executable calls the orchestration facade and handles command
|
||
parsing, output, and exit codes. Library errors and outcomes report meaningful
|
||
publication state, including partial progress and uncertainty. Go declarations
|
||
and GoDoc own exact public API contracts; additional public packages and
|
||
extension interfaces require a deliberate decision grounded in durable consumer
|
||
needs.
|
||
|
||
Compiled-export and dependency checks will protect facade ownership.
|
||
External-package behavioral tests will protect error identity, value ownership,
|
||
callback lifetime, and observable outcomes across forwarding and conversions.
|
||
|
||
## Shared tool boundary
|
||
|
||
Provide a versioned ReleaseKit executable backed by a Go library. Existing
|
||
shell entry points can become short invocations of that executable. A library
|
||
alone would still require a caller in every shell-driven repository.
|
||
|
||
ReleaseKit should own:
|
||
|
||
- stable-version parsing and committed-note identity checks;
|
||
- authoritative Git endpoint resolution, candidate capture, clean `main` and
|
||
remote-branch agreement checks, and unused local/remote tag checks;
|
||
- one validation invocation bound to the captured candidate, followed by cheap
|
||
rechecks before publication;
|
||
- annotated-tag creation and verification, explicit tag-object push, and one
|
||
bounded authoritative-ref check after a failed or ambiguous push;
|
||
- a standard version accessor in the public `buildinfo` facade, backed by
|
||
release-build injection of the selected tag;
|
||
- a cross-build API that accepts the application's main package and selected
|
||
targets, initially Linux amd64, Linux arm64, and macOS arm64 (Go's
|
||
`darwin/arm64`), including an empty set for source-only projects;
|
||
- consistent release build flags and asset naming, plus a host-runnable build
|
||
that checks the executable's reported version; and
|
||
- Git publication tests using temporary local remotes, including remote
|
||
advancement, rejected pushes, lost acknowledgments, and unavailable remotes.
|
||
|
||
Each repository should retain its product name, compatibility decisions,
|
||
candidate-specific checks, main package, selected build targets, delivery
|
||
declaration, and a small test of its ReleaseKit wiring. For example, PromptKit
|
||
exercises its consumer examples. Applications would adapt their existing
|
||
version output to use the `buildinfo` accessor; one agreed version command
|
||
or flag would let ReleaseKit smoke-test a host-runnable build the same way.
|
||
Cross-built binaries would use the same injection mechanism, but compilation
|
||
alone would not establish that they run correctly on another platform.
|
||
ReleaseKit should run the selected checks and builds without silently adding
|
||
binary gates to source-only projects.
|
||
|
||
The build API should own the mechanics rather than require each application to
|
||
provide a custom linker symbol or shell loop. The initial target list reflects
|
||
the current projects, not a permanent limit on future platforms. It should
|
||
support both candidate cross-build checks and artifact builds from the tagged
|
||
source, using the same version and target selection.
|
||
|
||
## Woodpecker and Gitea delivery
|
||
|
||
A common tag-triggered Woodpecker workflow can run ReleaseKit against the
|
||
tagged source. Repository-local YAML should declare the pinned tool image and
|
||
credentials; each repository's ReleaseKit configuration should declare its
|
||
main package and target set. ReleaseKit can own version injection, cross-builds,
|
||
checksum generation, and delivery. The source-only repositories can declare
|
||
zero binary assets.
|
||
|
||
For projects that declare a hosted Gitea release, a shared delivery command
|
||
could create or find the release for the tag, use the checked-in note, and add
|
||
the declared assets and checksum manifest. A retry should inspect existing
|
||
assets, verify matching content, add only missing items, and stop on a conflict.
|
||
An existing checksum manifest must remain consistent with any later addition.
|
||
The release and its attachments are separate remote operations, so partial
|
||
delivery must be expected and reported clearly.
|
||
|
||
Audita, Narratio, and Scriptorium currently use the Woodpecker release plugin
|
||
with `file-exists: skip` and `overwrite: false`. Those settings avoid deliberate
|
||
replacement by filename, but they do not prove that a rebuilt file has the
|
||
same bytes as a previously published file. Content comparison would be a
|
||
reason to implement a shared Gitea delivery client rather than rely solely on
|
||
the plugin's skip behavior.
|
||
|
||
Requiring a hosted Gitea release for PromptKit, ConfigKit, or Notarius would
|
||
add a deliverable and credentials to their current source-only policies. That
|
||
decision can be made separately from standardizing delivery for projects that
|
||
already publish hosted assets.
|
||
|
||
## Failure states
|
||
|
||
ReleaseKit should report source publication and artifact delivery separately:
|
||
|
||
| Event | Result |
|
||
| --- | --- |
|
||
| Candidate validation or a pre-push guard fails | No release was published; correct and validate the candidate again. |
|
||
| Tag push reports success | Source release is complete; delivery may still be pending. |
|
||
| Tag push fails or its acknowledgment is lost | Query the authoritative ref once. Confirm success only if the exact intended tag object is present on the intended commit; otherwise report failed or unknown status and retain the local tag. |
|
||
| Woodpecker or Gitea delivery fails after tag publication | Keep the source release successful. Retry missing delivery from the immutable tag after checking existing content. |
|
||
| Published source or content needs correction | Publish a new version; never replace content under the published version. |
|
||
|
||
## First implementation slice
|
||
|
||
Pilot the common publication path with PromptKit and Scriptorium, which already
|
||
require `main` to equal the candidate and publish annotated tags. Develop the
|
||
pilot through the root `releasekit` facade, with implementation behind its
|
||
internal boundary and tests exercising the supported consumer contract. Their
|
||
different validation and remote configuration will test the proposed boundary.
|
||
Migrate the other projects after the shared Git contract is exercised against
|
||
temporary remotes. Add common delivery only after publication behavior is
|
||
settled, using one of the existing binary-publishing projects as the first
|
||
delivery pilot.
|
||
|
||
## Reviewed implementations
|
||
|
||
- [PromptKit release script](../promptkit/scripts/release.sh),
|
||
[checker](../promptkit/scripts/release-check.sh)
|
||
- [ConfigKit release script](../ConfigKit/scripts/release.sh),
|
||
[checker](../ConfigKit/scripts/release-check.sh)
|
||
- [Audita release script](../audita/scripts/release.sh),
|
||
[Woodpecker job](../audita/.woodpecker/release.yml)
|
||
- [Notarius release script](../notarius/scripts/release.sh),
|
||
[checker](../notarius/scripts/check-release-source.sh)
|
||
- [Narratio release script](../narratio/scripts/release.sh),
|
||
[Woodpecker job](../narratio/.woodpecker/release.yml)
|
||
- [Scriptorium release script](../scriptorium/scripts/release.sh),
|
||
[Woodpecker job](../scriptorium/.woodpecker/release.yml)
|