Make product checks additive

This commit is contained in:
2026-10-06 17:15:46 +00:00
parent 08093124af
commit 3e9b5c1b99
23 changed files with 368 additions and 169 deletions

View File

@@ -24,8 +24,9 @@ go run ./cmd/releasekit check [--repo DIR] [--config FILE] VERSION
version. The repository defaults to the current directory, and the profile
defaults to `<repository>/releasekit.json`. A successful command prints the
captured local commit and version. It checks the committed release note, local
Git state, and configured validation, but does not inspect remote availability
or publish a tag.
Git state, runs the standard offline Go test suite, and then runs any configured
additional checks in profile order. It does not inspect remote availability or
publish a tag.
`check` writes errors and diagnostics to standard error. It returns status 0
after local validation, 1 after a candidate validation failure, and 2 for usage,
@@ -38,10 +39,12 @@ Publish a source tag:
go run ./cmd/releasekit publish [--repo DIR] [--config FILE] VERSION
```
`publish` reads the same product, remote, and validation settings as `check`.
It validates the candidate once, checks the authoritative remote, creates an
annotated local tag, and pushes only its exact tag object. It prints the
confirmed version, candidate commit, and tag object on success. Validation
`publish` reads the same product, remote, and additional-check settings as
`check`. It checks the authoritative remote and validates the candidate: the
standard offline Go test suite runs first, followed by configured checks in
profile order. It then creates an annotated local tag and pushes only its exact
tag object. It prints the confirmed version, candidate commit, and tag object
on success. Validation
progress and failed publication state, including the captured publication
endpoint for recovery, are written to standard error. A signal
cancels the active operation; if it arrives during a push, the library's

View File

@@ -10,7 +10,7 @@ The current profile fields are:
| --- | --- | --- |
| `product` | Yes | Product name used by release operations. |
| `remote` | No | Selected Git remote; defaults to `origin`. |
| `checks` | No | Ordered child commands, each with an argument vector and optional environment map. |
| `checks` | No | Ordered additional child commands, each with an argument vector and optional environment map. |
| `build` | No | Optional main package, binary name, and selected `GOOS/GOARCH` target strings. |
| `delivery` | No | Optional Gitea `base_url`, `owner`, `repository`, and `token_env` settings for tagged-source delivery. |
@@ -32,12 +32,13 @@ environment variable containing the Gitea token; it defaults to
`RELEASEKIT_GITEA_TOKEN`. The CLI reads this variable only for `deliver` and
passes its value explicitly to the library operation.
The `check` and `publish` commands use `product`, `remote`, and `checks`, and
build selected targets after the single validation callback. If `checks` is
omitted or empty, it runs `go test -mod=readonly -count=1 ./...` with Go module
and toolchain network access disabled. If checks are configured, they replace
that default and run in order. The `build` command uses the selected build
settings and writes binaries to `dist` unless `--output` selects another path.
The `check` and `publish` commands always run
`go test -mod=readonly -count=1 ./...` with Go module and toolchain network
access disabled. They then run each configured `checks` command once, in order,
as an additional product check. Selected-target builds run after both. Do not
repeat the standard suite in `checks`; doing so runs it twice. The `build`
command uses the selected build settings and writes binaries to `dist` unless
`--output` selects another path.
The `deliver` command uses the delivery destination and token, then either
builds selected tagged-source targets into temporary storage or loads verified
assets from the directory selected by `--assets`. An absent build object or an

View File

@@ -70,9 +70,9 @@ silently adding a binary check.
The [`build` command](../cli.md) reads the same build specification from the
profile and writes a private receipt alongside its artifacts. `check` and
`publish` run selected builds after their one validation callback using
temporary output outside the worktree; those candidate checks do not write a
receipt or leave build files in the repository.
`publish` run selected builds after the standard suite and any additional
checks using temporary output outside the worktree. Candidate checks do not
write a receipt or leave build files in the repository.
For delivery from a tagged checkout, `deliver` builds selected tagged-source
targets into temporary storage by default. Use `--assets DIR` to deliver a

View File

@@ -8,7 +8,7 @@ result, err := releasekit.Check(ctx, releasekit.ReleaseRequest{
RepositoryDir: "/work/project",
Product: "Example",
Version: "v1.2.3",
Validate: func(ctx context.Context, candidate releasekit.Candidate) error {
AdditionalChecks: func(ctx context.Context, candidate releasekit.Candidate) error {
return runProjectChecks(ctx, candidate.RepositoryDir)
},
})
@@ -24,10 +24,9 @@ version must be stable `vMAJOR.MINOR.PATCH`. The committed
product name and exact version as complete title elements. No local tag with
that version may already exist.
The callback receives the captured repository, commit, and version. ReleaseKit
calls it once. When omitted, ReleaseKit runs
`go test -mod=readonly -count=1 ./...` with the available compatible local Go
toolchain; it does not download a different toolchain automatically. The suite
ReleaseKit always runs `go test -mod=readonly -count=1 ./...` with the available
compatible local Go toolchain, then calls `AdditionalChecks` once when supplied.
It does not download a different toolchain automatically. The standard suite
uses the Go host target, disables workspace and module network access, and
ignores ambient `GOFLAGS`, experiment, and CPU feature overrides. Effective
cache and native cgo/C-tool settings are preserved, including values from Go's
@@ -36,18 +35,20 @@ cleanliness after validation.
A successful check is evidence about the local candidate only. It does not
check whether a remote branch or tag is available and does not create a tag.
The [`check` command](../cli.md) reads the product and optional validation
The [`check` command](../cli.md) reads the product and optional additional
commands from `releasekit.json`.
## Publishing the source tag
`releasekit.Publish` uses the same request and performs candidate validation,
`releasekit.Publish` uses the same request and validation order: the standard
suite runs first, followed by `AdditionalChecks` when supplied. It then performs
authoritative remote checks, annotated-tag creation, and an exact tag push. The
selected remote must publish `main` at the captured commit and must not already
contain the selected version tag. A successful result includes the endpoint and
tag object ID. Publication states distinguish an unattempted push, a tag that
was created locally but not pushed, a successful push, a guard conflict, and an
unresolved push error.
unresolved push error. See the [publication guide](publish.md) for the complete
workflow.
When a push reports an error, ReleaseKit makes one bounded query for the
authoritative tag. An exact tag object match confirms success. An absent tag is

41
docs/consumers/publish.md Normal file
View File

@@ -0,0 +1,41 @@
# Source tag publication
Use `releasekit.Publish` to validate a committed candidate and push its exact
annotated source tag:
```go
result, err := releasekit.Publish(ctx, releasekit.ReleaseRequest{
RepositoryDir: "/work/project",
Product: "Example",
Version: "v1.2.3",
AdditionalChecks: func(ctx context.Context, candidate releasekit.Candidate) error {
return runProjectChecks(ctx, candidate.RepositoryDir)
},
})
if err != nil {
return err
}
fmt.Println(result.Version, result.CandidateCommit, result.TagObject)
```
Publication captures a clean candidate on `main`, validates its committed
release note, and checks the selected remote before running candidate checks.
It always runs `go test -mod=readonly -count=1 ./...` with the available
compatible local Go toolchain. It then calls `AdditionalChecks` once when
supplied. The standard suite cannot be replaced; move product-specific behavior
into `AdditionalChecks` and remove repeated standard-suite commands from custom
checks. A failed suite or additional check prevents tag creation, and wrapped
errors preserve their causes.
If `Build` selects targets, ReleaseKit verifies them after the standard suite
and additional checks, before creating the tag. The operation then rechecks the
candidate and remote guards, creates an annotated tag for the captured commit,
and pushes only that tag object.
A successful result means Git confirmed the push or an exact authoritative tag
query confirmed it after a push error. The local tag is retained after a failed
push. `PublicationState` distinguishes a known failure, conflict, confirmed
publication, and unresolved outcome. Resolve an unknown result against the
captured endpoint before retrying; see the [maintainer procedure](../release.md)
for the recovery steps. The [CLI contract](../cli.md) documents command output
and exit statuses.

View File

@@ -52,7 +52,8 @@ root with:
go run ./internal/apicheck
```
See the [candidate check guide](consumers/check.md), [build consumer guide](consumers/build.md),
See the [candidate check guide](consumers/check.md), [source publication guide](consumers/publish.md),
[build consumer guide](consumers/build.md),
and [internal package inventory](internal/overview.md) for implemented behavior
and capability ownership. The [product roadmap](roadmap/releasekit.md) records
remaining adoption and deployment work.

