Validate source equivalence and layered scaling

This commit is contained in:
2026-09-29 18:15:10 +00:00
parent 36362ee6af
commit 34e18c84c1
6 changed files with 423 additions and 13 deletions

View File

@@ -461,6 +461,28 @@ checker and full race suite.
## Stage 9: Validate cross-feature consistency and scaling ## Stage 9: Validate cross-feature consistency and scaling
**Completed 2026-09-29.** A compact public matrix now checks equivalent host,
native, byte, reader, and mapping inputs; tier-local repetition versus cross-tier
reuse; reader consumption; and safe diagnostics/effective/history output after
secret replacement. Engine tests verify exact occurrence accounting across
imports, shared glob/source limits, and the mixed-source CLI ceiling. Rendering
now indexes source labels once per explanation instead of scanning all records
for every location. Loading and passive context ownership were reviewed against
ADR 0008; no handles or background work are retained by queries.
On Linux amd64 with `-benchmem` and 100 ms samples, the 512-entry assembly-only
composition measured about 3.5 ms/op (the earlier post-index baseline was about
3.3 ms), and recursive overlay about 2.2 ms/op (about 12 ms at the first-stage
handoff before subsequent provenance work). The 128-application owned assembly
remained about 0.30 ms/op versus 6.35 ms/op for copy-preserving application.
End-to-end layered measurements were about 7.15 ms/op for 512 broad catalog
entries, 2.59 ms/op for 128 small tiers, 3.27 ms/op for 128 alternating subtree
replacements, 1.86 ms/op for 64 imported native defaults, and 1.36 ms/op for
64 retained-history applications. Allocations grew roughly with these fixture
sizes. I/O, sorting, decoding, callbacks, and retained output contribute to
full-path cost; these observations are not timing gates or an unconditional
complexity guarantee. The everyday checker and full race suite passed.
1. Consolidate a small semantic equivalence matrix across host, native, bytes, 1. Consolidate a small semantic equivalence matrix across host, native, bytes,
readers, and mappings, using equivalent logical inputs and contexts. Cover readers, and mappings, using equivalent logical inputs and contexts. Cover
strict conflict versus intentional precedence and version-path preservation; strict conflict versus intentional precedence and version-path preservation;

164
facade_equivalence_test.go Normal file
View File

