package config import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestPipelineConfigKitImportsOverlayAndBaseSelection(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: config/workspace.yml - path: config/artifact.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "config/workspace.yml", "workspace:\n root: /srv/base\n") writePipelineFixture(t, dir, "config/artifact.yml", `scriptorium: artifacts: recap: enabled: false output_path: artifacts/recap.md `) writePipelineFixture(t, dir, "overlays/testing.yml", "workspace:\n root: /srv/testing\n") writePipelineFixture(t, dir, "overlays/identical.yml", "workspace:\n root: /srv/base\n") writePipelineFixture(t, dir, "overlays/unselected.yml", "unknown: [\n") root := filepath.Join(dir, "pipeline.yml") var baseDigest string for _, profile := range []*string{nil, stringPointer(""), stringPointer("default")} { cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: profile}) if err != nil { t.Fatalf("load base selection %#v: %v", profile, err) } if cfg.Workspace.Root != "/srv/base" { t.Fatalf("base workspace = %q", cfg.Workspace.Root) } if baseDigest == "" { baseDigest = EffectivePipelineDigest(cfg) } else if EffectivePipelineDigest(cfg) != baseDigest { t.Fatal("equivalent base selections changed effective digest") } } identical := "identical" unchanged, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &identical}) if err != nil { t.Fatal(err) } if EffectivePipelineDigest(unchanged) != baseDigest { t.Fatal("equivalent overlay changed effective digest") } selected := "testing" cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &selected}) if err != nil { t.Fatal(err) } if cfg.Workspace.Root != "/srv/testing" || len(EffectivePipelineImports(cfg)) != 2 { t.Fatalf("profiled pipeline = %#v, imports=%#v", cfg.Workspace, EffectivePipelineImports(cfg)) } profile, ok := SelectedPipelineOverlay(cfg) if !ok || profile.Name != "testing" || profile.OverlayPath != filepath.Join(dir, "overlays", "testing.yml") { t.Fatalf("selected profile = %#v, %v", profile, ok) } } func TestPipelineOverlayDoesNotDiscoverLegacyDirectory(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", "configkit:\n imports:\n - path: profiles/shared.yml\n") writePipelineFixture(t, dir, "profiles/shared.yml", "workspace:\n root: /srv/base\n") writePipelineFixture(t, dir, "profiles/testing.yml", "workspace:\n root: /srv/old\n") if base, err := LoadPipeline(filepath.Join(dir, "pipeline.yml")); err != nil || base.Workspace.Root != "/srv/base" { t.Fatalf("explicit import from profiles directory = %#v, %v", base, err) } selected := "testing" if _, err := LoadPipelineWithOptions(filepath.Join(dir, "pipeline.yml"), PipelineLoadOptions{Overlay: &selected}); err == nil || !strings.Contains(err.Error(), "overlay.missing") { t.Fatalf("legacy selected file result = %v, want missing overlay", err) } } func TestPipelineConfigKitRejectsDuplicateBaseDeclarations(t *testing.T) { for _, test := range []struct { name, first, second, clearedContainer string }{ {name: "artifact", first: "scriptorium:\n artifacts:\n recap:\n prompt_id: recap\n", second: "scriptorium:\n artifacts:\n recap:\n output_path: artifacts/recap.md\n", clearedContainer: "scriptorium:\n artifacts: null\n"}, {name: "artifact family", first: "scriptorium:\n artifact_families:\n recap:\n for_each: party.characters\n", second: "scriptorium:\n artifact_families:\n recap:\n output_path_pattern: artifacts/{character_id}.md\n", clearedContainer: "scriptorium:\n artifact_families: null\n"}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: first.yml - path: second.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "first.yml", test.first) writePipelineFixture(t, dir, "second.yml", test.second) writePipelineFixture(t, dir, "overlays/clear-container.yml", test.clearedContainer) writePipelineFixture(t, dir, "overlays/clear-scriptorium.yml", "scriptorium: null\n") for _, profile := range []string{"", "clear-container", "clear-scriptorium"} { t.Run(profile, func(t *testing.T) { options := PipelineLoadOptions{} if profile != "" { options.Overlay = &profile } _, err := LoadPipelineWithOptions(filepath.Join(dir, "pipeline.yml"), options) if err == nil || !strings.Contains(err.Error(), "declared by multiple base files") || !strings.Contains(err.Error(), "first.yml") || !strings.Contains(err.Error(), "second.yml") { t.Fatalf("duplicate declaration error = %v", err) } }) } }) } } func TestPipelineOverlayDoesNotRequireUnusedBaseCredentials(t *testing.T) { const production = "NARRATIO_PROFILE_UNUSED_PROD_KEY" const testingKey = "NARRATIO_PROFILE_TEST_KEY" t.Setenv(production, "") if err := os.Unsetenv(production); err != nil { t.Fatal(err) } t.Setenv(testingKey, "synthetic-test-key") for _, credential := range []string{testingKey, "null"} { t.Run(credential, func(t *testing.T) { dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\naudita:\n credentials: ["+production+"]\n") selected := "[" + credential + "]" if credential == "null" { selected = credential } writePipelineFixture(t, dir, "overlays/testing.yml", "audita:\n credentials: "+selected+"\n") profile := "testing" cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &profile}) if err != nil { t.Fatal(err) } if credential == "null" { if len(cfg.Audita.Credentials) != 0 { t.Fatal("profile did not remove the base credential") } } else if len(cfg.Audita.Credentials) != 1 || cfg.Audita.Credentials[0].EnvName() != testingKey || !cfg.Audita.Credentials[0].IsResolved() { t.Fatal("profile credential was not resolved") } }) } } func TestAuditaModulesPreserveOmittedEmptyAndReplacementSelections(t *testing.T) { for _, test := range []struct { name, base, overlay string wantNil bool want []string }{ {name: "omitted", wantNil: true}, {name: "null", base: " modules: null\n", wantNil: true}, {name: "empty", base: " modules: []\n", want: []string{}}, {name: "overlay empty", base: " modules: [glossary]\n", overlay: "audita:\n modules: []\n", want: []string{}}, {name: "overlay order", base: " modules: []\n", overlay: "audita:\n modules: [grammar, glossary, grammar]\n", want: []string{"grammar", "glossary", "grammar"}}, } { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\naudita:\n binary: audita\n"+test.base) options := PipelineLoadOptions{} if test.overlay != "" { writePipelineFixture(t, dir, "overlays/testing.yml", test.overlay) selection := "testing" options.Overlay = &selection } cfg, err := LoadPipelineWithOptions(root, options) if err != nil { t.Fatal(err) } if (cfg.Audita.Modules == nil) != test.wantNil || !reflect.DeepEqual(cfg.Audita.Modules, test.want) { t.Fatalf("modules = %#v, want %#v (nil=%v)", cfg.Audita.Modules, test.want, test.wantNil) } }) } } func TestAuditaRejectsRetiredConfigurationFields(t *testing.T) { for _, field := range []string{"llm_api_key", "base_url", "model", "validation_model", "total_llm_concurrency", "proposal_llm_concurrency", "validation_llm_concurrency", "work_dir_retention", "report"} { t.Run(field, func(t *testing.T) { root := writePipelineFixture(t, t.TempDir(), "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\naudita:\n "+field+": retired\n") if _, err := LoadPipeline(root); err == nil || !strings.Contains(err.Error(), "decode.unknown_field") { t.Fatalf("error = %v, want unknown field", err) } }) } } func TestNotariusCredentialOverlayReplacementAndProvenance(t *testing.T) { const baseKey = "NARRATIO_NOTARIUS_UNUSED_BASE_KEY" const profileKey = "NARRATIO_NOTARIUS_PROFILE_KEY" if err := os.Unsetenv(baseKey); err != nil { t.Fatal(err) } t.Setenv(profileKey, "synthetic-notarius-profile-key") for _, credential := range []string{"[" + profileKey + "]", "null", "[]"} { t.Run(credential, func(t *testing.T) { dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\nnotarius:\n enabled: false\n credentials: ["+baseKey+"]\n") overlay := writePipelineFixture(t, dir, "overlays/testing.yml", "notarius:\n credentials: "+credential+"\n") profile := "testing" cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &profile}) if err != nil { t.Fatal(err) } if credential == "null" || credential == "[]" { if len(cfg.Notarius.Credentials) != 0 { t.Fatal("overlay did not clear base credentials") } return } if len(cfg.Notarius.Credentials) != 1 || cfg.Notarius.Credentials[0].EnvName() != profileKey || !cfg.Notarius.Credentials[0].IsResolved() { t.Fatal("overlay credential was not resolved") } sources, err := EffectivePipelineSources(cfg, ResolvedParty{}) if err != nil { t.Fatal(err) } found := false for _, source := range sources { if source.Path == "notarius.credentials" { found = source.Source == overlay } } if !found { t.Fatal("Notarius credential provenance did not identify the selected overlay") } }) } } func TestNotariusConfigProfileComposition(t *testing.T) { tests := []struct { name string baseValue string overlayName string overlay string want *string }{ {name: "omitted"}, {name: "explicit empty", baseValue: `""`, want: stringPointer("")}, {name: "explicit default", baseValue: "default", want: stringPointer("default")}, {name: "base named selection", baseValue: "production", want: stringPointer("production")}, {name: "profile replaces named selection", baseValue: "production", overlayName: "testing", overlay: "notarius:\n config_profile: inexpensive\n", want: stringPointer("inexpensive")}, {name: "profile clears named selection", baseValue: "production", overlayName: "testing", overlay: "notarius:\n config_profile: null\n"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() rootYAML := "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\nnotarius:\n enabled: false\n" if test.baseValue != "" { rootYAML += " config_profile: " + test.baseValue + "\n" } root := writePipelineFixture(t, dir, "pipeline.yml", rootYAML) options := PipelineLoadOptions{} if test.overlayName != "" { writePipelineFixture(t, dir, "overlays/"+test.overlayName+".yml", test.overlay) options.Overlay = &test.overlayName } cfg, err := LoadPipelineWithOptions(root, options) if err != nil { t.Fatal(err) } if cfg.Notarius == nil || !reflect.DeepEqual(cfg.Notarius.ConfigProfile, test.want) { t.Fatalf("config profile = %#v, want %#v", cfg.Notarius.ConfigProfile, test.want) } }) } } func TestNotariusCredentialRootProvenance(t *testing.T) { const envName = "NARRATIO_NOTARIUS_ROOT_KEY" t.Setenv(envName, "synthetic-notarius-root-key") dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\nnotarius:\n enabled: false\n credentials: ["+envName+"]\n") cfg, err := LoadPipeline(root) if err != nil { t.Fatal(err) } sources, err := EffectivePipelineSources(cfg, ResolvedParty{}) if err != nil { t.Fatal(err) } for _, source := range sources { if source.Path == "notarius.credentials" && source.Source == root { return } } t.Fatal("Notarius credential provenance did not identify the root pipeline") } func TestNotariusCacheControlProfileReplacementAndNullReset(t *testing.T) { tests := []struct { name string overlay string wantResume bool wantMode string wantSource bool wantNil bool }{ {name: "replacement", overlay: "notarius:\n resume: true\n chunk_cache: refresh\n", wantResume: true, wantMode: "refresh", wantSource: true}, {name: "null reset", overlay: "notarius: null\n", wantNil: true}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://transcription.example.test/transcribe\nnotarius:\n enabled: false\n resume: false\n chunk_cache: bypass\n") overlay := writePipelineFixture(t, dir, "overlays/testing.yml", test.overlay) profile := "testing" cfg, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &profile}) if err != nil { t.Fatal(err) } if test.wantNil { if cfg.Notarius != nil { t.Fatalf("Notarius = %#v, want null reset", cfg.Notarius) } return } if cfg.Notarius == nil || cfg.Notarius.Resume != test.wantResume || cfg.Notarius.ChunkCache != test.wantMode { t.Fatalf("cache controls = resume %v, mode %q; want resume %v, mode %q", cfg.Notarius.Resume, cfg.Notarius.ChunkCache, test.wantResume, test.wantMode) } if !test.wantSource { return } sources, err := EffectivePipelineSources(cfg, ResolvedParty{}) if err != nil { t.Fatal(err) } found := map[string]bool{} for _, source := range sources { if source.Path == "notarius.resume" || source.Path == "notarius.chunk_cache" { found[source.Path] = source.Source == overlay } } if !found["notarius.resume"] || !found["notarius.chunk_cache"] { t.Fatalf("cache-control provenance did not identify profile: %#v", found) } }) } } func TestPipelineConfigKitAllowsOneBaseDeclarationWithProfileOverride(t *testing.T) { dir := t.TempDir() writePipelineFixture(t, dir, "pipeline.yml", `configkit: imports: - path: artifact.yml whisperx: transcribe_url: https://transcription.example.test/transcribe `) writePipelineFixture(t, dir, "artifact.yml", "scriptorium:\n artifacts:\n recap:\n prompt_id: base\n output_path: artifacts/recap.md\n") writePipelineFixture(t, dir, "overlays/testing.yml", "scriptorium:\n artifacts:\n recap:\n prompt_id: testing\n") profile := "testing" cfg, err := LoadPipelineWithOptions(filepath.Join(dir, "pipeline.yml"), PipelineLoadOptions{Overlay: &profile}) if err != nil { t.Fatal(err) } if got := cfg.Scriptorium.Artifacts["recap"].PromptID; got != "testing" { t.Fatalf("profile prompt = %q, want testing", got) } } func TestPipelineConfigKitRejectsUnknownDirectiveAlias(t *testing.T) { dir := t.TempDir() path := writePipelineFixture(t, dir, "pipeline.yml", "composition:\n imports: []\n") _, err := LoadPipeline(path) if err == nil || !strings.Contains(err.Error(), "decode.unknown_field") { t.Fatalf("composition alias error = %v", err) } } func writePipelineFixture(t *testing.T, root, relative, contents string) string { t.Helper() path := filepath.Join(root, filepath.FromSlash(relative)) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(contents), 0o644); err != nil { t.Fatal(err) } return path } func stringPointer(value string) *string { return &value }