Simplify downstream tool configuration
All checks were successful
ci/woodpecker/tag/release Pipeline was successful

This commit is contained in:
2026-05-16 23:30:40 +00:00
parent d5a9ad38f8
commit 33f7ae8f2e
12 changed files with 223 additions and 139 deletions

View File

@@ -162,21 +162,40 @@ Archive run-upload details and boundaries are documented in [docs/archive-storag
- `transcripts/normalized.json`: Seriatim-normalized transcript from the normalize stage - `transcripts/normalized.json`: Seriatim-normalized transcript from the normalize stage
- `transcripts/trimmed.json`: gameplay-only normalized polished transcript from trim stage - `transcripts/trimmed.json`: gameplay-only normalized polished transcript from trim stage
## Seriatim Configuration
`pipeline.seriatim` configures the Seriatim subprocess adapter used by `merge`, `normalize`, and `trim`.
Minimal behavior:
- `pipeline.seriatim` may be omitted entirely.
- when omitted, Narratio defaults to:
- `binary: seriatim`
- `timeout: 10m`
- `output_schema: seriatim-intermediate`
- `coalesce_gap: 3.0`
- `report: true`
Optional overrides in `pipeline.seriatim` continue to work, including explicit binary paths and advanced `env` tuning values.
## Audita Configuration ## Audita Configuration
`pipeline.audita` configures the real Audita subprocess adapter used by `polish`. `pipeline.audita` configures the real Audita subprocess adapter used by `polish`.
Required: Minimal behavior:
- `binary` - `pipeline.audita` may be omitted entirely.
- `timeout` - when omitted, Narratio defaults to:
- `base_url` - `binary: audita`
- `model` - `timeout: 3h`
- `report: true`
Optional: Optional:
- `llm_api_key_env` (when set, Narratio requires that env var and passes it to Audita as `AUDITA_LLM_API_KEY`) - `llm_api_key_env` (when set, Narratio requires that env var and passes it to Audita as `AUDITA_LLM_API_KEY`)
- `modules` override list (when empty/omitted, Narratio does not pass `--modules`) - `modules` override list (when empty/omitted, Narratio does not pass `--modules`)
- `base_url` (when omitted, Narratio does not pass `--base-url`; Audita runtime defaults/config may apply)
- `model` (when omitted, Narratio does not pass `--model`; Audita runtime defaults/config may apply)
- `transcript_description` - `transcript_description`
- `config_path` - `config_path`
- `output_schema` (`bare-segments` or `audita-v1`) - `output_schema` (`bare-segments` or `audita-v1`)
@@ -187,7 +206,7 @@ Optional:
- `validation_llm_concurrency` (> 0 when provided) - `validation_llm_concurrency` (> 0 when provided)
- `report` (defaults to `true`) - `report` (defaults to `true`)
Narratio passes only configured optional Audita flags. Omitted optional values are left to Audita runtime defaults. Narratio passes only configured optional Audita flags. Omitted optional values are left to Audita runtime defaults/config.
## Normalize Configuration ## Normalize Configuration
@@ -282,7 +301,7 @@ Render-debug files are diagnostics and are not treated as canonical stage output
Key points: Key points:
- `scriptorium.binary` is required when section is present - `scriptorium.binary` defaults to `scriptorium` when section is present
- `scriptorium.config_path` is optional - `scriptorium.config_path` is optional
- `scriptorium.timeout` defaults to `10m` when omitted - `scriptorium.timeout` defaults to `10m` when omitted
- `scriptorium.render_debug` enables render diagnostics globally - `scriptorium.render_debug` enables render diagnostics globally
@@ -370,6 +389,7 @@ Expected session output paths:
Starter files: Starter files:
- `examples/pipeline.minimal.yml` - `examples/pipeline.minimal.yml`
- `examples/pipeline.audita-overrides.yml`
- `examples/session.minimal.yml` - `examples/session.minimal.yml`
- `examples/session.template.yml` - `examples/session.template.yml`
- `examples/speakers.yml` - `examples/speakers.yml`

View File

@@ -247,17 +247,18 @@ Archive publishing behavior (implemented):
`pipeline.audita` drives the real Audita subprocess adapter for the `polish` stage. `pipeline.audita` drives the real Audita subprocess adapter for the `polish` stage.
Audita required fields: Audita defaulted fields:
- `binary` - `binary` defaults to `audita`
- `timeout` - `timeout` defaults to `3h`
- `base_url` - `report` defaults to `true`
- `model`
Audita optional fields: Audita optional fields:
- `llm_api_key_env` (enforced only when configured) - `llm_api_key_env` (enforced only when configured)
- `modules` override list (when omitted/empty, Narratio does not pass `--modules`) - `modules` override list (when omitted/empty, Narratio does not pass `--modules`)
- `base_url` (when omitted, Narratio does not pass `--base-url`)
- `model` (when omitted, Narratio does not pass `--model`)
- `transcript_description` - `transcript_description`
- `config_path` - `config_path`
- `output_schema` (`bare-segments` or `audita-v1`) - `output_schema` (`bare-segments` or `audita-v1`)
@@ -266,9 +267,18 @@ Audita optional fields:
- `proposal_llm_concurrency` (> 0 when provided) - `proposal_llm_concurrency` (> 0 when provided)
- `validation_model` - `validation_model`
- `validation_llm_concurrency` (> 0 when provided) - `validation_llm_concurrency` (> 0 when provided)
- `report` (default `true`) - `report` override
Narratio passes only configured optional Audita flags; omitted optional values defer to Audita defaults. Narratio passes only configured optional Audita flags; omitted optional values defer to Audita runtime defaults/config.
Seriatim defaults:
- `pipeline.seriatim` may be omitted
- `binary` defaults to `seriatim`
- `timeout` defaults to `10m`
- `output_schema` defaults to `seriatim-intermediate`
- `coalesce_gap` defaults to `3.0`
- `report` defaults to `true`
When `pipeline.normalize` is omitted, defaults are applied: When `pipeline.normalize` is omitted, defaults are applied:
@@ -302,7 +312,7 @@ When `pipeline.trim.enabled: true`:
When `pipeline.scriptorium` is present: When `pipeline.scriptorium` is present:
- `binary` is required and non-empty - `binary` defaults to `scriptorium` when omitted
- `config_path` is optional; when provided it must be non-empty - `config_path` is optional; when provided it must be non-empty
- `timeout` is optional; when provided it must parse as a Go duration - `timeout` is optional; when provided it must parse as a Go duration
- default `timeout` is `10m` - default `timeout` is `10m`

View File

@@ -0,0 +1,26 @@
workspace:
root: ./tmp/narratio-workspace
whisperx:
transcribe_url: "https://transcription.example.com/transcribe"
seriatim:
binary: "seriatim"
timeout: "10m"
output_schema: "seriatim-intermediate"
coalesce_gap: 3.0
audita:
binary: "audita"
timeout: "3h"
base_url: "https://openrouter.ai/api/v1"
model: "openrouter/google/gemma-4-31b-it"
llm_api_key_env: "AUDITA_LLM_API_KEY"
modules: ["glossary", "homophones", "spoken_word", "grammar"]
output_schema: "audita-v1"
work_dir_retention: "auto"
total_llm_concurrency: 2
proposal_llm_concurrency: 1
validation_model: "openrouter/google/gemma-4-31b-it"
validation_llm_concurrency: 1
report: true

View File

@@ -3,13 +3,11 @@ workspace:
cleanup_after_archive: false cleanup_after_archive: false
storage: storage:
backend: local backend: s3
s3: s3:
bucket: "my-dnd-archive" bucket: "my-dnd-archive"
root_prefix: "dnd" root_prefix: "dnd"
region: "us-east-1" region: "us-east-1"
endpoint: ""
force_path_style: false
spool: spool:
root: "/var/spool/narratio" root: "/var/spool/narratio"
@@ -18,106 +16,33 @@ spool:
archive: archive:
enabled: true enabled: true
upload_run: true upload_run: true
promote_artifacts:
- from: "transcripts/trimmed.json"
to: "transcripts/trimmed.json"
required: true
- from: "artifacts/session_recap.md"
to: "artifacts/session_recap.md"
required: true
secrets:
# Optional: load environment variables from files in this directory.
# File name = env var name; file contents = env var value.
env_dir: /var/local/narratio/secrets
whisperx: whisperx:
transcribe_url: "https://transcription.example.com/transcribe" transcribe_url: "https://transcription.example.com/transcribe"
language: "en"
timeout: "30m"
retries: 3
retry_delay: "2s"
concurrency: 2
seriatim: # Optional. When omitted entirely, Narratio defaults to seriatim binary + runtime defaults.
binary: "seriatim" seriatim: {}
timeout: "10m"
output_schema: "seriatim-intermediate"
coalesce_gap: 3.0
report: true
env:
overlap_word_run_gap: 1.0
overlap_word_run_reorder_window: 1.0
backchannel_max_duration: 2.0
filler_max_duration: 1.25
# Optional runtime overrides. Model/provider can be owned by Audita runtime config.
audita: audita:
binary: "audita" config_path: "/usr/local/etc/audita/config.yml"
timeout: "3h"
llm_api_key_env: "AUDITA_LLM_API_KEY" llm_api_key_env: "AUDITA_LLM_API_KEY"
# Optional: pass only when overriding Audita's default module sequence.
modules: []
base_url: "https://openrouter.ai/api/v1"
model: "openrouter/google/gemma-4-31b-it"
transcript_description: ""
config_path: ""
output_schema: "audita-v1"
work_dir_retention: "auto"
total_llm_concurrency: 1
proposal_llm_concurrency: 1
validation_model: ""
validation_llm_concurrency: 1
report: true
normalize:
# Session-workdir-relative when not absolute.
output_path: "transcripts/normalized.json"
output_schema: "seriatim-intermediate"
report: true
trim:
enabled: true
# Session-workdir-relative when not absolute.
output_path: "transcripts/trimmed.json"
bounds:
prompt_id: "dnd_session.bounds"
# Empty means use prompt default profile.
profile_id: ""
transcript_input_name: "transcript"
output_path: "artifacts/session_bounds.json"
timeout: "10m"
render_debug: false
render_output_path: "artifacts/session_bounds.render.json"
seriatim:
report: false
# Optional Scriptorium integration for analyze artifacts.
scriptorium: scriptorium:
binary: "scriptorium" config_path: "/usr/local/etc/scriptorium/config.yml"
config_path: "/etc/scriptorium/config.yml"
timeout: "10m"
render_debug: false
artifacts: artifacts:
session_recap: session_recap:
enabled: true enabled: true
prompt_id: "dnd.session_recap" prompt_id: "dnd.session_recap"
profile_id: "local-quality"
output_path: "artifacts/session_recap.md" output_path: "artifacts/session_recap.md"
timeout: "10m"
# Optional per-artifact override of global scriptorium.render_debug.
# render_debug: true
inputs: inputs:
transcript: transcript:
# Available transcript sources:
# - trimmed_transcript (recommended for session_recap)
# - normalized_transcript (recommended for future full-session analysis)
# - processed_transcript (raw Audita-polished output)
source: "trimmed_transcript" source: "trimmed_transcript"
required: true required: true
previous_recap: previous_recap:
source: "previous_session_artifact" source: "previous_session_artifact"
artifact: "session_recap" artifact: "session_recap"
# Optional: set when previous recap is available.
path: ""
required: false required: false
vars: vars:
session_id: true session_id: true
@@ -125,11 +50,3 @@ scriptorium:
campaign_name: true campaign_name: true
previous_session_id: true previous_session_id: true
output_kind: "session_recap" output_kind: "session_recap"
analyzer:
timeout: 20m
artifacts:
output_dir: artifacts
notification:
timeout: 10s

View File

@@ -108,18 +108,14 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
return nil, fmt.Errorf("audita module at index %d is empty", i) return nil, fmt.Errorf("audita module at index %d is empty", i)
} }
} }
if strings.TrimSpace(cfg.BaseURL) == "" { if strings.TrimSpace(cfg.BaseURL) != "" {
return nil, fmt.Errorf("audita base url is required") u, err := url.Parse(cfg.BaseURL)
} if err != nil || u.Scheme == "" || u.Host == "" {
u, err := url.Parse(cfg.BaseURL) if err != nil {
if err != nil || u.Scheme == "" || u.Host == "" { return nil, fmt.Errorf("audita base url %q is invalid: %w", cfg.BaseURL, err)
if err != nil { }
return nil, fmt.Errorf("audita base url %q is invalid: %w", cfg.BaseURL, err) return nil, fmt.Errorf("audita base url %q is invalid", cfg.BaseURL)
} }
return nil, fmt.Errorf("audita base url %q is invalid", cfg.BaseURL)
}
if strings.TrimSpace(cfg.Model) == "" {
return nil, fmt.Errorf("audita model is required")
} }
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 { if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
return nil, fmt.Errorf("audita total llm concurrency must be > 0 when provided") return nil, fmt.Errorf("audita total llm concurrency must be > 0 when provided")
@@ -307,10 +303,14 @@ func (r *SubprocessRunner) buildArgs(req PolishRequest, modules []string) []stri
req.MergedTranscriptPath, req.MergedTranscriptPath,
"--glossary", req.GlossaryPath, "--glossary", req.GlossaryPath,
"--output", req.OutputProcessedPath, "--output", req.OutputProcessedPath,
"--base-url", r.baseURL,
"--model", r.model,
"--work-dir", req.WorkDir, "--work-dir", req.WorkDir,
} }
if r.baseURL != "" {
args = append(args, "--base-url", r.baseURL)
}
if r.model != "" {
args = append(args, "--model", r.model)
}
if len(modules) > 0 { if len(modules) > 0 {
args = append(args, "--modules", strings.Join(modules, ",")) args = append(args, "--modules", strings.Join(modules, ","))
} }

