diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 440f85c..5133839 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -191,7 +191,12 @@ named `version`. ## 6. Preserve absence for ordinary defaults-only loads — P2 -- [ ] Addressed and regression-tested. +- [x] Addressed and regression-tested. + +Completion: absent ordinary input stays absent until an existing explicit layer +needs assembly. Public parity tests cover values, root presence/origin, optional +registrations/defaults, and real empty mappings. Existing defaults-only override +coverage and the everyday check passed. **Owner:** `executeLoadRequest` in [load.go](../../internal/engine/load.go), lines 210–216, and diff --git a/facade_sequence_test.go b/facade_sequence_test.go index 71cf1f4..1491ab7 100644 --- a/facade_sequence_test.go +++ b/facade_sequence_test.go @@ -4,6 +4,7 @@ import ( "bytes" "os" "path/filepath" + "reflect" "strings" "testing" "testing/fstest" @@ -11,6 +12,45 @@ import ( configkit "gitea.maximumdirect.net/eric/ConfigKit" ) +func TestFacadeNoSourcePreservesAbsenceWithSequencePolicy(t *testing.T) { + t.Parallel() + for _, defaults := range []bool{false, true} { + for _, registered := range []bool{false, true} { + definition := configkit.Definition[map[string]any]{} + if defaults { + definition.Defaults = func() map[string]any { return map[string]any{"fallback": true} } + } + if registered { + definition.SequenceCompositions = []configkit.SequenceComposition{{Path: configkit.Keys("records"), IdentityField: "id"}} + } + ordinary, err := configkit.Load(configkit.NoSource(), configkit.Options[map[string]any]{Definition: definition}) + if err != nil { + t.Fatal(err) + } + layered, err := configkit.LoadLayered[map[string]any](configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[map[string]any]{Definition: definition}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(ordinary.Config, layered.Config) { + t.Fatal("NoSource values differ between entry points") + } + for _, result := range []*configkit.Result[map[string]any]{ordinary, layered} { + if len(result.Sources()) != 0 || result.Metadata().IsExplicit(configkit.Keys()) { + t.Fatal("NoSource contributed an explicit root") + } + info, ok := result.Metadata().Lookup(configkit.Keys()) + if !ok || info.Origin.Layer != configkit.LayerDefault { + t.Fatalf("NoSource root origin = %#v, %t", info, ok) + } + } + empty, err := configkit.Load(configkit.Mapping("empty", map[string]any{}), configkit.Options[map[string]any]{Definition: definition}) + if err != nil || !empty.Metadata().IsExplicit(configkit.Keys()) { + t.Fatal("real empty mapping lost its presence") + } + } + } +} + func TestFacadeItemHelperHistoryPreservesTraversalOrder(t *testing.T) { t.Parallel() filesystem := configkit.NewFilesystem("history", fstest.MapFS{ diff --git a/internal/engine/load.go b/internal/engine/load.go index 937542f..a74f173 100644 --- a/internal/engine/load.go +++ b/internal/engine/load.go @@ -207,7 +207,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo return nil, accumulator.loadError() } } - if request.ordinary { + if request.ordinary && input.document != nil { input.composition, diagnostics = compositionForInputWithPolicy(input, request.sequencePolicy) if len(diagnostics) != 0 { accumulator.add(diagnostics...)