Files
ReleaseKit/docs/roadmap/releasekit.md

12 KiB
Raw Blame History

ReleaseKit: shared release process

This is a proposal based on a review of PromptKit, ConfigKit, Audita, Notarius, Narratio, and Scriptorium on 2026-09-29. The implementation plan records the current implemented boundary; builds, delivery, and broader integrations remain proposed. The public facade architecture is accepted in ADR 0001 and is implemented for runtime version, candidate validation, and source publication. The implementation plan owns ordered stages, execution instructions, acceptance checks, and progress toward this target state.

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 owns the accepted facade decision. The architecture policy owns the adopted responsibility and dependency rules, including the runtime version boundary. The root releasekit package provides the public facade for candidate validation and source publication; builds and delivery remain planned. The separate public buildinfo package provides lightweight runtime version access.

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 delivery destination and any binary main package and target set. ReleaseKit can own version injection, cross-builds, checksum generation, and delivery. All projects should receive hosted Gitea release pages. Source-only repositories declare zero binary assets and deliver the committed release note without a binary build or checksum manifest.

The shared delivery command should create or find the release for the tag, use the checked-in note, and add declared binary assets and their checksum manifest where applicable. 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.

Hosted pages are selected for source-only projects, including PromptKit, ConfigKit, Notarius, and ReleaseKit. Adoption adds a page deliverable and delivery credentials to their procedures while preserving successful tag publication as source completion. The initial implementation should support both page-only and binary delivery through the same facade and command.

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.

Consumer adoption target

All six projects should use the same selected publication, runtime version, build, and delivery conventions where those responsibilities apply. Repository entry points should contain only tool invocation and product-specific settings or checks. Shared mechanics and their repeated tests belong in ReleaseKit.

Application runtime code should obtain version information through buildinfo. Source-only libraries need no runtime import or automatic binary deliverable. Projects declaring binaries should use the common target selection and version injection; hosted delivery should use content-aware retries for both page-only and binary releases. Existing published tags, notes, pages, and assets retain their historical contents.

Consumer policy and procedure changes should describe the deliberately adopted standard and its compatibility impact. Adoption should demonstrate reduced local infrastructure and one coherent release model. The implementation plan owns the complete initial ReleaseKit implementation and its development evidence. Architecture review follows that implementation; broader testing and consumer migration steps will be chosen afterward.

Reviewed implementations