View File

@@ -99,9 +99,9 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
"process", req.MergedTranscriptPath, "process", req.MergedTranscriptPath,
"--glossary", req.GlossaryPath, "--glossary", req.GlossaryPath,
"--output", req.OutputProcessedPath, "--output", req.OutputProcessedPath,
"--work-dir", req.WorkDir,
"--base-url", "https://openrouter.ai/api/v1", "--base-url", "https://openrouter.ai/api/v1",
"--model", "openrouter/google/gemma-4-31b-it", "--model", "openrouter/google/gemma-4-31b-it",
"--work-dir", req.WorkDir,
"--modules", "glossary,homophones,glossary", "--modules", "glossary,homophones,glossary",
"--report-json", req.ReportPath, "--report-json", req.ReportPath,
"--transcript-description", "Campaign Session 42", "--transcript-description", "Campaign Session 42",
@@ -248,6 +248,36 @@ func TestSubprocessRunnerOmitsModulesFlagWhenNotConfigured(t *testing.T) {
} }
} }
func TestSubprocessRunnerOmitsBaseURLAndModelFlagsWhenNotConfigured(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("helper wrapper script uses /bin/sh")
}
t.Setenv("GO_WANT_AUDITA_HELPER", "1")
t.Setenv("AUDITA_HELPER_MODE", "success")
recordPath := filepath.Join(t.TempDir(), "record.json")
t.Setenv("AUDITA_HELPER_RECORD_PATH", recordPath)
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
Binary: writeAuditaHelperWrapper(t),
Timeout: mustParseAuditaDuration(t, "2s"),
LLMAPIKeyEnv: "",
Report: false,
})
req := auditaReqForTest(t, false)
if _, err := runner.Run(context.Background(), req); err != nil {
t.Fatalf("Run() error = %v", err)
}
rec := readAuditaHelperRecord(t, recordPath)
for i := 0; i < len(rec.Args); i++ {
if rec.Args[i] == "--base-url" {
t.Fatalf("args contained --base-url unexpectedly: %#v", rec.Args)
}
if rec.Args[i] == "--model" {
t.Fatalf("args contained --model unexpectedly: %#v", rec.Args)
}
}
}
func TestSubprocessRunnerSubprocessFailure(t *testing.T) { func TestSubprocessRunnerSubprocessFailure(t *testing.T) {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
t.Skip("helper wrapper script uses /bin/sh") t.Skip("helper wrapper script uses /bin/sh")

View File

@@ -250,7 +250,7 @@ func buildDefaultAuditaRunner(cfg *config.Config) (audita.Runner, error) {
} }
a := cfg.Pipeline.Audita a := cfg.Pipeline.Audita
if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" { if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" {
// Compatibility fallback for tests or internal call paths that bypass config validation/defaults. // Compatibility fallback for tests or internal call paths that bypass config validation/defaults.
return &audita.NoopRunner{}, nil return &audita.NoopRunner{}, nil
} }

