Refactor validators into package-owned components
This commit is contained in:
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms"
|
||||
)
|
||||
|
||||
var builtInChains = map[string][]string{
|
||||
@@ -70,7 +70,10 @@ func ResolveBuiltInChain(moduleKey string, registry *Registry) ([]contracts.Vali
|
||||
if moduleKey == "glossary" && key == KeyProtectedTerms {
|
||||
// Glossary stages preserve current stricter protection semantics while
|
||||
// reporting the stable protected_terms key.
|
||||
v := frameworkvalidators.GlossaryStageProtectedGlossaryTermValidator{}
|
||||
v, buildErr := protected_terms.NewGlossaryStage()
|
||||
if buildErr != nil {
|
||||
return nil, buildErr
|
||||
}
|
||||
out = append(out, v)
|
||||
continue
|
||||
}
|
||||
|
||||
13
internal/validators/confidence_threshold/validator.go
Normal file
13
internal/validators/confidence_threshold/validator.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package confidence_threshold
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "confidence_threshold"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(frameworkvalidators.ConfidenceThresholdValidator{}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
17
internal/validators/editorial_review/validator.go
Normal file
17
internal/validators/editorial_review/validator.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package editorial_review
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "editorial_review"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
v, err := frameworkvalidators.NewLLMBackedValidator(Key, frameworkvalidators.LLMValidatorTypeEditorialReview, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validatormetadata.Wrap(v, validatormetadata.ExecutionClassLLMBacked), nil
|
||||
}
|
||||
17
internal/validators/grammar_review/validator.go
Normal file
17
internal/validators/grammar_review/validator.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package grammar_review
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "grammar_review"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
v, err := frameworkvalidators.NewLLMBackedValidator(Key, frameworkvalidators.LLMValidatorTypeGrammarReview, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validatormetadata.Wrap(v, validatormetadata.ExecutionClassLLMBacked), nil
|
||||
}
|
||||
17
internal/validators/meaning_reversal_review/validator.go
Normal file
17
internal/validators/meaning_reversal_review/validator.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package meaning_reversal_review
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "meaning_reversal_review"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
v, err := frameworkvalidators.NewLLMBackedValidator(Key, frameworkvalidators.LLMValidatorTypeMeaningReversal, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validatormetadata.Wrap(v, validatormetadata.ExecutionClassLLMBacked), nil
|
||||
}
|
||||
55
internal/validators/metadata/metadata.go
Normal file
55
internal/validators/metadata/metadata.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package metadata
|
||||
|
||||
import "gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
|
||||
type ExecutionClass string
|
||||
|
||||
const (
|
||||
ExecutionClassDeterministic ExecutionClass = "deterministic"
|
||||
ExecutionClassLLMBacked ExecutionClass = "llm_backed"
|
||||
)
|
||||
|
||||
type ClassifiedValidator interface {
|
||||
contracts.Validator
|
||||
ExecutionClass() ExecutionClass
|
||||
}
|
||||
|
||||
func ClassOf(v contracts.Validator) ExecutionClass {
|
||||
if v == nil {
|
||||
return ExecutionClassDeterministic
|
||||
}
|
||||
classified, ok := v.(ClassifiedValidator)
|
||||
if !ok {
|
||||
return ExecutionClassDeterministic
|
||||
}
|
||||
switch classified.ExecutionClass() {
|
||||
case ExecutionClassLLMBacked:
|
||||
return ExecutionClassLLMBacked
|
||||
case ExecutionClassDeterministic:
|
||||
return ExecutionClassDeterministic
|
||||
default:
|
||||
return ExecutionClassDeterministic
|
||||
}
|
||||
}
|
||||
|
||||
func Wrap(v contracts.Validator, class ExecutionClass) contracts.Validator {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
if class != ExecutionClassLLMBacked {
|
||||
class = ExecutionClassDeterministic
|
||||
}
|
||||
return wrappedValidator{
|
||||
Validator: v,
|
||||
class: class,
|
||||
}
|
||||
}
|
||||
|
||||
type wrappedValidator struct {
|
||||
contracts.Validator
|
||||
class ExecutionClass
|
||||
}
|
||||
|
||||
func (w wrappedValidator) ExecutionClass() ExecutionClass {
|
||||
return w.class
|
||||
}
|
||||
30
internal/validators/metadata/metadata_test.go
Normal file
30
internal/validators/metadata/metadata_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
)
|
||||
|
||||
type unclassifiedValidator struct{}
|
||||
|
||||
func (u unclassifiedValidator) Name() string { return "unclassified" }
|
||||
|
||||
func (u unclassifiedValidator) Validate(_ context.Context, _ contracts.ValidationRequest) (frameworkvalidators.Result, error) {
|
||||
return frameworkvalidators.Result{ValidatorName: u.Name(), Decisions: nil}, nil
|
||||
}
|
||||
|
||||
func TestClassOfDefaultsToDeterministic(t *testing.T) {
|
||||
if got := ClassOf(unclassifiedValidator{}); got != ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic default class, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapExposesExecutionClass(t *testing.T) {
|
||||
wrapped := Wrap(unclassifiedValidator{}, ExecutionClassLLMBacked)
|
||||
if got := ClassOf(wrapped); got != ExecutionClassLLMBacked {
|
||||
t.Fatalf("expected llm_backed class, got %q", got)
|
||||
}
|
||||
}
|
||||
13
internal/validators/no_effect/validator.go
Normal file
13
internal/validators/no_effect/validator.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package no_effect
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "no_effect"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(frameworkvalidators.NoEffectValidator{}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
13
internal/validators/non_empty_corrected_text/validator.go
Normal file
13
internal/validators/non_empty_corrected_text/validator.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package non_empty_corrected_text
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "non_empty_corrected_text"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(frameworkvalidators.NonEmptyCorrectionValidator{}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
13
internal/validators/original_text_presence/validator.go
Normal file
13
internal/validators/original_text_presence/validator.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package original_text_presence
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "original_text_presence"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(frameworkvalidators.OriginalTextPresenceValidator{}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
44
internal/validators/protected_terms/validator.go
Normal file
44
internal/validators/protected_terms/validator.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package protected_terms
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "protected_terms"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(validator{}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
|
||||
func NewGlossaryStage() (contracts.Validator, error) {
|
||||
return validatormetadata.Wrap(validator{glossaryStage: true}, validatormetadata.ExecutionClassDeterministic), nil
|
||||
}
|
||||
|
||||
type validator struct {
|
||||
glossaryStage bool
|
||||
}
|
||||
|
||||
func (v validator) Name() string {
|
||||
return Key
|
||||
}
|
||||
|
||||
func (v validator) Validate(_ context.Context, req contracts.ValidationRequest) (frameworkvalidators.Result, error) {
|
||||
if !v.glossaryStage && req.ModuleKey == "glossary" {
|
||||
decisions := make([]frameworkvalidators.Decision, 0, len(req.CandidateProposal))
|
||||
for _, c := range req.CandidateProposal {
|
||||
decisions = append(decisions, frameworkvalidators.Decision{
|
||||
ProposalIndex: c.ProposalIndex,
|
||||
Approved: true,
|
||||
ReasonCode: frameworkvalidators.ReasonApproved,
|
||||
Message: "approved",
|
||||
})
|
||||
}
|
||||
return frameworkvalidators.Result{ValidatorName: Key, Decisions: decisions}, nil
|
||||
}
|
||||
|
||||
return frameworkvalidators.ValidateProtectedTerms(req, v.glossaryStage)
|
||||
}
|
||||
164
internal/validators/protected_terms/validator_test.go
Normal file
164
internal/validators/protected_terms/validator_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package protected_terms
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
func proposal(index int, moduleKey, original, corrected string) proposals.EnrichedCorrectionProposal {
|
||||
return proposals.EnrichedCorrectionProposal{
|
||||
CorrectionProposal: proposals.CorrectionProposal{
|
||||
TargetSegmentID: 1,
|
||||
OriginalText: original,
|
||||
CorrectedText: corrected,
|
||||
Confidence: 0.95,
|
||||
},
|
||||
ProposalMetadata: proposals.ProposalMetadata{
|
||||
ProposalIndex: index,
|
||||
ModuleKey: moduleKey,
|
||||
ModuleInstance: moduleKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func tinyGlossary() *schema.Glossary {
|
||||
return &schema.Glossary{
|
||||
Entries: []schema.GlossaryEntry{
|
||||
{Name: "Jesters", Category: "faction", Summary: "Faction"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewReportsStableKeyAndDeterministicClass(t *testing.T) {
|
||||
v, err := New()
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
if v.Name() != Key {
|
||||
t.Fatalf("expected key %q, got %q", Key, v.Name())
|
||||
}
|
||||
if got := validatormetadata.ClassOf(v); got != validatormetadata.ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic class, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUsesGeneralBehaviorForGlossaryModule(t *testing.T) {
|
||||
v, err := New()
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "glossary",
|
||||
Glossary: tinyGlossary(),
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
proposal(0, "glossary", "Jesters", "Gestures"),
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Validate: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if !result.Decisions[0].Approved {
|
||||
t.Fatalf("expected glossary-module approval in general variant, got %+v", result.Decisions[0])
|
||||
}
|
||||
if result.Decisions[0].ReasonCode != frameworkvalidators.ReasonApproved {
|
||||
t.Fatalf("expected reason %q, got %+v", frameworkvalidators.ReasonApproved, result.Decisions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRejectsProtectedTermChangeOutsideGlossaryStage(t *testing.T) {
|
||||
v, err := New()
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "homophones",
|
||||
Glossary: tinyGlossary(),
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
proposal(0, "homophones", "Jesters", "Gestures"),
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Validate: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if result.Decisions[0].Approved {
|
||||
t.Fatalf("expected protected_terms rejection, got %+v", result.Decisions[0])
|
||||
}
|
||||
if result.Decisions[0].ReasonCode != frameworkvalidators.ReasonProtectedGlossaryTerm {
|
||||
t.Fatalf("expected reason %q, got %+v", frameworkvalidators.ReasonProtectedGlossaryTerm, result.Decisions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGlossaryStageRejectsProtectedTermChange(t *testing.T) {
|
||||
v, err := NewGlossaryStage()
|
||||
if err != nil {
|
||||
t.Fatalf("NewGlossaryStage: %v", err)
|
||||
}
|
||||
if v.Name() != Key {
|
||||
t.Fatalf("expected key %q, got %q", Key, v.Name())
|
||||
}
|
||||
if got := validatormetadata.ClassOf(v); got != validatormetadata.ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic class, got %q", got)
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "glossary",
|
||||
Glossary: tinyGlossary(),
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
proposal(0, "glossary", "Jesters", "Gestures"),
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Validate: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if result.Decisions[0].Approved {
|
||||
t.Fatalf("expected glossary-stage protected_terms rejection, got %+v", result.Decisions[0])
|
||||
}
|
||||
if result.Decisions[0].ReasonCode != frameworkvalidators.ReasonProtectedGlossaryTerm {
|
||||
t.Fatalf("expected reason %q, got %+v", frameworkvalidators.ReasonProtectedGlossaryTerm, result.Decisions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGlossaryStageAllowsProtectedTermInsertion(t *testing.T) {
|
||||
v, err := NewGlossaryStage()
|
||||
if err != nil {
|
||||
t.Fatalf("NewGlossaryStage: %v", err)
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "glossary",
|
||||
Glossary: tinyGlossary(),
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
proposal(0, "glossary", "gestures", "Jesters"),
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Validate: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if !result.Decisions[0].Approved {
|
||||
t.Fatalf("expected glossary-stage approval for protected-term insertion, got %+v", result.Decisions[0])
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,16 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/confidence_threshold"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/editorial_review"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/grammar_review"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/meaning_reversal_review"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/no_effect"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/non_empty_corrected_text"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/original_text_presence"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/spoken_form_plausibility"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/spoken_word_review"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,26 +43,16 @@ type Registry struct {
|
||||
|
||||
func NewBuiltInRegistry() *Registry {
|
||||
defs := []BuiltInValidatorDefinition{
|
||||
{Key: KeyConfidenceThreshold, Build: func() (contracts.Validator, error) { return frameworkvalidators.ConfidenceThresholdValidator{}, nil }},
|
||||
{Key: KeyOriginalTextPresence, Build: func() (contracts.Validator, error) { return frameworkvalidators.OriginalTextPresenceValidator{}, nil }},
|
||||
{Key: KeyNonEmptyCorrectedText, Build: func() (contracts.Validator, error) { return frameworkvalidators.NonEmptyCorrectionValidator{}, nil }},
|
||||
{Key: KeyNoEffect, Build: func() (contracts.Validator, error) { return frameworkvalidators.NoEffectValidator{}, nil }},
|
||||
{Key: KeyProtectedTerms, Build: func() (contracts.Validator, error) { return frameworkvalidators.ProtectedGlossaryTermValidator{}, nil }},
|
||||
{Key: KeySpokenFormPlausibility, LLMBacked: true, Build: func() (contracts.Validator, error) {
|
||||
return frameworkvalidators.NewLLMBackedValidator(KeySpokenFormPlausibility, frameworkvalidators.LLMValidatorTypeSpokenFormPlausibility, "")
|
||||
}},
|
||||
{Key: KeyMeaningReversalReview, LLMBacked: true, Build: func() (contracts.Validator, error) {
|
||||
return frameworkvalidators.NewLLMBackedValidator(KeyMeaningReversalReview, frameworkvalidators.LLMValidatorTypeMeaningReversal, "")
|
||||
}},
|
||||
{Key: KeyEditorialReview, LLMBacked: true, Build: func() (contracts.Validator, error) {
|
||||
return frameworkvalidators.NewLLMBackedValidator(KeyEditorialReview, frameworkvalidators.LLMValidatorTypeEditorialReview, "")
|
||||
}},
|
||||
{Key: KeyGrammarReview, LLMBacked: true, Build: func() (contracts.Validator, error) {
|
||||
return frameworkvalidators.NewLLMBackedValidator(KeyGrammarReview, frameworkvalidators.LLMValidatorTypeGrammarReview, "")
|
||||
}},
|
||||
{Key: KeySpokenWordReview, LLMBacked: true, Build: func() (contracts.Validator, error) {
|
||||
return frameworkvalidators.NewLLMBackedValidator(KeySpokenWordReview, frameworkvalidators.LLMValidatorTypeSpokenWordReview, "")
|
||||
}},
|
||||
{Key: KeyConfidenceThreshold, Build: confidence_threshold.New},
|
||||
{Key: KeyOriginalTextPresence, Build: original_text_presence.New},
|
||||
{Key: KeyNonEmptyCorrectedText, Build: non_empty_corrected_text.New},
|
||||
{Key: KeyNoEffect, Build: no_effect.New},
|
||||
{Key: KeyProtectedTerms, Build: protected_terms.New},
|
||||
{Key: KeySpokenFormPlausibility, LLMBacked: true, Build: spoken_form_plausibility.New},
|
||||
{Key: KeyMeaningReversalReview, LLMBacked: true, Build: meaning_reversal_review.New},
|
||||
{Key: KeyEditorialReview, LLMBacked: true, Build: editorial_review.New},
|
||||
{Key: KeyGrammarReview, LLMBacked: true, Build: grammar_review.New},
|
||||
{Key: KeySpokenWordReview, LLMBacked: true, Build: spoken_word_review.New},
|
||||
}
|
||||
|
||||
m := make(map[string]BuiltInValidatorDefinition, len(defs))
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/confidence_threshold"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/editorial_review"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/grammar_review"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/meaning_reversal_review"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/no_effect"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/non_empty_corrected_text"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/original_text_presence"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/spoken_form_plausibility"
|
||||
"gitea.maximumdirect.net/eric/audita/internal/validators/spoken_word_review"
|
||||
)
|
||||
|
||||
func TestBuiltInRegistryRegistersAllKeys(t *testing.T) {
|
||||
@@ -32,6 +48,155 @@ func TestBuiltInRegistryRegistersAllKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltInValidatorPackagesConstruct(t *testing.T) {
|
||||
type validatorCtor struct {
|
||||
name string
|
||||
key string
|
||||
build func() (contracts.Validator, error)
|
||||
wantClass validatormetadata.ExecutionClass
|
||||
}
|
||||
cases := []validatorCtor{
|
||||
{name: "confidence_threshold", key: KeyConfidenceThreshold, build: confidence_threshold.New, wantClass: validatormetadata.ExecutionClassDeterministic},
|
||||
{name: "original_text_presence", key: KeyOriginalTextPresence, build: original_text_presence.New, wantClass: validatormetadata.ExecutionClassDeterministic},
|
||||
{name: "non_empty_corrected_text", key: KeyNonEmptyCorrectedText, build: non_empty_corrected_text.New, wantClass: validatormetadata.ExecutionClassDeterministic},
|
||||
{name: "no_effect", key: KeyNoEffect, build: no_effect.New, wantClass: validatormetadata.ExecutionClassDeterministic},
|
||||
{name: "protected_terms", key: KeyProtectedTerms, build: protected_terms.New, wantClass: validatormetadata.ExecutionClassDeterministic},
|
||||
{name: "spoken_form_plausibility", key: KeySpokenFormPlausibility, build: spoken_form_plausibility.New, wantClass: validatormetadata.ExecutionClassLLMBacked},
|
||||
{name: "meaning_reversal_review", key: KeyMeaningReversalReview, build: meaning_reversal_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked},
|
||||
{name: "editorial_review", key: KeyEditorialReview, build: editorial_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked},
|
||||
{name: "grammar_review", key: KeyGrammarReview, build: grammar_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked},
|
||||
{name: "spoken_word_review", key: KeySpokenWordReview, build: spoken_word_review.New, wantClass: validatormetadata.ExecutionClassLLMBacked},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
v, err := tc.build()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: build: %v", tc.name, err)
|
||||
}
|
||||
if v.Name() != tc.key {
|
||||
t.Fatalf("%s: expected key %q, got %q", tc.name, tc.key, v.Name())
|
||||
}
|
||||
if got := validatormetadata.ClassOf(v); got != tc.wantClass {
|
||||
t.Fatalf("%s: expected class %q, got %q", tc.name, tc.wantClass, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryBuildsClassifiedValidators(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
llmKeys := map[string]bool{
|
||||
KeySpokenFormPlausibility: true,
|
||||
KeyMeaningReversalReview: true,
|
||||
KeyEditorialReview: true,
|
||||
KeyGrammarReview: true,
|
||||
KeySpokenWordReview: true,
|
||||
}
|
||||
for _, key := range r.RegisteredKeys() {
|
||||
v, err := r.MustBuild(key)
|
||||
if err != nil {
|
||||
t.Fatalf("must build %q: %v", key, err)
|
||||
}
|
||||
if _, ok := v.(validatormetadata.ClassifiedValidator); !ok {
|
||||
t.Fatalf("expected built validator %q to expose execution classification metadata", key)
|
||||
}
|
||||
want := validatormetadata.ExecutionClassDeterministic
|
||||
if llmKeys[key] {
|
||||
want = validatormetadata.ExecutionClassLLMBacked
|
||||
}
|
||||
if got := validatormetadata.ClassOf(v); got != want {
|
||||
t.Fatalf("expected class %q for %q, got %q", want, key, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryProtectedTermsUsesNonGlossaryStageBehavior(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
v, err := r.MustBuild(KeyProtectedTerms)
|
||||
if err != nil {
|
||||
t.Fatalf("must build protected_terms: %v", err)
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "glossary",
|
||||
Glossary: &schema.Glossary{
|
||||
Entries: []schema.GlossaryEntry{{Name: "Jesters", Category: "faction", Summary: "Protected"}},
|
||||
},
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
{
|
||||
CorrectionProposal: proposals.CorrectionProposal{
|
||||
TargetSegmentID: 1,
|
||||
OriginalText: "Jesters",
|
||||
CorrectedText: "Gestures",
|
||||
Confidence: 1.0,
|
||||
},
|
||||
ProposalMetadata: proposals.ProposalMetadata{
|
||||
ProposalIndex: 0,
|
||||
ModuleKey: "glossary",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("validate protected_terms: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if !result.Decisions[0].Approved {
|
||||
t.Fatalf("expected glossary module proposal to be approved by non-glossary-stage protected_terms validator, got %+v", result.Decisions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveBuiltInChainUsesGlossaryStageProtectedTermsBehavior(t *testing.T) {
|
||||
chain, err := ResolveBuiltInChain("glossary", NewBuiltInRegistry())
|
||||
if err != nil {
|
||||
t.Fatalf("resolve glossary chain: %v", err)
|
||||
}
|
||||
|
||||
var v contracts.Validator
|
||||
for _, candidate := range chain {
|
||||
if candidate.Name() == KeyProtectedTerms {
|
||||
v = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if v == nil {
|
||||
t.Fatalf("expected protected_terms validator in glossary chain")
|
||||
}
|
||||
|
||||
req := contracts.ValidationRequest{
|
||||
ModuleKey: "glossary",
|
||||
Glossary: &schema.Glossary{
|
||||
Entries: []schema.GlossaryEntry{{Name: "Jesters", Category: "faction", Summary: "Protected"}},
|
||||
},
|
||||
CandidateProposal: []proposals.EnrichedCorrectionProposal{
|
||||
{
|
||||
CorrectionProposal: proposals.CorrectionProposal{
|
||||
TargetSegmentID: 1,
|
||||
OriginalText: "Jesters",
|
||||
CorrectedText: "Gestures",
|
||||
Confidence: 1.0,
|
||||
},
|
||||
ProposalMetadata: proposals.ProposalMetadata{
|
||||
ProposalIndex: 0,
|
||||
ModuleKey: "glossary",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := v.Validate(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("validate protected_terms: %v", err)
|
||||
}
|
||||
if len(result.Decisions) != 1 {
|
||||
t.Fatalf("expected one decision, got %+v", result.Decisions)
|
||||
}
|
||||
if result.Decisions[0].Approved {
|
||||
t.Fatalf("expected glossary-stage protected_terms rejection, got %+v", result.Decisions[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltInRegistryUnknownKeyFails(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
if _, err := r.MustBuild("missing"); err == nil {
|
||||
|
||||
17
internal/validators/spoken_form_plausibility/validator.go
Normal file
17
internal/validators/spoken_form_plausibility/validator.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package spoken_form_plausibility
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "spoken_form_plausibility"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
v, err := frameworkvalidators.NewLLMBackedValidator(Key, frameworkvalidators.LLMValidatorTypeSpokenFormPlausibility, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validatormetadata.Wrap(v, validatormetadata.ExecutionClassLLMBacked), nil
|
||||
}
|
||||
17
internal/validators/spoken_word_review/validator.go
Normal file
17
internal/validators/spoken_word_review/validator.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package spoken_word_review
|
||||
|
||||
import (
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
frameworkvalidators "gitea.maximumdirect.net/eric/audita/internal/framework/validators"
|
||||
validatormetadata "gitea.maximumdirect.net/eric/audita/internal/validators/metadata"
|
||||
)
|
||||
|
||||
const Key = "spoken_word_review"
|
||||
|
||||
func New() (contracts.Validator, error) {
|
||||
v, err := frameworkvalidators.NewLLMBackedValidator(Key, frameworkvalidators.LLMValidatorTypeSpokenWordReview, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validatormetadata.Wrap(v, validatormetadata.ExecutionClassLLMBacked), nil
|
||||
}
|
||||
Reference in New Issue
Block a user