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

@@ -42,21 +42,11 @@ type ReleaseRequest struct {
// branch, HEAD, worktree, index, and local tag state. Success establishes local // branch, HEAD, worktree, index, and local tag state. Success establishes local
// candidate evidence only; it does not establish remote availability. // candidate evidence only; it does not establish remote availability.
func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) { 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{ checked, err := candidate.Check(ctx, candidate.Request{
RepositoryDir: request.RepositoryDir, RepositoryDir: request.RepositoryDir,
Product: request.Product, Product: request.Product,
Version: request.Version, Version: request.Version,
AdditionalChecks: validate, AdditionalChecks: candidateChecks(request.AdditionalChecks),
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output), AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
Output: request.Output, Output: request.Output,
}) })
@@ -69,3 +59,16 @@ func Check(ctx context.Context, request ReleaseRequest) (Candidate, error) {
Version: checked.Version, Version: checked.Version,
}, nil }, 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/tempdir` | Disposable system temporary storage outside source worktrees. |
| `internal/process` | Cancellable child-process execution with per-command environment overlays and removals. | | `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/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/publish` | Publication guards, one validation run, annotated tag creation, push, and recovery state. |
| `internal/runtimeinfo` | Private version lookup used by the runtime facade. | | `internal/runtimeinfo` | Private version lookup used by the runtime facade. |
| `internal/versioncontract` | Shared stable-version rules and linker-symbol identity. | | `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 also recorded in [ADR 0001](../adr/0001-public-facade.md) and the
[architecture policy](../policy/architecture.md). [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 ## Capability owners and behavioral evidence
The following tests are representative evidence from the current offline suite, 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 | | 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`, `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` | | 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` |

View File

@@ -1,6 +1,6 @@
# Validation model implementation plan # 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 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 [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 ## 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 **Outcome:** One internal owner runs current default validation, with current
candidate and publication behavior preserved. candidate and publication behavior preserved.

View File

@@ -11,8 +11,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/gotool"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/process" "gitea.maximumdirect.net/eric/ReleaseKit/internal/process"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/versioncontract" "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 // Validate runs the standard offline Go suite, followed by AdditionalChecks
// when supplied, for the captured candidate. // when supplied, for the captured candidate.
func Validate(ctx context.Context, request Request, captured Candidate) error { 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) return fmt.Errorf("validate candidate %s: %w", captured.Commit, err)
} }
if request.AdditionalChecks != nil { if request.AdditionalChecks != nil {
@@ -214,16 +218,6 @@ func hasLocalTag(ctx context.Context, repositoryDir, version string) (bool, erro
return false, err 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 { func validStableVersion(version string) bool {
return versioncontract.ValidStableVersion(version) 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" "context"
internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build" internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
"gitea.maximumdirect.net/eric/ReleaseKit/internal/publish" "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 // an unreadable result is unknown. A nil error means publication is confirmed
// by the successful push or exact remote object match. // by the successful push or exact remote object match.
func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, error) { 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{ result, err := publish.Publish(ctx, publish.Request{
RepositoryDir: request.RepositoryDir, RepositoryDir: request.RepositoryDir,
Product: request.Product, Product: request.Product,
Version: request.Version, Version: request.Version,
Remote: request.Remote, Remote: request.Remote,
AdditionalChecks: validate, AdditionalChecks: candidateChecks(request.AdditionalChecks),
AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output), AfterValidate: internalbuild.CandidateCallback(internalBuildSpec(request.Build), request.Output),
Output: request.Output, Output: request.Output,
}) })