Audita API key credentials are now optional
This commit is contained in:
@@ -26,7 +26,7 @@ Seriatim config contract in `pipeline.yml`:
|
|||||||
Audita config contract in `pipeline.yml`:
|
Audita config contract in `pipeline.yml`:
|
||||||
|
|
||||||
- required: `audita.binary` (name or path; existence is checked at execution time, not config validation time)
|
- required: `audita.binary` (name or path; existence is checked at execution time, not config validation time)
|
||||||
- required: `audita.llm_api_key_env` (environment variable name holding the API key secret)
|
- optional: `audita.llm_api_key_env` (environment variable name holding the API key secret; defaults to `AUDITA_LLM_API_KEY`)
|
||||||
- defaulted when omitted: `audita.timeout` (`3h`), `audita.llm_api_key_env` (`AUDITA_LLM_API_KEY`), `audita.modules` (`glossary,homophones,glossary,spoken_word,grammar,homophones,glossary`), `audita.base_url` (`https://openrouter.ai/api/v1`), `audita.model` (`openrouter/google/gemma-4-31b-it`), `audita.llm_concurrency` (`1`), `audita.validation_model` (`""`), `audita.validation_llm_concurrency` (`1`), `audita.report` (`true`)
|
- defaulted when omitted: `audita.timeout` (`3h`), `audita.llm_api_key_env` (`AUDITA_LLM_API_KEY`), `audita.modules` (`glossary,homophones,glossary,spoken_word,grammar,homophones,glossary`), `audita.base_url` (`https://openrouter.ai/api/v1`), `audita.model` (`openrouter/google/gemma-4-31b-it`), `audita.llm_concurrency` (`1`), `audita.validation_model` (`""`), `audita.validation_llm_concurrency` (`1`), `audita.report` (`true`)
|
||||||
- allowed `audita.modules` values: `glossary`, `homophones`, `spoken_word`, `grammar` (order and repeats are allowed)
|
- allowed `audita.modules` values: `glossary`, `homophones`, `spoken_word`, `grammar` (order and repeats are allowed)
|
||||||
- `audita.base_url` must be a valid URL when provided
|
- `audita.base_url` must be a valid URL when provided
|
||||||
@@ -36,6 +36,7 @@ Audita credentials note:
|
|||||||
|
|
||||||
- store only the environment variable **name** in config (`audita.llm_api_key_env`), never the API key value itself
|
- store only the environment variable **name** in config (`audita.llm_api_key_env`), never the API key value itself
|
||||||
- API key values must not be written to pipeline config, generated configs, logs, or manifest metadata
|
- API key values must not be written to pipeline config, generated configs, logs, or manifest metadata
|
||||||
|
- if the named env var is not set (or is empty), Narratio omits `AUDITA_LLM_API_KEY` from the Audita subprocess environment instead of failing
|
||||||
|
|
||||||
Audita runtime note:
|
Audita runtime note:
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ Audita config keys:
|
|||||||
|
|
||||||
- `pipeline.audita.binary` (required)
|
- `pipeline.audita.binary` (required)
|
||||||
- `pipeline.audita.timeout` (default: `3h`)
|
- `pipeline.audita.timeout` (default: `3h`)
|
||||||
- `pipeline.audita.llm_api_key_env` (required; default: `AUDITA_LLM_API_KEY`)
|
- `pipeline.audita.llm_api_key_env` (optional; default: `AUDITA_LLM_API_KEY`)
|
||||||
- `pipeline.audita.modules` (default sequence: `glossary,homophones,glossary,spoken_word,grammar,homophones,glossary`)
|
- `pipeline.audita.modules` (default sequence: `glossary,homophones,glossary,spoken_word,grammar,homophones,glossary`)
|
||||||
- `pipeline.audita.base_url` (default: `https://openrouter.ai/api/v1`)
|
- `pipeline.audita.base_url` (default: `https://openrouter.ai/api/v1`)
|
||||||
- `pipeline.audita.model` (default: `openrouter/google/gemma-4-31b-it`)
|
- `pipeline.audita.model` (default: `openrouter/google/gemma-4-31b-it`)
|
||||||
@@ -145,6 +145,7 @@ Audita secret-handling policy:
|
|||||||
|
|
||||||
- `llm_api_key_env` stores only the environment variable **name**.
|
- `llm_api_key_env` stores only the environment variable **name**.
|
||||||
- API key values are read from the process environment at runtime and are not stored in `pipeline.yml`, manifest metadata, generated configs, or logs.
|
- API key values are read from the process environment at runtime and are not stored in `pipeline.yml`, manifest metadata, generated configs, or logs.
|
||||||
|
- If the named env var is missing/empty, the Audita adapter omits `AUDITA_LLM_API_KEY` from subprocess env overrides and continues.
|
||||||
|
|
||||||
Validation currently enforces:
|
Validation currently enforces:
|
||||||
|
|
||||||
@@ -160,7 +161,6 @@ Validation currently enforces:
|
|||||||
- optional `pipeline.seriatim.env.*` values must be `> 0` when provided.
|
- optional `pipeline.seriatim.env.*` values must be `> 0` when provided.
|
||||||
- `pipeline.audita.binary` is required.
|
- `pipeline.audita.binary` is required.
|
||||||
- `pipeline.audita.timeout` must parse as Go duration.
|
- `pipeline.audita.timeout` must parse as Go duration.
|
||||||
- `pipeline.audita.llm_api_key_env` is required.
|
|
||||||
- `pipeline.audita.modules` must be non-empty and each entry must be one of `glossary|homophones|spoken_word|grammar`.
|
- `pipeline.audita.modules` must be non-empty and each entry must be one of `glossary|homophones|spoken_word|grammar`.
|
||||||
- `pipeline.audita.base_url` must be a valid URL when provided.
|
- `pipeline.audita.base_url` must be a valid URL when provided.
|
||||||
- `pipeline.audita.model` is required.
|
- `pipeline.audita.model` is required.
|
||||||
|
|||||||
@@ -83,9 +83,6 @@ func NewSubprocessRunner(cfg SubprocessRunnerConfig) (*SubprocessRunner, error)
|
|||||||
if cfg.Timeout <= 0 {
|
if cfg.Timeout <= 0 {
|
||||||
return nil, fmt.Errorf("audita timeout must be > 0")
|
return nil, fmt.Errorf("audita timeout must be > 0")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.LLMAPIKeyEnv) == "" {
|
|
||||||
return nil, fmt.Errorf("audita llm api key env var name is required")
|
|
||||||
}
|
|
||||||
if len(cfg.Modules) == 0 {
|
if len(cfg.Modules) == 0 {
|
||||||
return nil, fmt.Errorf("audita modules must include at least one module")
|
return nil, fmt.Errorf("audita modules must include at least one module")
|
||||||
}
|
}
|
||||||
@@ -160,13 +157,14 @@ func (r *SubprocessRunner) Run(ctx context.Context, req PolishRequest) (PolishRe
|
|||||||
}
|
}
|
||||||
args := r.buildArgs(req, reqModules)
|
args := r.buildArgs(req, reqModules)
|
||||||
|
|
||||||
credential, credentialPresent := os.LookupEnv(r.llmAPIKeyEnv)
|
credentialPresent := false
|
||||||
if !credentialPresent || strings.TrimSpace(credential) == "" {
|
env := map[string]string{}
|
||||||
return PolishResult{}, fmt.Errorf("audita credential env var %q is required but not set", r.llmAPIKeyEnv)
|
if strings.TrimSpace(r.llmAPIKeyEnv) != "" {
|
||||||
}
|
credential, ok := os.LookupEnv(r.llmAPIKeyEnv)
|
||||||
|
if ok && strings.TrimSpace(credential) != "" {
|
||||||
env := map[string]string{
|
env["AUDITA_LLM_API_KEY"] = credential
|
||||||
"AUDITA_LLM_API_KEY": credential,
|
credentialPresent = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
primaryConcurrencyViaEnv := false
|
primaryConcurrencyViaEnv := false
|
||||||
if r.llmConcurrency != nil {
|
if r.llmConcurrency != nil {
|
||||||
|
|||||||
@@ -120,33 +120,38 @@ func TestSubprocessRunnerSuccessArgsEnvAndValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubprocessRunnerMissingCredentialFails(t *testing.T) {
|
func TestSubprocessRunnerMissingCredentialEnvOmitsCredential(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)
|
||||||
|
|
||||||
llmConcurrency := 1
|
llmConcurrency := 1
|
||||||
runner, err := NewSubprocessRunner(SubprocessRunnerConfig{
|
runner := mustAuditaRunner(t, SubprocessRunnerConfig{
|
||||||
Binary: "audita",
|
Binary: writeAuditaHelperWrapper(t),
|
||||||
Timeout: mustParseAuditaDuration(t, "1s"),
|
Timeout: mustParseAuditaDuration(t, "2s"),
|
||||||
LLMAPIKeyEnv: "MISSING_AUDITA_KEY",
|
LLMAPIKeyEnv: "MISSING_AUDITA_KEY",
|
||||||
Modules: []string{"glossary"},
|
Modules: []string{"glossary"},
|
||||||
BaseURL: "https://openrouter.ai/api/v1",
|
BaseURL: "https://openrouter.ai/api/v1",
|
||||||
Model: "openrouter/google/gemma-4-31b-it",
|
Model: "openrouter/google/gemma-4-31b-it",
|
||||||
LLMConcurrency: &llmConcurrency,
|
LLMConcurrency: &llmConcurrency,
|
||||||
|
Report: false,
|
||||||
})
|
})
|
||||||
if err != nil {
|
req := auditaReqForTest(t, false)
|
||||||
t.Fatalf("NewSubprocessRunner() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := PolishRequest{
|
res, err := runner.Run(context.Background(), req)
|
||||||
MergedTranscriptPath: "/tmp/merged.json",
|
if err != nil {
|
||||||
GlossaryPath: "/tmp/glossary.yml",
|
t.Fatalf("Run() error = %v", err)
|
||||||
OutputProcessedPath: "/tmp/processed.json",
|
|
||||||
WorkDir: "/tmp/audita-work",
|
|
||||||
}
|
}
|
||||||
_, err = runner.Run(context.Background(), req)
|
if res.Metadata["credential_present"] != false {
|
||||||
if err == nil {
|
t.Fatalf("credential_present = %#v, want false", res.Metadata["credential_present"])
|
||||||
t.Fatal("expected error, got nil")
|
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "MISSING_AUDITA_KEY") {
|
rec := readAuditaHelperRecord(t, recordPath)
|
||||||
t.Fatalf("error = %q, want env var name context", err.Error())
|
if rec.Env["AUDITA_LLM_API_KEY"] != "" {
|
||||||
|
t.Fatalf("AUDITA_LLM_API_KEY = %q, want omitted/empty", rec.Env["AUDITA_LLM_API_KEY"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,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.LLMAPIKeyEnv) == "" || len(a.Modules) == 0 || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" {
|
if strings.TrimSpace(a.Binary) == "" || strings.TrimSpace(a.Timeout) == "" || len(a.Modules) == 0 || strings.TrimSpace(a.BaseURL) == "" || strings.TrimSpace(a.Model) == "" {
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,27 +412,6 @@ inputs:
|
|||||||
`,
|
`,
|
||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.timeout must be a valid duration",
|
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.timeout must be a valid duration",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "empty audita llm_api_key_env fails",
|
|
||||||
pipelineYAML: `workspace:
|
|
||||||
root: /tmp/narratio
|
|
||||||
whisperx:
|
|
||||||
transcribe_url: https://transcription.ai.rakestrawhome.com/transcribe
|
|
||||||
seriatim:
|
|
||||||
binary: seriatim
|
|
||||||
audita:
|
|
||||||
binary: audita
|
|
||||||
llm_api_key_env: " "
|
|
||||||
`,
|
|
||||||
sessionYAML: `session_id: 2026-05-03
|
|
||||||
inputs:
|
|
||||||
audio_dir: ./audio
|
|
||||||
speakers_file: ./speakers.yml
|
|
||||||
autocorrect_file: ./autocorrect.yml
|
|
||||||
glossary_file: ./glossary.yml
|
|
||||||
`,
|
|
||||||
wantValidate: "pipeline config \"pipeline.yml\" invalid: pipeline.audita.llm_api_key_env is required",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "empty audita modules fails",
|
name: "empty audita modules fails",
|
||||||
pipelineYAML: `workspace:
|
pipelineYAML: `workspace:
|
||||||
|
|||||||
@@ -129,9 +129,6 @@ func validateAudita(cfg AuditaConfig) error {
|
|||||||
if err := validateDuration("pipeline.audita.timeout", cfg.Timeout); err != nil {
|
if err := validateDuration("pipeline.audita.timeout", cfg.Timeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(cfg.LLMAPIKeyEnv) == "" {
|
|
||||||
return fmt.Errorf("pipeline.audita.llm_api_key_env is required")
|
|
||||||
}
|
|
||||||
if len(cfg.Modules) == 0 {
|
if len(cfg.Modules) == 0 {
|
||||||
return fmt.Errorf("pipeline.audita.modules must include at least one module")
|
return fmt.Errorf("pipeline.audita.modules must include at least one module")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user