View File

@@ -15,6 +15,7 @@ two Go facades and a command-line interface:
See the [CLI contract](../cli.md), [configuration reference](../config.md),
[candidate check guide](../consumers/check.md),
[source publication guide](../consumers/publish.md),
[build guide](../consumers/build.md),
[delivery guide](../consumers/delivery.md), and
[runtime version guide](../consumers/runtime-version.md) for task-oriented use.
@@ -62,7 +63,7 @@ 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, committed note, one validation, and rechecks | `releasekit.Check`, `internal/candidate`, `cmd/releasekit/internal/cli` | `TestCheckCapturesCandidateAndCallsValidationOnce`, `TestCheckRechecksHeadAndWorktreeAfterCallback`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate` |
| Candidate capture, standard suite, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `cmd/releasekit/internal/cli` | `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate` |
| 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` |

View File

@@ -54,9 +54,10 @@ Then run the single publication command from the repository root:
./scripts/release vX.Y.Z
```
The committed `releasekit.json` selects the offline default test suite:
The command always runs the offline default test suite,
`go test -mod=readonly -count=1 ./...`, with toolchain and module network
access disabled for validation. The command validates once, checks the
access disabled. Any profile `checks` run afterward as additional product
checks; they should not repeat the standard suite. The command checks the
authoritative remote state, creates a local annotated tag from the captured
candidate and checked-in note, rechecks the tag and remote guards, and pushes
only that tag object. It prints the published version, commit, and tag object

View File

@@ -1,6 +1,6 @@
# ReleaseKit audit implementation plan
Status: Stages 1 and 2 complete; Stage 3 not started.
Status: Stages 1–3 complete; Stage 4 not started.
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.
@@ -201,6 +201,15 @@ its captured candidate. Reuse existing mutation/recheck tests with the new
callback. One CLI profile test covers ordered extra commands. Keep callback
unit coverage and end-to-end suite-count coverage at distinct boundaries.
**Completed:** `AdditionalChecks` now runs after the standard offline suite in
the public check and publish flows; selected builds remain after both. The CLI
keeps ordered JSON `checks` as additional commands, and the bundled profiles no
longer repeat the standard suite. Facade tests prove suite failure stops checks
and publication, additional-check errors retain identity, and successful
publication runs both once for its captured candidate. CLI coverage verifies
ordered checks. The full offline suite, API boundary check, and documentation
whitespace checks pass.
## Stage 4 — Consolidate artifact identity and declaration rules
**Covers:** artifact portion of F8. **Primary owners:** `internal/build/`,