Remove superseded history attribution paths
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 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`.
|
||||
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
|
||||
|
||||
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
|
||||
helpers. Remove history-building portions of `metadataFromDecoded`,
|
||||
|
||||
@@ -231,11 +231,12 @@ func TestPreparedInheritanceEvidenceSharesOwnerAndDetachesPublicViews(t *testing
|
||||
builder.observeBaseline(baseline)
|
||||
builder.setScratch(input)
|
||||
builder.observeInheritanceEvidence(expanded.operations)
|
||||
builder.observePreCLICheckpoint(expanded.root, expanded.origins)
|
||||
builder.observePostCLICheckpoint(expanded.root, expanded.origins)
|
||||
builder.prefixReady = true
|
||||
metadata := Metadata{historyBuilder: builder}
|
||||
resolved := resolvedLoad[historyBudgetSimpleConfig]{
|
||||
metadata: metadata, historyBuilder: builder, final: expanded.root,
|
||||
decoded: decodedConfiguration{baseline: baseline}, structuralInput: input,
|
||||
inheritance: expanded,
|
||||
metadata: metadata, final: expanded.root,
|
||||
}
|
||||
counter := newRetainedNodeCounter(100)
|
||||
inspection, ok := prepareInspectionHistory(resolved, counter)
|
||||
|
||||
@@ -281,32 +281,3 @@ func resolveInheritanceAttribution(operation inheritanceOperation, destination P
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -63,9 +63,6 @@ func TestInheritanceEvidenceUsesOrderedPayloadAndConcreteTransferShapes(t *testi
|
||||
displaced[0].attribution.origin.Locations[0].SourceID != "child" {
|
||||
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 {
|
||||
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()
|
||||
|
||||
from, to, reference := Keys("parents", "base"), Keys("children", "item"), Keys("parents", "base")
|
||||
path := to.Key("nested").Key("value")
|
||||
parent := Origin{Layer: LayerBase, Locations: []Location{{SourceID: "base"}}}
|
||||
child := Origin{Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}}
|
||||
parentDocument := parseDocumentForDecode(t, "nested: {value: parent}\n")
|
||||
effectiveDocument := parseDocumentForDecode(t, "nested: {value: child}\n")
|
||||
operation := inheritanceOperation{
|
||||
transfer: &inheritanceTransfer{from: from, to: to, reference: reference},
|
||||
parent: parentDocument,
|
||||
effective: effectiveDocument,
|
||||
parentOrigins: map[string]Origin{from.Key("nested").Key("value").String(): parent},
|
||||
effectiveOrigins: map[string]Origin{path.String(): child},
|
||||
}
|
||||
selections := resolveInheritanceAttribution(operation, path)
|
||||
if len(selections) != 2 || selections[0].kind != inheritanceEvidenceParent || selections[1].kind != inheritanceEvidenceChild {
|
||||
t.Fatalf("shared resolver selections = %#v", selections)
|
||||
root := parseDocumentForDecode(t, "children: {item: {nested: {value: old}}}\n")
|
||||
builder := newHistoryBuilder()
|
||||
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 {
|
||||
t.Fatalf("parent and child attributions did not share one resolver: %#v", selections)
|
||||
if selections[0].reportedPath.String() != to.String() || selections[1].reportedPath.String() != to.String() ||
|
||||
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 len(changes) != 2 || changes[0].Origin.Locations[0].SourceID != selections[0].origin.Locations[0].SourceID ||
|
||||
changes[1].Origin.Locations[0].SourceID != selections[1].origin.Locations[0].SourceID ||
|
||||
changes[0].Dependencies[0].String() != from.String() || changes[0].Dependencies[1].String() != reference.String() ||
|
||||
changes[1].Dependencies[0].String() != reference.String() {
|
||||
t.Fatalf("metadata projection attribution = %#v; resolver = %#v", changes, selections)
|
||||
if selections[0].attribution.origin.Locations[0].SourceID != "base" ||
|
||||
selections[1].attribution.origin.Locations[0].SourceID != "child" ||
|
||||
len(selections[0].attribution.dependencies) != 2 || selections[0].attribution.dependencies[0].String() != from.String() ||
|
||||
selections[0].attribution.dependencies[1].String() != reference.String() ||
|
||||
len(selections[1].attribution.dependencies) != 1 || selections[1].attribution.dependencies[0].String() != reference.String() {
|
||||
t.Fatalf("model attribution dependencies or origins differ: %#v", selections)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,14 @@ func TestMetadataFinalizationRegistersResolutionInventoryAndRoots(t *testing.T)
|
||||
|
||||
var metadata Metadata
|
||||
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" {
|
||||
t.Fatalf("historical paths = %#v", got)
|
||||
}
|
||||
|
||||
@@ -272,19 +272,18 @@ func TestProjectedSequenceHistoryAgreesAcrossMetadataAndExplanation(t *testing.T
|
||||
t.Fatalf("compose diagnostics = %#v", diagnostics)
|
||||
}
|
||||
path := Keys("records").Index(1).Key("token")
|
||||
var metadata Metadata
|
||||
finalizeMetadataPaths(&metadata, composed.root, composed.applications)
|
||||
baseline := &document.Node{Kind: document.Mapping}
|
||||
model := sourceHistoryModelForInspection(baseline, composed.root, composed.origins, composed.applications, composed.overlays)
|
||||
metadata := Metadata{historyModel: model}
|
||||
finalizeEffectiveMetadataPaths(&metadata, composed.root)
|
||||
if !metadata.hasHistoricalPath(path) {
|
||||
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.secrets = classifySecretPaths(nil, func(candidate Path) bool {
|
||||
return reflect.DeepEqual(candidate, path)
|
||||
}, metadata, composed.root, nil)
|
||||
}, metadata)
|
||||
explanation, ok := inspection.Explain(path)
|
||||
if !ok || !explanation.Effective.Redacted {
|
||||
t.Fatalf("shifted explanation = %#v, %v", explanation, ok)
|
||||
@@ -323,15 +322,14 @@ func TestReplacedProjectedSensitiveRecordRemainsRedacted(t *testing.T) {
|
||||
})
|
||||
final := applications[len(applications)-1].value
|
||||
path := Keys("records").Index(1).Key("token")
|
||||
var metadata Metadata
|
||||
finalizeMetadataPaths(&metadata, final, applications)
|
||||
baseline := &document.Node{Kind: document.Mapping}
|
||||
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.secrets = classifySecretPaths(nil, func(candidate Path) bool {
|
||||
return reflect.DeepEqual(candidate, path)
|
||||
}, metadata, final, nil)
|
||||
}, metadata)
|
||||
explanation, ok := inspection.Explain(path)
|
||||
if !ok || explanation.Effective.Present {
|
||||
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.Sequence = document.Clone(combined.value).Sequence
|
||||
|
||||
var metadata Metadata
|
||||
finalizeMetadataPaths(&metadata, finalRoot, applications)
|
||||
baseline := &document.Node{Kind: document.Mapping}
|
||||
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}
|
||||
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)
|
||||
if !ok || !explanation.Effective.Redacted {
|
||||
t.Fatalf("mapped replacement explanation = %#v, %v", explanation, ok)
|
||||
|
||||
@@ -1,63 +1,6 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"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)
|
||||
}
|
||||
import "slices"
|
||||
|
||||
func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) {
|
||||
if inheritanceOperationIdentity(operation, destination) {
|
||||
|
||||
@@ -125,9 +125,11 @@ func inheritanceInspectionFromComposition(t *testing.T, composed *composedDocume
|
||||
metadata := metadataFromDecoded(decoded)
|
||||
metadata.historyBuilder.setScratch(composed.root)
|
||||
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.observeInheritanceEvidence(expanded.operations)
|
||||
metadata.historyBuilder.observePreCLICheckpoint(expanded.root, expanded.origins)
|
||||
metadata.historyBuilder.observePostCLICheckpoint(expanded.root, expanded.origins)
|
||||
metadata.historyBuilder.prefixReady = true
|
||||
final := expanded.root
|
||||
if len(snapshots) != 0 {
|
||||
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{
|
||||
metadata: metadata, history: metadata.historyModel, final: final,
|
||||
}, expanded
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestInheritanceSensitivityFollowsCopiesWithoutMaskingNeighbors(t *testing.T
|
||||
inspection, _ := inheritanceInspectionForTest(t, input)
|
||||
inspection.secrets = classifySecretPaths(nil, func(path Path) bool {
|
||||
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")} {
|
||||
explanation, found := inspection.Explain(path)
|
||||
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.secrets = classifySecretPaths(nil, func(path Path) bool {
|
||||
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")
|
||||
explanation, found := inspection.Explain(path)
|
||||
if !found || explanation.Effective.Present {
|
||||
@@ -85,7 +85,7 @@ func TestInheritanceSensitivityIncludesSchemaTaggedSource(t *testing.T) {
|
||||
t.Fatal(issue)
|
||||
}
|
||||
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"))
|
||||
if !found || !child.Effective.Redacted || strings.Contains(child.Effective.YAML, "TAGGED-CANARY") {
|
||||
t.Fatalf("tagged source copy = %#v, %v", child, found)
|
||||
@@ -104,7 +104,7 @@ func TestSourceVersionEvidenceSensitivityFollowsInheritanceAndDisplacedHistory(t
|
||||
}
|
||||
inspection.secrets = classifySecretPathsWithEvidence(nil, func(path Path) bool {
|
||||
return path.String() == secretPath.String()
|
||||
}, inspection.metadata, inspection.final, nil, []*sourceVersionEvidence{evidence})
|
||||
}, inspection.metadata, []*sourceVersionEvidence{evidence})
|
||||
|
||||
path := Keys("child", "branch", "token")
|
||||
explanation, found := inspection.Explain(path)
|
||||
@@ -162,7 +162,7 @@ func TestInheritedSecretReferencesResolveOnlyAfterExpansion(t *testing.T) {
|
||||
t.Fatalf("inherited reference resolution = %#v, %d", resolved.diagnostics, lookupCount)
|
||||
}
|
||||
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
|
||||
if err := inspection.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), "RESOLVED-CREDENTIAL-CANARY") {
|
||||
t.Fatalf("reference rendering = %q, %v", rendered.String(), err)
|
||||
|
||||
@@ -60,108 +60,13 @@ func (input resolutionInput) sourceContext(id string) (ResourceContext, bool) {
|
||||
func metadataFromDecoded(decoded decodedConfiguration) Metadata {
|
||||
metadata := Metadata{
|
||||
fields: make(map[string]FieldInfo),
|
||||
history: make(map[string][]Change),
|
||||
historyBuilder: newHistoryBuilder(),
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
if candidate.Layer != container.Layer || candidate.Operation != container.Operation {
|
||||
return false
|
||||
@@ -195,14 +100,6 @@ func originContainedBy(candidate, container Origin) bool {
|
||||
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) {
|
||||
pointer := path.String()
|
||||
_, explicit := decoded.presence[pointer]
|
||||
@@ -224,13 +121,11 @@ func collectFieldInfo(node *document.Node, path Path, decoded decodedConfigurati
|
||||
}
|
||||
|
||||
func cloneMetadata(metadata Metadata) Metadata {
|
||||
if metadata.fields == nil && metadata.history == nil && metadata.historyModel == nil {
|
||||
if metadata.fields == nil && metadata.historyModel == nil {
|
||||
return Metadata{}
|
||||
}
|
||||
cloned := Metadata{
|
||||
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,
|
||||
}
|
||||
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
|
||||
@@ -240,13 +135,6 @@ func cloneMetadata(metadata Metadata) Metadata {
|
||||
for path, info := range metadata.fields {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -268,56 +156,16 @@ func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult, entr
|
||||
|
||||
for snapshotID, snapshot := range resolved.snapshots {
|
||||
pointer := snapshotPointers[snapshotID]
|
||||
change := cloneChange(snapshot.change)
|
||||
if info, ok := metadata.fields[pointer]; ok {
|
||||
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
||||
info.Origin = cloneOrigin(change.Origin)
|
||||
info.Origin = cloneOrigin(snapshot.change.Origin)
|
||||
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)
|
||||
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) {
|
||||
buckets := make([][]string, len(snapshotIDs))
|
||||
for pointer := range fields {
|
||||
|
||||
@@ -10,19 +10,10 @@ import (
|
||||
// it owns no source access, options, or long-lived schema state.
|
||||
type resolvedLoad[T any] struct {
|
||||
configuration T
|
||||
decoded decodedConfiguration
|
||||
metadata Metadata
|
||||
historyBuilder *historyBuilder
|
||||
final *document.Node
|
||||
snapshots []resolutionSnapshot
|
||||
applications []historyApplication
|
||||
postDecodeApps []historyApplication
|
||||
structuralInput *document.Node
|
||||
preCLI *document.Node
|
||||
preCLIOrigins map[string]Origin
|
||||
referenceInput *document.Node
|
||||
versionEvidence []*sourceVersionEvidence
|
||||
inheritance *inheritanceScope
|
||||
contexts map[string]ResourceContext
|
||||
}
|
||||
|
||||
@@ -57,10 +48,7 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
|
||||
decoded := prepared.decoded
|
||||
configuration := decoded.value.Interface().(T)
|
||||
metadata := prepared.metadata
|
||||
builder := prepared.historyBuilder
|
||||
if builder == nil {
|
||||
builder = metadata.historyBuilder
|
||||
}
|
||||
builder := metadata.historyBuilder
|
||||
final := decoded.final
|
||||
referenceInput := prepared.referenceInput
|
||||
var resolutionSnapshots []resolutionSnapshot
|
||||
@@ -86,22 +74,13 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T],
|
||||
if refreshReferenceInput {
|
||||
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
|
||||
if input.versionState != nil {
|
||||
versionEvidence = append(versionEvidence, input.versionState.evidence...)
|
||||
}
|
||||
return resolvedLoad[T]{
|
||||
configuration: configuration, decoded: decoded, metadata: metadata, historyBuilder: builder, final: final,
|
||||
snapshots: resolutionSnapshots, applications: applications,
|
||||
postDecodeApps: prepared.postDecodeApps, structuralInput: document.Clone(prepared.structuralInput),
|
||||
preCLI: prepared.preCLI, preCLIOrigins: prepared.preCLIOrigins, referenceInput: referenceInput,
|
||||
versionEvidence: versionEvidence, inheritance: input.inheritance, contexts: input.contexts,
|
||||
configuration: configuration, metadata: metadata, final: final, referenceInput: referenceInput,
|
||||
versionEvidence: versionEvidence, contexts: input.contexts,
|
||||
}, 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) {
|
||||
metadata := resolved.metadata
|
||||
if resolved.historyBuilder != nil {
|
||||
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,
|
||||
)
|
||||
freezeMetadataHistory(&metadata, resolved.final)
|
||||
model := metadata.historyModel
|
||||
inspection := &Inspection{
|
||||
metadata: metadata,
|
||||
@@ -152,8 +117,8 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
|
||||
accumulator.add(*issue)
|
||||
return nil, accumulator.loadError()
|
||||
}
|
||||
metadata, final := inspection.metadata, inspection.final
|
||||
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, nil, resolved.versionEvidence)
|
||||
metadata := inspection.metadata
|
||||
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, resolved.versionEvidence)
|
||||
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
|
||||
inspection.metadata = metadata
|
||||
inspection.sources = sources
|
||||
|
||||
@@ -7,22 +7,10 @@ import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
type Metadata struct {
|
||||
effectivePaths map[string]Path
|
||||
fields map[string]FieldInfo
|
||||
history map[string][]Change
|
||||
inheritance []inheritanceOperation
|
||||
historyBuilder *historyBuilder
|
||||
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
|
||||
// return false.
|
||||
func (m Metadata) Lookup(path Path) (FieldInfo, bool) {
|
||||
@@ -155,80 +143,13 @@ func (m Metadata) hasHistoricalPath(path Path) bool {
|
||||
return m.historyEvidence().hasObservedShape(path)
|
||||
}
|
||||
|
||||
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) {
|
||||
finalizeMetadataPathsWithResolution(m, final, applications, nil, earlier...)
|
||||
}
|
||||
|
||||
func finalizeMetadataPathsWithResolution(m *Metadata, final *document.Node, applications []historyApplication, snapshots []resolutionSnapshot, earlier ...*document.Node) {
|
||||
func freezeMetadataHistory(m *Metadata, final *document.Node) {
|
||||
finalizeEffectiveMetadataPaths(m, final)
|
||||
builder := m.historyBuilder
|
||||
if builder != nil && builder.prefixReady {
|
||||
if len(snapshots) != 0 && builder.model.resolutionPayload == nil {
|
||||
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]
|
||||
if builder == nil || !builder.prefixReady {
|
||||
panic("configkit: metadata history prefix is not ready")
|
||||
}
|
||||
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.historyBuilder = nil
|
||||
}
|
||||
|
||||
@@ -47,12 +47,16 @@ func TestMetadataHistoricalPaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataPathIndexesOwnIndependentSegments(t *testing.T) {
|
||||
func TestEffectivePathIndexOwnsIndependentSegments(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
final := parseDocumentForDecode(t, "service: {workers: [1]}\n")
|
||||
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"
|
||||
effective := metadata.effectivePaths[pointer]
|
||||
historical := metadata.historyModel.concreteShapes(pointer)
|
||||
@@ -75,9 +79,13 @@ func TestMetadataPathIndexesRetainDistinctHistoricalShapes(t *testing.T) {
|
||||
|
||||
final := parseDocumentForDecode(t, "shape: [replacement]\n")
|
||||
earlier := parseDocumentForDecode(t, "shape: {'0': base}\n")
|
||||
var metadata Metadata
|
||||
// Repeated trees must not add duplicate shapes to the historical index.
|
||||
finalizeMetadataPaths(&metadata, final, nil, earlier, earlier, final)
|
||||
builder := newHistoryBuilder()
|
||||
builder.observeBaseline(earlier)
|
||||
builder.setScratch(earlier)
|
||||
builder.observePostCLICheckpoint(final, nil)
|
||||
builder.model.final = final
|
||||
metadata := Metadata{historyModel: builder.freeze()}
|
||||
finalizeEffectiveMetadataPaths(&metadata, final)
|
||||
keyPath := Keys("shape", "0")
|
||||
indexPath := Keys("shape").Index(0)
|
||||
pointer := indexPath.String()
|
||||
|
||||
@@ -2,19 +2,13 @@ package engine
|
||||
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
|
||||
// postDecodeOverrideContext keeps the structural, typed pre-CLI, and effective
|
||||
// post-CLI views distinct while preparing the existing inspection machinery.
|
||||
// postDecodeOverrideContext carries the typed post-CLI state and detached
|
||||
// resolution views after the history builder has taken ownership of evidence.
|
||||
type postDecodeOverrideContext struct {
|
||||
structuralInput *document.Node
|
||||
preCLI *document.Node
|
||||
preCLIOrigins map[string]Origin
|
||||
decoded decodedConfiguration
|
||||
metadata Metadata
|
||||
historyBuilder *historyBuilder
|
||||
resolution resolutionInput
|
||||
applications []historyApplication
|
||||
postDecodeApps []historyApplication
|
||||
referenceInput *document.Node
|
||||
decoded decodedConfiguration
|
||||
metadata Metadata
|
||||
resolution resolutionInput
|
||||
referenceInput *document.Node
|
||||
}
|
||||
|
||||
func preparePostDecodeOverrideContext(input assembledInput, before decodedConfiguration, assigned decodedOverrideResult) postDecodeOverrideContext {
|
||||
@@ -32,14 +26,12 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
|
||||
structuralOverlays = input.composition.overlays
|
||||
metadata.historyBuilder.setScratch(input.composition.root)
|
||||
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.observeInheritanceEvidence(input.inheritance.operations)
|
||||
} else if input.composition != nil {
|
||||
structuralOverlays = input.composition.overlays
|
||||
metadata.historyBuilder.setScratch(input.composition.root)
|
||||
metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays)
|
||||
applyOverlayMetadata(&metadata, structuralOverlays)
|
||||
} else if len(applications) != 0 {
|
||||
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)
|
||||
}
|
||||
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.prefixReady = true
|
||||
@@ -82,33 +75,10 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
|
||||
finalizeEffectiveMetadataPaths(&metadata, assigned.final)
|
||||
|
||||
return postDecodeOverrideContext{
|
||||
structuralInput: document.Clone(input.document),
|
||||
preCLI: document.Clone(before.final),
|
||||
preCLIOrigins: cloneOrigins(before.origins),
|
||||
decoded: after,
|
||||
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)
|
||||
}
|
||||
}
|
||||
decoded: after,
|
||||
metadata: metadata,
|
||||
resolution: resolution,
|
||||
referenceInput: documentWithMetadataLocations(assigned.final, metadata, input.document),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
if input.composition != nil {
|
||||
return cloneHistoryApplications(input.composition.applications)
|
||||
return input.composition.applications
|
||||
}
|
||||
if input.document == 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
|
||||
@@ -289,7 +254,7 @@ func metadataFieldLocation(metadata Metadata, path Path, origin Origin) (documen
|
||||
if location, ok := latestOriginDocumentLocation(origin); ok {
|
||||
return location, true
|
||||
}
|
||||
if model := metadata.currentHistoryModel(); model != nil {
|
||||
if model := metadata.historyEvidence(); model != nil {
|
||||
location, ok := model.latestHistoricalLocation(path)
|
||||
if ok {
|
||||
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
|
||||
|
||||
@@ -50,21 +50,14 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
t.Fatalf("applyDecodedOverrides() issues = %#v", issues)
|
||||
}
|
||||
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")
|
||||
}
|
||||
if prepared.resolution.metadata.historyBuilder != nil || prepared.resolution.metadata.historyModel != nil {
|
||||
t.Fatal("resolution entry view borrowed mutable or historical state")
|
||||
}
|
||||
if prepared.structuralInput == structural || prepared.preCLI == decoded.final ||
|
||||
prepared.decoded.final == assigned.final || prepared.referenceInput == assigned.final {
|
||||
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.decoded.final == assigned.final || prepared.referenceInput == assigned.final {
|
||||
t.Fatal("post-CLI configuration and located resolution views are not detached")
|
||||
}
|
||||
if prepared.metadata.fields["/count"].Origin.Layer != LayerCLI || !prepared.metadata.fields["/count"].Explicit {
|
||||
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 {
|
||||
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" {
|
||||
t.Fatalf("resolution source contexts = %#v", prepared.resolution.contexts)
|
||||
}
|
||||
@@ -101,8 +91,8 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
if len(resolveDiagnostics) != 0 {
|
||||
t.Fatalf("resolveLoadInput() diagnostics = %#v", resolveDiagnostics)
|
||||
}
|
||||
if resolvedContext.historyBuilder == nil || resolvedContext.historyBuilder != resolvedContext.metadata.historyBuilder ||
|
||||
resolvedContext.historyBuilder.model.resolutionPayload == nil || len(resolvedContext.historyBuilder.model.resolutionPayload.snapshots) != 1 {
|
||||
if resolvedContext.metadata.historyBuilder == nil || resolvedContext.metadata.historyBuilder.model.resolutionPayload == nil ||
|
||||
len(resolvedContext.metadata.historyBuilder.model.resolutionPayload.snapshots) != 1 {
|
||||
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 {
|
||||
@@ -115,7 +105,7 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
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{
|
||||
metadata: prepared.metadata, history: prepared.metadata.historyModel, final: assigned.final,
|
||||
}
|
||||
|
||||
@@ -211,47 +211,51 @@ func TestApplyResolutionMetadataPreservesOverlappingDeltaOrder(t *testing.T) {
|
||||
oldOrigin := Origin{Layer: LayerBase}
|
||||
parentOrigin := Origin{Layer: LayerResolution, Operation: ChangeNormalization}
|
||||
childOrigin := Origin{Layer: LayerResolution, Operation: ChangeDerivedDefault}
|
||||
leaf := &document.Node{Kind: document.String, Text: "value"}
|
||||
final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
||||
Key: "group",
|
||||
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
||||
Key: "item",
|
||||
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: leaf}}},
|
||||
}}},
|
||||
}}}
|
||||
beforeLeaf := &document.Node{Kind: document.String, Text: "before"}
|
||||
afterLeaf := &document.Node{Kind: document.String, Text: "after"}
|
||||
beforeItem := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: beforeLeaf}}}
|
||||
afterItem := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: afterLeaf}}}
|
||||
beforeGroup := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "item", Value: beforeItem}}}
|
||||
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{
|
||||
fields: map[string]FieldInfo{"/group/item/leaf": {Origin: oldOrigin}},
|
||||
history: make(map[string][]Change),
|
||||
fields: map[string]FieldInfo{path.String(): {Explicit: true, Origin: oldOrigin}},
|
||||
historyBuilder: builder,
|
||||
}
|
||||
entry := resolutionInput{metadata: Metadata{
|
||||
fields: map[string]FieldInfo{"/group/item/leaf": {Explicit: true, Origin: oldOrigin}},
|
||||
effectivePaths: effectivePathIndex(final),
|
||||
fields: map[string]FieldInfo{path.String(): {Explicit: true, Origin: oldOrigin}},
|
||||
effectivePaths: effectivePathIndex(before),
|
||||
}}
|
||||
resolved := resolutionResult{
|
||||
final: final,
|
||||
deltas: []documentDelta{{path: Keys("group")}, {path: Keys("group", "item")}},
|
||||
snapshots: []resolutionSnapshot{
|
||||
{change: Change{Path: Keys("group"), Origin: parentOrigin}},
|
||||
{change: Change{Path: Keys("group", "item"), Origin: childOrigin}},
|
||||
},
|
||||
snapshots: snapshots,
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ type secretDependency struct {
|
||||
dependency Path
|
||||
}
|
||||
|
||||
func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, projections ...*document.Node) map[string]bool {
|
||||
return classifySecretPathsWithEvidence(schema, predicate, metadata, final, applications, nil, projections...)
|
||||
func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata) map[string]bool {
|
||||
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)
|
||||
candidateShapes := make(map[string][]Path)
|
||||
var candidates []Path
|
||||
@@ -30,19 +30,9 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
|
||||
candidateShapes[pointer] = append(candidateShapes[pointer], 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)
|
||||
var dependencies, inherited []secretDependency
|
||||
if model := metadata.currentHistoryModel(); model != nil {
|
||||
if model := metadata.historyEvidence(); model != nil {
|
||||
dependencies, inherited = model.sensitivityDependencies()
|
||||
for _, candidate := range model.sensitivityCandidates() {
|
||||
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) {
|
||||
if root, ok := schemaSecretRoot(schema, path); ok {
|
||||
secret[root.String()] = true
|
||||
|
||||
@@ -42,6 +42,7 @@ func TestSecretClassificationParsesTypedHistoryAfterNumericShapeChanges(t *testi
|
||||
pointer := test.historical.String()
|
||||
builder := newHistoryBuilder()
|
||||
builder.observeDocument(earlier, historyPhaseSources)
|
||||
builder.observeDocument(final, historyPhasePostCLI)
|
||||
metadata := Metadata{historyModel: builder.freeze()}
|
||||
seen := false
|
||||
classified := classifySecretPaths(nil, func(path Path) bool {
|
||||
@@ -55,7 +56,7 @@ func TestSecretClassificationParsesTypedHistoryAfterNumericShapeChanges(t *testi
|
||||
}
|
||||
}
|
||||
return true
|
||||
}, metadata, final, []historyApplication{{value: earlier}}, earlier)
|
||||
}, metadata)
|
||||
if !seen || !classified[pointer] {
|
||||
t.Fatalf("historical/final numeric path was not classified: %#v", classified)
|
||||
}
|
||||
@@ -77,7 +78,7 @@ func TestSecretPredicateReceivesDetachedHistoricalPath(t *testing.T) {
|
||||
seen = true
|
||||
candidate.segments[0].key = "changed"
|
||||
return true
|
||||
}, metadata, nil, nil)
|
||||
}, metadata)
|
||||
|
||||
if !seen || !classified[path.String()] || classified["/changed/1"] {
|
||||
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")
|
||||
var sawOriginalSequencePath, sawAncestorPredicate bool
|
||||
builder := newHistoryBuilder()
|
||||
builder.observeDocument(final, historyPhaseSources)
|
||||
metadata := Metadata{historyModel: builder.freeze()}
|
||||
classified := classifySecretPathsWithEvidence(schema, func(path Path) bool {
|
||||
if path.String() == appendDestination(destination, predicatePath).String() {
|
||||
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
|
||||
}
|
||||
return false
|
||||
}, Metadata{}, final, []historyApplication{{path: destination, value: final}}, evidence)
|
||||
}, metadata, evidence)
|
||||
|
||||
for _, path := range []Path{
|
||||
appendDestination(destination, sequencePath),
|
||||
@@ -197,7 +201,7 @@ func TestSourceVersionEvidenceClassificationKeepsCollidingShapes(t *testing.T) {
|
||||
sawKey = sawKey || reflect.DeepEqual(path, keyPath)
|
||||
sawIndex = sawIndex || reflect.DeepEqual(path, indexPath)
|
||||
return false
|
||||
}, Metadata{}, nil, nil, evidence)
|
||||
}, Metadata{}, evidence)
|
||||
if !classified[keyPath.String()] || !sawKey || !sawIndex {
|
||||
t.Errorf("reverse=%v: classified=%v, key seen=%v, index seen=%v", reverse, classified, sawKey, sawIndex)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user