Resolve typed credentials safely

This commit is contained in:
2026-09-07 04:29:00 +00:00
parent d31ba5a410
commit 92432232ef
34 changed files with 572 additions and 260 deletions

View File

@@ -161,6 +161,10 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
if reportEnabled {
reportCanonicalPath = canonicalReportPath
}
credentialReference := ""
if env.Config.Pipeline.Audita.LLMAPIKey != nil {
credentialReference = env.Config.Pipeline.Audita.LLMAPIKey.EnvName()
}
meta := map[string]any{
"stage": "polish",
@@ -184,7 +188,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
"total_llm_concurrency": totalLLMConcurrency,
"proposal_llm_concurrency": proposalLLMConcurrency,
"validation_llm_concurrency": validationConcurrency,
"llm_api_key_env": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
"llm_api_key": credentialReference,
"timeout": env.Config.Pipeline.Audita.Timeout,
"binary": env.Config.Pipeline.Audita.Binary,
"generated_config_path": generatedConfigPath,
@@ -199,7 +203,7 @@ func (polishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*St
"adapter_work_dir": res.WorkDir,
"adapter_stdout_log_path": res.StdoutLogPath,
"adapter_stderr_log_path": res.StderrLogPath,
"credential_env_var": env.Config.Pipeline.Audita.LLMAPIKeyEnv,
"credential_env_var": credentialReference,
"credential_present": false,
}
if res.Metadata != nil {

View File

@@ -257,7 +257,7 @@ func setupPolishEnv(t *testing.T) (*Env, *manifest.Manifest) {
Audita: config.AuditaConfig{
Binary: "audita",
Timeout: "3h",
LLMAPIKeyEnv: "AUDITA_LLM_API_KEY",
LLMAPIKey: testSecretReference("AUDITA_LLM_API_KEY"),
Modules: []string{"glossary", "homophones", "grammar"},
BaseURL: "https://openrouter.ai/api/v1",
Model: "openrouter/google/gemma-4-31b-it",

View File

@@ -18,7 +18,6 @@ import (
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
"gopkg.in/yaml.v3"
)
type prepareStage struct{}
@@ -277,7 +276,7 @@ func (prepareStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
}
func renderResolvedPipeline(cfg *config.PipelineConfig) ([]byte, error) {
return yaml.Marshal(cfg)
return config.MarshalEffectivePipeline(cfg)
}
func stableInputSource(resolved config.ResolvedInputFile, fallbackPath, fallbackConfigPath string) config.ResolvedInputFile {

View File

@@ -4,10 +4,16 @@ import (
"context"
"testing"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
func testSecretReference(name string) *configkit.Secret {
reference := configkit.SecretEnv(name)
return &reference
}
func TestExtractSemanticConfigSensitivityAndOperationalExclusions(t *testing.T) {
baseline := downstreamSemanticEnv()
want, err := (extractStage{}).SemanticConfigFingerprint(baseline)
@@ -121,8 +127,8 @@ func TestPublishSemanticConfigSensitivityAndOperationalExclusions(t *testing.T)
}
operational := downstreamSemanticEnv()
operational.Config.Pipeline.Storage.S3.AccessKeyIDEnv = "OTHER_ACCESS_KEY"
operational.Config.Pipeline.Storage.S3.SecretKeyEnv = "OTHER_SECRET_KEY"
operational.Config.Pipeline.Storage.S3.AccessKeyID = testSecretReference("OTHER_ACCESS_KEY")
operational.Config.Pipeline.Storage.S3.SecretAccessKey = testSecretReference("OTHER_SECRET_KEY")
operational.Config.Pipeline.Storage.S3.ForcePathStyle = true
operational.Config.Pipeline.Workspace.Root = "/relocated/workspace"
got, err := (publishStage{}).SemanticConfigFingerprint(operational)
@@ -199,7 +205,7 @@ func downstreamSemanticEnv() *Env {
Workspace: config.WorkspaceConfig{Root: "/workspace"},
Storage: config.StorageConfig{Backend: config.StorageBackendS3, S3: &config.StorageS3Config{
Bucket: "campaign-bucket", RootPrefix: "narratio", Region: "us-east-1",
Endpoint: "https://objects.example", AccessKeyIDEnv: "ACCESS_KEY", SecretKeyEnv: "SECRET_KEY",
Endpoint: "https://objects.example", AccessKeyID: testSecretReference("ACCESS_KEY"), SecretAccessKey: testSecretReference("SECRET_KEY"),
}},
Notarius: &config.NotariusConfig{
Enabled: true, Binary: "notarius", ConfigPath: "/etc/notarius/private.yml",

View File

@@ -31,7 +31,7 @@ func TestPolishSemanticConfigSensitivity(t *testing.T) {
env.Config.Pipeline.Audita.TotalLLMConcurrency = intPointer(20)
env.Config.Pipeline.Audita.ProposalLLMConcurrency = intPointer(10)
env.Config.Pipeline.Audita.ValidationLLMConcurrency = intPointer(10)
env.Config.Pipeline.Audita.LLMAPIKeyEnv = "OTHER_KEY"
env.Config.Pipeline.Audita.LLMAPIKey = testSecretReference("OTHER_KEY")
env.Config.Pipeline.Audita.WorkDirRetention = "always"
env.Config.Pipeline.Audita.Report = boolPointer(false)
}},
@@ -119,7 +119,7 @@ func refinementSemanticEnv() *Env {
env := initialSemanticEnv()
env.Config.Session.Title = "Session title"
env.Config.Pipeline.Audita = config.AuditaConfig{
Binary: "audita", Timeout: "10m", LLMAPIKeyEnv: "AUDITA_KEY",
Binary: "audita", Timeout: "10m", LLMAPIKey: testSecretReference("AUDITA_KEY"),
Modules: []string{"speakers", "terms"}, BaseURL: "https://audita.example",
Model: "testing", ValidationModel: "validator", TranscriptDescription: "tabletop session",
ConfigPath: "configs/audita.yml", OutputSchema: "audita.transcript.v1",