@@ -0,0 +1,164 @@
package configkit_test
import (
"bytes"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"testing/fstest"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
)
type equivalentConfig struct {
Version uint64 `yaml:"version"`
Name string `yaml:"name"`
Enabled bool `yaml:"enabled"`
Count int `yaml:"count"`
Items []string `yaml:"items"`
Optional *string `yaml:"optional"`
}
func TestEquivalentSourceFormsPreserveInputSemantics(t *testing.T) {
const yaml = "version: 1\nname: retained\nenabled: false\ncount: 0\nitems: []\noptional: null\n"
hostPath := filepath.Join(t.TempDir(), "main.yml")
if err := os.WriteFile(hostPath, []byte(yaml), 0o600); err != nil {
t.Fatal(err)
}
namespace := configkit.NewFilesystem("bundle", fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte(yaml)}})
context := &configkit.FilesystemContext{Root: ".", BaseDir: ".", FS: &namespace}
for _, test := range []struct {
name string
source configkit.Source
context *configkit.FilesystemContext
kind configkit.SourceKind
}{
{"host", configkit.File(hostPath), nil, configkit.SourceKindFile},
{"native", namespace.File("main.yml"), nil, configkit.SourceKindFilesystem},
{"bytes", configkit.Bytes("memory", []byte(yaml)), context, configkit.SourceKindBytes},
{"reader", configkit.Reader("stream", strings.NewReader(yaml)), context, configkit.SourceKindReader},
{"mapping", configkit.Mapping("programmatic", map[string]any{
"version": uint64(1), "name": "retained", "enabled": false, "count": 0,
"items": []any{}, "optional": nil,
}), context, configkit.SourceKindMapping},
} {
t.Run(test.name, func(t *testing.T) {
checks := 0
result, err := configkit.LoadLayered(configkit.Input{Source: test.source, Filesystem: test.context}, configkit.LayeredOptions[equivalentConfig]{
Definition: configkit.Definition[equivalentConfig]{Version: &configkit.VersionRule{
Path: configkit.Keys("version"), Check: func(version configkit.Version, validation *configkit.Validation) error {
checks++
if !version.Present || version.Text != "1" {
t.Errorf("version = %#v", version)
}
return nil
},
}},
})
if err != nil {
t.Fatal(err)
}
want := equivalentConfig{Version: 1, Name: "retained", Items: []string{}}
if !reflect.DeepEqual(result.Config, want) || checks != 1 {
t.Fatalf("config = %#v, checks %d", result.Config, checks)
}
for _, field := range []string{"version", "name", "enabled", "count", "items", "optional"} {
info, ok := result.Metadata().Lookup(configkit.Keys(field))
if !ok || !info.Explicit || info.Origin.Layer != configkit.LayerBase || info.Null != (field == "optional") {
t.Errorf("%s input info = %#v, %v", field, info, ok)
}
}
sources := result.Sources()
if len(sources) != 1 || sources[0].Kind != test.kind || sources[0].ID != "source-1" || sources[0].UnderlayIndex != 0 {
t.Fatalf("sources = %#v", sources)
}
resource, ok := result.SourceContext("source-1")
if !ok || resource.BaseDir != "." || resource.FS == nil {
t.Fatalf("source context = %#v, %v", resource, ok)
}
})
}
}
func TestRepeatedImportsAcrossTiersAndReaderConsumption(t *testing.T) {
namespace := configkit.NewFilesystem("bundle", fstest.MapFS{
"left.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: shared.yml}]}\n")},
"right.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: shared.yml}]}\n")},
"shared.yml": &fstest.MapFile{Data: []byte("name: retained\n")},
})
type config struct {
Name string `yaml:"name"`
}
left := configkit.Input{Source: namespace.File("left.yml"), Imports: true}
right := configkit.Input{Source: namespace.File("right.yml"), Imports: true}
_, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[config]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{left, right}}},
})
if got := layeredErrorCode(t, err); got != configkit.CodeImportRepeated {
t.Fatalf("same-tier repeated import = %s", got)
}
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[config]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{left}}, {Inputs: []configkit.Input{right}}},
})
if err != nil || result.Config.Name != "retained" || len(result.Sources()) != 4 {
t.Fatalf("cross-tier repeated import = %#v, %v", result, err)
}
reader := configkit.Reader("single-use", strings.NewReader("name: retained\n"))
_, err = configkit.LoadLayered(configkit.Input{Source: configkit.NoSource()}, configkit.LayeredOptions[config]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: reader}}}, {Inputs: []configkit.Input{{Source: reader}}}},
})
if got := layeredErrorCode(t, err); got != configkit.CodeSourceReaderUsed {
t.Fatalf("reader re-consumption = %s", got)
}
}
func TestReplacedSecretRemainsAbsentFromSafeOutput(t *testing.T) {
type config struct {
Secret string `yaml:"secret"`
}
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.Bytes("main", []byte("secret: new-secret-canary\n"))}, configkit.LayeredOptions[config]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping("lower", map[string]any{"secret": "old-secret-canary"})}}}},
Definition: configkit.Definition[config]{Secret: func(path configkit.Path) bool { return path.String() == "/secret" }},
})
if err != nil || result.Config.Secret != "new-secret-canary" {
t.Fatalf("secret load = %#v, %v", result, err)
}
var effective, history bytes.Buffer
if err := result.WriteEffective(&effective); err != nil {
t.Fatal(err)
}
if err := result.WriteExplanation(&history, configkit.Keys("secret")); err != nil {
t.Fatal(err)
}
if strings.Contains(effective.String()+history.String(), "old-secret-canary") || strings.Contains(effective.String()+history.String(), "new-secret-canary") {
t.Fatalf("secret output leaked: %q %q", effective.String(), history.String())
}
_, err = configkit.LoadLayered(configkit.Input{Source: configkit.Bytes("main", []byte("secret: new-secret-canary\n"))}, configkit.LayeredOptions[config]{
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping("lower", map[string]any{"secret": "old-secret-canary"})}}}},
Definition: configkit.Definition[config]{Validate: func(config *config, validation *configkit.Validation) {
validation.Error(configkit.Keys("secret"), "invalid secret")
}},
})
loadError, ok := err.(*configkit.LoadError)
if !ok {
t.Fatalf("validation error = %#v", err)
}
var diagnostics bytes.Buffer
if err := configkit.WriteDiagnostics(&diagnostics, loadError.Diagnostics()); err != nil {
t.Fatal(err)
}
if strings.Contains(diagnostics.String(), "secret-canary") {
t.Fatalf("diagnostic leaked secret: %q", diagnostics.String())
}
}
func layeredErrorCode(t *testing.T, err error) configkit.Code {
t.Helper()
loadError, ok := err.(*configkit.LoadError)
if !ok || len(loadError.Diagnostics()) == 0 {
t.Fatalf("load error = %#v", err)
}
return loadError.Diagnostics()[0].Code
}

