From 55abcf834058880600448c794a6ed42b72b65313 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 19 Oct 2026 21:13:21 +0000 Subject: [PATCH] Audit scheduled import and inheritance lifecycles --- facade_import_lifecycle_test.go | 135 ++++++++++++++++++ .../engine/dependency_integration_test.go | 77 ++++++++++ 2 files changed, 212 insertions(+) create mode 100644 facade_import_lifecycle_test.go create mode 100644 internal/engine/dependency_integration_test.go diff --git a/facade_import_lifecycle_test.go b/facade_import_lifecycle_test.go new file mode 100644 index 0000000..63e25ad --- /dev/null +++ b/facade_import_lifecycle_test.go @@ -0,0 +1,135 @@ +package configkit + +import ( + "errors" + "io/fs" + "path" + "strings" + "testing" + "testing/fstest" +) + +func TestDiscoveredItemHelpersPreserveNestedInheritedResourceEvidence(t *testing.T) { + const canary = "PRIVATE-CONSUMED-HEADER-CANARY" + filesystem := NewFilesystem("bundle", fstest.MapFS{ + "bundle/config/main.yml": {Data: []byte(`configkit: + imports: + - dir: records + pattern: '*-item.yml' + destination: [services, records] + placement: sequence-item +services: + records: + - id: authored + version: authored +`)}, + "bundle/config/records/a-item.yml": {Data: []byte(`configkit: + imports: + - path: helper.yml +version: ` + canary + ` +entries: + parent: + resource: icon.txt + child: + extends: parent +`)}, + "bundle/config/records/helper.yml": {Data: []byte("id: helper-supplied\n")}, + "bundle/config/records/icon.txt": {Data: []byte("icon payload")}, + }) + result, err := Load(filesystem.File("bundle/config/main.yml"), Options[map[string]any]{ + Imports: true, + Filesystem: &FilesystemContext{Root: "bundle", FS: &filesystem}, + Definition: Definition[map[string]any]{ + Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, + SequenceCompositions: []SequenceComposition{{Path: Keys("services", "records"), IdentityField: "id"}}, + Secret: func(candidate Path) bool { + return candidate.String() == "/services/records/0/version" + }, + }, + SourceVersionMode: SourceVersionModeIfPresent, + }) + if err != nil { + var loadError *LoadError + if errors.As(err, &loadError) { + t.Fatalf("load discovered item: %v; diagnostics: %#v", err, loadError.Diagnostics()) + } + t.Fatalf("load discovered item: %v", err) + } + services := result.Config["services"].(map[string]any) + records := services["records"].([]any) + if len(records) != 2 || records[0].(map[string]any)["id"] != "helper-supplied" || records[0].(map[string]any)["version"] != nil || records[1].(map[string]any)["version"] != "authored" { + t.Fatalf("nested records = %#v", records) + } + childPath := Keys("services", "records").Index(0).Key("entries").Key("child").Key("resource") + child := records[0].(map[string]any)["entries"].(map[string]any)["child"].(map[string]any) + if child["resource"] != "icon.txt" { + t.Fatalf("inherited relative resource = %#v", child) + } + + sources := result.Sources() + filesByID := make(map[string]string, len(sources)) + for _, source := range sources { + filesByID[source.ID] = source.Path + } + var itemSource SourceInfo + for _, source := range sources { + if source.Path == "bundle/config/records/a-item.yml" { + itemSource = source + } + } + if itemSource.VersionCheck == nil || !itemSource.VersionCheck.Version.Present || !itemSource.VersionCheck.Redacted || itemSource.VersionCheck.Version.Text != "" { + t.Fatalf("consumed header inspection = %#v", itemSource.VersionCheck) + } + idInfo, ok := result.Metadata().Lookup(Keys("services", "records").Index(0).Key("id")) + if !ok || len(idInfo.Origin.Locations) != 1 || filesByID[idInfo.Origin.Locations[0].SourceID] != "bundle/config/records/helper.yml" { + t.Fatalf("assigned helper ID origin = %#v", idInfo) + } + resourceInfo, ok := result.Metadata().Lookup(childPath) + if !ok || len(resourceInfo.Origin.Locations) != 1 || filesByID[resourceInfo.Origin.Locations[0].SourceID] != "bundle/config/records/a-item.yml" { + t.Fatalf("inherited resource metadata = %#v", resourceInfo) + } + explanation, ok := result.Explain(childPath) + if !ok || explanation.Effective.YAML != "icon.txt\n" || len(explanation.Origin.Locations) != 1 || explanation.Origin.Locations[0].SourceID != resourceInfo.Origin.Locations[0].SourceID { + t.Fatalf("inherited resource explanation = %#v", explanation) + } + context, ok := result.SourceContext(resourceInfo.Origin.Locations[0].SourceID) + if !ok || context.FS == nil || context.BaseDir != "config/records" { + t.Fatalf("inherited resource context = %#v, %v", context, ok) + } + contents, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, child["resource"].(string))) + if err != nil || string(contents) != "icon payload" { + t.Fatalf("inherited resource read = %q, %v", contents, err) + } + + _, err = Load(filesystem.File("bundle/config/main.yml"), Options[map[string]any]{ + Imports: true, + Filesystem: &FilesystemContext{Root: "bundle", FS: &filesystem}, + Definition: Definition[map[string]any]{ + Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return nil }}, + SequenceCompositions: []SequenceComposition{{Path: Keys("services", "records"), IdentityField: "version"}}, + Secret: func(candidate Path) bool { + return candidate.String() == "/services/records/0/version" + }, + }, + SourceVersionMode: SourceVersionModeIfPresent, + }) + loadError, ok := err.(*LoadError) + if !ok || len(loadError.Diagnostics()) != 1 || loadError.Diagnostics()[0].Code != CodeComposeInvalid { + t.Fatalf("consumed header supplied an identity: %v", err) + } + if strings.Contains(err.Error(), canary) { + t.Fatalf("diagnostic exposed the consumed secret header: %v", err) + } + var failedItemSource SourceInfo + foundFailedItem := false + for _, source := range loadError.Sources() { + if source.Path == "bundle/config/records/a-item.yml" { + failedItemSource = source + foundFailedItem = true + break + } + } + if !foundFailedItem || failedItemSource.VersionCheck == nil || failedItemSource.VersionCheck.Version.Text != "" || !failedItemSource.VersionCheck.Redacted { + t.Fatalf("failed-load source inspection exposed or omitted header evidence: %#v", failedItemSource) + } +} diff --git a/internal/engine/dependency_integration_test.go b/internal/engine/dependency_integration_test.go new file mode 100644 index 0000000..4e38946 --- /dev/null +++ b/internal/engine/dependency_integration_test.go @@ -0,0 +1,77 @@ +package engine + +import ( + "reflect" + "testing" +) + +func TestImportAndInheritanceIntegrationsUseEquivalentReadySchedules(t *testing.T) { + names := []string{"a-base", "b-independent", "z-dependent"} + keys := make(map[string]importNodeKey, len(names)) + contributions := make([]acquiredContribution, len(names)) + for index, name := range names { + key := importNodeKey{ + namespaceKind: "host", namespaceAnchor: "/shared", + resourceKind: "file", resourcePath: "/" + name, + } + keys[name] = key + contributions[index] = acquiredContribution{ + identity: key, + source: SourceInfo{ID: "source-" + name, Path: name}, + } + } + + var want []string + for _, reverse := range []bool{false, true} { + graph := newImportInclusionGraph() + orderedNames := append([]string(nil), names...) + if reverse { + orderedNames[0], orderedNames[2] = orderedNames[2], orderedNames[0] + } + for _, name := range orderedNames { + graph.addNode(importGraphNode{key: keys[name], sourceID: "source-" + name, role: importRoleMapping}) + } + graph.addEdge(keys["z-dependent"], keys["a-base"], Location{}) + + scheduled := scheduleContributionSlots(contributions, []int{0, 1, 2}, graph) + got := make([]string, len(scheduled)) + for index, contribution := range scheduled { + got[index] = contribution.identity.resourcePath[1:] + } + if !reflect.DeepEqual(got, []string{"a-base", "b-independent", "z-dependent"}) { + t.Fatalf("import schedule reverse=%t: %v", reverse, got) + } + if !reflect.DeepEqual( + []string{contributions[0].source.ID, contributions[1].source.ID, contributions[2].source.ID}, + []string{"source-a-base", "source-b-independent", "source-z-dependent"}, + ) { + t.Fatal("scheduling mutated acquisition source records") + } + + inheritanceText := "a-base: {value: ready}\nb-independent: {value: independent}\nz-dependent: {extends: a-base}\n" + if reverse { + inheritanceText = "z-dependent: {extends: a-base}\nb-independent: {value: independent}\na-base: {value: ready}\n" + } + scope := parseCompositionDocument(t, "inheritance", inheritanceText) + inheritance, diagnostics := analyzeInheritanceGraph(scope, Path{}) + if len(diagnostics) != 0 { + t.Fatalf("analyze inheritance reverse=%t: %#v", reverse, diagnostics) + } + order, diagnostics := inheritance.schedule() + if len(diagnostics) != 0 { + t.Fatalf("schedule inheritance reverse=%t: %#v", reverse, diagnostics) + } + inheritanceNames := make([]string, len(order)) + for index, node := range order { + inheritanceNames[index] = inheritance.nodes[node].name + } + if !reflect.DeepEqual(inheritanceNames, got) { + t.Fatalf("equivalent graph schedules differ reverse=%t: imports=%v inheritance=%v", reverse, got, inheritanceNames) + } + if want == nil { + want = got + } else if !reflect.DeepEqual(got, want) { + t.Fatalf("import graph construction changed schedule: got %v, want %v", got, want) + } + } +}