diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 78da8e7..486f63d 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -79,6 +79,8 @@ or replace the final suite in Stage 5. ## Stage 2 — Verify Inherited Configuration, Defaults, and Credentials +**Completion:** Complete. + **Primary files:** `internal/core/config/config_test.go`; change `internal/core/config/config.go` only if these checks expose an integration defect. diff --git a/internal/core/config/config_test.go b/internal/core/config/config_test.go index fcb5f74..9b07f58 100644 --- a/internal/core/config/config_test.go +++ b/internal/core/config/config_test.go @@ -137,9 +137,167 @@ func TestSurvivingCredentials(t *testing.T) { if err != nil || loaded.Config.Backends["other"].Credential != nil { t.Fatalf("null credential: %+v %v", loaded, err) } + + inherited := `version: 2 +backends: + parent: + endpoint: https://parent.invalid/v1 + credential: BASE_SECRET + local: + extends: parent + credential: null +llm: + proposal_profile: proposal +` + if _, err := loadText(inherited, ""); err == nil { + t.Fatal("null child credential removed the unselected parent's surviving reference") + } + t.Setenv("BASE_SECRET", "synthetic-parent-secret") + loaded, err = loadText(strings.Replace(inherited, " credential: null\n", "", 1), "") + if err != nil { + t.Fatal(err) + } + if got := loaded.Config.Backends["local"].Credential.Value(); got != "synthetic-parent-secret" { + t.Fatalf("inherited secret: %q", got) + } + if got := loaded.Metadata().IsExplicit(ck.Keys("backends", "local", "credential")); !got { + t.Fatal("inherited credential is not explicit") + } + var inheritedEffective bytes.Buffer + if err := loaded.WriteEffective(&inheritedEffective); err != nil { + t.Fatal(err) + } + if strings.Contains(inheritedEffective.String(), "synthetic-parent-secret") { + t.Fatal("effective output leaked an inherited secret") + } + loaded, err = loadText(inherited, "") + if err != nil || loaded.Config.Backends["local"].Credential != nil { + t.Fatalf("child null credential: %+v %v", loaded, err) + } + if got := loaded.Config.Backends["parent"].Credential.Value(); got != "synthetic-parent-secret" { + t.Fatalf("parent credential after child null: %q", got) + } + t.Setenv("AUDITA_MODEL", "legacy-model") loaded, err = loadText(base, "") if err != nil || loaded.Config.LLM.ProposalProfile != "proposal" { t.Fatalf("legacy environment affected loading: %+v %v", loaded, err) } } + +func TestSiblingInheritancePreservesBackendSettings(t *testing.T) { + text := `version: 2 +backends: + base: + endpoint: https://base.invalid/v1 + concurrency: 3 + queue_capacity: 0 + local: + extends: base +llm: + proposal_profile: proposal +` + loaded, err := loadText(text, "") + if err != nil { + t.Fatal(err) + } + backend := loaded.Config.Backends["local"] + if backend.Endpoint != "https://base.invalid/v1" || backend.Concurrency != 3 || backend.QueueCapacity != 0 { + t.Fatalf("inherited backend settings: %+v", backend) + } + for _, path := range []ck.Path{ + ck.Keys("backends", "local", "endpoint"), + ck.Keys("backends", "local", "concurrency"), + ck.Keys("backends", "local", "queue_capacity"), + } { + if !loaded.Metadata().IsExplicit(path) { + t.Errorf("inherited field %s is not explicit", path.String()) + } + } + + overridden, err := loadText(strings.Replace(text, " extends: base\n", " extends: base\n concurrency: 2\n", 1), "") + if err != nil { + t.Fatal(err) + } + if got := overridden.Config.Backends["local"].Concurrency; got != 2 { + t.Fatalf("child concurrency override: %d", got) + } + if got := overridden.Config.Backends["local"].QueueCapacity; got != 0 { + t.Fatalf("inherited zero queue capacity: %d", got) + } +} + +func TestSiblingInheritanceUsesSelectedOverlay(t *testing.T) { + dir := t.TempDir() + if err := os.Mkdir(filepath.Join(dir, "overlays"), 0700); err != nil { + t.Fatal(err) + } + main := filepath.Join(dir, "config.yml") + text := `version: 2 +backends: + base: + endpoint: https://base.invalid/v1 + concurrency: 3 + queue_capacity: 0 + local: + extends: base + concurrency: 2 +llm: + proposal_profile: proposal +` + if err := os.WriteFile(main, []byte(text), 0600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "overlays", "testing.yml"), []byte("backends:\n base:\n endpoint: https://overlay.invalid/v1\n concurrency: 4\n"), 0600); err != nil { + t.Fatal(err) + } + loaded, err := Load(ck.File(main), "testing") + if err != nil { + t.Fatal(err) + } + child := loaded.Config.Backends["local"] + if child.Endpoint != "https://overlay.invalid/v1" || child.Concurrency != 2 || child.QueueCapacity != 0 { + t.Fatalf("overlay inheritance and child override: %+v", child) + } +} + +func TestSiblingInheritanceDoesNotBypassBackendValidation(t *testing.T) { + for _, tc := range []struct { + name string + text string + }{ + { + name: "invalid inherited parent setting", + text: `version: 2 +backends: + base: + endpoint: https://base.invalid/v1 + concurrency: 0 + local: + extends: base +llm: + proposal_profile: proposal +`, + }, + { + name: "invalid child override", + text: `version: 2 +backends: + base: + endpoint: https://base.invalid/v1 + concurrency: 3 + local: + extends: base + concurrency: 0 +llm: + proposal_profile: proposal +`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if _, err := loadText(tc.text, ""); err == nil { + t.Fatal("invalid backend concurrency accepted") + } + }) + } +}