View File

@@ -497,6 +497,55 @@ func testConfig(t *testing.T) *config.Config {
} }
} }
func TestBuildDefaultRunnersWithOmittedToolSections(t *testing.T) {
dir := t.TempDir()
pipelinePath := filepath.Join(dir, "pipeline.yml")
sessionPath := filepath.Join(dir, "session.yml")
pipelineYAML := `workspace:
root: ` + t.TempDir() + `
whisperx:
transcribe_url: https://example.com/transcribe
analyzer:
timeout: 20m
notification:
timeout: 10s
`
sessionYAML := `session_id: 2026-05-03
campaign: sample-campaign
inputs:
audio_dir: ./audio
speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`
mustWriteFile(t, pipelinePath, pipelineYAML)
mustWriteFile(t, sessionPath, sessionYAML)
cfg, err := config.Load(pipelinePath, sessionPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if err := config.Validate(cfg); err != nil {
t.Fatalf("Validate() error = %v", err)
}
serRunner, err := buildDefaultSeriatimRunner(cfg)
if err != nil {
t.Fatalf("buildDefaultSeriatimRunner() error = %v", err)
}
if _, ok := serRunner.(*seriatim.SubprocessRunner); !ok {
t.Fatalf("seriatim runner type = %T, want *seriatim.SubprocessRunner", serRunner)
}
audRunner, err := buildDefaultAuditaRunner(cfg)
if err != nil {
t.Fatalf("buildDefaultAuditaRunner() error = %v", err)
}
if _, ok := audRunner.(*audita.SubprocessRunner); !ok {
t.Fatalf("audita runner type = %T, want *audita.SubprocessRunner", audRunner)
}
}
func mustWriteFile(t *testing.T, path, contents string) { func mustWriteFile(t *testing.T, path, contents string) {
t.Helper() t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {

View File

@@ -244,6 +244,9 @@ func applySeriatimDefaults(cfg *SeriatimConfig) {
if cfg == nil { if cfg == nil {
return return
} }
if cfg.Binary == "" {
cfg.Binary = "seriatim"
}
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "10m" cfg.Timeout = "10m"
} }
@@ -262,15 +265,12 @@ func applyAuditaDefaults(cfg *AuditaConfig) {
if cfg == nil { if cfg == nil {
return return
} }
if cfg.Binary == "" {
cfg.Binary = "audita"
}
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "3h" cfg.Timeout = "3h"
} }
if cfg.BaseURL == "" {
cfg.BaseURL = "https://openrouter.ai/api/v1"
}
if cfg.Model == "" {
cfg.Model = "openrouter/google/gemma-4-31b-it"
}
if cfg.Report == nil { if cfg.Report == nil {
cfg.Report = boolPtr(true) cfg.Report = boolPtr(true)
} }
@@ -280,6 +280,9 @@ func applyScriptoriumDefaults(cfg *ScriptoriumConfig) {
if cfg == nil { if cfg == nil {
return return
} }
if cfg.Binary == "" {
cfg.Binary = "scriptorium"
}
if cfg.Timeout == "" { if cfg.Timeout == "" {
cfg.Timeout = "10m" cfg.Timeout = "10m"
} }

View File

@@ -37,6 +37,26 @@ inputs:
speakers_file: ./speakers.yml speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml glossary_file: ./glossary.yml
`,
checkDefault: true,
},
{
name: "seriatim and audita sections can be omitted",
pipelineYAML: `workspace:
root: /tmp/narratio
whisperx:
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
analyzer:
timeout: 20m
notification:
timeout: 15s
`,
sessionYAML: `session_id: 2026-05-03
inputs:
audio_dir: ./audio
speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
`, `,
checkDefault: true, checkDefault: true,
}, },
@@ -296,7 +316,7 @@ inputs:
wantLoadErr: "strict decode failed", wantLoadErr: "strict decode failed",
}, },
{ {
name: "missing seriatim binary fails", name: "missing seriatim binary uses default",
pipelineYAML: `workspace: pipelineYAML: `workspace:
root: /tmp/narratio root: /tmp/narratio
whisperx: whisperx:
@@ -311,7 +331,6 @@ inputs:
autocorrect_file: ./autocorrect.yml autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml glossary_file: ./glossary.yml
`, `,
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.seriatim.binary is required",
}, },
{ {
name: "invalid seriatim timeout fails", name: "invalid seriatim timeout fails",
@@ -412,7 +431,7 @@ inputs:
wantLoadErr: "strict decode failed", wantLoadErr: "strict decode failed",
}, },
{ {
name: "missing audita binary fails", name: "missing audita binary uses default",
pipelineYAML: `workspace: pipelineYAML: `workspace:
root: /tmp/narratio root: /tmp/narratio
whisperx: whisperx:
@@ -429,7 +448,6 @@ inputs:
autocorrect_file: ./autocorrect.yml autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml glossary_file: ./glossary.yml
`, `,
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.binary is required",
}, },
{ {
name: "invalid audita timeout fails", name: "invalid audita timeout fails",
@@ -713,6 +731,9 @@ inputs:
if cfg.Pipeline.Seriatim.Timeout != "10m" { if cfg.Pipeline.Seriatim.Timeout != "10m" {
t.Fatalf("seriatim.timeout = %q, want %q", cfg.Pipeline.Seriatim.Timeout, "10m") t.Fatalf("seriatim.timeout = %q, want %q", cfg.Pipeline.Seriatim.Timeout, "10m")
} }
if cfg.Pipeline.Seriatim.Binary != "seriatim" {
t.Fatalf("seriatim.binary = %q, want %q", cfg.Pipeline.Seriatim.Binary, "seriatim")
}
if cfg.Pipeline.Seriatim.OutputSchema != "seriatim-intermediate" { if cfg.Pipeline.Seriatim.OutputSchema != "seriatim-intermediate" {
t.Fatalf("seriatim.output_schema = %q, want %q", cfg.Pipeline.Seriatim.OutputSchema, "seriatim-intermediate") t.Fatalf("seriatim.output_schema = %q, want %q", cfg.Pipeline.Seriatim.OutputSchema, "seriatim-intermediate")
} }
@@ -725,17 +746,20 @@ inputs:
if cfg.Pipeline.Audita.Timeout != "3h" { if cfg.Pipeline.Audita.Timeout != "3h" {
t.Fatalf("audita.timeout = %q, want %q", cfg.Pipeline.Audita.Timeout, "3h") t.Fatalf("audita.timeout = %q, want %q", cfg.Pipeline.Audita.Timeout, "3h")
} }
if cfg.Pipeline.Audita.Binary != "audita" {
t.Fatalf("audita.binary = %q, want %q", cfg.Pipeline.Audita.Binary, "audita")
}
if cfg.Pipeline.Audita.LLMAPIKeyEnv != "" { if cfg.Pipeline.Audita.LLMAPIKeyEnv != "" {
t.Fatalf("audita.llm_api_key_env = %q, want empty by default", cfg.Pipeline.Audita.LLMAPIKeyEnv) t.Fatalf("audita.llm_api_key_env = %q, want empty by default", cfg.Pipeline.Audita.LLMAPIKeyEnv)
} }
if cfg.Pipeline.Audita.Modules != nil { if cfg.Pipeline.Audita.Modules != nil {
t.Fatalf("audita.modules = %#v, want nil default (optional override)", cfg.Pipeline.Audita.Modules) t.Fatalf("audita.modules = %#v, want nil default (optional override)", cfg.Pipeline.Audita.Modules)
} }
if cfg.Pipeline.Audita.BaseURL != "https://openrouter.ai/api/v1" { if cfg.Pipeline.Audita.BaseURL != "" {
t.Fatalf("audita.base_url = %q, want %q", cfg.Pipeline.Audita.BaseURL, "https://openrouter.ai/api/v1") t.Fatalf("audita.base_url = %q, want empty default", cfg.Pipeline.Audita.BaseURL)
} }
if cfg.Pipeline.Audita.Model != "openrouter/google/gemma-4-31b-it" { if cfg.Pipeline.Audita.Model != "" {
t.Fatalf("audita.model = %q, want %q", cfg.Pipeline.Audita.Model, "openrouter/google/gemma-4-31b-it") t.Fatalf("audita.model = %q, want empty default", cfg.Pipeline.Audita.Model)
} }
if cfg.Pipeline.Audita.ValidationModel != "" { if cfg.Pipeline.Audita.ValidationModel != "" {
t.Fatalf("audita.validation_model = %q, want empty default", cfg.Pipeline.Audita.ValidationModel) t.Fatalf("audita.validation_model = %q, want empty default", cfg.Pipeline.Audita.ValidationModel)

View File

@@ -49,11 +49,19 @@ func TestScriptoriumLoadAndValidate(t *testing.T) {
wantLoadErr: "strict decode failed", wantLoadErr: "strict decode failed",
}, },
{ {
name: "missing binary fails when section present", name: "missing binary defaults when section present",
scriptoriumYAML: `scriptorium: scriptoriumYAML: `scriptorium:
timeout: 10m timeout: 10m
`, `,
wantValidateErr: "pipeline.scriptorium.binary is required", assert: func(t *testing.T, cfg *Config) {
t.Helper()
if cfg.Pipeline.Scriptorium == nil {
t.Fatal("scriptorium config should be present")
}
if cfg.Pipeline.Scriptorium.Binary != "scriptorium" {
t.Fatalf("scriptorium.binary = %q, want scriptorium", cfg.Pipeline.Scriptorium.Binary)
}
},
}, },
{ {
name: "enabled artifact missing prompt id fails", name: "enabled artifact missing prompt id fails",

View File

@@ -276,9 +276,6 @@ func validateAudita(cfg AuditaConfig) error {
return fmt.Errorf("pipeline.audita.base_url must be a valid URL") return fmt.Errorf("pipeline.audita.base_url must be a valid URL")
} }
} }
if strings.TrimSpace(cfg.Model) == "" {
return fmt.Errorf("pipeline.audita.model is required")
}
if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 { if cfg.TotalLLMConcurrency != nil && *cfg.TotalLLMConcurrency <= 0 {
return fmt.Errorf("pipeline.audita.total_llm_concurrency must be > 0") return fmt.Errorf("pipeline.audita.total_llm_concurrency must be > 0")
} }