View File

@@ -23,10 +23,14 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
return errExplanationUnknownPath return errExplanationUnknownPath
} }
var rendered strings.Builder var rendered strings.Builder
labels := make(map[string]string, len(r.sources))
for _, source := range r.sources {
labels[source.ID] = source.Label
}
rendered.WriteString("path: ") rendered.WriteString("path: ")
rendered.WriteString(strconv.Quote(explanation.Path.String())) rendered.WriteString(strconv.Quote(explanation.Path.String()))
rendered.WriteByte('\n') rendered.WriteByte('\n')
writeExplanationOrigin(&rendered, "effective source", explanation.Origin, r.sources) writeExplanationOrigin(&rendered, "effective source", explanation.Origin, labels)
writeExplanationSnapshot(&rendered, "effective (canonical typed)", explanation.Effective, 0) writeExplanationSnapshot(&rendered, "effective (canonical typed)", explanation.Effective, 0)
if explanation.SecretReference != nil { if explanation.SecretReference != nil {
rendered.WriteString("secret reference: ") rendered.WriteString("secret reference: ")
@@ -43,7 +47,7 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
} else { } else {
rendered.WriteString("canonical typed\n") rendered.WriteString("canonical typed\n")
} }
writeExplanationOrigin(&rendered, " source", change.Origin, r.sources) writeExplanationOrigin(&rendered, " source", change.Origin, labels)
writeExplanationSnapshot(&rendered, " before", change.Before, 4) writeExplanationSnapshot(&rendered, " before", change.Before, 4)
writeExplanationSnapshot(&rendered, " after", change.After, 4) writeExplanationSnapshot(&rendered, " after", change.After, 4)
for _, reason := range change.Reasons { for _, reason := range change.Reasons {
@@ -65,7 +69,7 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
return err return err
} }
func writeExplanationOrigin(output *strings.Builder, label string, origin Origin, sources []SourceInfo) { func writeExplanationOrigin(output *strings.Builder, label string, origin Origin, labels map[string]string) {
output.WriteString(label) output.WriteString(label)
output.WriteString(": layer=") output.WriteString(": layer=")
output.WriteString(strconv.Quote(string(origin.Layer))) output.WriteString(strconv.Quote(string(origin.Layer)))
@@ -77,7 +81,7 @@ func writeExplanationOrigin(output *strings.Builder, label string, origin Origin
for _, location := range origin.Locations { for _, location := range origin.Locations {
output.WriteString(" location: source=") output.WriteString(" location: source=")
output.WriteString(strconv.Quote(location.SourceID)) output.WriteString(strconv.Quote(location.SourceID))
if label := sourceLabel(sources, location.SourceID); label != "" { if label := labels[location.SourceID]; label != "" {
output.WriteString(" label=") output.WriteString(" label=")
output.WriteString(strconv.Quote(label)) output.WriteString(strconv.Quote(label))
} }
@@ -89,15 +93,6 @@ func writeExplanationOrigin(output *strings.Builder, label string, origin Origin
} }
} }
func sourceLabel(sources []SourceInfo, sourceID string) string {
for _, source := range sources {
if source.ID == sourceID {
return source.Label
}
}
return ""
}
func writeExplanationSnapshot(output *strings.Builder, label string, snapshot ValueSnapshot, indent int) { func writeExplanationSnapshot(output *strings.Builder, label string, snapshot ValueSnapshot, indent int) {
if !snapshot.Present { if !snapshot.Present {
output.WriteString(label) output.WriteString(label)

View File

@@ -126,6 +126,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a
} }
func acquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { func acquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) {
initialBytes, initialNodes := accounting.bytes, accounting.nodes
visited := make(map[resourceIdentity]importRoute) visited := make(map[resourceIdentity]importRoute)
hostToken := &namespaceToken{} hostToken := &namespaceToken{}
var documents []*document.Node var documents []*document.Node
@@ -168,6 +169,8 @@ func acquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, account
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
return nil, sources, contexts, diagnostics return nil, sources, contexts, diagnostics
} }
composition.inputBytes = accounting.bytes - initialBytes
composition.inputNodes = accounting.nodes - initialNodes
for pointer, origin := range composition.origins { for pointer, origin := range composition.origins {
origin.Layer = LayerUnderlay origin.Layer = LayerUnderlay
composition.origins[pointer] = origin composition.origins[pointer] = origin

View File

@@ -208,3 +208,73 @@ func groupedErrorCode(t *testing.T, err error) Code {
} }
return loadError.Diagnostics()[0].Code return loadError.Diagnostics()[0].Code
} }
func TestGroupedAccountingChargesMixedOccurrencesOnce(t *testing.T) {
const (
one = "configkit: {imports: [{path: child-one.yml}]}\nleft: first\n"
child = "extra: imported\n"
two = "configkit: {imports: [{path: child-two.yml}]}\nright: second\n"
)
filesystem := NewFilesystem("bundle", fstest.MapFS{
"one.yml": &fstest.MapFile{Data: []byte(one)},
"child-one.yml": &fstest.MapFile{Data: []byte(child)},
"two.yml": &fstest.MapFile{Data: []byte(two)},
"child-two.yml": &fstest.MapFile{Data: []byte(child)},
})
accounting := &importAccounting{}
first, sources, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("one.yml"), imports: true}}}, 1, nil, accounting)
if len(diagnostics) != 0 || first == nil || len(sources) != 2 || accounting.sources != 2 || accounting.bytes != int64(len(one)+len(child)) {
t.Fatalf("first acquisition = %#v, %#v, %#v, accounting %#v", first, sources, diagnostics, accounting)
}
second, sources, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("two.yml"), imports: true}}}, 2, nil, accounting)
if len(diagnostics) != 0 || second == nil || len(sources) != 2 || sources[0].ID != "source-3" || sources[1].ID != "source-4" || accounting.sources != 4 || accounting.bytes != int64(len(one)+len(two)+2*len(child)) {
t.Fatalf("second acquisition = %#v, %#v, %#v, accounting %#v", second, sources, diagnostics, accounting)
}
var wantNodes int
for _, text := range []string{one, child, two, child} {
parsed, diagnostics := parseSource(Bytes("fixture", []byte(text)), "source-1", defaultParseLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
wantNodes += countInputNodes(parsed.document)
}
if accounting.nodes != wantNodes {
t.Fatalf("charged nodes = %d, want %d", accounting.nodes, wantNodes)
}
if first.inputBytes != int64(len(one)+len(child)) || second.inputBytes != int64(len(two)+len(child)) {
t.Fatalf("composition costs = %d, %d, accounting %d", first.inputBytes, second.inputBytes, accounting.bytes)
}
}
func TestGroupedGlobEntryBudgetIsShared(t *testing.T) {
filesystem := NewFilesystem("bundle", fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{glob: 'parts/*.yml'}]}\n")},
"parts/a.yml": &fstest.MapFile{Data: []byte("left: one\n")},
"parts/b.yml": &fstest.MapFile{Data: []byte("right: two\n")},
})
accounting := &importAccounting{entries: maxGlobEntries - 1}
_, _, _, diagnostics := acquireUnderlayGroup(loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}, 1, nil, accounting)
if len(diagnostics) == 0 || diagnostics[0].Code != CodeImportLimitExceeded || accounting.entries <= maxGlobEntries {
t.Fatalf("shared glob budget = %#v, accounting %#v", diagnostics, accounting)
}
}
func TestGroupedMixedSourcesShareCLIRecordLimit(t *testing.T) {
filesystem := NewFilesystem("bundle", fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte("value: base\n")}})
inputs := make([]Input, maxImportSources-1)
inputs[0] = Input{Source: Bytes("empty-bytes", []byte("{}\n"))}
for index := 1; index < len(inputs); index++ {
inputs[index] = Input{Source: Mapping("empty-map", map[string]any{})}
}
type config struct {
Value string `yaml:"value"`
}
_, err := LoadLayered(Input{Source: filesystem.File("main.yml")}, testSecretBinding(), LayeredOptions[config]{
Underlays: []Underlay{{Inputs: inputs}},
Overrides: []Override{{Path: Keys("value"), Value: "cli", Flag: "--value"}},
})
loadError, ok := err.(*LoadError)
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != CodeOverrideLimitExceeded || len(loadError.Sources()) != maxImportSources {
t.Fatalf("mixed source/CLI limit = %#v", err)
}
}

