Move shared checks into validation package

This commit is contained in:
2026-10-07 18:26:36 +00:00
parent 0c7129ab75
commit 1de7555b3e
6 changed files with 74 additions and 42 deletions

View File

@@ -23,13 +23,13 @@ type Candidate struct {
// local candidate. Remote is retained for publication operations; Check makes
// no remote availability claim.
type ReleaseRequest struct {
RepositoryDir string
Product string
Version string
Remote string
RepositoryDir string
Product string
Version string
Remote string
// AdditionalChecks optionally runs product checks after the standard suite.
AdditionalChecks func(context.Context, Candidate) error
Build *BuildSpec // optional selected-target build after validation.
Build *BuildSpec // optional selected-target build after validation.
Output io.Writer
}
@@ -42,21 +42,11 @@ type ReleaseRequest struct {
// branch, HEAD, worktree, index, and local tag state. Success establishes local
// candidate evidence only; it does not establish remote availability.
func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) {
var validate func(context.Context, candidate.Candidate) error
if request.AdditionalChecks != nil {
validate = func(ctx context.Context, captured candidate.Candidate) error {
return request.AdditionalChecks(ctx, Candidate{
RepositoryDir: captured.RepositoryDir,
Commit: captured.Commit,
Version: captured.Version,
})
}
}
checked, err := candidate.Check(ctx, candidate.Request{
RepositoryDir: request.RepositoryDir,
Product: request.Product,
Version: request.Version,
AdditionalChecks: validate,
AdditionalChecks: candidateChecks(request.AdditionalChecks),
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
Output: request.Output,
})
@@ -69,3 +59,16 @@ func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) {
Version: checked.Version,
}, nil
}
func candidateChecks(checks func(context.Context, Candidate) error) func(context.Context, candidate.Candidate) error {
if checks == nil {
return nil
}
return func(ctx context.Context, captured candidate.Candidate) error {
return checks(ctx, Candidate{
RepositoryDir: captured.RepositoryDir,
Commit: captured.Commit,
Version: captured.Version,
})
}
}

View File

@@ -46,6 +46,7 @@ these implemented packages:
| `internal/tempdir` | Disposable system temporary storage outside source worktrees. |
| `internal/process` | Cancellable child-process execution with per-command environment overlays and removals. |
| `internal/preparation` | Coordination for expected-commit tag checkout and Go module dependency preparation. |
| `internal/validation` | Shared default Go validation for captured candidates; `internal/candidate` retains capture, callback sequencing, and local-state rechecks. |
| `internal/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. |
| `internal/runtimeinfo` | Private version lookup used by the runtime facade. |
| `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. |
@@ -58,6 +59,13 @@ dependency direction with `go run ./internal/apicheck`. These constraints are
also recorded in [ADR 0001](../adr/0001-public-facade.md) and the
[architecture policy](../policy/architecture.md).
Candidate checking captures and validates one clean committed source revision
in `internal/candidate`. Its validation bridge calls `internal/validation` for
the default offline Go suite, then runs product-specific checks once. The
candidate package owns post-validation builds and local-state rechecks;
publication owns endpoint guards, tag creation, push, and recovery around that
same candidate validation path.
## Capability owners and behavioral evidence
The following tests are representative evidence from the current offline suite,
@@ -66,7 +74,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, standard suite, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `cmd/releasekit/internal/cli` | `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate` |
| Candidate capture, shared validation, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `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` |

View File

@@ -1,6 +1,6 @@
# Validation model implementation plan
Status: Ready for implementation using incremental API rollout.
Status: In progress; the shared validation executor is complete.
This plan implements the enabling work in [model.md](model.md), guided by
[ADR 0003](../adr/0003-declarative-validation.md). It is written for a Luna
@@ -178,6 +178,11 @@ filesystem. Their tests use synthetic trees and local Git repositories.
## Stage 1: Extract the shared validation executor
**Completion:** Complete. The default Go suite now runs through
`internal/validation`, candidate capture and callbacks remain in
`internal/candidate`, and `Check` and `Publish` share the public callback
adapter.
**Outcome:** One internal owner runs current default validation, with current
candidate and publication behavior preserved.

View File

@@ -11,8 +11,8 @@ import (
"path/filepath"
"strings"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/versioncontract"
)
@@ -98,7 +98,11 @@ func Capture(ctx context.Context, request Request) (Candidate, error) {
// Validate runs the standard offline Go suite, followed by AdditionalChecks
// when supplied, for the captured candidate.
func Validate(ctx context.Context, request Request, captured Candidate) error {
if err := runDefaultValidation(ctx, captured.RepositoryDir, request.Output); err != nil {
if err := validation.Run(ctx, validation.Request{
RepositoryDir: captured.RepositoryDir,
Commit: captured.Commit,
Output: request.Output,
}); err != nil {
return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
}
if request.AdditionalChecks != nil {
@@ -214,16 +218,6 @@ func hasLocalTag(ctx context.Context, repositoryDir, version string) (bool, erro
return false, err
}
func runDefaultValidation(ctx context.Context, repositoryDir string, output io.Writer) error {
tool, err := gotool.New(ctx, repositoryDir)
if err != nil {
return fmt.Errorf("prepare Go toolchain: %w", err)
}
_, err = tool.Run(ctx, repositoryDir, gotool.Native, gotool.Target{}, output,
"test", "-mod=readonly", "-count=1", "./...")
return err
}
func validStableVersion(version string) bool {
return versioncontract.ValidStableVersion(version)
}

View File

@@ -0,0 +1,33 @@
// Package validation owns the shared checks run against a captured candidate.
package validation
import (
"context"
"fmt"
"io"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
)
// Settings selects shared validation behavior. Its zero value runs the current
// default Go suite.
type Settings struct{}
// Request identifies the captured source and output used by shared validation.
type Request struct {
RepositoryDir string
Commit string
Output io.Writer
Settings Settings
}
// Run executes shared validation for the captured candidate.
func Run(ctx context.Context, request Request) error {
tool, err := gotool.New(ctx, request.RepositoryDir)
if err != nil {
return fmt.Errorf("prepare Go toolchain: %w", err)
}
_, err = tool.Run(ctx, request.RepositoryDir, gotool.Native, gotool.Target{}, request.Output,
"test", "-mod=readonly", "-count=1", "./...")
return err
}

View File

@@ -4,7 +4,6 @@ import (
"context"
internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/publish"
)
@@ -61,22 +60,12 @@ type PublicationResult struct {
// an unreadable result is unknown. A nil error means publication is confirmed
// by the successful push or exact remote object match.
func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, error) {
var validate func(context.Context, candidate.Candidate) error
if request.AdditionalChecks != nil {
validate = func(ctx context.Context, captured candidate.Candidate) error {
return request.AdditionalChecks(ctx, Candidate{
RepositoryDir: captured.RepositoryDir,
Commit: captured.Commit,
Version: captured.Version,
})
}
}
result, err := publish.Publish(ctx, publish.Request{
RepositoryDir: request.RepositoryDir,
Product: request.Product,
Version: request.Version,
Remote: request.Remote,
AdditionalChecks: validate,
AdditionalChecks: candidateChecks(request.AdditionalChecks),
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
Output: request.Output,
})