Centralize validator classification and malformed output handling
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package metadata
|
||||
|
||||
import "gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||
)
|
||||
|
||||
type ExecutionClass string
|
||||
|
||||
@@ -9,6 +13,31 @@ const (
|
||||
ExecutionClassLLMBacked ExecutionClass = "llm_backed"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyProposalShape = "proposal_shape"
|
||||
KeyConfidenceThreshold = "confidence_threshold"
|
||||
KeyOriginalTextPresence = "original_text_presence"
|
||||
KeyNonEmptyCorrectedText = "non_empty_corrected_text"
|
||||
KeyNoEffect = "no_effect"
|
||||
KeyProtectedTerms = "protected_terms"
|
||||
|
||||
KeySpokenFormPlausibility = "spoken_form_plausibility"
|
||||
KeyMeaningReversalReview = "meaning_reversal_review"
|
||||
KeyEditorialReview = "editorial_review"
|
||||
)
|
||||
|
||||
var executionClassByKey = map[string]ExecutionClass{
|
||||
KeyProposalShape: ExecutionClassDeterministic,
|
||||
KeyConfidenceThreshold: ExecutionClassDeterministic,
|
||||
KeyOriginalTextPresence: ExecutionClassDeterministic,
|
||||
KeyNonEmptyCorrectedText: ExecutionClassDeterministic,
|
||||
KeyNoEffect: ExecutionClassDeterministic,
|
||||
KeyProtectedTerms: ExecutionClassDeterministic,
|
||||
KeySpokenFormPlausibility: ExecutionClassLLMBacked,
|
||||
KeyMeaningReversalReview: ExecutionClassLLMBacked,
|
||||
KeyEditorialReview: ExecutionClassLLMBacked,
|
||||
}
|
||||
|
||||
type ClassifiedValidator interface {
|
||||
contracts.Validator
|
||||
ExecutionClass() ExecutionClass
|
||||
@@ -18,9 +47,11 @@ func ClassOf(v contracts.Validator) ExecutionClass {
|
||||
if v == nil {
|
||||
return ExecutionClassDeterministic
|
||||
}
|
||||
|
||||
classFromKey := ClassForKey(v.Name())
|
||||
classified, ok := v.(ClassifiedValidator)
|
||||
if !ok {
|
||||
return ExecutionClassDeterministic
|
||||
return classFromKey
|
||||
}
|
||||
switch classified.ExecutionClass() {
|
||||
case ExecutionClassLLMBacked:
|
||||
@@ -28,8 +59,16 @@ func ClassOf(v contracts.Validator) ExecutionClass {
|
||||
case ExecutionClassDeterministic:
|
||||
return ExecutionClassDeterministic
|
||||
default:
|
||||
return classFromKey
|
||||
}
|
||||
}
|
||||
|
||||
func ClassForKey(key string) ExecutionClass {
|
||||
class, ok := executionClassByKey[strings.TrimSpace(key)]
|
||||
if !ok {
|
||||
return ExecutionClassDeterministic
|
||||
}
|
||||
return class
|
||||
}
|
||||
|
||||
func Wrap(v contracts.Validator, class ExecutionClass) contracts.Validator {
|
||||
|
||||
@@ -16,6 +16,16 @@ func (u unclassifiedValidator) Validate(_ context.Context, _ contracts.Validatio
|
||||
return frameworkvalidators.Result{ValidatorName: u.Name(), Decisions: nil}, nil
|
||||
}
|
||||
|
||||
type namedUnclassifiedValidator struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (n namedUnclassifiedValidator) Name() string { return n.name }
|
||||
|
||||
func (n namedUnclassifiedValidator) Validate(_ context.Context, _ contracts.ValidationRequest) (frameworkvalidators.Result, error) {
|
||||
return frameworkvalidators.Result{ValidatorName: n.Name(), Decisions: nil}, nil
|
||||
}
|
||||
|
||||
func TestClassOfDefaultsToDeterministic(t *testing.T) {
|
||||
if got := ClassOf(unclassifiedValidator{}); got != ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic default class, got %q", got)
|
||||
@@ -28,3 +38,22 @@ func TestWrapExposesExecutionClass(t *testing.T) {
|
||||
t.Fatalf("expected llm_backed class, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassForKey(t *testing.T) {
|
||||
if got := ClassForKey(KeyProposalShape); got != ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic class for %q, got %q", KeyProposalShape, got)
|
||||
}
|
||||
if got := ClassForKey(KeySpokenFormPlausibility); got != ExecutionClassLLMBacked {
|
||||
t.Fatalf("expected llm_backed class for %q, got %q", KeySpokenFormPlausibility, got)
|
||||
}
|
||||
if got := ClassForKey("unknown"); got != ExecutionClassDeterministic {
|
||||
t.Fatalf("expected deterministic fallback for unknown key, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassOfFallsBackToStableValidatorKey(t *testing.T) {
|
||||
v := namedUnclassifiedValidator{name: KeyEditorialReview}
|
||||
if got := ClassOf(v); got != ExecutionClassLLMBacked {
|
||||
t.Fatalf("expected llm_backed fallback by key, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"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/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"
|
||||
@@ -17,22 +18,21 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
KeyProposalShape = "proposal_shape"
|
||||
KeyConfidenceThreshold = "confidence_threshold"
|
||||
KeyOriginalTextPresence = "original_text_presence"
|
||||
KeyNonEmptyCorrectedText = "non_empty_corrected_text"
|
||||
KeyNoEffect = "no_effect"
|
||||
KeyProtectedTerms = "protected_terms"
|
||||
KeyProposalShape = validatormetadata.KeyProposalShape
|
||||
KeyConfidenceThreshold = validatormetadata.KeyConfidenceThreshold
|
||||
KeyOriginalTextPresence = validatormetadata.KeyOriginalTextPresence
|
||||
KeyNonEmptyCorrectedText = validatormetadata.KeyNonEmptyCorrectedText
|
||||
KeyNoEffect = validatormetadata.KeyNoEffect
|
||||
KeyProtectedTerms = validatormetadata.KeyProtectedTerms
|
||||
|
||||
KeySpokenFormPlausibility = "spoken_form_plausibility"
|
||||
KeyMeaningReversalReview = "meaning_reversal_review"
|
||||
KeyEditorialReview = "editorial_review"
|
||||
KeySpokenFormPlausibility = validatormetadata.KeySpokenFormPlausibility
|
||||
KeyMeaningReversalReview = validatormetadata.KeyMeaningReversalReview
|
||||
KeyEditorialReview = validatormetadata.KeyEditorialReview
|
||||
)
|
||||
|
||||
type BuiltInValidatorDefinition struct {
|
||||
Key string
|
||||
Build func() (contracts.Validator, error)
|
||||
LLMBacked bool
|
||||
Key string
|
||||
Build func() (contracts.Validator, error)
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
@@ -47,9 +47,9 @@ func NewBuiltInRegistry() *Registry {
|
||||
{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: KeySpokenFormPlausibility, Build: spoken_form_plausibility.New},
|
||||
{Key: KeyMeaningReversalReview, Build: meaning_reversal_review.New},
|
||||
{Key: KeyEditorialReview, Build: editorial_review.New},
|
||||
}
|
||||
|
||||
m := make(map[string]BuiltInValidatorDefinition, len(defs))
|
||||
|
||||
@@ -82,11 +82,6 @@ func TestBuiltInValidatorPackagesConstruct(t *testing.T) {
|
||||
|
||||
func TestRegistryBuildsClassifiedValidators(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
llmKeys := map[string]bool{
|
||||
KeySpokenFormPlausibility: true,
|
||||
KeyMeaningReversalReview: true,
|
||||
KeyEditorialReview: true,
|
||||
}
|
||||
for _, key := range r.RegisteredKeys() {
|
||||
v, err := r.MustBuild(key)
|
||||
if err != nil {
|
||||
@@ -95,16 +90,28 @@ func TestRegistryBuildsClassifiedValidators(t *testing.T) {
|
||||
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
|
||||
}
|
||||
want := validatormetadata.ClassForKey(key)
|
||||
if got := validatormetadata.ClassOf(v); got != want {
|
||||
t.Fatalf("expected class %q for %q, got %q", want, key, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionClassResolvableByStableKeyAndByValidatorInstance(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
for _, key := range r.RegisteredKeys() {
|
||||
v, err := r.MustBuild(key)
|
||||
if err != nil {
|
||||
t.Fatalf("must build %q: %v", key, err)
|
||||
}
|
||||
fromKey := validatormetadata.ClassForKey(key)
|
||||
fromInstance := validatormetadata.ClassOf(v)
|
||||
if fromInstance != fromKey {
|
||||
t.Fatalf("class mismatch for %q: key=%q instance=%q", key, fromKey, fromInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryProtectedTermsUsesNonGlossaryStageBehavior(t *testing.T) {
|
||||
r := NewBuiltInRegistry()
|
||||
v, err := r.MustBuild(KeyProtectedTerms)
|
||||
|
||||
Reference in New Issue
Block a user