Require validation preparation internally

This commit is contained in:
2026-11-05 13:54:06 +00:00
parent 2f0f5fa607
commit 1e122f0473
9 changed files with 25 additions and 36 deletions

View File

@@ -288,6 +288,10 @@ runner constructors, and their test doubles.
compatibility adapters. Run applicable validation/preparation tests when this
signature change is wired; temporary compilation gaps may continue into Stage 11.
Completion: runner and engine validation dependencies now require preparation;
concrete constructors retain direct validation, and nil-plan behavior is tested.
Validation, use-case, and engine tests pass on Go 1.27.3.
## Stage 11: Share Root Loading And Schema Compilation
**Catalog:** 6, 21, compilation part. **Files:** `internal/validate` implementations.

View File

@@ -43,7 +43,7 @@ type ConfigurationSources struct {
type Options struct {
llmClient llm.Client
artifactReader artifact.Reader
validator validate.Validator
validator validate.ValidationPreparer
validatorSource bool
artifactSource bool
disableBundledOpenRouter bool

View File

@@ -53,10 +53,10 @@ type recordingValidationPreparer struct {
directValidateCalls int
}
type validationOnly struct{}
type nilPlanPreparer struct{}
func (validationOnly) Validate(context.Context, *domain.Artifact, domain.OutputContract) (domain.ValidationResult, error) {
return domain.ValidationResult{}, nil
func (nilPlanPreparer) PrepareValidation(context.Context, domain.OutputContract) (validate.PreparedValidation, error) {
return nil, nil
}
func (v *recordingValidationPreparer) Validate(
@@ -791,7 +791,7 @@ func TestRunnerPreparedExecutionWithoutValidatorSkipsValidation(t *testing.T) {
}
}
func TestRunnerPrepareExecutionRequiresValidationPreparer(t *testing.T) {
func TestRunnerPrepareExecutionRejectsNilValidationPlan(t *testing.T) {
reader := defaultArtifactReader()
runner := NewRunner(
&fakePromptRepo{def: promptDef(domain.FormatText, domain.ValidationBasic, 0)},
@@ -800,7 +800,7 @@ func TestRunnerPrepareExecutionRequiresValidationPreparer(t *testing.T) {
reader,
defaultRenderer(),
&fakeLLM{forbid: true},
validationOnly{},
nilPlanPreparer{},
nil,
)

View File

@@ -49,7 +49,7 @@ type Runner struct {
artifacts artifact.Reader
renderer prompt.Renderer
llm llm.Client
validator validate.Validator
validator validate.ValidationPreparer
repairer OutputRepairer
admitter RunAdmitter
defaultTransientRetries uint64
@@ -87,7 +87,7 @@ func NewRunner(
artifacts artifact.Reader,
renderer prompt.Renderer,
llmClient llm.Client,
validator validate.Validator,
validator validate.ValidationPreparer,
admitter RunAdmitter,
) *Runner {
return NewRunnerWithRepairer(
@@ -108,7 +108,7 @@ func NewRunnerWithRepairer(
artifacts artifact.Reader,
renderer prompt.Renderer,
llmClient llm.Client,
validator validate.Validator,
validator validate.ValidationPreparer,
repairer OutputRepairer,
admitter RunAdmitter,
defaultTransientRetries ...uint64,
@@ -409,11 +409,7 @@ func (r *Runner) prepareValidation(
if r.validator == nil {
return noOpPreparedValidation{contract: contract}, nil
}
preparer, ok := r.validator.(validate.ValidationPreparer)
if !ok {
return nil, fmt.Errorf("%w: validator does not support prepared validation", ErrValidation)
}
plan, err := preparer.PrepareValidation(ctx, contract)
plan, err := r.validator.PrepareValidation(ctx, contract)
if err != nil {
return nil, &OperationError{Category: ErrValidation, Cause: err}
}

View File

@@ -52,11 +52,7 @@ func prepareSmokeInferenceCase(
}, nil
case domain.SmokeTestStructuredOutput:
validator := validate.NewFSValidator(smokeSchemaFS, ".")
preparer, ok := validator.(validate.ValidationPreparer)
if !ok {
return nil, fmt.Errorf("%w: private validator does not support prepared validation", ErrValidation)
}
plan, err := preparer.PrepareValidation(ctx, domain.OutputContract{
plan, err := validator.PrepareValidation(ctx, domain.OutputContract{
Format: domain.FormatJSON,
ValidationMode: domain.ValidationJSONSchema,
SchemaPath: smokeStructuredSchemaPath,

View File

@@ -9,6 +9,7 @@ import (
"go.maximumdirect.net/promptkit/internal/configuration"
"go.maximumdirect.net/promptkit/internal/domain"
"go.maximumdirect.net/promptkit/internal/profile"
"go.maximumdirect.net/promptkit/internal/validate"
)
type selectionTestCollaborators struct {
@@ -41,9 +42,9 @@ func (c selectionTestLLM) Generate(context.Context, domain.GenerateRequest) (*do
type selectionTestValidator struct{ state *selectionTestCollaborators }
func (v selectionTestValidator) Validate(context.Context, *domain.Artifact, domain.OutputContract) (domain.ValidationResult, error) {
func (v selectionTestValidator) PrepareValidation(context.Context, domain.OutputContract) (validate.PreparedValidation, error) {
v.state.validationCalls++
return domain.ValidationResult{}, errors.New("validation must not occur during target selection")
return nil, errors.New("validation must not occur during target selection")
}
type selectionTestRepositories struct {

View File

@@ -19,7 +19,7 @@ func TestValidationCancellationBeforeWorkDoesNotOpenSchemaSource(t *testing.T) {
source := &countingSchemaFS{FS: fstest.MapFS{
"schema.json": {Data: []byte(`{"type":"object"}`)},
}}
validator := NewFSValidator(source, ".").(ValidationPreparer)
validator := NewFSValidator(source, ".")
ctx, cancel := context.WithCancel(context.Background())
cancel()
@@ -45,7 +45,7 @@ func TestValidationCancellationBetweenReferencedSchemaReadChunks(t *testing.T) {
target: "child.json",
cancel: cancel,
}
validator := NewFSValidator(source, ".").(ValidationPreparer)
validator := NewFSValidator(source, ".")
plan, err := validator.PrepareValidation(ctx, domain.OutputContract{
ValidationMode: domain.ValidationJSONSchema,
@@ -113,7 +113,7 @@ func TestCancellationDoesNotDetachBlockedSchemaRead(t *testing.T) {
started: started,
release: release,
}
validator := NewFSValidator(source, ".").(ValidationPreparer)
validator := NewFSValidator(source, ".")
type outcome struct {
plan PreparedValidation
err error

View File

@@ -34,11 +34,11 @@ type FSValidator struct {
root string
}
func NewStandardValidator(schemaBaseDir string) Validator {
func NewStandardValidator(schemaBaseDir string) *StandardValidator {
return &StandardValidator{schemaBaseDir: schemaBaseDir}
}
func NewFSValidator(fsys fs.FS, root string) Validator {
func NewFSValidator(fsys fs.FS, root string) *FSValidator {
return &FSValidator{fsys: fsys, root: root}
}

View File

@@ -250,11 +250,7 @@ func TestStandardValidatorPreparedSchemaSurvivesSourceRemoval(t *testing.T) {
}
validator := NewStandardValidator(tmp)
preparer, ok := validator.(ValidationPreparer)
if !ok {
t.Fatal("standard validator does not support validation preparation")
}
prepared, err := preparer.PrepareValidation(context.Background(), domain.OutputContract{
prepared, err := validator.PrepareValidation(context.Background(), domain.OutputContract{
ValidationMode: domain.ValidationJSONSchema,
SchemaPath: "schema.json",
})
@@ -432,11 +428,7 @@ func TestFSValidatorPreparedSchemaSurvivesSourceMutation(t *testing.T) {
}`)},
}
validator := NewFSValidator(fsys, ".")
preparer, ok := validator.(ValidationPreparer)
if !ok {
t.Fatal("filesystem validator does not support validation preparation")
}
prepared, err := preparer.PrepareValidation(context.Background(), domain.OutputContract{
prepared, err := validator.PrepareValidation(context.Background(), domain.OutputContract{
ValidationMode: domain.ValidationJSONSchema,
SchemaPath: "schema.json",
})