Refactor validators into package-owned components

This commit is contained in:
2026-05-14 00:28:28 +00:00
parent 3b160cf05b
commit 52ffe42e73
26 changed files with 775 additions and 98 deletions

View File

@@ -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 {