Preserve inherited presence and validation

This commit is contained in:
2026-10-03 17:55:58 +00:00
parent d7ea0fd32d
commit 01c08ec8e4
3 changed files with 210 additions and 0 deletions

View File

@@ -1,6 +1,9 @@
package config
import (
"errors"
"os"
"path/filepath"
"testing"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
@@ -131,3 +134,200 @@ pipelines:
t.Fatalf("Resolve() error = %v", err)
}
}
func TestInheritedPresenceOverridesRemainExplicit(t *testing.T) {
t.Parallel()
loaded, err := loadConfigSource(configkit.Bytes("presence.yml", []byte(`version: 5
pipelines:
parent:
input: {module: input}
structured_output_repair_attempts: 3
chunk:
module: generic
validators: []
artifacts:
score:
extract:
module: extract
validators: [{module: configured-a}]
child:
extends: parent
structured_output_repair_attempts: 0
artifacts:
score:
extract:
validators: []
`)), LoadOptions{})
if err != nil {
var loadError *configkit.LoadError
if errors.As(err, &loadError) {
t.Fatalf("loadConfigSource() diagnostics = %#v", loadError.Diagnostics())
}
t.Fatalf("loadConfigSource() error = %v", err)
}
parent := loaded.Config.Pipelines["parent"]
child := loaded.Config.Pipelines["child"]
if parent.Chunk.Module != "generic" || !parent.Chunk.Validators.Set || parent.Chunk.Validators.Validators == nil || len(parent.Chunk.Validators.Validators) != 0 {
t.Fatalf("parent empty chunk validators = %#v", parent.Chunk)
}
if !parent.Artifacts["score"].Extract.Validators.Set || len(parent.Artifacts["score"].Extract.Validators.Validators) != 1 ||
parent.Artifacts["score"].Extract.Validators.Validators[0].Module != "configured-a" {
t.Fatalf("parent extraction validators = %#v", parent.Artifacts["score"].Extract.Validators)
}
if parent.StructuredOutputRepairAttempts == nil || *parent.StructuredOutputRepairAttempts != 3 {
t.Fatalf("parent repair budget = %#v", parent.StructuredOutputRepairAttempts)
}
if child.StructuredOutputRepairAttempts == nil || *child.StructuredOutputRepairAttempts != 0 {
t.Fatalf("child zero repair budget = %#v", child.StructuredOutputRepairAttempts)
}
extract := child.Artifacts["score"].Extract
if !extract.Validators.Set || extract.Validators.Validators == nil || len(extract.Validators.Validators) != 0 {
t.Fatalf("child empty extraction validators = %#v", extract.Validators)
}
if !child.Chunk.Validators.Set || child.Chunk.Validators.Validators == nil || len(child.Chunk.Validators.Validators) != 0 {
t.Fatalf("inherited empty chunk validators = %#v", child.Chunk.Validators)
}
resolved, err := loaded.Config.Resolve(ResolveInput{PipelineID: "child", Catalog: effectiveCatalog(t)})
if err != nil {
t.Fatalf("Resolve(child) error = %v", err)
}
lanes := resolved.ResolvedPipeline.AllArtifactLanes()
if len(lanes) != 1 || lanes[0].Extract.StructuredOutputRepairAttempts == nil || *lanes[0].Extract.StructuredOutputRepairAttempts != 0 {
t.Fatalf("resolved child repair budget = %#v", lanes)
}
if !resolved.ResolvedPipeline.Chunk.Validators.Set || len(resolved.ResolvedPipeline.Chunk.Validators.Validators) != 0 ||
!lanes[0].Extract.Validators.Set || len(lanes[0].Extract.Validators.Validators) != 0 {
t.Fatalf("resolved empty validator overrides = chunk %#v, extraction %#v", resolved.ResolvedPipeline.Chunk.Validators, lanes[0].Extract.Validators)
}
}
func TestInheritedFalseOptionRetainsExplicitMetadata(t *testing.T) {
t.Parallel()
loaded, err := loadConfigSource(configkit.Bytes("false-option.yml", []byte(`version: 5
pipelines:
parent:
input: {module: input}
output:
module: json
options:
evidence_context: {enabled: false}
child:
extends: parent
`)), LoadOptions{})
if err != nil {
t.Fatalf("loadConfigSource() error = %v", err)
}
options := loaded.Config.Pipelines["child"].Output.Options["evidence_context"].(map[string]any)
if enabled, ok := options["enabled"].(bool); !ok || enabled {
t.Fatalf("inherited evidence_context.enabled = %#v", options["enabled"])
}
path := configkit.Keys("pipelines", "child", "output", "options", "evidence_context", "enabled")
info, ok := loaded.result.Metadata().Lookup(path)
if !ok || !info.Explicit || info.Null || len(info.Origin.Inheritance) == 0 {
t.Fatalf("inherited false metadata = %#v, found=%v", info, ok)
}
}
func TestInheritedAndDirectNullRepairBudgetsRemainInvalid(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
source string
path string
}{
{
name: "child null",
source: `version: 5
pipelines:
parent: {input: {module: input}, structured_output_repair_attempts: 2}
child:
extends: parent
structured_output_repair_attempts: null
`,
path: "/pipelines/child/structured_output_repair_attempts",
},
{
name: "inherited null",
source: `version: 5
pipelines:
parent: {input: {module: input}, structured_output_repair_attempts: null}
child: {extends: parent}
`,
path: "/pipelines/parent/structured_output_repair_attempts",
},
} {
t.Run(test.name, func(t *testing.T) {
_, err := loadConfigSource(configkit.Bytes("null.yml", []byte(test.source)), LoadOptions{})
assertDocumentDiagnosticAt(t, err, test.path)
})
}
}
func TestOverlayAndOverridesComposeBeforeInheritance(t *testing.T) {
t.Parallel()
root := t.TempDir()
overlays := filepath.Join(root, "overlays")
if err := os.MkdirAll(overlays, 0o700); err != nil {
t.Fatal(err)
}
main := filepath.Join(root, "config.yml")
if err := os.WriteFile(main, []byte(`version: 5
pipelines:
parent:
input: {module: input}
structured_output_repair_attempts: 1
inherited:
extends: parent
overridden:
extends: parent
structured_output_repair_attempts: 0
cancelled:
extends: parent
input: {module: input}
`), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(overlays, "development.yml"), []byte(`pipelines:
parent:
structured_output_repair_attempts: 2
cancelled:
extends: null
`), 0o600); err != nil {
t.Fatal(err)
}
loaded, err := Load(main, LoadOptions{Overlay: "development"})
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if got := *loaded.Config.Pipelines["inherited"].StructuredOutputRepairAttempts; got != 2 {
t.Fatalf("overlay-inherited repair budget = %d, want 2", got)
}
if got := *loaded.Config.Pipelines["overridden"].StructuredOutputRepairAttempts; got != 0 {
t.Fatalf("child replacement repair budget = %d, want 0", got)
}
if loaded.Config.Pipelines["cancelled"].StructuredOutputRepairAttempts != nil {
t.Fatalf("cancelled child inherited parent settings: %#v", loaded.Config.Pipelines["cancelled"])
}
withOverride, err := Load(main, LoadOptions{
Overlay: "development",
Overrides: []configkit.Override{{
Path: configkit.Keys("pipelines", "parent", "structured_output_repair_attempts"),
Value: 3,
Flag: "--repair-budget",
}},
})
if err != nil {
t.Fatalf("Load() with explicit override error = %v", err)
}
if got := *withOverride.Config.Pipelines["inherited"].StructuredOutputRepairAttempts; got != 3 {
t.Fatalf("override-inherited repair budget = %d, want 3", got)
}
}