Preserve import provenance and resource contexts
This commit is contained in:
@@ -302,7 +302,7 @@ overlay/CLI accounting. No limitations.
|
||||
|
||||
## Stage 6 — Preserve diagnostics, source inspection, and resource contexts
|
||||
|
||||
Status: planned. Scope: coordinates, detached records, and resource ownership.
|
||||
Status: complete. Scope: coordinates, detached records, and resource ownership.
|
||||
|
||||
1. Check entry/child/grandchild source records' individual destinations, kinds,
|
||||
IDs, physical paths, and ImportedFrom locations. Include public failure
|
||||
@@ -329,6 +329,19 @@ diagnostic/import tests, `go test -race ./internal/engine`, and everyday check.
|
||||
Done: logical placement is inspectable without changing physical identity,
|
||||
filesystem lifetime, or the public facade boundary.
|
||||
|
||||
Evidence (2026-10-14): based on `43e55dfa85963d050d5899aec9d96f4ef637ebf6`,
|
||||
using `go1.26.8 linux/amd64`. Focused public facade import/destination/resource
|
||||
tests, the engine import/destination/provenance/diagnostic/resource suite,
|
||||
`go test -race ./internal/engine`, and `./scripts/check.sh` all passed. New
|
||||
coverage verifies exact host and native physical source paths, IDs, kinds,
|
||||
effective destinations, declaration locations, native resource opens from
|
||||
their declaring directories, detached destination copies across repeated and
|
||||
independent loads, and public source/diagnostic records for imported parse,
|
||||
source-local directive, effective-path composition/version, and typed validation
|
||||
failures. Existing layered inheritance/context and source-acquisition tests
|
||||
cover inherited lower values, explicit filesystem contexts, and cleanup. No
|
||||
limitations.
|
||||
|
||||
## Stage 7 — Verify inheritance, history, sensitivity, and diffs
|
||||
|
||||
Status: planned. Scope: retained placed data and one final application lifecycle.
|
||||
|
||||
@@ -247,6 +247,16 @@ name: profile
|
||||
wantPaths := []string{
|
||||
"main.yml", "metadata.yml", "a.yml", "b.yml", "main.yml", "details.yml", "sibling.yml",
|
||||
}
|
||||
wantPhysicalPaths := []string{
|
||||
mainPath,
|
||||
filepath.Join(directory, "metadata.yml"),
|
||||
filepath.Join(directory, "prompts", "a.yml"),
|
||||
filepath.Join(directory, "prompts", "b.yml"),
|
||||
filepath.Join(directory, "entries", "main.yml"),
|
||||
filepath.Join(directory, "entries", "nested", "details.yml"),
|
||||
filepath.Join(directory, "sibling.yml"),
|
||||
}
|
||||
wantImportParents := []string{"", "source-1", "source-1", "source-1", "source-1", "source-5", "source-1"}
|
||||
wantDestinations := []configkit.Path{configkit.Keys(test.prefix...)}
|
||||
for _, suffix := range [][]string{{"metadata"}, {"prompts"}, {"prompts"}, {"profiles"}, {"profiles", "details"}, {"sibling"}} {
|
||||
destination := append([]string(nil), test.prefix...)
|
||||
@@ -260,11 +270,18 @@ name: profile
|
||||
t.Fatalf("sources = %#v, want %d", sources, len(wantPaths))
|
||||
}
|
||||
for index, source := range sources {
|
||||
if filepath.Base(source.Path) != wantPaths[index] || source.Destination.String() != wantDestinations[index].String() {
|
||||
t.Errorf("source %d path/destination = (%q, %q), want base path %q and destination %q", index, source.Path, source.Destination.String(), wantPaths[index], wantDestinations[index].String())
|
||||
if filepath.Base(source.Path) != wantPaths[index] || source.Path != wantPhysicalPaths[index] || source.Destination.String() != wantDestinations[index].String() {
|
||||
t.Errorf("source %d path/destination = (%q, %q), want physical path %q and destination %q", index, source.Path, source.Destination.String(), wantPhysicalPaths[index], wantDestinations[index].String())
|
||||
}
|
||||
if index > 0 && source.ImportedFrom == nil {
|
||||
t.Errorf("imported source %d has no physical declaration location", index)
|
||||
if source.ID != fmt.Sprintf("source-%d", index+1) || source.Kind != configkit.SourceKindFile {
|
||||
t.Errorf("source %d identity/kind = (%q, %q)", index, source.ID, source.Kind)
|
||||
}
|
||||
if wantImportParents[index] == "" {
|
||||
if source.ImportedFrom != nil {
|
||||
t.Errorf("entry source unexpectedly has an import location: %#v", source.ImportedFrom)
|
||||
}
|
||||
} else if source.ImportedFrom == nil || source.ImportedFrom.SourceID != wantImportParents[index] || source.ImportedFrom.Line == 0 || source.ImportedFrom.Column == 0 {
|
||||
t.Errorf("source %d import location = %#v, want declaration in %s", index, source.ImportedFrom, wantImportParents[index])
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -473,6 +490,135 @@ func TestFacadeFailedLoadExposesDetachedSourceDestination(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeRelativeImportFailuresRetainSourceRecordsAndCoordinates(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
write := func(name, contents string) string {
|
||||
t.Helper()
|
||||
filename := filepath.Join(directory, name)
|
||||
if err := os.MkdirAll(filepath.Dir(filename), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filename, []byte(contents), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
t.Run("imported parse failure retains effective source destination", func(t *testing.T) {
|
||||
mainPath := write("parse/main.yml", "configkit:\n imports:\n - path: child.yml\n destination: [tenant]\n")
|
||||
childPath := write("parse/child.yml", "count: [unterminated\n")
|
||||
_, err := configkit.LoadLayered[map[string]any](configkit.Input{
|
||||
Source: configkit.File(mainPath), Imports: true, Destination: configkit.Keys("mount"),
|
||||
})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %T %v", err, err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeYAMLInvalid || diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" {
|
||||
t.Fatalf("imported parse diagnostic = %#v", diagnostics)
|
||||
}
|
||||
sources := loadError.Sources()
|
||||
if len(sources) != 2 || sources[0].Destination.String() != "/mount" || sources[1].Destination.String() != "/mount/tenant" || sources[1].Path != childPath {
|
||||
t.Fatalf("imported parse failure sources = %#v", sources)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("directive failure stays source local", func(t *testing.T) {
|
||||
mainPath := write("directive/main.yml", "configkit:\n imports:\n - path: parent.yml\n destination: [tenant]\n")
|
||||
parentPath := write("directive/parent.yml", "configkit:\n imports:\n - path: details.yml\n destination: [3]\n")
|
||||
_, err := configkit.LoadLayered[map[string]any](configkit.Input{
|
||||
Source: configkit.File(mainPath), Imports: true, Destination: configkit.Keys("mount"),
|
||||
})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %T %v", err, err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
wantPath := configkit.Keys("configkit", "imports").Index(0).Key("destination").Index(0)
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeImportInvalid || !reflect.DeepEqual(diagnostics[0].Path, wantPath) || diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" {
|
||||
t.Fatalf("import directive diagnostic = %#v", diagnostics)
|
||||
}
|
||||
sources := loadError.Sources()
|
||||
if len(sources) != 2 || sources[0].Destination.String() != "/mount" || sources[1].Destination.String() != "/mount/tenant" || sources[1].Path != parentPath {
|
||||
t.Fatalf("directive failure sources = %#v", sources)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("composition failure reports effective path and all sources", func(t *testing.T) {
|
||||
mainPath := write("compose/main.yml", "configkit:\n imports:\n - path: left.yml\n destination: [tenant]\n - path: right.yml\n destination: [tenant]\n")
|
||||
write("compose/left.yml", "value: same\n")
|
||||
write("compose/right.yml", "value: same\n")
|
||||
_, err := configkit.LoadLayered[map[string]any](configkit.Input{
|
||||
Source: configkit.File(mainPath), Imports: true, Destination: configkit.Keys("mount"),
|
||||
})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %T %v", err, err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeComposeConflict || diagnostics[0].Path.String() != "/mount/tenant/value" {
|
||||
t.Fatalf("composition diagnostic = %#v", diagnostics)
|
||||
}
|
||||
sources := loadError.Sources()
|
||||
if len(sources) != 3 || sources[0].Destination.String() != "/mount" || sources[1].Destination.String() != "/mount/tenant" || sources[2].Destination.String() != "/mount/tenant" {
|
||||
t.Fatalf("composition failure sources = %#v", sources)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("version failure reports effective path and imported source", func(t *testing.T) {
|
||||
mainPath := write("version/main.yml", "configkit:\n imports:\n - path: child.yml\n destination: [tenant]\n")
|
||||
childPath := write("version/child.yml", "version: forbidden\n")
|
||||
_, err := configkit.LoadLayered[map[string]any](configkit.Input{
|
||||
Source: configkit.File(mainPath), Imports: true, Destination: configkit.Keys("mount"),
|
||||
}, configkit.LayeredOptions[map[string]any]{
|
||||
Definition: configkit.Definition[map[string]any]{Version: &configkit.VersionRule{
|
||||
Path: configkit.Keys("mount", "tenant", "version"),
|
||||
Check: func(configkit.Version, *configkit.Validation) error { return nil },
|
||||
}},
|
||||
})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %T %v", err, err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeImportVersion || diagnostics[0].Path.String() != "/mount/tenant/version" || diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" {
|
||||
t.Fatalf("version diagnostic = %#v", diagnostics)
|
||||
}
|
||||
sources := loadError.Sources()
|
||||
if len(sources) != 2 || sources[0].Destination.String() != "/mount" || sources[1].Destination.String() != "/mount/tenant" || sources[1].Path != childPath {
|
||||
t.Fatalf("version failure sources = %#v", sources)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("typed validation reports effective path and authored location", func(t *testing.T) {
|
||||
type configuration struct {
|
||||
Mount struct {
|
||||
Tenant struct {
|
||||
Count int `yaml:"count"`
|
||||
} `yaml:"tenant"`
|
||||
} `yaml:"mount"`
|
||||
}
|
||||
mainPath := write("typed/main.yml", "configkit:\n imports:\n - path: child.yml\n destination: [tenant]\n")
|
||||
childPath := write("typed/child.yml", "count: invalid\n")
|
||||
_, err := configkit.LoadLayered[configuration](configkit.Input{
|
||||
Source: configkit.File(mainPath), Imports: true, Destination: configkit.Keys("mount"),
|
||||
})
|
||||
var loadError *configkit.LoadError
|
||||
if !errors.As(err, &loadError) {
|
||||
t.Fatalf("load error = %T %v", err, err)
|
||||
}
|
||||
diagnostics := loadError.Diagnostics()
|
||||
if len(diagnostics) != 1 || diagnostics[0].Code != configkit.CodeDecodeType || diagnostics[0].Path.String() != "/mount/tenant/count" || diagnostics[0].Primary == nil || diagnostics[0].Primary.SourceID != "source-2" || diagnostics[0].Primary.Line != 1 {
|
||||
t.Fatalf("typed validation diagnostic = %#v", diagnostics)
|
||||
}
|
||||
sources := loadError.Sources()
|
||||
if len(sources) != 2 || sources[0].Destination.String() != "/mount" || sources[1].Destination.String() != "/mount/tenant" || sources[1].Path != childPath {
|
||||
t.Fatalf("typed validation sources = %#v", sources)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFacadeDestinationMappingFailurePaths(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@ package configkit_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
@@ -219,3 +221,77 @@ func TestFacadeSyntheticSourceContextsAndContextlessSources(t *testing.T) {
|
||||
t.Fatal("CLI source returned a context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeRelativeImportDestinationsPreserveNativeResourceContexts(t *testing.T) {
|
||||
borrowed := &observedResourceFS{files: fstest.MapFS{
|
||||
"bundle/config/main.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: entries/parent.yml\n destination: [logical]\n")},
|
||||
"bundle/config/entries/parent.yml": &fstest.MapFile{Data: []byte("configkit:\n imports:\n - path: nested/details.yml\n destination: [details]\nresource: icon.txt\n")},
|
||||
"bundle/config/entries/nested/details.yml": &fstest.MapFile{Data: []byte("resource: detail.txt\n")},
|
||||
"bundle/config/entries/icon.txt": &fstest.MapFile{Data: []byte("parent-resource")},
|
||||
"bundle/config/entries/nested/detail.txt": &fstest.MapFile{Data: []byte("nested-resource")},
|
||||
}}
|
||||
namespace := configkit.NewFilesystem("bundle", borrowed)
|
||||
input := configkit.Input{
|
||||
Source: namespace.File("bundle/config/main.yml"), Imports: true,
|
||||
Filesystem: &configkit.FilesystemContext{Root: "bundle", FS: &namespace},
|
||||
}
|
||||
load := func() *configkit.Result[map[string]any] {
|
||||
t.Helper()
|
||||
result, err := configkit.LoadLayered[map[string]any](input)
|
||||
if err != nil {
|
||||
t.Fatalf("load relative native imports: %v", err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
result := load()
|
||||
wantDestinations := []string{configkit.Keys().String(), "/logical", "/logical/details"}
|
||||
wantPaths := []string{"bundle/config/main.yml", "bundle/config/entries/parent.yml", "bundle/config/entries/nested/details.yml"}
|
||||
sources := result.Sources()
|
||||
if len(sources) != len(wantPaths) {
|
||||
t.Fatalf("sources = %#v", sources)
|
||||
}
|
||||
for index, source := range sources {
|
||||
if source.ID != fmt.Sprintf("source-%d", index+1) || source.Kind != configkit.SourceKindFilesystem || source.Path != wantPaths[index] || source.Destination.String() != wantDestinations[index] {
|
||||
t.Errorf("source %d = %#v", index, source)
|
||||
}
|
||||
if index == 0 {
|
||||
if source.ImportedFrom != nil {
|
||||
t.Errorf("entry source has import location %#v", source.ImportedFrom)
|
||||
}
|
||||
} else if source.ImportedFrom == nil || source.ImportedFrom.SourceID != fmt.Sprintf("source-%d", index) || source.ImportedFrom.Line == 0 || source.ImportedFrom.Column == 0 {
|
||||
t.Errorf("source %d import location = %#v", index, source.ImportedFrom)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
field, effectivePath, wantBase, wantResource, wantContents string
|
||||
}{
|
||||
{"parent resource", "/logical/resource", "config/entries", "icon.txt", "parent-resource"},
|
||||
{"nested resource", "/logical/details/resource", "config/entries/nested", "detail.txt", "nested-resource"},
|
||||
} {
|
||||
info, ok := result.Metadata().Lookup(configkit.Keys(strings.Split(strings.Trim(test.effectivePath, "/"), "/")...))
|
||||
if !ok || len(info.Origin.Locations) != 1 {
|
||||
t.Fatalf("%s origin = %#v, %v", test.field, info, ok)
|
||||
}
|
||||
context, ok := result.SourceContext(info.Origin.Locations[0].SourceID)
|
||||
if !ok || context.BaseDir != test.wantBase || context.FS == nil {
|
||||
t.Fatalf("%s context = %#v, %v", test.field, context, ok)
|
||||
}
|
||||
contents, err := fs.ReadFile(context.FS, path.Join(context.BaseDir, test.wantResource))
|
||||
if err != nil || string(contents) != test.wantContents {
|
||||
t.Fatalf("%s opened resource = %q, %v", test.field, contents, err)
|
||||
}
|
||||
}
|
||||
|
||||
sources[1].Destination = configkit.Keys("caller", "mutation")
|
||||
for query := 0; query < 2; query++ {
|
||||
fresh := result.Sources()
|
||||
if fresh[1].Destination.String() != "/logical" {
|
||||
t.Fatalf("query %d source destination = %q", query, fresh[1].Destination.String())
|
||||
}
|
||||
}
|
||||
second := load()
|
||||
if !reflect.DeepEqual(second.Sources(), result.Sources()) {
|
||||
t.Fatalf("independent source records differ: %#v vs %#v", second.Sources(), result.Sources())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user