From a20ba0af275bf1e2d04f0ba8630c8846cf9798b1 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 14 Oct 2026 17:39:28 +0000 Subject: [PATCH] Verify relative imports through the application lifecycle --- docs/roadmap/implementation.md | 14 +- .../destination_import_lifecycle_test.go | 211 ++++++++++++++++++ 2 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 internal/engine/destination_import_lifecycle_test.go diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 0088fc6..17146e8 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -344,7 +344,7 @@ limitations. ## Stage 7 — Verify inheritance, history, sensitivity, and diffs -Status: planned. Scope: retained placed data and one final application lifecycle. +Status: complete. Scope: retained placed data and one final application lifecycle. 1. Resolve a sibling parent/child supplied by separate relative imports after all tiers. Reuse nested inheritance/cancellation tests; add no cross-collection @@ -366,6 +366,18 @@ Validate: engine `Import|Destination|Inheritance|Secret|Sensitivity|Explain|Diff tests, representative facade tests, and everyday check. Done: the feature feeds existing inspection/completion mechanisms without reconstructing them. +Evidence (2026-10-14): based on `6458dbd`, using `go1.26.8 linux/amd64`. The +engine import/destination/inheritance/secret/sensitivity/explanation/diff suite, +representative facade import/destination/resource tests, and +`./scripts/check.sh` all passed. New integration coverage resolves siblings +from separate relative imports after a higher-tier parent replacement, then +checks inherited secret fields, one-time defaults/resolution/validation/secret +lookup, effective-path sensitivity, derived-value history, safe explanations, +and bilateral redacted diffs. Relative-import output also matches equivalent +wrapped placement in values, field presence, and safe diffs. Existing nested +inheritance and displaced-descendant tests continue to cover cancellation and +removed descendants. No limitations. + ## Stage 8 — Demonstrate ordinary and mounted collection entries Status: planned. Scope: maintained public examples and their behavior tests. diff --git a/internal/engine/destination_import_lifecycle_test.go b/internal/engine/destination_import_lifecycle_test.go new file mode 100644 index 0000000..4cd073b --- /dev/null +++ b/internal/engine/destination_import_lifecycle_test.go @@ -0,0 +1,211 @@ +package engine + +import ( + "bytes" + "fmt" + "reflect" + "strings" + "testing" +) + +func TestRelativeImportsFeedFinalInheritanceAndInspectionLifecycle(t *testing.T) { + t.Parallel() + + type entry struct { + Extends string `yaml:"extends"` + Label string `yaml:"label"` + Token *Secret `yaml:"token" configkit:"secret"` + Dynamic string `yaml:"dynamic"` + Derived string `yaml:"derived"` + Height int `yaml:"height"` + } + type configuration struct { + Catalog struct { + Entries map[string]entry `yaml:"entries"` + } `yaml:"catalog"` + } + + load := func(dynamic string) *testResult[configuration] { + t.Helper() + directory := t.TempDir() + mainPath := writeImportFile(t, directory, "entry.yml", `configkit: + imports: + - path: parent.yml + destination: [entries] + - path: child.yml + destination: [entries] +`) + writeImportFile(t, directory, "parent.yml", `parent: + label: lower + token: PARENT_TOKEN + height: 1 +`) + writeImportFile(t, directory, "child.yml", fmt.Sprintf(`child: + extends: parent + label: child + dynamic: %s + height: 9 +`, dynamic)) + + var defaultsCalls, resolveCalls, validateCalls int + secretPaths := make(map[string]bool) + lookupCalls := make(map[string]int) + request := loadRequest[configuration]{ + main: loadInput{ + source: Bytes("main", []byte("parent: {label: final, height: 4}\n")), + destination: Keys("catalog", "entries"), + }, + underlays: []loadGroup{{inputs: []loadInput{{ + source: File(mainPath), imports: true, destination: Keys("catalog"), + }}}}, + definition: Definition[configuration]{ + Defaults: func() configuration { + defaultsCalls++ + var defaults configuration + defaults.Catalog.Entries = map[string]entry{"fallback": {Label: "default"}} + return defaults + }, + Resolve: func(config *configuration, resolution *Resolution) error { + resolveCalls++ + child := config.Catalog.Entries["child"] + child.Derived = child.Label + ":" + child.Dynamic + ":" + fmt.Sprint(child.Height) + config.Catalog.Entries["child"] = child + resolution.Record(Keys("catalog", "entries", "child", "derived"), ChangeNormalization, "derive final child summary", Keys("catalog", "entries", "child", "label"), Keys("catalog", "entries", "child", "dynamic"), Keys("catalog", "entries", "child", "height")) + return nil + }, + Validate: func(config *configuration, validation *Validation) { + validateCalls++ + parent := config.Catalog.Entries["parent"] + child := config.Catalog.Entries["child"] + if parent.Label != "final" || parent.Height != 4 || child.Label != "child" || child.Height != 9 || child.Derived != child.Label+":"+child.Dynamic+":9" || child.Token == nil || !child.Token.IsResolved() || child.Token.Value() != "parent-secret-canary" { + validation.Error(Keys("catalog", "entries", "child"), "final imported configuration was not available to validation") + } + }, + Secret: func(path Path) bool { + secretPaths[path.String()] = true + return path.String() == "/catalog/entries/child/dynamic" + }, + }, + } + loaded, err := loadGroupedWithSecretLookup(request, testSecretBinding(), func(name string) (string, bool) { + lookupCalls[name]++ + if name == "PARENT_TOKEN" { + return "parent-secret-canary", true + } + return "", false + }) + if err != nil { + if loadError, ok := err.(*LoadError); ok { + t.Fatalf("relative import lifecycle diagnostics: %#v", loadError.Diagnostics()) + } + t.Fatalf("relative import lifecycle: %v", err) + } + if defaultsCalls != 1 || resolveCalls != 1 || validateCalls != 1 || lookupCalls["PARENT_TOKEN"] != 1 { + t.Fatalf("lifecycle calls defaults=%d resolve=%d validate=%d lookups=%#v", defaultsCalls, resolveCalls, validateCalls, lookupCalls) + } + if !secretPaths["/catalog/entries/child/dynamic"] { + t.Fatalf("secret classifier did not see effective paths: %#v", secretPaths) + } + return testLoaded(loaded) + } + + left := load("LEFT-DYNAMIC-CANARY") + child := left.Config.Catalog.Entries["child"] + if child.Extends != "" || child.Label != "child" || child.Height != 9 || child.Derived != "child:LEFT-DYNAMIC-CANARY:9" || child.Token == nil || child.Token.Value() != "parent-secret-canary" { + t.Fatalf("resolved imported child = %#v", child) + } + if left.Config.Catalog.Entries["parent"].Label != "final" || left.Config.Catalog.Entries["parent"].Height != 4 || left.Config.Catalog.Entries["fallback"].Label != "default" { + t.Fatalf("final parent/default shape = %#v", left.Config.Catalog.Entries) + } + + childTokenPath := Keys("catalog", "entries", "child", "token") + childToken, found := left.Inspection.Explain(childTokenPath) + childTokenInfo, tokenInfoFound := left.Inspection.Metadata().Lookup(childTokenPath) + if !found || !tokenInfoFound || !childToken.Effective.Redacted || strings.Contains(childToken.Effective.YAML, "parent-secret-canary") || len(childTokenInfo.Origin.Inheritance) != 1 { + t.Fatalf("inherited field explanation = %#v, %v", childToken, found) + } + var explanation bytes.Buffer + if err := left.Inspection.WriteExplanation(&explanation, childTokenPath); err != nil || strings.Contains(explanation.String(), "parent-secret-canary") || strings.Contains(explanation.String(), "LEFT-DYNAMIC-CANARY") { + t.Fatalf("safe explanation = %q, %v", explanation.String(), err) + } + labelHistory := left.Inspection.Metadata().History(Keys("catalog", "entries", "parent", "label")) + if len(labelHistory) < 2 { + t.Fatalf("parent label history omitted replaced contribution: %#v", labelHistory) + } + derived, found := left.Inspection.Explain(Keys("catalog", "entries", "child", "derived")) + if !found || !derived.Effective.Redacted || strings.Contains(derived.Effective.YAML, "LEFT-DYNAMIC-CANARY") || len(derived.History) == 0 || derived.History[len(derived.History)-1].Origin.Operation != ChangeNormalization { + t.Fatalf("recorded derived field explanation = %#v, %v", derived, found) + } + + right := load("RIGHT-DYNAMIC-CANARY") + differences, err := Diff(left.Inspection, right.Inspection) + if err != nil { + t.Fatal(err) + } + seen := make(map[string]bool) + for _, difference := range differences { + path := difference.Path.String() + seen[path] = true + if !difference.Left.Redacted || !difference.Right.Redacted || !difference.HiddenChanged || strings.Contains(difference.Left.YAML+difference.Right.YAML, "DYNAMIC-CANARY") || strings.Contains(difference.Left.YAML+difference.Right.YAML, "parent-secret-canary") { + t.Errorf("sensitive difference at %s = %#v", path, difference) + } + } + for _, path := range []string{"/catalog/entries/child/dynamic", "/catalog/entries/child/derived"} { + if !seen[path] { + t.Errorf("missing inherited/derived difference at %s: %#v", path, differences) + } + } +} + +func TestRelativeImportMatchesWrappedPlacementAndSafeDiff(t *testing.T) { + t.Parallel() + + load := func(relativeImport bool, value string) *testResult[map[string]any] { + t.Helper() + input := Input{Source: NoSource()} + options := LayeredOptions[map[string]any]{} + if relativeImport { + directory := t.TempDir() + mainPath := writeImportFile(t, directory, "main.yml", `configkit: + imports: + - path: fragment.yml + destination: [application] +`) + writeImportFile(t, directory, "fragment.yml", "field: "+value+"\n") + options.Underlays = []Underlay{{Inputs: []Input{{Source: File(mainPath), Imports: true}}}} + } else { + options.Underlays = []Underlay{{Inputs: []Input{{Source: Bytes("wrapped", []byte("application: {field: "+value+"}\n"))}}}} + } + loaded, err := LoadLayered(input, testSecretBinding(), options) + if err != nil { + t.Fatalf("LoadLayered: %v", err) + } + return testLoaded(loaded) + } + + relativeBefore := load(true, "before") + relativeAfter := load(true, "after") + wrappedBefore := load(false, "before") + wrappedAfter := load(false, "after") + for _, pair := range [][2]*testResult[map[string]any]{{relativeBefore, wrappedBefore}, {relativeAfter, wrappedAfter}} { + if !reflect.DeepEqual(pair[0].Config, pair[1].Config) { + t.Fatalf("relative and wrapped configurations differ: %#v vs %#v", pair[0].Config, pair[1].Config) + } + for _, result := range pair { + if info, ok := result.Metadata().Lookup(Keys("application", "field")); !ok || !info.Explicit { + t.Fatalf("effective field presence = %#v, %v", info, ok) + } + } + } + relativeDiff, err := Diff(relativeBefore.Inspection, relativeAfter.Inspection) + if err != nil { + t.Fatal(err) + } + wrappedDiff, err := Diff(wrappedBefore.Inspection, wrappedAfter.Inspection) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(relativeDiff, wrappedDiff) { + t.Fatalf("relative safe diff = %#v, wrapped safe diff = %#v", relativeDiff, wrappedDiff) + } +}