Validate representative ReleaseKit consumers

This commit is contained in:
2026-10-06 20:15:14 +00:00
parent 21294404d7
commit 55333b08ae
6 changed files with 123 additions and 9 deletions

View File

@@ -99,9 +99,28 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) { func TestPublishCommandPublishesConfiguredCandidate(t *testing.T) {
repository := newCLIRepository(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{ settings := cli.Settings{
Product: "Example Product", Product: "Example Product",
Remote: "origin", 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) contents, err := json.Marshal(settings)
if err != nil { 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 { if got := cliGitOutput(t, remote, "rev-parse", "refs/tags/v1.2.3"); got != tagObject {
t.Fatalf("remote tag object = %s, want %s", 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) { func TestBuildCommandWritesValidatedReceiptForConfiguredArtifacts(t *testing.T) {

View File

@@ -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) { func TestDeliverPrebuiltArtifactsRemainCallerOwned(t *testing.T) {
repository := newTaggedDeliveryConsumer(t) repository := newTaggedDeliveryConsumer(t)
outputDir := filepath.Join(t.TempDir(), "caller-owned-assets") outputDir := filepath.Join(t.TempDir(), "caller-owned-assets")

View File

@@ -56,8 +56,7 @@ selected build targets; source-only delivery does not need application
dependencies. This preparation may access the network and may update dependencies. This preparation may access the network and may update
`go.sum`. The command takes CI values as arguments, so profile settings remain `go.sum`. The command takes CI values as arguments, so profile settings remain
independent of Woodpecker environment variable names. See the independent of Woodpecker environment variable names. See the
[CI command contract](cli.md#prepare-a-tagged-ci-checkout-and-deliver-it) for [CLI contract](cli.md) for the invocation and exit statuses.
the invocation and exit statuses.
ReleaseKit's source-only profile is ReleaseKit's source-only profile is
[releasekit.json](../releasekit.json). [releasekit.json](../releasekit.json).

View File

@@ -11,8 +11,8 @@ two Go facades and a command-line interface:
- `buildinfo` is the small runtime facade. `Version` reports the injected - `buildinfo` is the small runtime facade. `Version` reports the injected
application version, the main-module version when available, or `dev`. application version, the main-module version when available, or `dev`.
- `cmd/releasekit` is the executable interface. Its `version`, `check`, - `cmd/releasekit` is the executable interface. Its `version`, `check`,
`publish`, `build`, and `deliver` commands adapt profiles and command output `publish`, `build`, `deliver`, and `ci-deliver` commands adapt profiles and
to the supported library operations. command output to the supported library operations.
See the [CLI contract](../cli.md), [configuration reference](../config.md), See the [CLI contract](../cli.md), [configuration reference](../config.md),
[candidate check guide](../consumers/check.md), [candidate check guide](../consumers/check.md),
@@ -66,12 +66,12 @@ not a separate release matrix:
| Capability | Implementation owner | Existing evidence | | Capability | Implementation owner | Existing evidence |
| --- | --- | --- | | --- | --- | --- |
| Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` | | 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` | | 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` | | 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` | | 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` | | 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` | | 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` | | 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` | | 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/) and hosted response handling. The [Woodpecker examples](../../examples/woodpecker/)
are prepared integrations; their YAML is not active in this repository, and no are prepared integrations; their YAML is not active in this repository, and no
live Woodpecker runner, configured secret, or hosted delivery has been used. live Woodpecker runner, configured secret, or hosted delivery has been used.
Consumer migrations and measurement of removed consumer infrastructure remain A read-only PromptKit and Narratio migration review and projected deletion
future adoption work. estimates are recorded in the [product roadmap](../roadmap/releasekit.md);
actual consumer migrations and measurement of realized savings remain future
adoption work.

View File

@@ -1,6 +1,6 @@
# ReleaseKit audit implementation plan # 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, Baseline: `e4be7fa` (2026-10-06). The [audit](audit.md) records the evidence,
product goals, and selected direction. This document owns implementation order product goals, and selected direction. This document owns implementation order
and completion criteria for that work. 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 resolution and appropriately scoped evidence. Review and commit the final
changes with a clean worktree. 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 ## Open Questions
No unresolved product choice is required to implement these stages. The No unresolved product choice is required to implement these stages. The

View File

@@ -204,6 +204,27 @@ Consumer policy and procedure changes should describe the deliberately adopted
standard and its compatibility impact. Adoption should demonstrate reduced local standard and its compatibility impact. Adoption should demonstrate reduced local
infrastructure and one coherent release model. 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 <version>` 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 ## Remaining work
- Prepare and publish the first ReleaseKit source release under the - Prepare and publish the first ReleaseKit source release under the