View File

@@ -0,0 +1,156 @@
package engine
import (
"io"
"strconv"
"testing"
"testing/fstest"
)
type benchmarkCatalogConfig struct {
Catalog map[string]string `yaml:"catalog"`
}
type benchmarkNodeConfig struct {
Node string `yaml:"node"`
}
func BenchmarkUnderlayBroadCatalog(b *testing.B) {
for _, width := range []int{128, 256, 512} {
b.Run(strconv.Itoa(width), func(b *testing.B) {
inputs := make([]Input, 2)
for half := range inputs {
catalog := make(map[string]any, width/2)
for index := range width / 2 {
catalog["item-"+strconv.Itoa(half*width/2+index)] = "value"
}
inputs[half] = Input{Source: Mapping("catalog-"+strconv.Itoa(half), map[string]any{"catalog": catalog})}
}
options := LayeredOptions[benchmarkCatalogConfig]{Underlays: []Underlay{{Inputs: inputs}}}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if _, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), options); err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkUnderlayManySmallTiers(b *testing.B) {
for _, count := range []int{32, 64, 128} {
b.Run(strconv.Itoa(count), func(b *testing.B) {
groups := make([]Underlay, count)
for index := range groups {
groups[index].Inputs = []Input{{Source: Mapping("item", map[string]any{
"catalog": map[string]any{"item-" + strconv.Itoa(index): "value"},
})}}
}
options := LayeredOptions[benchmarkCatalogConfig]{Underlays: groups}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if _, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), options); err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkUnderlaySubtreeReplacement(b *testing.B) {
for _, count := range []int{32, 64, 128} {
b.Run(strconv.Itoa(count), func(b *testing.B) {
groups := make([]Underlay, count)
for index := range groups {
var node any = "temporary"
if index%2 == 0 {
node = map[string]any{"left": "one", "right": "two", "nested": map[string]any{"enabled": false}}
}
groups[index].Inputs = []Input{{Source: Mapping("replacement", map[string]any{"node": node})}}
}
options := LayeredOptions[benchmarkNodeConfig]{Underlays: groups}
main := Input{Source: Mapping("main", map[string]any{"node": "final"})}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if _, err := LoadLayered(main, testSecretBinding(), options); err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkUnderlayNativeImports(b *testing.B) {
for _, count := range []int{16, 32, 64} {
b.Run(strconv.Itoa(count), func(b *testing.B) {
files := fstest.MapFS{
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{glob: 'defaults/*.yml'}]}\n")},
}
for index := range count {
name := "item-" + strconv.Itoa(index)
files["defaults/"+name+".yml"] = &fstest.MapFile{Data: []byte("catalog: {" + name + ": value}\n")}
}
filesystem := NewFilesystem("bundle", files)
main := Input{Source: filesystem.File("main.yml"), Imports: true}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if _, err := LoadLayered[benchmarkCatalogConfig](main, testSecretBinding()); err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkUnderlayRetainedHistory(b *testing.B) {
for _, count := range []int{16, 32, 64} {
b.Run(strconv.Itoa(count), func(b *testing.B) {
groups := make([]Underlay, count)
for index := range groups {
groups[index].Inputs = []Input{{Source: Mapping("value", map[string]any{
"catalog": map[string]any{"shared": strconv.Itoa(index)},
})}}
}
options := LayeredOptions[benchmarkCatalogConfig]{Underlays: groups}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
result, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), options)
if err != nil {
b.Fatal(err)
}
if _, ok := result.Inspection.Explain(Keys("catalog", "shared")); !ok {
b.Fatal("retained path was not explained")
}
}
})
}
}
func BenchmarkUnderlayRenderedHistory(b *testing.B) {
for _, count := range []int{32, 64, 128} {
b.Run(strconv.Itoa(count), func(b *testing.B) {
groups := make([]Underlay, count)
for index := range groups {
groups[index].Inputs = []Input{{Source: Mapping("value", map[string]any{
"catalog": map[string]any{"shared": strconv.Itoa(index)},
})}}
}
result, err := LoadLayered(Input{Source: NoSource()}, testSecretBinding(), LayeredOptions[benchmarkCatalogConfig]{Underlays: groups})
if err != nil {
b.Fatal(err)
}
b.ReportAllocs()
b.ResetTimer()
for range b.N {
if err := result.Inspection.WriteExplanation(io.Discard, Keys("catalog", "shared")); err != nil {
b.Fatal(err)
}
}
})
}
}