Remove superseded history attribution paths

This commit is contained in:
2026-10-26 18:11:04 +00:00
parent 6a1447c327
commit 3ffaef7ede
18 changed files with 146 additions and 534 deletions

View File

@@ -1,6 +1,6 @@
# Interface Contract Remediation Implementation Plan # Interface Contract Remediation Implementation Plan
Status: in progress. Stages 1–15 are complete; Stages 16–18 remain pending. Status: in progress. Stages 1–16 are complete; Stages 17–18 remain pending.
Baseline: `f6d54a094a2b5ada5748686620659399dce7ebf0`. Baseline: `f6d54a094a2b5ada5748686620659399dce7ebf0`.
Audience: a Luna coding agent executing the stages sequentially. Audience: a Luna coding agent executing the stages sequentially.
@@ -692,7 +692,9 @@ quota or representation-specific node total is promised. Focused check:
## Stage 16 — Remove Superseded Attribution And Repair Test Seams ## Stage 16 — Remove Superseded Attribution And Repair Test Seams
Owners: all entries in the migration map and tests constructing private inspection Owners: all entries in the migration map and tests constructing private inspection
or metadata values. Status: pending. or metadata values. Status: complete. Validation: `GOTOOLCHAIN=local
GOPROXY=off GOTELEMETRY=off go test ./internal/engine` and
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./...` passed.
1. Trace remaining production reads/writes of old history/inheritance fields and 1. Trace remaining production reads/writes of old history/inheritance fields and
helpers. Remove history-building portions of `metadataFromDecoded`, helpers. Remove history-building portions of `metadataFromDecoded`,

View File

@@ -231,11 +231,12 @@ func TestPreparedInheritanceEvidenceSharesOwnerAndDetachesPublicViews(t *testing
builder.observeBaseline(baseline) builder.observeBaseline(baseline)
builder.setScratch(input) builder.setScratch(input)
builder.observeInheritanceEvidence(expanded.operations) builder.observeInheritanceEvidence(expanded.operations)
builder.observePreCLICheckpoint(expanded.root, expanded.origins)
builder.observePostCLICheckpoint(expanded.root, expanded.origins)
builder.prefixReady = true
metadata := Metadata{historyBuilder: builder} metadata := Metadata{historyBuilder: builder}
resolved := resolvedLoad[historyBudgetSimpleConfig]{ resolved := resolvedLoad[historyBudgetSimpleConfig]{
metadata: metadata, historyBuilder: builder, final: expanded.root, metadata: metadata, final: expanded.root,
decoded: decodedConfiguration{baseline: baseline}, structuralInput: input,
inheritance: expanded,
} }
counter := newRetainedNodeCounter(100) counter := newRetainedNodeCounter(100)
inspection, ok := prepareInspectionHistory(resolved, counter) inspection, ok := prepareInspectionHistory(resolved, counter)

View File

@@ -281,32 +281,3 @@ func resolveInheritanceAttribution(operation inheritanceOperation, destination P
} }
return selections return selections
} }
func operationPathForQuery(root, query Path) Path {
if pathContains(query, root) {
return root
}
return query
}
func inheritanceChangesForQuery(query Path, operation inheritanceOperation) []Change {
if operation.control != nil {
for _, selection := range resolveInheritanceAttribution(operation, query) {
return []Change{{Path: selection.path.clone(), Origin: cloneOrigin(selection.origin), Dependencies: clonePaths(selection.dependencies)}}
}
return nil
}
if operation.transfer == nil {
return nil
}
path := operationPathForQuery(operation.transfer.to, query)
selections := resolveInheritanceAttribution(operation, path)
changes := make([]Change, 0, len(selections))
for _, selection := range selections {
if selection.kind == inheritanceEvidenceDisplaced {
continue
}
changes = append(changes, Change{Path: selection.path.clone(), Origin: cloneOrigin(selection.origin), Dependencies: clonePaths(selection.dependencies)})
}
return changes
}

View File

@@ -63,9 +63,6 @@ func TestInheritanceEvidenceUsesOrderedPayloadAndConcreteTransferShapes(t *testi
displaced[0].attribution.origin.Locations[0].SourceID != "child" { displaced[0].attribution.origin.Locations[0].SourceID != "child" {
t.Fatalf("pre-transfer child shape was not retained: %#v", displaced) t.Fatalf("pre-transfer child shape was not retained: %#v", displaced)
} }
if public := projectedInheritanceChanges(displacedPath, model.inheritancePayloads[0].operations[1]); len(public) != 0 {
t.Fatalf("displaced child path unexpectedly projected publicly: %#v", public)
}
if builder.scratch != nil { if builder.scratch != nil {
t.Fatal("frozen builder unexpectedly retained scratch") t.Fatal("frozen builder unexpectedly retained scratch")
} }
@@ -74,31 +71,41 @@ func TestInheritanceEvidenceUsesOrderedPayloadAndConcreteTransferShapes(t *testi
} }
} }
func TestInheritanceResolverSharesProjectionAttribution(t *testing.T) { func TestInheritanceModelSharesProjectionAttribution(t *testing.T) {
t.Parallel() t.Parallel()
from, to, reference := Keys("parents", "base"), Keys("children", "item"), Keys("parents", "base") from, to, reference := Keys("parents", "base"), Keys("children", "item"), Keys("parents", "base")
path := to.Key("nested").Key("value") path := to.Key("nested").Key("value")
parent := Origin{Layer: LayerBase, Locations: []Location{{SourceID: "base"}}} parent := Origin{Layer: LayerBase, Locations: []Location{{SourceID: "base"}}}
child := Origin{Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}} child := Origin{Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}}
parentDocument := parseDocumentForDecode(t, "nested: {value: parent}\n")
effectiveDocument := parseDocumentForDecode(t, "nested: {value: child}\n")
operation := inheritanceOperation{ operation := inheritanceOperation{
transfer: &inheritanceTransfer{from: from, to: to, reference: reference}, transfer: &inheritanceTransfer{from: from, to: to, reference: reference},
parent: parentDocument,
effective: effectiveDocument,
parentOrigins: map[string]Origin{from.Key("nested").Key("value").String(): parent}, parentOrigins: map[string]Origin{from.Key("nested").Key("value").String(): parent},
effectiveOrigins: map[string]Origin{path.String(): child}, effectiveOrigins: map[string]Origin{path.String(): child},
} }
selections := resolveInheritanceAttribution(operation, path) root := parseDocumentForDecode(t, "children: {item: {nested: {value: old}}}\n")
if len(selections) != 2 || selections[0].kind != inheritanceEvidenceParent || selections[1].kind != inheritanceEvidenceChild { builder := newHistoryBuilder()
t.Fatalf("shared resolver selections = %#v", selections) builder.setScratch(root)
builder.observeInheritanceEvidence([]inheritanceOperation{operation})
model := builder.freeze()
selections := model.selectHistory(path)
if len(selections) != 2 || selections[0].role != historyRoleOperation || selections[1].role != historyRoleOperation {
t.Fatalf("model inheritance selections = %#v", selections)
} }
if selections[0].path.String() != selections[1].path.String() || selections[0].origin.Operation != ChangeInheritance || selections[1].origin.Operation != ChangeInheritance { if selections[0].reportedPath.String() != to.String() || selections[1].reportedPath.String() != to.String() ||
t.Fatalf("parent and child attributions did not share one resolver: %#v", selections) selections[0].attribution.origin.Operation != ChangeInheritance || selections[1].attribution.origin.Operation != ChangeInheritance {
t.Fatalf("parent and child attributions did not share the model owner: %#v", selections)
} }
changes := projectedInheritanceChanges(path, operation) if selections[0].attribution.origin.Locations[0].SourceID != "base" ||
if len(changes) != 2 || changes[0].Origin.Locations[0].SourceID != selections[0].origin.Locations[0].SourceID || selections[1].attribution.origin.Locations[0].SourceID != "child" ||
changes[1].Origin.Locations[0].SourceID != selections[1].origin.Locations[0].SourceID || len(selections[0].attribution.dependencies) != 2 || selections[0].attribution.dependencies[0].String() != from.String() ||
changes[0].Dependencies[0].String() != from.String() || changes[0].Dependencies[1].String() != reference.String() || selections[0].attribution.dependencies[1].String() != reference.String() ||
changes[1].Dependencies[0].String() != reference.String() { len(selections[1].attribution.dependencies) != 1 || selections[1].attribution.dependencies[0].String() != reference.String() {
t.Fatalf("metadata projection attribution = %#v; resolver = %#v", changes, selections) t.Fatalf("model attribution dependencies or origins differ: %#v", selections)
} }
} }

View File

@@ -124,7 +124,14 @@ func TestMetadataFinalizationRegistersResolutionInventoryAndRoots(t *testing.T)
var metadata Metadata var metadata Metadata
snapshots := []resolutionSnapshot{parentSnapshot, snapshot} snapshots := []resolutionSnapshot{parentSnapshot, snapshot}
finalizeMetadataPathsWithResolution(&metadata, final, nil, snapshots, before, before) builder := newHistoryBuilder()
builder.setScratch(before)
builder.observeDocument(before, historyPhaseBaseline)
builder.observeDocument(final, historyPhasePostCLI)
builder.prefixReady = true
builder.observeResolutionEvidence(snapshots)
metadata.historyBuilder = builder
freezeMetadataHistory(&metadata, final)
if got := metadata.HistoricalPaths(); len(got) != 3 || got[0].String() != "" || got[1].String() != "/shape" || got[2].String() != "/shape/0" { if got := metadata.HistoricalPaths(); len(got) != 3 || got[0].String() != "" || got[1].String() != "/shape" || got[2].String() != "/shape/0" {
t.Fatalf("historical paths = %#v", got) t.Fatalf("historical paths = %#v", got)
} }

View File

@@ -272,19 +272,18 @@ func TestProjectedSequenceHistoryAgreesAcrossMetadataAndExplanation(t *testing.T
t.Fatalf("compose diagnostics = %#v", diagnostics) t.Fatalf("compose diagnostics = %#v", diagnostics)
} }
path := Keys("records").Index(1).Key("token") path := Keys("records").Index(1).Key("token")
var metadata Metadata baseline := &document.Node{Kind: document.Mapping}
finalizeMetadataPaths(&metadata, composed.root, composed.applications) model := sourceHistoryModelForInspection(baseline, composed.root, composed.origins, composed.applications, composed.overlays)
metadata := Metadata{historyModel: model}
finalizeEffectiveMetadataPaths(&metadata, composed.root)
if !metadata.hasHistoricalPath(path) { if !metadata.hasHistoricalPath(path) {
t.Fatalf("projected metadata omitted shifted path %s: %#v", path, metadata.HistoricalPaths()) t.Fatalf("projected metadata omitted shifted path %s: %#v", path, metadata.HistoricalPaths())
} }
baseline := &document.Node{Kind: document.Mapping}
model := sourceHistoryModelForInspection(baseline, composed.root, composed.origins, composed.applications, composed.overlays)
metadata.historyModel = model
inspection := &Inspection{metadata: metadata, history: model, final: composed.root} inspection := &Inspection{metadata: metadata, history: model, final: composed.root}
inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool { inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool {
return reflect.DeepEqual(candidate, path) return reflect.DeepEqual(candidate, path)
}, metadata, composed.root, nil) }, metadata)
explanation, ok := inspection.Explain(path) explanation, ok := inspection.Explain(path)
if !ok || !explanation.Effective.Redacted { if !ok || !explanation.Effective.Redacted {
t.Fatalf("shifted explanation = %#v, %v", explanation, ok) t.Fatalf("shifted explanation = %#v, %v", explanation, ok)
@@ -323,15 +322,14 @@ func TestReplacedProjectedSensitiveRecordRemainsRedacted(t *testing.T) {
}) })
final := applications[len(applications)-1].value final := applications[len(applications)-1].value
path := Keys("records").Index(1).Key("token") path := Keys("records").Index(1).Key("token")
var metadata Metadata
finalizeMetadataPaths(&metadata, final, applications)
baseline := &document.Node{Kind: document.Mapping} baseline := &document.Node{Kind: document.Mapping}
model := sourceHistoryModelForInspection(baseline, final, nil, applications, nil) model := sourceHistoryModelForInspection(baseline, final, nil, applications, nil)
metadata.historyModel = model metadata := Metadata{historyModel: model}
finalizeEffectiveMetadataPaths(&metadata, final)
inspection := &Inspection{metadata: metadata, history: model, final: final} inspection := &Inspection{metadata: metadata, history: model, final: final}
inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool { inspection.secrets = classifySecretPaths(nil, func(candidate Path) bool {
return reflect.DeepEqual(candidate, path) return reflect.DeepEqual(candidate, path)
}, metadata, final, nil) }, metadata)
explanation, ok := inspection.Explain(path) explanation, ok := inspection.Explain(path)
if !ok || explanation.Effective.Present { if !ok || explanation.Effective.Present {
t.Fatalf("replaced shifted record explanation = %#v, %v", explanation, ok) t.Fatalf("replaced shifted record explanation = %#v, %v", explanation, ok)
@@ -404,14 +402,13 @@ func TestMappedUpsertProjectsSensitiveReplacementEvidence(t *testing.T) {
finalRecords, _ := documentAtPath(finalRoot, Keys("records")) finalRecords, _ := documentAtPath(finalRoot, Keys("records"))
finalRecords.Sequence = document.Clone(combined.value).Sequence finalRecords.Sequence = document.Clone(combined.value).Sequence
var metadata Metadata
finalizeMetadataPaths(&metadata, finalRoot, applications)
baseline := &document.Node{Kind: document.Mapping} baseline := &document.Node{Kind: document.Mapping}
model := sourceHistoryModelForInspection(baseline, finalRoot, nil, applications, nil) model := sourceHistoryModelForInspection(baseline, finalRoot, nil, applications, nil)
metadata.historyModel = model metadata := Metadata{historyModel: model}
finalizeEffectiveMetadataPaths(&metadata, finalRoot)
inspection := &Inspection{metadata: metadata, history: model, final: finalRoot} inspection := &Inspection{metadata: metadata, history: model, final: finalRoot}
secretRule := func(candidate Path) bool { return reflect.DeepEqual(candidate, secretPath) } secretRule := func(candidate Path) bool { return reflect.DeepEqual(candidate, secretPath) }
inspection.secrets = classifySecretPaths(nil, secretRule, metadata, finalRoot, nil) inspection.secrets = classifySecretPaths(nil, secretRule, metadata)
explanation, ok := inspection.Explain(secretPath) explanation, ok := inspection.Explain(secretPath)
if !ok || !explanation.Effective.Redacted { if !ok || !explanation.Effective.Redacted {
t.Fatalf("mapped replacement explanation = %#v, %v", explanation, ok) t.Fatalf("mapped replacement explanation = %#v, %v", explanation, ok)

View File

@@ -1,63 +1,6 @@
package engine package engine
import ( import "slices"
"slices"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
// Retain source history at original paths. Structural operations are held once
// per relationship and projected onto a requested destination path only when
// History or Explain is queried.
func applyInheritanceMetadata(metadata *Metadata, raw *document.Node, rawOrigins map[string]Origin, overlays []overlayRecord, expansion *inheritanceScope) {
if metadata == nil || expansion == nil {
return
}
sourceView := Metadata{
fields: make(map[string]FieldInfo),
history: metadata.history,
}
walkDocument(raw, Path{}, func(path Path, node *document.Node) {
origin, found := rawOrigins[path.String()]
if !found {
origin = Origin{Layer: LayerBase, Locations: []Location{publicLocation(node.Location)}}
}
sourceView.fields[path.String()] = FieldInfo{Explicit: true, Null: node.Kind == document.Null, Origin: cloneOrigin(origin)}
})
applyOverlayMetadata(&sourceView, overlays)
metadata.history = sourceView.history
}
func historyWithInheritance(path Path, stored []Change, operations []inheritanceOperation) []Change {
split := len(stored)
for index, change := range stored {
if change.Origin.Layer == LayerCLI || change.Origin.Layer == LayerResolution {
split = index
break
}
}
history := cloneChanges(stored[:split])
for _, operation := range operations {
for _, change := range projectedInheritanceChanges(path, operation) {
if len(history) != 0 {
change.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
}
history = append(history, change)
}
}
for _, change := range stored[split:] {
cloned := cloneChange(change)
if len(history) != 0 {
cloned.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
}
history = append(history, cloned)
}
return history
}
func projectedInheritanceChanges(query Path, operation inheritanceOperation) []Change {
return inheritanceChangesForQuery(query, operation)
}
func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) { func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) {
if inheritanceOperationIdentity(operation, destination) { if inheritanceOperationIdentity(operation, destination) {

View File

@@ -125,9 +125,11 @@ func inheritanceInspectionFromComposition(t *testing.T, composed *composedDocume
metadata := metadataFromDecoded(decoded) metadata := metadataFromDecoded(decoded)
metadata.historyBuilder.setScratch(composed.root) metadata.historyBuilder.setScratch(composed.root)
metadata.historyBuilder.observeSourceEvidence(composed.root, composed.origins, composed.applications, composed.overlays) metadata.historyBuilder.observeSourceEvidence(composed.root, composed.origins, composed.applications, composed.overlays)
applyInheritanceMetadata(&metadata, composed.root, composed.origins, composed.overlays, expanded)
metadata.historyBuilder.setScratch(composed.root) metadata.historyBuilder.setScratch(composed.root)
metadata.historyBuilder.observeInheritanceEvidence(expanded.operations) metadata.historyBuilder.observeInheritanceEvidence(expanded.operations)
metadata.historyBuilder.observePreCLICheckpoint(expanded.root, expanded.origins)
metadata.historyBuilder.observePostCLICheckpoint(expanded.root, expanded.origins)
metadata.historyBuilder.prefixReady = true
final := expanded.root final := expanded.root
if len(snapshots) != 0 { if len(snapshots) != 0 {
final = document.Clone(expanded.root) final = document.Clone(expanded.root)
@@ -137,7 +139,10 @@ func inheritanceInspectionFromComposition(t *testing.T, composed *composedDocume
} }
} }
} }
finalizeMetadataPathsWithResolution(&metadata, final, composed.applications, snapshots, baseline, composed.root, expanded.root) if len(snapshots) != 0 {
metadata.historyBuilder.observeResolutionEvidence(snapshots)
}
freezeMetadataHistory(&metadata, final)
return &Inspection{ return &Inspection{
metadata: metadata, history: metadata.historyModel, final: final, metadata: metadata, history: metadata.historyModel, final: final,
}, expanded }, expanded

View File

@@ -15,7 +15,7 @@ func TestInheritanceSensitivityFollowsCopiesWithoutMaskingNeighbors(t *testing.T
inspection, _ := inheritanceInspectionForTest(t, input) inspection, _ := inheritanceInspectionForTest(t, input)
inspection.secrets = classifySecretPaths(nil, func(path Path) bool { inspection.secrets = classifySecretPaths(nil, func(path Path) bool {
return path.String() == classified.String() return path.String() == classified.String()
}, inspection.metadata, inspection.final, nil) }, inspection.metadata)
for _, path := range []Path{Keys("parent", "token"), Keys("child", "token"), Keys("next", "token"), Keys("other", "token")} { for _, path := range []Path{Keys("parent", "token"), Keys("child", "token"), Keys("next", "token"), Keys("other", "token")} {
explanation, found := inspection.Explain(path) explanation, found := inspection.Explain(path)
if !found || !explanation.Effective.Redacted || strings.Contains(explanation.Effective.YAML, canary) { if !found || !explanation.Effective.Redacted || strings.Contains(explanation.Effective.YAML, canary) {
@@ -44,7 +44,7 @@ func TestInheritanceSensitivityPreservesDisplacedHistoryAndControl(t *testing.T)
inspection, _ := inheritanceInspectionForTest(t, parseCompositionDocument(t, "source-1", "parent: {branch: {token: "+canary+"}}\nchild: {extends: parent, branch: public}\n")) inspection, _ := inheritanceInspectionForTest(t, parseCompositionDocument(t, "source-1", "parent: {branch: {token: "+canary+"}}\nchild: {extends: parent, branch: public}\n"))
inspection.secrets = classifySecretPaths(nil, func(path Path) bool { inspection.secrets = classifySecretPaths(nil, func(path Path) bool {
return path.String() == Keys("parent", "branch", "token").String() || path.String() == Keys("child", inheritanceKey).String() return path.String() == Keys("parent", "branch", "token").String() || path.String() == Keys("child", inheritanceKey).String()
}, inspection.metadata, inspection.final, nil) }, inspection.metadata)
path := Keys("child", "branch", "token") path := Keys("child", "branch", "token")
explanation, found := inspection.Explain(path) explanation, found := inspection.Explain(path)
if !found || explanation.Effective.Present { if !found || explanation.Effective.Present {
@@ -85,7 +85,7 @@ func TestInheritanceSensitivityIncludesSchemaTaggedSource(t *testing.T) {
t.Fatal(issue) t.Fatal(issue)
} }
inspection, _ := inheritanceInspectionForTest(t, parseCompositionDocument(t, "source-1", "parent: {token: TAGGED-CANARY}\nchild: {extends: parent}\n")) inspection, _ := inheritanceInspectionForTest(t, parseCompositionDocument(t, "source-1", "parent: {token: TAGGED-CANARY}\nchild: {extends: parent}\n"))
inspection.secrets = classifySecretPaths(schema, nil, inspection.metadata, inspection.final, nil) inspection.secrets = classifySecretPaths(schema, nil, inspection.metadata)
child, found := inspection.Explain(Keys("child", "token")) child, found := inspection.Explain(Keys("child", "token"))
if !found || !child.Effective.Redacted || strings.Contains(child.Effective.YAML, "TAGGED-CANARY") { if !found || !child.Effective.Redacted || strings.Contains(child.Effective.YAML, "TAGGED-CANARY") {
t.Fatalf("tagged source copy = %#v, %v", child, found) t.Fatalf("tagged source copy = %#v, %v", child, found)
@@ -104,7 +104,7 @@ func TestSourceVersionEvidenceSensitivityFollowsInheritanceAndDisplacedHistory(t
} }
inspection.secrets = classifySecretPathsWithEvidence(nil, func(path Path) bool { inspection.secrets = classifySecretPathsWithEvidence(nil, func(path Path) bool {
return path.String() == secretPath.String() return path.String() == secretPath.String()
}, inspection.metadata, inspection.final, nil, []*sourceVersionEvidence{evidence}) }, inspection.metadata, []*sourceVersionEvidence{evidence})
path := Keys("child", "branch", "token") path := Keys("child", "branch", "token")
explanation, found := inspection.Explain(path) explanation, found := inspection.Explain(path)
@@ -162,7 +162,7 @@ func TestInheritedSecretReferencesResolveOnlyAfterExpansion(t *testing.T) {
t.Fatalf("inherited reference resolution = %#v, %d", resolved.diagnostics, lookupCount) t.Fatalf("inherited reference resolution = %#v, %d", resolved.diagnostics, lookupCount)
} }
inspection, _ := inheritanceInspectionFromComposition(t, composed) inspection, _ := inheritanceInspectionFromComposition(t, composed)
inspection.secrets = classifySecretPaths(schema, nil, inspection.metadata, inspection.final, nil) inspection.secrets = classifySecretPaths(schema, nil, inspection.metadata)
var rendered bytes.Buffer var rendered bytes.Buffer
if err := inspection.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), "RESOLVED-CREDENTIAL-CANARY") { if err := inspection.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), "RESOLVED-CREDENTIAL-CANARY") {
t.Fatalf("reference rendering = %q, %v", rendered.String(), err) t.Fatalf("reference rendering = %q, %v", rendered.String(), err)

View File

@@ -60,108 +60,13 @@ func (input resolutionInput) sourceContext(id string) (ResourceContext, bool) {
func metadataFromDecoded(decoded decodedConfiguration) Metadata { func metadataFromDecoded(decoded decodedConfiguration) Metadata {
metadata := Metadata{ metadata := Metadata{
fields: make(map[string]FieldInfo), fields: make(map[string]FieldInfo),
history: make(map[string][]Change),
historyBuilder: newHistoryBuilder(), historyBuilder: newHistoryBuilder(),
} }
metadata.historyBuilder.observeBaseline(decoded.baseline) metadata.historyBuilder.observeBaseline(decoded.baseline)
baseline := make(map[string]*document.Node)
indexDocument(decoded.baseline, Path{}, baseline)
for pointer := range baseline {
path, err := ParsePath(pointer)
if err != nil {
continue
}
metadata.history[pointer] = append(metadata.history[pointer], Change{
Path: path, Origin: Origin{Layer: LayerDefault},
})
}
for pointer := range decoded.presence {
path, err := ParsePath(pointer)
if err != nil {
continue
}
origin := cloneOrigin(decoded.origins[pointer])
change := Change{Path: path, Origin: origin}
if _, ok := baseline[pointer]; ok {
change.Previous = []Origin{{Layer: LayerDefault}}
}
metadata.history[pointer] = append(metadata.history[pointer], change)
}
collectFieldInfo(decoded.final, Path{}, decoded, metadata.fields) collectFieldInfo(decoded.final, Path{}, decoded, metadata.fields)
return metadata return metadata
} }
func applyOverlayMetadata(metadata *Metadata, records []overlayRecord) {
baseline := make(map[string][]Change, len(metadata.history))
for pointer, history := range metadata.history {
for _, change := range history {
if change.Origin.Layer == LayerDefault {
baseline[pointer] = append(baseline[pointer], cloneChange(change))
}
}
}
events := make(map[string][]Change)
for _, record := range records {
exact := false
for _, displaced := range record.displaced {
pointer := displaced.path.String()
appendInputOriginEvent(events, baseline, pointer, displaced.path, displaced.origin)
appendInputChangeEvent(events, baseline, pointer, record.change)
if pointer == record.change.Path.String() {
exact = true
}
}
if !exact {
pointer := record.change.Path.String()
for _, previous := range record.change.Previous {
appendInputOriginEvent(events, baseline, pointer, record.change.Path, previous)
}
appendInputChangeEvent(events, baseline, pointer, record.change)
}
}
for pointer, info := range metadata.fields {
if !info.Explicit {
continue
}
path, err := ParsePath(pointer)
if err != nil {
continue
}
appendInputOriginEvent(events, baseline, pointer, path, info.Origin)
}
metadata.history = make(map[string][]Change, len(baseline)+len(events))
for pointer, history := range baseline {
metadata.history[pointer] = cloneChanges(history)
}
for pointer, history := range events {
metadata.history[pointer] = append(metadata.history[pointer], cloneChanges(history)...)
}
}
func appendInputOriginEvent(events map[string][]Change, baseline map[string][]Change, pointer string, path Path, origin Origin) {
if origin.Layer == "" {
return
}
history := events[pointer]
if len(history) != 0 && originRepresentedByChange(path, origin, history[len(history)-1]) {
return
}
appendInputChangeEvent(events, baseline, pointer, Change{Path: path.clone(), Origin: cloneOrigin(origin)})
}
func appendInputChangeEvent(events map[string][]Change, baseline map[string][]Change, pointer string, value Change) {
change := cloneChange(value)
history := events[pointer]
if len(history) != 0 {
change.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
} else if defaults := baseline[pointer]; len(defaults) != 0 {
change.Previous = []Origin{cloneOrigin(defaults[len(defaults)-1].Origin)}
} else {
change.Previous = nil
}
events[pointer] = append(history, change)
}
func originContainedBy(candidate, container Origin) bool { func originContainedBy(candidate, container Origin) bool {
if candidate.Layer != container.Layer || candidate.Operation != container.Operation { if candidate.Layer != container.Layer || candidate.Operation != container.Operation {
return false return false
@@ -195,14 +100,6 @@ func originContainedBy(candidate, container Origin) bool {
return true return true
} }
func cloneChanges(changes []Change) []Change {
cloned := make([]Change, len(changes))
for index, change := range changes {
cloned[index] = cloneChange(change)
}
return cloned
}
func collectFieldInfo(node *document.Node, path Path, decoded decodedConfiguration, fields map[string]FieldInfo) { func collectFieldInfo(node *document.Node, path Path, decoded decodedConfiguration, fields map[string]FieldInfo) {
pointer := path.String() pointer := path.String()
_, explicit := decoded.presence[pointer] _, explicit := decoded.presence[pointer]
@@ -224,13 +121,11 @@ func collectFieldInfo(node *document.Node, path Path, decoded decodedConfigurati
} }
func cloneMetadata(metadata Metadata) Metadata { func cloneMetadata(metadata Metadata) Metadata {
if metadata.fields == nil && metadata.history == nil && metadata.historyModel == nil { if metadata.fields == nil && metadata.historyModel == nil {
return Metadata{} return Metadata{}
} }
cloned := Metadata{ cloned := Metadata{
fields: make(map[string]FieldInfo, len(metadata.fields)), fields: make(map[string]FieldInfo, len(metadata.fields)),
history: make(map[string][]Change, len(metadata.history)),
inheritance: append([]inheritanceOperation(nil), metadata.inheritance...),
historyModel: metadata.historyModel, historyModel: metadata.historyModel,
} }
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths)) cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
@@ -240,13 +135,6 @@ func cloneMetadata(metadata Metadata) Metadata {
for path, info := range metadata.fields { for path, info := range metadata.fields {
cloned.fields[path] = cloneFieldInfo(info) cloned.fields[path] = cloneFieldInfo(info)
} }
for path, changes := range metadata.history {
clonedChanges := make([]Change, len(changes))
for index, change := range changes {
clonedChanges[index] = cloneChange(change)
}
cloned.history[path] = clonedChanges
}
return cloned return cloned
} }
@@ -268,56 +156,16 @@ func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult, entr
for snapshotID, snapshot := range resolved.snapshots { for snapshotID, snapshot := range resolved.snapshots {
pointer := snapshotPointers[snapshotID] pointer := snapshotPointers[snapshotID]
change := cloneChange(snapshot.change)
if info, ok := metadata.fields[pointer]; ok { if info, ok := metadata.fields[pointer]; ok {
change.Previous = []Origin{cloneOrigin(info.Origin)} info.Origin = cloneOrigin(snapshot.change.Origin)
info.Origin = cloneOrigin(change.Origin)
metadata.fields[pointer] = info metadata.fields[pointer] = info
} }
metadata.history[pointer] = append(metadata.history[pointer], change)
} }
appendResolutionDescendantHistory(metadata, resolved.snapshots, snapshotPointers, finalPaths, &deltaCoverage, snapshotIDs)
applyResolutionFieldOrigins(metadata.fields, resolved.snapshots, &deltaCoverage, snapshotIDs) applyResolutionFieldOrigins(metadata.fields, resolved.snapshots, &deltaCoverage, snapshotIDs)
refreshResolutionFields(metadata.fields, finalPaths, resolved.final, entry, resolved.snapshots, snapshotCoverage) refreshResolutionFields(metadata.fields, finalPaths, resolved.final, entry, resolved.snapshots, snapshotCoverage)
} }
func appendResolutionDescendantHistory(metadata *Metadata, snapshots []resolutionSnapshot, snapshotPointers []string, finalPaths map[string]*document.Node, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
allPointers := make(map[string]struct{}, len(metadata.history)+len(finalPaths))
for pointer := range metadata.history {
if _, err := ParsePath(pointer); err == nil {
allPointers[pointer] = struct{}{}
}
}
for pointer := range finalPaths {
allPointers[pointer] = struct{}{}
}
buckets := make([][]string, len(snapshotIDs))
for pointer := range allPointers {
deltaIndex.visitAncestors(pointer, func(deltaID int) {
snapshotID := snapshotIDs[deltaID]
if snapshotID < 0 || pointer == snapshotPointers[snapshotID] {
return
}
buckets[deltaID] = append(buckets[deltaID], pointer)
})
}
for deltaID, pointers := range buckets {
snapshotID := snapshotIDs[deltaID]
if snapshotID < 0 {
continue
}
slices.Sort(pointers)
for _, pointer := range pointers {
change := cloneChange(snapshots[snapshotID].change)
if info, ok := metadata.fields[pointer]; ok {
change.Previous = []Origin{cloneOrigin(info.Origin)}
}
metadata.history[pointer] = append(metadata.history[pointer], change)
}
}
}
func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolutionSnapshot, deltaIndex *pathPrefixIndex, snapshotIDs []int) { func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolutionSnapshot, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
buckets := make([][]string, len(snapshotIDs)) buckets := make([][]string, len(snapshotIDs))
for pointer := range fields { for pointer := range fields {

View File

@@ -10,19 +10,10 @@ import (
// it owns no source access, options, or long-lived schema state. // it owns no source access, options, or long-lived schema state.
type resolvedLoad[T any] struct { type resolvedLoad[T any] struct {
configuration T configuration T
decoded decodedConfiguration
metadata Metadata metadata Metadata
historyBuilder *historyBuilder
final *document.Node final *document.Node
snapshots []resolutionSnapshot
applications []historyApplication
postDecodeApps []historyApplication
structuralInput *document.Node
preCLI *document.Node
preCLIOrigins map[string]Origin
referenceInput *document.Node referenceInput *document.Node
versionEvidence []*sourceVersionEvidence versionEvidence []*sourceVersionEvidence
inheritance *inheritanceScope
contexts map[string]ResourceContext contexts map[string]ResourceContext
} }
@@ -57,10 +48,7 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
decoded := prepared.decoded decoded := prepared.decoded
configuration := decoded.value.Interface().(T) configuration := decoded.value.Interface().(T)
metadata := prepared.metadata metadata := prepared.metadata
builder := prepared.historyBuilder builder := metadata.historyBuilder
if builder == nil {
builder = metadata.historyBuilder
}
final := decoded.final final := decoded.final
referenceInput := prepared.referenceInput referenceInput := prepared.referenceInput
var resolutionSnapshots []resolutionSnapshot var resolutionSnapshots []resolutionSnapshot
@@ -86,22 +74,13 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
if refreshReferenceInput { if refreshReferenceInput {
referenceInput = documentWithMetadataLocations(final, metadata, input.document) referenceInput = documentWithMetadataLocations(final, metadata, input.document)
} }
applications := []historyApplication(nil)
if input.composition != nil {
applications = input.composition.applications
} else if input.document != nil {
applications = []historyApplication{{layer: LayerBase, value: input.document}}
}
var versionEvidence []*sourceVersionEvidence var versionEvidence []*sourceVersionEvidence
if input.versionState != nil { if input.versionState != nil {
versionEvidence = append(versionEvidence, input.versionState.evidence...) versionEvidence = append(versionEvidence, input.versionState.evidence...)
} }
return resolvedLoad[T]{ return resolvedLoad[T]{
configuration: configuration, decoded: decoded, metadata: metadata, historyBuilder: builder, final: final, configuration: configuration, metadata: metadata, final: final, referenceInput: referenceInput,
snapshots: resolutionSnapshots, applications: applications, versionEvidence: versionEvidence, contexts: input.contexts,
postDecodeApps: prepared.postDecodeApps, structuralInput: document.Clone(prepared.structuralInput),
preCLI: prepared.preCLI, preCLIOrigins: prepared.preCLIOrigins, referenceInput: referenceInput,
versionEvidence: versionEvidence, inheritance: input.inheritance, contexts: input.contexts,
}, diagnostics }, diagnostics
} }
@@ -117,21 +96,7 @@ func detachLoadConfiguration[T any](schema *schemaDescriptor, configuration T, m
func prepareInspectionHistory[T any](resolved resolvedLoad[T], counter *retainedNodeCounter) (*Inspection, bool) { func prepareInspectionHistory[T any](resolved resolvedLoad[T], counter *retainedNodeCounter) (*Inspection, bool) {
metadata := resolved.metadata metadata := resolved.metadata
if resolved.historyBuilder != nil { freezeMetadataHistory(&metadata, resolved.final)
metadata.historyBuilder = resolved.historyBuilder
} else if resolved.inheritance != nil {
builder := newHistoryBuilder()
if resolved.decoded.baseline != nil {
builder.observeBaseline(resolved.decoded.baseline)
}
builder.setScratch(resolved.final)
builder.observeInheritanceEvidence(resolved.inheritance.operations)
metadata.historyBuilder = builder
}
finalizeMetadataPathsWithResolution(
&metadata, resolved.final, resolved.applications, resolved.snapshots,
resolved.decoded.baseline, resolved.structuralInput, resolved.preCLI, resolved.decoded.final,
)
model := metadata.historyModel model := metadata.historyModel
inspection := &Inspection{ inspection := &Inspection{
metadata: metadata, metadata: metadata,
@@ -152,8 +117,8 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
accumulator.add(*issue) accumulator.add(*issue)
return nil, accumulator.loadError() return nil, accumulator.loadError()
} }
metadata, final := inspection.metadata, inspection.final metadata := inspection.metadata
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, nil, resolved.versionEvidence) secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, resolved.versionEvidence)
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false) sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
inspection.metadata = metadata inspection.metadata = metadata
inspection.sources = sources inspection.sources = sources

View File

@@ -7,22 +7,10 @@ import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type Metadata struct { type Metadata struct {
effectivePaths map[string]Path effectivePaths map[string]Path
fields map[string]FieldInfo fields map[string]FieldInfo
history map[string][]Change
inheritance []inheritanceOperation
historyBuilder *historyBuilder historyBuilder *historyBuilder
historyModel *historyModel historyModel *historyModel
} }
func (m Metadata) currentHistoryModel() *historyModel {
if m.historyModel != nil {
return m.historyModel
}
if m.historyBuilder != nil {
return m.historyBuilder.model
}
return nil
}
// Lookup returns information for an effective path. Unknown and invalid paths // Lookup returns information for an effective path. Unknown and invalid paths
// return false. // return false.
func (m Metadata) Lookup(path Path) (FieldInfo, bool) { func (m Metadata) Lookup(path Path) (FieldInfo, bool) {
@@ -155,80 +143,13 @@ func (m Metadata) hasHistoricalPath(path Path) bool {
return m.historyEvidence().hasObservedShape(path) return m.historyEvidence().hasObservedShape(path)
} }
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) { func freezeMetadataHistory(m *Metadata, final *document.Node) {
finalizeMetadataPathsWithResolution(m, final, applications, nil, earlier...)
}
func finalizeMetadataPathsWithResolution(m *Metadata, final *document.Node, applications []historyApplication, snapshots []resolutionSnapshot, earlier ...*document.Node) {
finalizeEffectiveMetadataPaths(m, final) finalizeEffectiveMetadataPaths(m, final)
builder := m.historyBuilder builder := m.historyBuilder
if builder != nil && builder.prefixReady { if builder == nil || !builder.prefixReady {
if len(snapshots) != 0 && builder.model.resolutionPayload == nil { panic("configkit: metadata history prefix is not ready")
if len(earlier) != 0 {
builder.setScratch(earlier[len(earlier)-1])
}
builder.observeResolutionEvidence(snapshots)
}
if builder.model.final != final {
builder.model.final = final
}
m.historyModel = builder.freeze()
m.historyBuilder = nil
return
}
if builder == nil {
builder = newHistoryBuilder()
}
if len(earlier) != 0 {
builder.model.baseline = earlier[0]
} }
builder.model.final = final builder.model.final = final
for index, tree := range earlier {
phase := historyPhaseSources
switch index {
case 0:
phase = historyPhaseBaseline
case 2:
phase = historyPhasePreCLI
case 3:
phase = historyPhasePostCLI
}
builder.observeDocument(tree, phase)
}
if final != nil {
builder.observeDocument(final, historyPhasePostCLI)
}
for _, application := range applications {
phase := historyPhaseSources
if application.layer == LayerCLI {
phase = historyPhaseCLI
}
payload := builder.nextPayload(phase)
walkHistoryApplication(application, func(_, effective Path, node *document.Node) {
builder.addInventoryObservation(effective, effective, payload, phase, node.Sensitive)
})
}
for _, operation := range m.inheritance {
payload := builder.nextPayload(historyPhaseInheritance)
if operation.control != nil {
builder.addInventoryObservation(operation.control.path, operation.control.path, payload, historyPhaseInheritance, false)
continue
}
if operation.transfer == nil {
continue
}
for _, root := range []*document.Node{operation.parent, operation.effective} {
walkDocument(root, operation.transfer.to, func(path Path, node *document.Node) {
builder.addInventoryObservation(path, path, payload, historyPhaseInheritance, node.Sensitive)
})
}
}
if len(snapshots) != 0 {
if len(earlier) != 0 {
builder.setScratch(earlier[len(earlier)-1])
}
builder.observeResolutionEvidence(snapshots)
}
m.historyModel = builder.freeze() m.historyModel = builder.freeze()
m.historyBuilder = nil m.historyBuilder = nil
} }

View File

@@ -47,12 +47,16 @@ func TestMetadataHistoricalPaths(t *testing.T) {
} }
} }
func TestMetadataPathIndexesOwnIndependentSegments(t *testing.T) { func TestEffectivePathIndexOwnsIndependentSegments(t *testing.T) {
t.Parallel() t.Parallel()
final := parseDocumentForDecode(t, "service: {workers: [1]}\n") final := parseDocumentForDecode(t, "service: {workers: [1]}\n")
var metadata Metadata var metadata Metadata
finalizeMetadataPaths(&metadata, final, nil) finalizeEffectiveMetadataPaths(&metadata, final)
builder := newHistoryBuilder()
builder.observePostCLICheckpoint(final, nil)
builder.model.final = final
metadata.historyModel = builder.freeze()
pointer := "/service/workers/0" pointer := "/service/workers/0"
effective := metadata.effectivePaths[pointer] effective := metadata.effectivePaths[pointer]
historical := metadata.historyModel.concreteShapes(pointer) historical := metadata.historyModel.concreteShapes(pointer)
@@ -75,9 +79,13 @@ func TestMetadataPathIndexesRetainDistinctHistoricalShapes(t *testing.T) {
final := parseDocumentForDecode(t, "shape: [replacement]\n") final := parseDocumentForDecode(t, "shape: [replacement]\n")
earlier := parseDocumentForDecode(t, "shape: {'0': base}\n") earlier := parseDocumentForDecode(t, "shape: {'0': base}\n")
var metadata Metadata builder := newHistoryBuilder()
// Repeated trees must not add duplicate shapes to the historical index. builder.observeBaseline(earlier)
finalizeMetadataPaths(&metadata, final, nil, earlier, earlier, final) builder.setScratch(earlier)
builder.observePostCLICheckpoint(final, nil)
builder.model.final = final
metadata := Metadata{historyModel: builder.freeze()}
finalizeEffectiveMetadataPaths(&metadata, final)
keyPath := Keys("shape", "0") keyPath := Keys("shape", "0")
indexPath := Keys("shape").Index(0) indexPath := Keys("shape").Index(0)
pointer := indexPath.String() pointer := indexPath.String()

View File

@@ -2,19 +2,13 @@ package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
// postDecodeOverrideContext keeps the structural, typed pre-CLI, and effective // postDecodeOverrideContext carries the typed post-CLI state and detached
// post-CLI views distinct while preparing the existing inspection machinery. // resolution views after the history builder has taken ownership of evidence.
type postDecodeOverrideContext struct { type postDecodeOverrideContext struct {
structuralInput *document.Node decoded decodedConfiguration
preCLI *document.Node metadata Metadata
preCLIOrigins map[string]Origin resolution resolutionInput
decoded decodedConfiguration referenceInput *document.Node
metadata Metadata
historyBuilder *historyBuilder
resolution resolutionInput
applications []historyApplication
postDecodeApps []historyApplication
referenceInput *document.Node
} }
func preparePostDecodeOverrideContext(input assembledInput, before decodedConfiguration, assigned decodedOverrideResult) postDecodeOverrideContext { func preparePostDecodeOverrideContext(input assembledInput, before decodedConfiguration, assigned decodedOverrideResult) postDecodeOverrideContext {
@@ -32,14 +26,12 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
structuralOverlays = input.composition.overlays structuralOverlays = input.composition.overlays
metadata.historyBuilder.setScratch(input.composition.root) metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays) metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays)
applyInheritanceMetadata(&metadata, input.composition.root, input.composition.origins, structuralOverlays, input.inheritance)
metadata.historyBuilder.setScratch(input.composition.root) metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeInheritanceEvidence(input.inheritance.operations) metadata.historyBuilder.observeInheritanceEvidence(input.inheritance.operations)
} else if input.composition != nil { } else if input.composition != nil {
structuralOverlays = input.composition.overlays structuralOverlays = input.composition.overlays
metadata.historyBuilder.setScratch(input.composition.root) metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays) metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays)
applyOverlayMetadata(&metadata, structuralOverlays)
} else if len(applications) != 0 { } else if len(applications) != 0 {
metadata.historyBuilder.observeSourceEvidence(input.document, nil, applications, nil) metadata.historyBuilder.observeSourceEvidence(input.document, nil, applications, nil)
} }
@@ -58,7 +50,8 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
collectFieldInfo(after.final, Path{}, after, metadata.fields) collectFieldInfo(after.final, Path{}, after, metadata.fields)
} }
if len(cliRecords) != 0 || len(postDecodeApps) != 0 { if len(cliRecords) != 0 || len(postDecodeApps) != 0 {
appendPostDecodeCLIHistory(&metadata, cliRecords, postDecodeApps, before.final) metadata.historyBuilder.setScratch(before.final)
metadata.historyBuilder.observeCLIEvidence(postDecodeApps, cliRecords)
} }
metadata.historyBuilder.observePostCLICheckpoint(after.final, after.origins) metadata.historyBuilder.observePostCLICheckpoint(after.final, after.origins)
metadata.historyBuilder.prefixReady = true metadata.historyBuilder.prefixReady = true
@@ -82,33 +75,10 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
finalizeEffectiveMetadataPaths(&metadata, assigned.final) finalizeEffectiveMetadataPaths(&metadata, assigned.final)
return postDecodeOverrideContext{ return postDecodeOverrideContext{
structuralInput: document.Clone(input.document), decoded: after,
preCLI: document.Clone(before.final), metadata: metadata,
preCLIOrigins: cloneOrigins(before.origins), resolution: resolution,
decoded: after, referenceInput: documentWithMetadataLocations(assigned.final, metadata, input.document),
metadata: metadata,
historyBuilder: metadata.historyBuilder,
resolution: resolution,
applications: applications,
postDecodeApps: postDecodeApps,
referenceInput: documentWithMetadataLocations(assigned.final, metadata, input.document),
}
}
// Source history is already assembled, with inheritance projected at query time.
// Reuse overlay event construction for CLI changes without reapplying displaced
// source definitions after those later assignments.
func appendPostDecodeCLIHistory(metadata *Metadata, records []overlayRecord, applications []historyApplication, before *document.Node) {
metadata.historyBuilder.setScratch(before)
metadata.historyBuilder.observeCLIEvidence(applications, records)
cli := Metadata{fields: metadata.fields}
applyOverlayMetadata(&cli, records)
for pointer, history := range cli.history {
for _, change := range history {
if change.Origin.Layer == LayerCLI {
appendInputChangeEvent(metadata.history, nil, pointer, change)
}
}
} }
} }
@@ -169,19 +139,14 @@ func collectAffectedOverrideOrigins(displaced *[]displacedDefinition, existing,
} }
} }
func (context postDecodeOverrideContext) historyApplications() []historyApplication {
applications := cloneHistoryApplications(context.applications)
return append(applications, cloneHistoryApplications(context.postDecodeApps)...)
}
func structuralHistoryApplications(input assembledInput) []historyApplication { func structuralHistoryApplications(input assembledInput) []historyApplication {
if input.composition != nil { if input.composition != nil {
return cloneHistoryApplications(input.composition.applications) return input.composition.applications
} }
if input.document == nil { if input.document == nil {
return nil return nil
} }
return []historyApplication{{layer: LayerBase, value: document.Clone(input.document)}} return []historyApplication{{layer: LayerBase, value: input.document}}
} }
// documentWithMetadataLocations attaches effective origin coordinates to a // documentWithMetadataLocations attaches effective origin coordinates to a
@@ -289,7 +254,7 @@ func metadataFieldLocation(metadata Metadata, path Path, origin Origin) (documen
if location, ok := latestOriginDocumentLocation(origin); ok { if location, ok := latestOriginDocumentLocation(origin); ok {
return location, true return location, true
} }
if model := metadata.currentHistoryModel(); model != nil { if model := metadata.historyEvidence(); model != nil {
location, ok := model.latestHistoricalLocation(path) location, ok := model.latestHistoricalLocation(path)
if ok { if ok {
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true

View File

@@ -50,21 +50,14 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
t.Fatalf("applyDecodedOverrides() issues = %#v", issues) t.Fatalf("applyDecodedOverrides() issues = %#v", issues)
} }
prepared := preparePostDecodeOverrideContext(input, decoded, assigned) prepared := preparePostDecodeOverrideContext(input, decoded, assigned)
if prepared.historyBuilder == nil || prepared.historyBuilder != prepared.metadata.historyBuilder || !prepared.historyBuilder.prefixReady { if prepared.metadata.historyBuilder == nil || !prepared.metadata.historyBuilder.prefixReady {
t.Fatal("post-decode handoff did not retain the completed history prefix") t.Fatal("post-decode handoff did not retain the completed history prefix")
} }
if prepared.resolution.metadata.historyBuilder != nil || prepared.resolution.metadata.historyModel != nil { if prepared.resolution.metadata.historyBuilder != nil || prepared.resolution.metadata.historyModel != nil {
t.Fatal("resolution entry view borrowed mutable or historical state") t.Fatal("resolution entry view borrowed mutable or historical state")
} }
if prepared.structuralInput == structural || prepared.preCLI == decoded.final || if prepared.decoded.final == assigned.final || prepared.referenceInput == assigned.final {
prepared.decoded.final == assigned.final || prepared.referenceInput == assigned.final { t.Fatal("post-CLI configuration and located resolution views are not detached")
t.Fatal("structural, pre-CLI, post-CLI, and located snapshots are not detached")
}
if raw, ok := documentAtPath(prepared.structuralInput, Keys("version")); !ok || raw.Kind != document.Number || raw.Text != "1" {
t.Fatalf("structural version = %#v", raw)
}
if typed, ok := documentAtPath(prepared.preCLI, Keys("version")); !ok || typed.Kind != document.String || typed.Text != "1" {
t.Fatalf("pre-CLI typed version = %#v", typed)
} }
if prepared.metadata.fields["/count"].Origin.Layer != LayerCLI || !prepared.metadata.fields["/count"].Explicit { if prepared.metadata.fields["/count"].Origin.Layer != LayerCLI || !prepared.metadata.fields["/count"].Explicit {
t.Fatalf("equal-value CLI metadata = %#v", prepared.metadata.fields["/count"]) t.Fatalf("equal-value CLI metadata = %#v", prepared.metadata.fields["/count"])
@@ -75,9 +68,6 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
if _, explicit := prepared.decoded.presence["/items/0/enabled"]; explicit { if _, explicit := prepared.decoded.presence["/items/0/enabled"]; explicit {
t.Fatal("replaced list retained discarded-child presence") t.Fatal("replaced list retained discarded-child presence")
} }
if len(prepared.applications) != 1 || len(prepared.postDecodeApps) != len(batch.overrides) || len(prepared.historyApplications()) != 1+len(batch.overrides) {
t.Fatalf("history applications = structural:%d CLI:%d all:%d", len(prepared.applications), len(prepared.postDecodeApps), len(prepared.historyApplications()))
}
if prepared.resolution.contexts["source-1"].BaseDir != "/config" { if prepared.resolution.contexts["source-1"].BaseDir != "/config" {
t.Fatalf("resolution source contexts = %#v", prepared.resolution.contexts) t.Fatalf("resolution source contexts = %#v", prepared.resolution.contexts)
} }
@@ -101,8 +91,8 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
if len(resolveDiagnostics) != 0 { if len(resolveDiagnostics) != 0 {
t.Fatalf("resolveLoadInput() diagnostics = %#v", resolveDiagnostics) t.Fatalf("resolveLoadInput() diagnostics = %#v", resolveDiagnostics)
} }
if resolvedContext.historyBuilder == nil || resolvedContext.historyBuilder != resolvedContext.metadata.historyBuilder || if resolvedContext.metadata.historyBuilder == nil || resolvedContext.metadata.historyBuilder.model.resolutionPayload == nil ||
resolvedContext.historyBuilder.model.resolutionPayload == nil || len(resolvedContext.historyBuilder.model.resolutionPayload.snapshots) != 1 { len(resolvedContext.metadata.historyBuilder.model.resolutionPayload.snapshots) != 1 {
t.Fatal("resolution evidence was not appended once to the handed-off model") t.Fatal("resolution evidence was not appended once to the handed-off model")
} }
if location, ok := metadataFieldLocation(resolvedContext.metadata, Keys("count"), Origin{Layer: LayerResolution}); !ok || location.SourceID != countSource { if location, ok := metadataFieldLocation(resolvedContext.metadata, Keys("count"), Origin{Layer: LayerResolution}); !ok || location.SourceID != countSource {
@@ -115,7 +105,7 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
t.Fatalf("post-resolution reference location = %#v, %v", value, ok) t.Fatalf("post-resolution reference location = %#v, %v", value, ok)
} }
finalizeMetadataPaths(&prepared.metadata, assigned.final, prepared.historyApplications(), decoded.baseline, prepared.structuralInput, prepared.preCLI, assigned.final) freezeMetadataHistory(&prepared.metadata, assigned.final)
inspection := Inspection{ inspection := Inspection{
metadata: prepared.metadata, history: prepared.metadata.historyModel, final: assigned.final, metadata: prepared.metadata, history: prepared.metadata.historyModel, final: assigned.final,
} }

View File

@@ -211,47 +211,51 @@ func TestApplyResolutionMetadataPreservesOverlappingDeltaOrder(t *testing.T) {
oldOrigin := Origin{Layer: LayerBase} oldOrigin := Origin{Layer: LayerBase}
parentOrigin := Origin{Layer: LayerResolution, Operation: ChangeNormalization} parentOrigin := Origin{Layer: LayerResolution, Operation: ChangeNormalization}
childOrigin := Origin{Layer: LayerResolution, Operation: ChangeDerivedDefault} childOrigin := Origin{Layer: LayerResolution, Operation: ChangeDerivedDefault}
leaf := &document.Node{Kind: document.String, Text: "value"} beforeLeaf := &document.Node{Kind: document.String, Text: "before"}
final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{ afterLeaf := &document.Node{Kind: document.String, Text: "after"}
Key: "group", beforeItem := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: beforeLeaf}}}
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{ afterItem := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: afterLeaf}}}
Key: "item", beforeGroup := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "item", Value: beforeItem}}}
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: leaf}}}, afterGroup := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "item", Value: afterItem}}}
}}}, before := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "group", Value: beforeGroup}}}
}}} final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "group", Value: afterGroup}}}
path := Keys("group", "item", "leaf")
builder := newHistoryBuilder()
builder.setScratch(before)
snapshots := []resolutionSnapshot{
{change: Change{Path: Keys("group"), Origin: parentOrigin}, before: beforeGroup, after: afterGroup},
{change: Change{Path: Keys("group", "item"), Origin: childOrigin}, before: beforeItem, after: afterItem},
}
builder.observeResolutionEvidence(snapshots)
builder.model.final = final
metadata := &Metadata{ metadata := &Metadata{
fields: map[string]FieldInfo{"/group/item/leaf": {Origin: oldOrigin}}, fields: map[string]FieldInfo{path.String(): {Explicit: true, Origin: oldOrigin}},
history: make(map[string][]Change), historyBuilder: builder,
} }
entry := resolutionInput{metadata: Metadata{ entry := resolutionInput{metadata: Metadata{
fields: map[string]FieldInfo{"/group/item/leaf": {Explicit: true, Origin: oldOrigin}}, fields: map[string]FieldInfo{path.String(): {Explicit: true, Origin: oldOrigin}},
effectivePaths: effectivePathIndex(final), effectivePaths: effectivePathIndex(before),
}} }}
resolved := resolutionResult{ resolved := resolutionResult{
final: final, final: final,
deltas: []documentDelta{{path: Keys("group")}, {path: Keys("group", "item")}}, deltas: []documentDelta{{path: Keys("group")}, {path: Keys("group", "item")}},
snapshots: []resolutionSnapshot{ snapshots: snapshots,
{change: Change{Path: Keys("group"), Origin: parentOrigin}},
{change: Change{Path: Keys("group", "item"), Origin: childOrigin}},
},
} }
applyResolutionMetadata(metadata, resolved, entry) applyResolutionMetadata(metadata, resolved, entry)
metadata.effectivePaths = effectivePathIndex(final)
metadata.historyModel = builder.freeze()
metadata.historyBuilder = nil
field := metadata.fields["/group/item/leaf"] field := metadata.fields[path.String()]
if field.Origin.Operation != childOrigin.Operation { if field.Origin.Operation != childOrigin.Operation {
t.Fatalf("final leaf origin operation = %q, want %q", field.Origin.Operation, childOrigin.Operation) t.Fatalf("final leaf origin operation = %q, want %q", field.Origin.Operation, childOrigin.Operation)
} }
changes := metadata.history["/group/item/leaf"] changes := metadata.History(path)
if len(changes) != 2 { if len(changes) != 2 {
t.Fatalf("leaf history has %d entries, want 2: %#v", len(changes), changes) t.Fatalf("leaf history has %d entries, want 2: %#v", len(changes), changes)
} }
if changes[0].Origin.Operation != parentOrigin.Operation || changes[1].Origin.Operation != childOrigin.Operation { if changes[0].Origin.Operation != parentOrigin.Operation || changes[1].Origin.Operation != childOrigin.Operation {
t.Fatalf("leaf history operations = %q, %q; want %q, %q", changes[0].Origin.Operation, changes[1].Origin.Operation, parentOrigin.Operation, childOrigin.Operation) t.Fatalf("leaf history operations = %q, %q; want %q, %q", changes[0].Origin.Operation, changes[1].Origin.Operation, parentOrigin.Operation, childOrigin.Operation)
} }
for index, change := range changes {
if len(change.Previous) != 1 || change.Previous[0].Layer != oldOrigin.Layer {
t.Errorf("leaf history[%d] Previous = %#v, want original origin", index, change.Previous)
}
}
} }

View File

@@ -11,11 +11,11 @@ type secretDependency struct {
dependency Path dependency Path
} }
func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, projections ...*document.Node) map[string]bool { func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata) map[string]bool {
return classifySecretPathsWithEvidence(schema, predicate, metadata, final, applications, nil, projections...) return classifySecretPathsWithEvidence(schema, predicate, metadata, nil)
} }
func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, evidence []*sourceVersionEvidence, projections ...*document.Node) map[string]bool { func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, evidence []*sourceVersionEvidence) map[string]bool {
paths := make(map[string]Path) paths := make(map[string]Path)
candidateShapes := make(map[string][]Path) candidateShapes := make(map[string][]Path)
var candidates []Path var candidates []Path
@@ -30,19 +30,9 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
candidateShapes[pointer] = append(candidateShapes[pointer], cloned) candidateShapes[pointer] = append(candidateShapes[pointer], cloned)
candidates = append(candidates, cloned) candidates = append(candidates, cloned)
} }
for _, application := range applications {
walkHistoryApplication(application, func(_ Path, path Path, _ *document.Node) {
if len(application.sequences) != 0 {
addCandidate(path)
} else if parsed, err := ParsePath(path.String()); err == nil {
addCandidate(parsed)
}
addSecretPath(paths, path.String())
})
}
secret := make(map[string]bool) secret := make(map[string]bool)
var dependencies, inherited []secretDependency var dependencies, inherited []secretDependency
if model := metadata.currentHistoryModel(); model != nil { if model := metadata.historyEvidence(); model != nil {
dependencies, inherited = model.sensitivityDependencies() dependencies, inherited = model.sensitivityDependencies()
for _, candidate := range model.sensitivityCandidates() { for _, candidate := range model.sensitivityCandidates() {
pointer := candidate.path.String() pointer := candidate.path.String()
@@ -71,22 +61,6 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
} }
} }
classifyProjection := func(tree *document.Node) {
walkDocument(tree, Path{}, func(path Path, node *document.Node) {
pointer := path.String()
addSecretPath(paths, pointer)
if parsed, err := ParsePath(pointer); err == nil {
addCandidate(parsed)
}
if node.Sensitive {
secret[pointer] = true
}
})
}
for _, tree := range projections {
classifyProjection(tree)
}
classifyProjection(final)
classifyPath := func(path Path) { classifyPath := func(path Path) {
if root, ok := schemaSecretRoot(schema, path); ok { if root, ok := schemaSecretRoot(schema, path); ok {
secret[root.String()] = true secret[root.String()] = true

View File

@@ -42,6 +42,7 @@ func TestSecretClassificationParsesTypedHistoryAfterNumericShapeChanges(t *testi
pointer := test.historical.String() pointer := test.historical.String()
builder := newHistoryBuilder() builder := newHistoryBuilder()
builder.observeDocument(earlier, historyPhaseSources) builder.observeDocument(earlier, historyPhaseSources)
builder.observeDocument(final, historyPhasePostCLI)
metadata := Metadata{historyModel: builder.freeze()} metadata := Metadata{historyModel: builder.freeze()}
seen := false seen := false
classified := classifySecretPaths(nil, func(path Path) bool { classified := classifySecretPaths(nil, func(path Path) bool {
@@ -55,7 +56,7 @@ func TestSecretClassificationParsesTypedHistoryAfterNumericShapeChanges(t *testi
} }
} }
return true return true
}, metadata, final, []historyApplication{{value: earlier}}, earlier) }, metadata)
if !seen || !classified[pointer] { if !seen || !classified[pointer] {
t.Fatalf("historical/final numeric path was not classified: %#v", classified) t.Fatalf("historical/final numeric path was not classified: %#v", classified)
} }
@@ -77,7 +78,7 @@ func TestSecretPredicateReceivesDetachedHistoricalPath(t *testing.T) {
seen = true seen = true
candidate.segments[0].key = "changed" candidate.segments[0].key = "changed"
return true return true
}, metadata, nil, nil) }, metadata)
if !seen || !classified[path.String()] || classified["/changed/1"] { if !seen || !classified[path.String()] || classified["/changed/1"] {
t.Fatalf("classification after predicate mutation = %#v, predicate called: %v", classified, seen) t.Fatalf("classification after predicate mutation = %#v, predicate called: %v", classified, seen)
@@ -133,6 +134,9 @@ func TestSourceVersionEvidenceSensitivityUsesOriginalMountedPaths(t *testing.T)
final := parseCompositionDocument(t, "final", "tenant:\n groups:\n blue:\n - public: shifted-neighbor\n labels:\n public: visible\n") final := parseCompositionDocument(t, "final", "tenant:\n groups:\n blue:\n - public: shifted-neighbor\n labels:\n public: visible\n")
var sawOriginalSequencePath, sawAncestorPredicate bool var sawOriginalSequencePath, sawAncestorPredicate bool
builder := newHistoryBuilder()
builder.observeDocument(final, historyPhaseSources)
metadata := Metadata{historyModel: builder.freeze()}
classified := classifySecretPathsWithEvidence(schema, func(path Path) bool { classified := classifySecretPathsWithEvidence(schema, func(path Path) bool {
if path.String() == appendDestination(destination, predicatePath).String() { if path.String() == appendDestination(destination, predicatePath).String() {
return true return true
@@ -151,7 +155,7 @@ func TestSourceVersionEvidenceSensitivityUsesOriginalMountedPaths(t *testing.T)
sawOriginalSequencePath = len(path.segments) == 5 && path.segments[3].kind == pathSegmentIndex && path.segments[3].index == 2 sawOriginalSequencePath = len(path.segments) == 5 && path.segments[3].kind == pathSegmentIndex && path.segments[3].index == 2
} }
return false return false
}, Metadata{}, final, []historyApplication{{path: destination, value: final}}, evidence) }, metadata, evidence)
for _, path := range []Path{ for _, path := range []Path{
appendDestination(destination, sequencePath), appendDestination(destination, sequencePath),
@@ -197,7 +201,7 @@ func TestSourceVersionEvidenceClassificationKeepsCollidingShapes(t *testing.T) {
sawKey = sawKey || reflect.DeepEqual(path, keyPath) sawKey = sawKey || reflect.DeepEqual(path, keyPath)
sawIndex = sawIndex || reflect.DeepEqual(path, indexPath) sawIndex = sawIndex || reflect.DeepEqual(path, indexPath)
return false return false
}, Metadata{}, nil, nil, evidence) }, Metadata{}, evidence)
if !classified[keyPath.String()] || !sawKey || !sawIndex { if !classified[keyPath.String()] || !sawKey || !sawIndex {
t.Errorf("reverse=%v: classified=%v, key seen=%v, index seen=%v", reverse, classified, sawKey, sawIndex) t.Errorf("reverse=%v: classified=%v, key seen=%v, index seen=%v", reverse, classified, sawKey, sawIndex)
} }