Reuse frozen inspection mapping lookups

This commit is contained in:
2026-10-09 03:54:14 +00:00
parent 344f4f73d7
commit d32c94eca3
9 changed files with 85 additions and 14 deletions

View File

@@ -371,7 +371,7 @@ Roadmap candidate 7. Owners: engine metadata, metadata cloning, inheritance hist
Validate: `go test ./internal/engine -run 'Inheritance|Inherited|History|Metadata|Independent'`,
engine race tests, and the everyday check.
## Stage 15: Reuse frozen inspection mapping lookups
## Stage 15: Reuse frozen inspection mapping lookups — Complete
Roadmap candidate 7. Owners: publication, inspection, document/snapshot/history helpers.

View File

@@ -7,7 +7,7 @@ type documentLookup struct {
byNode map[*document.Node]map[string]int
}
func buildDocumentLookup(root *document.Node) *documentLookup {
func buildDocumentLookup(roots ...*document.Node) *documentLookup {
lookup := &documentLookup{byNode: make(map[*document.Node]map[string]int)}
visited := make(map[*document.Node]struct{})
var visit func(*document.Node)
@@ -35,7 +35,9 @@ func buildDocumentLookup(root *document.Node) *documentLookup {
}
}
}
visit(root)
for _, root := range roots {
visit(root)
}
return lookup
}

View File

