From 55333b08ae2e8ffbc6f53d988dc4301c56623f7d Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 6 Oct 2026 20:15:14 +0000 Subject: [PATCH] Validate representative ReleaseKit consumers --- cmd/releasekit/main_test.go | 26 ++++++++++++++++++++ delivery_test.go | 45 ++++++++++++++++++++++++++++++++++ docs/config.md | 3 +-- docs/internal/overview.md | 14 ++++++----- docs/roadmap/implementation.md | 23 ++++++++++++++++- docs/roadmap/releasekit.md | 21 ++++++++++++++++ 6 files changed, 123 insertions(+), 9 deletions(-) diff --git a/cmd/releasekit/main_test.go b/cmd/releasekit/main_test.go index b78b3ef..d25af38 100644 --- a/cmd/releasekit/main_test.go +++ b/cmd/releasekit/main_test.go @@ -99,9 +99,28 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) { func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) { repository := newCLIRepository(t) + writeRepositoryFile(t, repository, "docs/releases/v1.2.3.md", "# Example Product v1.2.3\n\n## Compatibility\n\nNo action required.\n") + writeRepositoryFile(t, repository, "scripts/check-release-note.sh", `#!/bin/sh +set -eu +version=$RELEASEKIT_VERSION +candidate=$RELEASEKIT_CANDIDATE +test "$(git rev-parse HEAD)" = "$candidate" +test "$(sed -n '1p' "docs/releases/$version.md")" = "# Example Product $version" +grep -Fxq '## Compatibility' "docs/releases/$version.md" +grep -Fxq 'No action required.' "docs/releases/$version.md" +grep -Fxq 'module example.com/cli-candidate' go.mod +printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG" +`) + cliGitRun(t, repository, "add", "docs/releases/v1.2.3.md", "scripts/check-release-note.sh") + cliGitRun(t, repository, "commit", "-m", "add compatibility note and check") + checkLog := filepath.Join(t.TempDir(), "compatibility-check.log") settings := cli.Settings{ Product: "Example Product", Remote: "origin", + Checks: []cli.CheckCommand{{ + Args: []string{"sh", "scripts/check-release-note.sh"}, + Env: map[string]string{"RELEASEKIT_COMPATIBILITY_LOG": checkLog}, + }}, } contents, err := json.Marshal(settings) if err != nil { @@ -133,6 +152,13 @@ func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) { if got := cliGitOutput(t, remote, "rev-parse", "refs/tags/v1.2.3"); got != tagObject { t.Fatalf("remote tag object = %s, want %s", got, tagObject) } + checkResult, err := os.ReadFile(checkLog) + if err != nil { + t.Fatalf("read product compatibility check result: %v", err) + } + if got, want := strings.Count(string(checkResult), "v1.2.3 "+commit), 1; got != want { + t.Fatalf("product compatibility check ran %d times, want %d", got, want) + } } func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts(t *testing.T) { diff --git a/delivery_test.go b/delivery_test.go index 505f10f..16c3df7 100644 --- a/delivery_test.go +++ b/delivery_test.go @@ -54,6 +54,51 @@ func TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput(t *testing. } } +func TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets(t *testing.T) { + repository := newTaggedDeliveryConsumer(t) + server, remote := newFacadeDeliveryServer(t, repository) + defer server.Close() + var progress strings.Builder + + result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{ + RepositoryDir: repository, + Product: "Fixture", + Version: "v1.2.3", + Destination: facadeDeliveryDestination(server.URL), + Build: &releasekit.BuildSpec{ + MainPackage: "./cmd/app", + BinaryName: "fixture", + Targets: []releasekit.Target{ + {OS: "linux", Arch: "amd64"}, + {OS: "linux", Arch: "arm64"}, + }, + }, + Output: &progress, + }) + if err != nil { + t.Fatalf("Deliver() error = %v; result = %#v", err, result) + } + if result.State != releasekit.DeliveryComplete || result.CandidateCommit != remote.commit { + t.Fatalf("delivery result = %#v", result) + } + wantAssets := []string{ + "fixture-v1.2.3-linux-amd64", + "fixture-v1.2.3-linux-arm64", + "SHA256SUMS", + } + if len(result.Uploaded) != len(wantAssets) || len(remote.assets) != len(wantAssets) { + t.Fatalf("uploaded = %#v; hosted assets = %v", result.Uploaded, mapKeys(remote.assets)) + } + for _, name := range wantAssets { + if _, ok := remote.assets[name]; !ok { + t.Errorf("hosted assets omit %q: %v", name, mapKeys(remote.assets)) + } + } + if !strings.Contains(progress.String(), "checking fixture version output") { + t.Fatalf("build progress = %q", progress.String()) + } +} + func TestDeliverPrebuiltArtifactsRemainCallerOwned(t *testing.T) { repository := newTaggedDeliveryConsumer(t) outputDir := filepath.Join(t.TempDir(), "caller-owned-assets") diff --git a/docs/config.md b/docs/config.md index e68eb97..db94d5c 100644 --- a/docs/config.md +++ b/docs/config.md @@ -56,8 +56,7 @@ selected build targets; source-only delivery does not need application dependencies. This preparation may access the network and may update `go.sum`. The command takes CI values as arguments, so profile settings remain independent of Woodpecker environment variable names. See the -[CI command contract](cli.md#prepare-a-tagged-ci-checkout-and-deliver-it) for -the invocation and exit statuses. +[CLI contract](cli.md) for the invocation and exit statuses. ReleaseKit's source-only profile is [releasekit.json](../releasekit.json). diff --git a/docs/internal/overview.md b/docs/internal/overview.md index 4acc80c..4a04e50 100644 --- a/docs/internal/overview.md +++ b/docs/internal/overview.md @@ -11,8 +11,8 @@ two Go facades and a command-line interface: - `buildinfo` is the small runtime facade. `Version` reports the injected application version, the main-module version when available, or `dev`. - `cmd/releasekit` is the executable interface. Its `version`, `check`, - `publish`, `build`, and `deliver` commands adapt profiles and command output - to the supported library operations. + `publish`, `build`, `deliver`, and `ci-deliver` commands adapt profiles and + command output to the supported library operations. See the [CLI contract](../cli.md), [configuration reference](../config.md), [candidate check guide](../consumers/check.md), @@ -66,12 +66,12 @@ not a separate release matrix: | Capability | Implementation owner | Existing evidence | | --- | --- | --- | | Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` | -| Candidate capture, standard suite, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `cmd/releasekit/internal/cli` | `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate` | +| Candidate capture, standard suite, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `cmd/releasekit/internal/cli` | `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate` | | Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` | | Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestResolveUsesPushURLAndQueriesWithoutMutation` | | Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` | | Cross-builds, version smoke checks, empty target sets, and partial outputs | `releasekit.Build`, `internal/build`, `buildinfo` | `TestBuildCreatesSelectedTargetsAndInjectsVersion`, `TestBuildWithNoTargetsDoesNotInvokeCompiler`, `TestBuildReturnsPartialArtifactsAfterTargetFailure` | -| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites` | +| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites` | | Tagged checkout and build dependency preparation | `releasekit.PrepareTaggedCheckout`, `releasekit.PrepareBuildDependencies`, `internal/preparation`, `internal/git`, `internal/gotool` | `TestPrepareTaggedCheckoutFetchesExpectedCommitAndDetaches`, `TestPrepareTaggedCheckoutRejectsWrongCommitBeforeCheckout`, `TestPrepareTaggedCheckoutReportsLocalCheckoutConflict`, `TestPrepareTaggedCheckoutReportsConflictingLocalTag`, `TestPrepareBuildDependenciesPreservesPreparationEnvironment`, `TestPrepareBuildDependenciesPropagatesCommandFailure`, `TestPrepareBuildDependenciesPropagatesCancellation` | | Page, manifest, asset comparison, and additive retry outcomes | `internal/gitea` | `TestEnsureReleaseCreatesMissingPageFromCommittedNote`, `TestInspectAssetsDetectsSameNameAndSizeWithDifferentBytes`, `TestUploadMissingPartialFailureThenRerunOnlyMissingAssets` | | CLI profiles and prepared Woodpecker sequences | `cmd/releasekit`, `cmd/releasekit/internal/cli`, `examples/woodpecker/` | `TestLoadSettingsRejectsInvalidProfiles`, `TestWoodpeckerTagDeliveryTemplate`, `TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout` | @@ -86,5 +86,7 @@ The [Gitea integration guide](../integrations/gitea.md) owns the HTTP contract and hosted response handling. The [Woodpecker examples](../../examples/woodpecker/) are prepared integrations; their YAML is not active in this repository, and no live Woodpecker runner, configured secret, or hosted delivery has been used. -Consumer migrations and measurement of removed consumer infrastructure remain -future adoption work. +A read-only PromptKit and Narratio migration review and projected deletion +estimates are recorded in the [product roadmap](../roadmap/releasekit.md); +actual consumer migrations and measurement of realized savings remain future +adoption work. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 1bed442..d2fda19 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # ReleaseKit audit implementation plan -Status: Stages 1–10 complete; Stage 11 next. +Status: All implementation stages complete. Baseline: `e4be7fa` (2026-10-06). The [audit](audit.md) records the evidence, product goals, and selected direction. This document owns implementation order and completion criteria for that work. @@ -568,6 +568,27 @@ migration, and remaining deployment work. Every F1–F8 finding has an implement resolution and appropriately scoped evidence. Review and commit the final changes with a clean worktree. +**Completed:** The source-only consumer publication fixture runs one meaningful +compatibility check against its committed note and module, then confirms the +annotated tag on a local Git remote. A separate binary consumer fixture builds +`buildinfo` binaries for selected Linux targets, checks native version output, +and delivers both binaries plus `SHA256SUMS` to a local HTTP Gitea fixture. +PromptKit and Narratio were reviewed read-only on 2026-10-06; the product +roadmap records retained settings, runtime and policy/procedure adaptations, +and projected deletion ranges. No sibling repositories were changed. + +The final documentation review reconciled GoDoc, CLI/configuration references, +consumer guidance, Woodpecker examples, the internal inventory, and product +roadmap. The pre-release API migration is `Validate` to `AdditionalChecks` and +`DeliveryRequest.Build` to `*BuildSpec`, with `Prebuilt` for retained artifacts; +JSON `checks` remains the ordered additional-check setting. No new product +decision was required. The completed stages address F1–F8; real source +publication, an immutable tool pin, credentials, workflow activation, and live +Gitea/Woodpecker verification remain deployment work. + +The full offline suite and API boundary check pass with Go 1.25.0. Go formatting, +documentation whitespace, and changed local-link checks pass. + ## Open Questions No unresolved product choice is required to implement these stages. The diff --git a/docs/roadmap/releasekit.md b/docs/roadmap/releasekit.md index 569b0dc..832321c 100644 --- a/docs/roadmap/releasekit.md +++ b/docs/roadmap/releasekit.md @@ -204,6 +204,27 @@ 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. +## Representative migration review + +Read-only reviews of the available PromptKit and Narratio checkouts were +verified on 2026-10-06 at `4b8169d` and `8afb49c`; both worktrees were clean. +These are migration assessments, not changes to either consumer. + +| Consumer | Retain in its profile and product checks | Adaptation and procedure changes | +| --- | --- | --- | +| PromptKit, source-only | Product identity; its `Summary`, `Compatibility`, `Upgrade`, and `Changes` release-note sections; checks that exercise the three maintained Go examples and their product outputs. No runtime `buildinfo` import or binary targets are needed. | Its current release scripts total 209 lines. The projected deletion is about 120–160 lines of shared publication and generic validation machinery; retain the note-content rule and product-example checks as a configured additional check plus a small wiring test. PromptKit already pushes `main` before publication and creates annotated tags. Its release policy and procedure still need to add the hosted note page, delivery credentials, versioned CI invocation, and separate delivery status and recovery. | +| Narratio, binary | Product/module identity, the `./cmd/narratio` main package, `narratio` executable name, Linux amd64, Linux arm64, and macOS arm64 targets, and the `narratio ` command. Keep the module-graph check (`go mod tidy -diff`) if it remains useful as a product-specific candidate check. | Its release, check, asset-builder, and helper scripts total 304 lines; two dedicated release test files total 741 lines. Projected deletions are about 220–280 shell lines and 450–650 repeated test lines, leaving a small consumer wiring test and any retained module/note checks. Replace the private linker-version variable with `buildinfo.Version()` while preserving the current command output. Its policy and procedure need to change lightweight tags to annotated tags, state that a successful tag push completes source publication, and move plugin-based page/assets delivery to the shared versioned CI command with content-aware retries and separately provisioned delivery credentials. | + +The deletion figures are estimates from the reviewed files, not measured +migration results or a promise that each line will disappear. Product checks, +version output, target choices, and at least one ReleaseKit integration check +remain with each consumer. The existing cross-project estimate of 1,000–1,250 +potentially removable lines remains directional until migrations are completed. +The self-contained ReleaseKit fixtures exercise a meaningful source-only +compatibility check through local Git publication, and a `buildinfo` binary +consumer with selected targets through local HTTP delivery. These are separate +publication and delivery operations. + ## Remaining work - Prepare and publish the first ReleaseKit source release under the