Centralize output schema and module key validation catalogs
This commit is contained in:
@@ -653,6 +653,10 @@ func runConfigValidate(args []string, stdout, stderr io.Writer) int {
|
|||||||
fmt.Fprintf(stderr, "audita config validate: %v\n", err)
|
fmt.Fprintf(stderr, "audita config validate: %v\n", err)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
fmt.Fprintf(stderr, "audita config validate: %v\n", err)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
fmt.Fprintln(stdout, "config is valid")
|
fmt.Fprintln(stdout, "config is valid")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/llm"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/modules"
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/runner"
|
||||||
@@ -210,6 +209,23 @@ func TestRunConfigValidateUnknownField(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunConfigValidateUnsupportedModuleKey(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cfgPath := writeFile(t, "config.yml", "version: 1\npipeline:\n modules: [made_up]\n")
|
||||||
|
|
||||||
|
exitCode := Run([]string{"config", "validate", "--config", cfgPath}, &stdout, &stderr)
|
||||||
|
if exitCode == 0 {
|
||||||
|
t.Fatalf("expected failure for unsupported module key")
|
||||||
|
}
|
||||||
|
if stdout.Len() != 0 {
|
||||||
|
t.Fatalf("expected empty stdout on failure, got %q", stdout.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stderr.String(), "unsupported module key") {
|
||||||
|
t.Fatalf("expected unsupported module key error, got %q", stderr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunConfigPrintEffectiveOutputsRedactedJSON(t *testing.T) {
|
func TestRunConfigPrintEffectiveOutputsRedactedJSON(t *testing.T) {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
@@ -369,8 +385,8 @@ diagnostics:
|
|||||||
|
|
||||||
func TestRunProcessEnvOverridesConfigFile(t *testing.T) {
|
func TestRunProcessEnvOverridesConfigFile(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -392,7 +408,7 @@ func TestRunProcessEnvOverridesConfigFile(t *testing.T) {
|
|||||||
cfgPath := writeFile(t, "config.yml", `
|
cfgPath := writeFile(t, "config.yml", `
|
||||||
version: 1
|
version: 1
|
||||||
pipeline:
|
pipeline:
|
||||||
modules: [m]
|
modules: [grammar]
|
||||||
llm:
|
llm:
|
||||||
proposal:
|
proposal:
|
||||||
model: file-model
|
model: file-model
|
||||||
@@ -404,7 +420,7 @@ llm:
|
|||||||
fixturePath("tiny_transcript.json"),
|
fixturePath("tiny_transcript.json"),
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--config", cfgPath,
|
"--config", cfgPath,
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
@@ -413,8 +429,8 @@ llm:
|
|||||||
|
|
||||||
func TestRunProcessCLIOverridesEnvAndConfigFile(t *testing.T) {
|
func TestRunProcessCLIOverridesEnvAndConfigFile(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -436,7 +452,7 @@ func TestRunProcessCLIOverridesEnvAndConfigFile(t *testing.T) {
|
|||||||
cfgPath := writeFile(t, "config.yml", `
|
cfgPath := writeFile(t, "config.yml", `
|
||||||
version: 1
|
version: 1
|
||||||
pipeline:
|
pipeline:
|
||||||
modules: [m]
|
modules: [grammar]
|
||||||
llm:
|
llm:
|
||||||
proposal:
|
proposal:
|
||||||
model: file-model
|
model: file-model
|
||||||
@@ -448,7 +464,7 @@ llm:
|
|||||||
fixturePath("tiny_transcript.json"),
|
fixturePath("tiny_transcript.json"),
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--config", cfgPath,
|
"--config", cfgPath,
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
"--model", "cli-model",
|
"--model", "cli-model",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
@@ -495,8 +511,8 @@ diagnostics:
|
|||||||
|
|
||||||
func TestRunProcessTranscriptDescriptionCLIOverridesConfigFileContextDescription(t *testing.T) {
|
func TestRunProcessTranscriptDescriptionCLIOverridesConfigFileContextDescription(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -517,7 +533,7 @@ func TestRunProcessTranscriptDescriptionCLIOverridesConfigFileContextDescription
|
|||||||
cfgPath := writeFile(t, "config.yml", `
|
cfgPath := writeFile(t, "config.yml", `
|
||||||
version: 1
|
version: 1
|
||||||
pipeline:
|
pipeline:
|
||||||
modules: [m]
|
modules: [grammar]
|
||||||
context:
|
context:
|
||||||
description: "file transcript description"
|
description: "file transcript description"
|
||||||
`)
|
`)
|
||||||
@@ -528,7 +544,7 @@ context:
|
|||||||
fixturePath("tiny_transcript.json"),
|
fixturePath("tiny_transcript.json"),
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--config", cfgPath,
|
"--config", cfgPath,
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
"--transcript-description", "cli transcript description",
|
"--transcript-description", "cli transcript description",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
@@ -692,8 +708,8 @@ func TestRunProcessCLIOverridesEnvironment(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessTranscriptDescriptionDefaultEmpty(t *testing.T) {
|
func TestRunProcessTranscriptDescriptionDefaultEmpty(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -720,7 +736,7 @@ func TestRunProcessTranscriptDescriptionDefaultEmpty(t *testing.T) {
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
@@ -729,8 +745,8 @@ func TestRunProcessTranscriptDescriptionDefaultEmpty(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessTranscriptDescriptionCLIOverrideAndTrim(t *testing.T) {
|
func TestRunProcessTranscriptDescriptionCLIOverrideAndTrim(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -757,7 +773,7 @@ func TestRunProcessTranscriptDescriptionCLIOverrideAndTrim(t *testing.T) {
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
"--transcript-description", " speaker background context ",
|
"--transcript-description", " speaker background context ",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
@@ -823,8 +839,8 @@ func TestRunProcessRejectsValidationConcurrencyAboveTotalConcurrency(t *testing.
|
|||||||
|
|
||||||
func TestRunProcessTotalLLMConcurrencyDrivesEffectiveValidationConcurrencyWhenUnset(t *testing.T) {
|
func TestRunProcessTotalLLMConcurrencyDrivesEffectiveValidationConcurrencyWhenUnset(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -865,7 +881,7 @@ func TestRunProcessTotalLLMConcurrencyDrivesEffectiveValidationConcurrencyWhenUn
|
|||||||
"--glossary",
|
"--glossary",
|
||||||
fixturePath("tiny_glossary.yaml"),
|
fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules",
|
"--modules",
|
||||||
"m",
|
"grammar",
|
||||||
"--total-llm-concurrency",
|
"--total-llm-concurrency",
|
||||||
"4",
|
"4",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
@@ -908,8 +924,8 @@ func TestRunProcessRejectsProposalConcurrencyAboveTotalConcurrency(t *testing.T)
|
|||||||
|
|
||||||
func TestRunProcessLegacyLLMConcurrencyAliasSetsTotalAndProposal(t *testing.T) {
|
func TestRunProcessLegacyLLMConcurrencyAliasSetsTotalAndProposal(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -944,7 +960,7 @@ func TestRunProcessLegacyLLMConcurrencyAliasSetsTotalAndProposal(t *testing.T) {
|
|||||||
"--glossary",
|
"--glossary",
|
||||||
fixturePath("tiny_glossary.yaml"),
|
fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules",
|
"--modules",
|
||||||
"m",
|
"grammar",
|
||||||
"--llm-concurrency",
|
"--llm-concurrency",
|
||||||
"3",
|
"3",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
@@ -955,8 +971,8 @@ func TestRunProcessLegacyLLMConcurrencyAliasSetsTotalAndProposal(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessLLMConcurrencyFlagsOverrideEnvironment(t *testing.T) {
|
func TestRunProcessLLMConcurrencyFlagsOverrideEnvironment(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -997,7 +1013,7 @@ func TestRunProcessLLMConcurrencyFlagsOverrideEnvironment(t *testing.T) {
|
|||||||
"--glossary",
|
"--glossary",
|
||||||
fixturePath("tiny_glossary.yaml"),
|
fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules",
|
"--modules",
|
||||||
"m",
|
"grammar",
|
||||||
"--total-llm-concurrency",
|
"--total-llm-concurrency",
|
||||||
"4",
|
"4",
|
||||||
"--proposal-llm-concurrency",
|
"--proposal-llm-concurrency",
|
||||||
@@ -1010,8 +1026,8 @@ func TestRunProcessLLMConcurrencyFlagsOverrideEnvironment(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessAcceptsLLMConcurrencyEnvironmentVariables(t *testing.T) {
|
func TestRunProcessAcceptsLLMConcurrencyEnvironmentVariables(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{
|
"grammar": fakeModule{
|
||||||
key: "m",
|
key: "grammar",
|
||||||
policy: proposals.ReplacementPolicyRequireUnique,
|
policy: proposals.ReplacementPolicyRequireUnique,
|
||||||
validators: []contracts.Validator{
|
validators: []contracts.Validator{
|
||||||
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
fakeValidator{name: "capture-config", validateF: func(req contracts.ValidationRequest) (validators.Result, error) {
|
||||||
@@ -1052,7 +1068,7 @@ func TestRunProcessAcceptsLLMConcurrencyEnvironmentVariables(t *testing.T) {
|
|||||||
"--glossary",
|
"--glossary",
|
||||||
fixturePath("tiny_glossary.yaml"),
|
fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules",
|
"--modules",
|
||||||
"m",
|
"grammar",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode != 0 {
|
if exitCode != 0 {
|
||||||
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
t.Fatalf("expected success, got %d stderr=%q", exitCode, stderr.String())
|
||||||
@@ -1883,12 +1899,12 @@ func TestRunProcessInjectedFactoryExecutesRunnerAndReportsModules(t *testing.T)
|
|||||||
return validators.Result{ValidatorName: "allow", Decisions: decisions}, nil
|
return validators.Result{ValidatorName: "allow", Decisions: decisions}, nil
|
||||||
}}
|
}}
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m1": fakeModule{key: "m1", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{allow}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
"glossary": fakeModule{key: "glossary", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{allow}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
||||||
return []proposals.CorrectionProposal{
|
return []proposals.CorrectionProposal{
|
||||||
{TargetSegmentID: 1, OriginalText: "Hello", CorrectedText: "Hi", Confidence: 1},
|
{TargetSegmentID: 1, OriginalText: "Hello", CorrectedText: "Hi", Confidence: 1},
|
||||||
}, nil
|
}, nil
|
||||||
}},
|
}},
|
||||||
"m2": fakeModule{key: "m2", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{allow}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
"homophones": fakeModule{key: "homophones", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{allow}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
||||||
if req.WorkingTranscript.Segments[0].Text != "Hi world" {
|
if req.WorkingTranscript.Segments[0].Text != "Hi world" {
|
||||||
t.Fatalf("expected module 2 to see module 1 changes, got %q", req.WorkingTranscript.Segments[0].Text)
|
t.Fatalf("expected module 2 to see module 1 changes, got %q", req.WorkingTranscript.Segments[0].Text)
|
||||||
}
|
}
|
||||||
@@ -1911,7 +1927,7 @@ func TestRunProcessInjectedFactoryExecutesRunnerAndReportsModules(t *testing.T)
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m1,m2",
|
"--modules", "glossary,homophones",
|
||||||
"--output", outputPath,
|
"--output", outputPath,
|
||||||
"--report-json", reportPath,
|
"--report-json", reportPath,
|
||||||
"--work-dir", workDir,
|
"--work-dir", workDir,
|
||||||
@@ -1970,7 +1986,7 @@ func TestRunProcessInjectedFactoryLLMValidatorResultsInReports(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m": fakeModule{key: "m", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{llmValidator}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
"grammar": fakeModule{key: "grammar", policy: proposals.ReplacementPolicyRequireUnique, validators: []contracts.Validator{llmValidator}, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
||||||
return []proposals.CorrectionProposal{{TargetSegmentID: 1, OriginalText: "Hello", CorrectedText: "Hi", Confidence: 1}}, nil
|
return []proposals.CorrectionProposal{{TargetSegmentID: 1, OriginalText: "Hello", CorrectedText: "Hi", Confidence: 1}}, nil
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
@@ -1990,7 +2006,7 @@ func TestRunProcessInjectedFactoryLLMValidatorResultsInReports(t *testing.T) {
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m",
|
"--modules", "grammar",
|
||||||
"--output", outputPath,
|
"--output", outputPath,
|
||||||
"--report-json", reportPath,
|
"--report-json", reportPath,
|
||||||
"--work-dir", workDir,
|
"--work-dir", workDir,
|
||||||
@@ -2011,7 +2027,7 @@ func TestRunProcessInjectedFactoryLLMValidatorResultsInReports(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessInjectedFactorySkippedKeepsAutoRetention(t *testing.T) {
|
func TestRunProcessInjectedFactorySkippedKeepsAutoRetention(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m1": fakeModule{key: "m1", policy: proposals.ReplacementPolicyRequireUnique, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
"glossary": fakeModule{key: "glossary", policy: proposals.ReplacementPolicyRequireUnique, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
||||||
return []proposals.CorrectionProposal{
|
return []proposals.CorrectionProposal{
|
||||||
{TargetSegmentID: 1, OriginalText: "word", CorrectedText: "term", Confidence: 1},
|
{TargetSegmentID: 1, OriginalText: "word", CorrectedText: "term", Confidence: 1},
|
||||||
}, nil
|
}, nil
|
||||||
@@ -2027,7 +2043,7 @@ func TestRunProcessInjectedFactorySkippedKeepsAutoRetention(t *testing.T) {
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m1",
|
"--modules", "glossary",
|
||||||
"--work-dir", workDir,
|
"--work-dir", workDir,
|
||||||
"--work-dir-retention", "auto",
|
"--work-dir-retention", "auto",
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
@@ -2041,7 +2057,7 @@ func TestRunProcessInjectedFactorySkippedKeepsAutoRetention(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunProcessInjectedFactoryFailureWritesFailedReport(t *testing.T) {
|
func TestRunProcessInjectedFactoryFailureWritesFailedReport(t *testing.T) {
|
||||||
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
processModuleFactory = fakeModuleFactory{modules: map[string]contracts.TranscriptModule{
|
||||||
"m1": fakeModule{key: "m1", policy: proposals.ReplacementPolicyRequireUnique, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
"glossary": fakeModule{key: "glossary", policy: proposals.ReplacementPolicyRequireUnique, proposeF: func(req contracts.ProposalRequest) ([]proposals.CorrectionProposal, error) {
|
||||||
return nil, errors.New("test failure")
|
return nil, errors.New("test failure")
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
@@ -2057,7 +2073,7 @@ func TestRunProcessInjectedFactoryFailureWritesFailedReport(t *testing.T) {
|
|||||||
exitCode := Run([]string{
|
exitCode := Run([]string{
|
||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "m1",
|
"--modules", "glossary",
|
||||||
"--work-dir", workDir,
|
"--work-dir", workDir,
|
||||||
"--work-dir-retention", "always",
|
"--work-dir-retention", "always",
|
||||||
"--report-json", reportPath,
|
"--report-json", reportPath,
|
||||||
@@ -2089,14 +2105,8 @@ func TestRunProcessInjectedFactoryFailureWritesFailedReport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunProcessProductionRegistryUnsupportedModuleFailsCleanly(t *testing.T) {
|
func TestRunProcessUnsupportedModuleFailsDuringConfigValidation(t *testing.T) {
|
||||||
cfg := modules.Dependencies{}
|
|
||||||
processModuleFactory = modules.NewFactory(cfg)
|
|
||||||
t.Cleanup(func() { processModuleFactory = nil })
|
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
workDir := t.TempDir()
|
|
||||||
reportPath := filepath.Join(t.TempDir(), "report.json")
|
|
||||||
transcriptPath := writeFile(t, "transcript.json", `[
|
transcriptPath := writeFile(t, "transcript.json", `[
|
||||||
{"id":1,"speaker":"Alice","start":0.0,"end":1.0,"text":"Hello"}
|
{"id":1,"speaker":"Alice","start":0.0,"end":1.0,"text":"Hello"}
|
||||||
]`)
|
]`)
|
||||||
@@ -2105,9 +2115,6 @@ func TestRunProcessProductionRegistryUnsupportedModuleFailsCleanly(t *testing.T)
|
|||||||
"process", transcriptPath,
|
"process", transcriptPath,
|
||||||
"--glossary", fixturePath("tiny_glossary.yaml"),
|
"--glossary", fixturePath("tiny_glossary.yaml"),
|
||||||
"--modules", "made_up",
|
"--modules", "made_up",
|
||||||
"--work-dir", workDir,
|
|
||||||
"--work-dir-retention", "always",
|
|
||||||
"--report-json", reportPath,
|
|
||||||
}, &stdout, &stderr)
|
}, &stdout, &stderr)
|
||||||
if exitCode == 0 {
|
if exitCode == 0 {
|
||||||
t.Fatal("expected failure exit code")
|
t.Fatal("expected failure exit code")
|
||||||
@@ -2115,23 +2122,12 @@ func TestRunProcessProductionRegistryUnsupportedModuleFailsCleanly(t *testing.T)
|
|||||||
if stdout.Len() != 0 {
|
if stdout.Len() != 0 {
|
||||||
t.Fatalf("expected empty stdout on failure, got %q", stdout.String())
|
t.Fatalf("expected empty stdout on failure, got %q", stdout.String())
|
||||||
}
|
}
|
||||||
if !strings.Contains(stderr.String(), "runner_execution") {
|
if !strings.Contains(stderr.String(), "invalid CLI configuration") {
|
||||||
t.Fatalf("expected runner_execution failure on stderr, got %q", stderr.String())
|
t.Fatalf("expected config validation failure on stderr, got %q", stderr.String())
|
||||||
}
|
}
|
||||||
if !strings.Contains(stderr.String(), "unsupported module key") {
|
if !strings.Contains(stderr.String(), "unsupported module key") {
|
||||||
t.Fatalf("expected explicit unsupported module message, got %q", stderr.String())
|
t.Fatalf("expected explicit unsupported module message, got %q", stderr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
report := readProcessReport(t, reportPath)
|
|
||||||
if report.Status != "failed" {
|
|
||||||
t.Fatalf("expected failed report status, got %q", report.Status)
|
|
||||||
}
|
|
||||||
if report.ErrorPhase != "runner_execution" {
|
|
||||||
t.Fatalf("expected runner_execution phase, got %q", report.ErrorPhase)
|
|
||||||
}
|
|
||||||
if !strings.Contains(report.ErrorMessage, "unsupported module key") {
|
|
||||||
t.Fatalf("expected report error message to mention unsupported module, got %q", report.ErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunProcessExplicitUnsupportedModulesFailClearly(t *testing.T) {
|
func TestRunProcessExplicitUnsupportedModulesFailClearly(t *testing.T) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkDirRetention string
|
type WorkDirRetention string
|
||||||
@@ -14,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultModulesCSV = "glossary,homophones,glossary,spoken_word,grammar"
|
DefaultModulesCSV = modulecatalog.KeyGlossary + "," + modulecatalog.KeyHomophones + "," + modulecatalog.KeyGlossary + "," + modulecatalog.KeySpokenWord + "," + modulecatalog.KeyGrammar
|
||||||
DefaultOutputSchema = "bare-segments"
|
DefaultOutputSchema = "bare-segments"
|
||||||
DefaultPrimaryModel = "openrouter/google/gemma-4-31b-it"
|
DefaultPrimaryModel = "openrouter/google/gemma-4-31b-it"
|
||||||
DefaultPrimaryBaseURL = "https://openrouter.ai/api/v1"
|
DefaultPrimaryBaseURL = "https://openrouter.ai/api/v1"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/outputschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultConfigValues(t *testing.T) {
|
func TestDefaultConfigValues(t *testing.T) {
|
||||||
@@ -318,6 +321,38 @@ func TestValidationFailures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidationRejectsUnsupportedModuleKey(t *testing.T) {
|
||||||
|
cfg := Default()
|
||||||
|
cfg.Modules = []string{modulecatalog.KeyGlossary, "made_up"}
|
||||||
|
|
||||||
|
err := cfg.Validate()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected validation error for unsupported module key")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), `unsupported module key "made_up"`) {
|
||||||
|
t.Fatalf("expected unsupported module key error, got %q", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationAllowsRepeatedSupportedModuleKeys(t *testing.T) {
|
||||||
|
cfg := Default()
|
||||||
|
cfg.Modules = []string{modulecatalog.KeyGlossary, modulecatalog.KeyGlossary, modulecatalog.KeyGrammar}
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
t.Fatalf("expected repeated supported module keys to validate, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationAcceptsAllSupportedOutputSchemas(t *testing.T) {
|
||||||
|
for _, schemaKey := range outputschema.SupportedKeys() {
|
||||||
|
cfg := Default()
|
||||||
|
cfg.OutputSchema = schemaKey
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
t.Fatalf("expected output schema %q to validate, got %v", schemaKey, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEffectiveValidationLLMInheritance(t *testing.T) {
|
func TestEffectiveValidationLLMInheritance(t *testing.T) {
|
||||||
cfg := Default()
|
cfg := Default()
|
||||||
cfg.PrimaryLLM.APIKey = "primary-key"
|
cfg.PrimaryLLM.APIKey = "primary-key"
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/outputschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c Config) Validate() error {
|
func (c Config) Validate() error {
|
||||||
@@ -12,19 +15,19 @@ func (c Config) Validate() error {
|
|||||||
issues = append(issues, "modules must not be empty")
|
issues = append(issues, "modules must not be empty")
|
||||||
}
|
}
|
||||||
for _, module := range c.Modules {
|
for _, module := range c.Modules {
|
||||||
if strings.TrimSpace(module) == "" {
|
moduleKey := strings.TrimSpace(module)
|
||||||
|
if moduleKey == "" {
|
||||||
issues = append(issues, "modules must not contain empty values")
|
issues = append(issues, "modules must not contain empty values")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if !modulecatalog.IsSupported(moduleKey) {
|
||||||
|
issues = append(issues, fmt.Sprintf("unsupported module key %q", moduleKey))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(c.OutputSchema) == "" {
|
if strings.TrimSpace(c.OutputSchema) == "" {
|
||||||
issues = append(issues, "output schema must not be empty")
|
issues = append(issues, "output schema must not be empty")
|
||||||
} else {
|
} else if !outputschema.IsSupported(c.OutputSchema) {
|
||||||
switch strings.TrimSpace(c.OutputSchema) {
|
issues = append(issues, fmt.Sprintf("unsupported output schema %q", c.OutputSchema))
|
||||||
case "bare-segments", "audita-v1":
|
|
||||||
default:
|
|
||||||
issues = append(issues, fmt.Sprintf("unsupported output schema %q", c.OutputSchema))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.PrimaryLLM.TimeoutSeconds <= 0 {
|
if c.PrimaryLLM.TimeoutSeconds <= 0 {
|
||||||
|
|||||||
35
internal/core/modulecatalog/catalog.go
Normal file
35
internal/core/modulecatalog/catalog.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package modulecatalog
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyGlossary = "glossary"
|
||||||
|
KeyHomophones = "homophones"
|
||||||
|
KeySpokenWord = "spoken_word"
|
||||||
|
KeyGrammar = "grammar"
|
||||||
|
)
|
||||||
|
|
||||||
|
var supportedKeys = []string{
|
||||||
|
KeyGlossary,
|
||||||
|
KeyHomophones,
|
||||||
|
KeySpokenWord,
|
||||||
|
KeyGrammar,
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportedKeySet = map[string]struct{}{
|
||||||
|
KeyGlossary: {},
|
||||||
|
KeyHomophones: {},
|
||||||
|
KeySpokenWord: {},
|
||||||
|
KeyGrammar: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
func SupportedKeys() []string {
|
||||||
|
out := make([]string, len(supportedKeys))
|
||||||
|
copy(out, supportedKeys)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSupported(key string) bool {
|
||||||
|
_, ok := supportedKeySet[strings.TrimSpace(key)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
24
internal/core/modulecatalog/catalog_test.go
Normal file
24
internal/core/modulecatalog/catalog_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package modulecatalog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSupportedKeys(t *testing.T) {
|
||||||
|
want := []string{KeyGlossary, KeyHomophones, KeySpokenWord, KeyGrammar}
|
||||||
|
if got := SupportedKeys(); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("unexpected supported keys: got=%v want=%v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsSupported(t *testing.T) {
|
||||||
|
for _, key := range SupportedKeys() {
|
||||||
|
if !IsSupported(key) {
|
||||||
|
t.Fatalf("expected key %q to be supported", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if IsSupported("made_up") {
|
||||||
|
t.Fatalf("did not expect made_up to be supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,15 +31,31 @@ var definitions = map[string]Definition{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var supportedKeys = []string{
|
||||||
|
SchemaBareSegments,
|
||||||
|
SchemaAuditaV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
func SupportedKeys() []string {
|
||||||
|
out := make([]string, len(supportedKeys))
|
||||||
|
copy(out, supportedKeys)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSupported(key string) bool {
|
||||||
|
_, ok := definitions[strings.TrimSpace(key)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func Resolve(key string) (Definition, error) {
|
func Resolve(key string) (Definition, error) {
|
||||||
normalized := strings.TrimSpace(key)
|
normalized := strings.TrimSpace(key)
|
||||||
if normalized == "" {
|
if normalized == "" {
|
||||||
return Definition{}, fmt.Errorf("output schema must not be empty")
|
return Definition{}, fmt.Errorf("output schema must not be empty")
|
||||||
}
|
}
|
||||||
def, ok := definitions[normalized]
|
if !IsSupported(normalized) {
|
||||||
if !ok {
|
|
||||||
return Definition{}, fmt.Errorf("unsupported output schema %q", normalized)
|
return Definition{}, fmt.Errorf("unsupported output schema %q", normalized)
|
||||||
}
|
}
|
||||||
|
def := definitions[normalized]
|
||||||
return def, nil
|
return def, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package outputschema
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -56,3 +57,19 @@ func TestResolveUnknown(t *testing.T) {
|
|||||||
t.Fatalf("expected unsupported output schema error, got %v", err)
|
t.Fatalf("expected unsupported output schema error, got %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSupportedKeysAndIsSupported(t *testing.T) {
|
||||||
|
want := []string{SchemaBareSegments, SchemaAuditaV1}
|
||||||
|
if got := SupportedKeys(); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Fatalf("unexpected supported schema keys: got=%v want=%v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range want {
|
||||||
|
if !IsSupported(key) {
|
||||||
|
t.Fatalf("expected schema key %q to be supported", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if IsSupported("seriatim-intermediate") {
|
||||||
|
t.Fatalf("did not expect unsupported schema to be reported as supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
glossarymodule "gitea.maximumdirect.net/eric/audita/internal/modules/glossary"
|
glossarymodule "gitea.maximumdirect.net/eric/audita/internal/modules/glossary"
|
||||||
@@ -15,28 +16,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ModuleKeyGlossary = "glossary"
|
ModuleKeyGlossary = modulecatalog.KeyGlossary
|
||||||
ModuleKeyHomophones = "homophones"
|
ModuleKeyHomophones = modulecatalog.KeyHomophones
|
||||||
ModuleKeySpokenWord = "spoken_word"
|
ModuleKeySpokenWord = modulecatalog.KeySpokenWord
|
||||||
ModuleKeyGrammar = "grammar"
|
ModuleKeyGrammar = modulecatalog.KeyGrammar
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ReasonUnsupportedModule = "unsupported_module"
|
ReasonUnsupportedModule = "unsupported_module"
|
||||||
)
|
)
|
||||||
|
|
||||||
var knownModuleKeys = map[string]struct{}{
|
|
||||||
ModuleKeyGlossary: {},
|
|
||||||
ModuleKeyHomophones: {},
|
|
||||||
ModuleKeySpokenWord: {},
|
|
||||||
ModuleKeyGrammar: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsKnownModuleKey reports whether a module key is recognized by the production
|
// IsKnownModuleKey reports whether a module key is recognized by the production
|
||||||
// registry scaffold.
|
// registry scaffold.
|
||||||
func IsKnownModuleKey(key string) bool {
|
func IsKnownModuleKey(key string) bool {
|
||||||
_, ok := knownModuleKeys[strings.TrimSpace(key)]
|
return modulecatalog.IsSupported(key)
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies holds explicit constructor dependencies for module creation.
|
// Dependencies holds explicit constructor dependencies for module creation.
|
||||||
@@ -69,7 +62,7 @@ type Factory struct {
|
|||||||
func NewFactory(deps Dependencies) *Factory {
|
func NewFactory(deps Dependencies) *Factory {
|
||||||
factory := &Factory{
|
factory := &Factory{
|
||||||
deps: deps,
|
deps: deps,
|
||||||
constructors: make(map[string]Constructor, len(knownModuleKeys)),
|
constructors: make(map[string]Constructor, len(modulecatalog.SupportedKeys())),
|
||||||
}
|
}
|
||||||
_ = factory.RegisterConstructor(ModuleKeyGlossary, constructGlossaryModule)
|
_ = factory.RegisterConstructor(ModuleKeyGlossary, constructGlossaryModule)
|
||||||
_ = factory.RegisterConstructor(ModuleKeyHomophones, constructHomophonesModule)
|
_ = factory.RegisterConstructor(ModuleKeyHomophones, constructHomophonesModule)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||||
)
|
)
|
||||||
@@ -26,7 +27,7 @@ func (m noopModule) Propose(ctx context.Context, req contracts.ProposalRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestKnownModuleKeyRecognition(t *testing.T) {
|
func TestKnownModuleKeyRecognition(t *testing.T) {
|
||||||
for _, key := range []string{ModuleKeyGlossary, ModuleKeyHomophones, ModuleKeySpokenWord, ModuleKeyGrammar} {
|
for _, key := range modulecatalog.SupportedKeys() {
|
||||||
if !IsKnownModuleKey(key) {
|
if !IsKnownModuleKey(key) {
|
||||||
t.Fatalf("expected key %q to be recognized", key)
|
t.Fatalf("expected key %q to be recognized", key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
"gitea.maximumdirect.net/eric/audita/internal/core/config"
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||||
stagewarnings "gitea.maximumdirect.net/eric/audita/internal/framework/warnings"
|
stagewarnings "gitea.maximumdirect.net/eric/audita/internal/framework/warnings"
|
||||||
@@ -118,13 +119,13 @@ func confidenceThresholdForModule(moduleKey string, cfg *config.Config) float64
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
switch moduleKey {
|
switch moduleKey {
|
||||||
case "glossary":
|
case modulecatalog.KeyGlossary:
|
||||||
return cfg.Thresholds.Glossary
|
return cfg.Thresholds.Glossary
|
||||||
case "grammar":
|
case modulecatalog.KeyGrammar:
|
||||||
return cfg.Thresholds.Grammar
|
return cfg.Thresholds.Grammar
|
||||||
case "homophones":
|
case modulecatalog.KeyHomophones:
|
||||||
return cfg.Thresholds.Homophones
|
return cfg.Thresholds.Homophones
|
||||||
case "spoken_word":
|
case modulecatalog.KeySpokenWord:
|
||||||
return cfg.Thresholds.SpokenWord
|
return cfg.Thresholds.SpokenWord
|
||||||
default:
|
default:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ package validators
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms"
|
"gitea.maximumdirect.net/eric/audita/internal/validators/protected_terms"
|
||||||
)
|
)
|
||||||
|
|
||||||
var builtInChains = map[string][]string{
|
var builtInChains = map[string][]string{
|
||||||
"glossary": {
|
modulecatalog.KeyGlossary: {
|
||||||
KeyProposalShape,
|
KeyProposalShape,
|
||||||
KeyNoEffect,
|
KeyNoEffect,
|
||||||
KeyOriginalTextPresence,
|
KeyOriginalTextPresence,
|
||||||
@@ -18,7 +20,7 @@ var builtInChains = map[string][]string{
|
|||||||
KeySpokenFormPlausibility,
|
KeySpokenFormPlausibility,
|
||||||
KeyMeaningReversalReview,
|
KeyMeaningReversalReview,
|
||||||
},
|
},
|
||||||
"homophones": {
|
modulecatalog.KeyHomophones: {
|
||||||
KeyProposalShape,
|
KeyProposalShape,
|
||||||
KeyNoEffect,
|
KeyNoEffect,
|
||||||
KeyOriginalTextPresence,
|
KeyOriginalTextPresence,
|
||||||
@@ -28,7 +30,7 @@ var builtInChains = map[string][]string{
|
|||||||
KeySpokenFormPlausibility,
|
KeySpokenFormPlausibility,
|
||||||
KeyMeaningReversalReview,
|
KeyMeaningReversalReview,
|
||||||
},
|
},
|
||||||
"spoken_word": {
|
modulecatalog.KeySpokenWord: {
|
||||||
KeyProposalShape,
|
KeyProposalShape,
|
||||||
KeyNoEffect,
|
KeyNoEffect,
|
||||||
KeyOriginalTextPresence,
|
KeyOriginalTextPresence,
|
||||||
@@ -38,7 +40,7 @@ var builtInChains = map[string][]string{
|
|||||||
KeyEditorialReview,
|
KeyEditorialReview,
|
||||||
KeyMeaningReversalReview,
|
KeyMeaningReversalReview,
|
||||||
},
|
},
|
||||||
"grammar": {
|
modulecatalog.KeyGrammar: {
|
||||||
KeyProposalShape,
|
KeyProposalShape,
|
||||||
KeyNoEffect,
|
KeyNoEffect,
|
||||||
KeyOriginalTextPresence,
|
KeyOriginalTextPresence,
|
||||||
@@ -51,9 +53,10 @@ var builtInChains = map[string][]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BuiltInChainKeys(moduleKey string) ([]string, error) {
|
func BuiltInChainKeys(moduleKey string) ([]string, error) {
|
||||||
keys, ok := builtInChains[moduleKey]
|
key := strings.TrimSpace(moduleKey)
|
||||||
|
keys, ok := builtInChains[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no built-in validator chain for module %q", moduleKey)
|
return nil, fmt.Errorf("no built-in validator chain for module %q", key)
|
||||||
}
|
}
|
||||||
out := make([]string, len(keys))
|
out := make([]string, len(keys))
|
||||||
copy(out, keys)
|
copy(out, keys)
|
||||||
@@ -61,6 +64,7 @@ func BuiltInChainKeys(moduleKey string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ResolveBuiltInChain(moduleKey string, registry *Registry) ([]contracts.Validator, error) {
|
func ResolveBuiltInChain(moduleKey string, registry *Registry) ([]contracts.Validator, error) {
|
||||||
|
moduleKey = strings.TrimSpace(moduleKey)
|
||||||
keys, err := BuiltInChainKeys(moduleKey)
|
keys, err := BuiltInChainKeys(moduleKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -71,7 +75,7 @@ func ResolveBuiltInChain(moduleKey string, registry *Registry) ([]contracts.Vali
|
|||||||
|
|
||||||
out := make([]contracts.Validator, 0, len(keys))
|
out := make([]contracts.Validator, 0, len(keys))
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if moduleKey == "glossary" && key == KeyProtectedTerms {
|
if moduleKey == modulecatalog.KeyGlossary && key == KeyProtectedTerms {
|
||||||
// Glossary stages preserve current stricter protection semantics while
|
// Glossary stages preserve current stricter protection semantics while
|
||||||
// reporting the stable protected_terms key.
|
// reporting the stable protected_terms key.
|
||||||
v, buildErr := protected_terms.NewGlossaryStage()
|
v, buildErr := protected_terms.NewGlossaryStage()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/audita/internal/core/modulecatalog"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
"gitea.maximumdirect.net/eric/audita/internal/core/schema"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/contracts"
|
||||||
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
"gitea.maximumdirect.net/eric/audita/internal/framework/proposals"
|
||||||
@@ -200,7 +201,7 @@ func TestBuiltInRegistryUnknownKeyFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBuiltInChainKeysResolveForProductionModules(t *testing.T) {
|
func TestBuiltInChainKeysResolveForProductionModules(t *testing.T) {
|
||||||
for _, moduleKey := range []string{"glossary", "homophones", "spoken_word", "grammar"} {
|
for _, moduleKey := range modulecatalog.SupportedKeys() {
|
||||||
keys, err := BuiltInChainKeys(moduleKey)
|
keys, err := BuiltInChainKeys(moduleKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("resolve keys for %q: %v", moduleKey, err)
|
t.Fatalf("resolve keys for %q: %v", moduleKey, err)
|
||||||
@@ -213,7 +214,7 @@ func TestBuiltInChainKeysResolveForProductionModules(t *testing.T) {
|
|||||||
|
|
||||||
func TestResolveBuiltInChainUsesRegisteredKeys(t *testing.T) {
|
func TestResolveBuiltInChainUsesRegisteredKeys(t *testing.T) {
|
||||||
r := NewBuiltInRegistry()
|
r := NewBuiltInRegistry()
|
||||||
for _, moduleKey := range []string{"glossary", "homophones", "spoken_word", "grammar"} {
|
for _, moduleKey := range modulecatalog.SupportedKeys() {
|
||||||
chain, err := ResolveBuiltInChain(moduleKey, r)
|
chain, err := ResolveBuiltInChain(moduleKey, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("resolve chain for %q: %v", moduleKey, err)
|
t.Fatalf("resolve chain for %q: %v", moduleKey, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user