@@ -40,7 +40,7 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
if r == nil || r.final == nil || r.baseline == nil || !r.metadata.hasHistoricalPath(path) {
return Explanation{}, false
}
explanation := Explanation{Path: path.clone(), Effective: valueSnapshot(r.final, path, r.secrets)}
explanation := Explanation{Path: path.clone(), Effective: valueSnapshotWithLookup(r.final, path, r.secrets, r.immutableLookup)}
if info, ok := r.metadata.Lookup(path); ok {
explanation.Origin = cloneOrigin(info.Origin)
}
@@ -53,7 +53,7 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
}
replayIndex := new(mappingIndex)
for _, application := range r.applications {
operationPath, relevant := applicationOperationPath(application, path)
operationPath, relevant := applicationOperationPath(application, path, r.immutableLookup)
var before ValueSnapshot
if relevant {
before = valueSnapshot(working, path, r.secrets)
@@ -69,7 +69,7 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
explanation.History = append(explanation.History, ValueChange{
Before: before,
After: after,
Origin: historyApplicationOrigin(application, operationPath),
Origin: historyApplicationOrigin(application, operationPath, r.immutableLookup),
})
}
appendInheritanceExplanation(&explanation, &working, path, r.inheritance, r.secrets)
@@ -78,13 +78,13 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
// decoding applies them to the final input tree. Show that reconciliation
// explicitly, including canonical scalar conversions, before domain changes.
replayed, _ := documentAtPath(working, path)
decoded, _ := documentAtPath(r.input, path)
decoded, _ := documentAtPathWithLookup(r.input, path, r.immutableLookup)
if !configurationValuesEqual(replayed, decoded) {
origin := cloneOrigin(r.inputOrigins[path.String()])
origin.Operation = ChangeDecoding
explanation.History = append(explanation.History, ValueChange{
Before: valueSnapshot(working, path, r.secrets),
After: valueSnapshot(r.input, path, r.secrets),
After: valueSnapshotWithLookup(r.input, path, r.secrets, r.immutableLookup),
Origin: origin,
Reasons: []string{"applied retained defaults and canonical typed decoding"},
})

View File

@@ -10,6 +10,7 @@ import (
"path/filepath"
"reflect"
"strings"
"sync"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
@@ -566,3 +567,49 @@ func TestExplanationReplayDescriptorsRemainDetachedAcrossQueries(t *testing.T) {
}
t.Fatalf("resolution change missing from repeated explanation: %#v", second.History)
}
func TestConcurrentReadOnlyInspectionQueries(t *testing.T) {
type config struct {
Group struct {
Value string `yaml:"value"`
} `yaml:"group"`
}
result, err := testLoad(Bytes("concurrent-inspection.yml", []byte("group: {value: input}\n")), Options[config]{
Definition: Definition[config]{Resolve: func(value *config, resolution *Resolution) error {
value.Group.Value = "resolved"
resolution.Record(Keys("group", "value"), ChangeNormalization, "normalized", Keys("group", "value"))
return nil
}},
})
if err != nil {
t.Fatal(err)
}
paths := []Path{Keys("group"), Keys("group", "value")}
var wait sync.WaitGroup
errors := make(chan string, 8)
for worker := 0; worker < cap(errors); worker++ {
wait.Add(1)
go func() {
defer wait.Done()
for iteration := 0; iteration < 8; iteration++ {
for _, path := range paths {
explanation, found := result.Explain(path)
if !found || !explanation.Effective.Present {
errors <- "missing effective explanation for " + path.String()
return
}
metadata := result.Metadata()
if _, found := metadata.Lookup(path); !found || len(metadata.History(path)) == 0 {
errors <- "missing metadata for " + path.String()
return
}
}
}
}()
}
wait.Wait()
close(errors)
for message := range errors {
t.Error(message)
}
}

View File

@@ -65,7 +65,7 @@ func cloneHistoryApplications(values []historyApplication) []historyApplication
return cloned
}
func applicationOperationPath(application historyApplication, query Path) (Path, bool) {
func applicationOperationPath(application historyApplication, query Path, lookup *documentLookup) (Path, bool) {
if !application.path.valid() || application.value == nil {
return Path{}, false
}
@@ -80,7 +80,7 @@ func applicationOperationPath(application historyApplication, query Path) (Path,
}
for length := 1; length <= len(query.segments); length++ {
prefix := Path{segments: clonePathSegments(query.segments[:length], 0)}
node, present := documentAtPath(application.value, prefix)
node, present := documentAtPathWithLookup(application.value, prefix, lookup)
if !present {
return Path{}, false
}
@@ -91,7 +91,7 @@ func applicationOperationPath(application historyApplication, query Path) (Path,
return Path{}, false
}
func historyApplicationOrigin(application historyApplication, operationPath Path) Origin {
func historyApplicationOrigin(application historyApplication, operationPath Path, lookup *documentLookup) Origin {
if application.origin.Layer != "" {
return cloneOrigin(application.origin)
}
@@ -100,7 +100,7 @@ func historyApplicationOrigin(application historyApplication, operationPath Path
if !ok {
return origin
}
if node, present := documentAtPath(application.value, relative); present {
if node, present := documentAtPathWithLookup(application.value, relative, lookup); present {
origin.Locations = []Location{publicLocation(node.Location)}
}
return origin

View File

@@ -84,6 +84,7 @@ type Inspection struct {
input *document.Node
inputOrigins map[string]Origin
final *document.Node
immutableLookup *documentLookup
resolution []resolutionSnapshot
resolutionReplay []resolutionSnapshot
applications []historyApplication

View File

@@ -98,6 +98,11 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
final, applications, resolutionSnapshots := resolved.final, resolved.applications, resolved.snapshots
finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, decoded.final)
secrets := classifySecretPaths(schema, definition.Secret, metadata, final, applications, decoded.baseline, decoded.final)
retainedTrees := make([]*document.Node, 0, len(applications)+2)
retainedTrees = append(retainedTrees, decoded.final, final)
for _, application := range applications {
retainedTrees = append(retainedTrees, application.value)
}
inspection := &Inspection{
metadata: metadata,
sources: cloneSourceInfos(accumulator.sources),
@@ -107,6 +112,7 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
input: decoded.final,
inputOrigins: cloneOriginIndex(decoded.origins),
final: final,
immutableLookup: buildDocumentLookup(retainedTrees...),
resolution: resolutionSnapshots,
resolutionReplay: coalescedResolutionSnapshots(resolutionSnapshots),
applications: applications,

View File

@@ -102,7 +102,11 @@ func TestDocumentLookupMatchesPathScan(t *testing.T) {
{Key: "01", Value: &document.Node{Kind: document.String, Text: "leading zero key"}},
{Key: "items", Value: sequence},
}}
lookup := buildDocumentLookup(root)
applicationRoot := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{
{Key: "shared", Value: sequence},
{Key: "application", Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "value", Value: &document.Node{Kind: document.String, Text: "application"}}}}},
}}
lookup := buildDocumentLookup(root, applicationRoot)
paths := []Path{
Path{},
Keys("0"),
@@ -123,6 +127,13 @@ func TestDocumentLookupMatchesPathScan(t *testing.T) {
t.Errorf("indexed lookup %s = (%p, %v), want (%p, %v)", path.String(), gotNode, gotFound, wantNode, wantFound)
}
}
for _, path := range []Path{Path{}, Keys("shared"), Keys("shared").Index(1).Key("name"), Keys("application", "value")} {
wantNode, wantFound := documentAtPath(applicationRoot, path)
gotNode, gotFound := documentAtPathWithLookup(applicationRoot, path, lookup)
if gotNode != wantNode || gotFound != wantFound {
t.Errorf("indexed application lookup %s = (%p, %v), want (%p, %v)", path.String(), gotNode, gotFound, wantNode, wantFound)
}
}
}
func TestApplyResolutionMetadataPreservesOverlappingDeltaOrder(t *testing.T) {

View File

@@ -12,7 +12,11 @@ import (
// Snapshot projection is the shared redaction boundary for effective YAML
// and explanation values. It borrows frozen trees and returns a detached tree.
func valueSnapshot(root *document.Node, path Path, secrets map[string]bool) ValueSnapshot {
node, present := documentAtPath(root, path)
return valueSnapshotWithLookup(root, path, secrets, nil)
}
func valueSnapshotWithLookup(root *document.Node, path Path, secrets map[string]bool, lookup *documentLookup) ValueSnapshot {
node, present := documentAtPathWithLookup(root, path, lookup)
if !present {
return ValueSnapshot{}
}