Complete historical inventory and query services
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Interface Contract Remediation Implementation Plan
|
||||
|
||||
Status: in progress. Stages 1–8 are complete; Stages 9–18 remain pending.
|
||||
Status: in progress. Stages 1–9 are complete; Stages 10–18 remain pending.
|
||||
Baseline: `f6d54a094a2b5ada5748686620659399dce7ebf0`.
|
||||
Audience: a Luna coding agent executing the stages sequentially.
|
||||
|
||||
@@ -502,7 +502,10 @@ attribution is not lost to replay grouping. Focused check:
|
||||
## Stage 9 — Complete Historical Inventory And Internal Query Services
|
||||
|
||||
Owners: model finalization, `metadata.go`, history selection indexes.
|
||||
Status: pending.
|
||||
Status: complete. Validation:
|
||||
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'HistoricalPaths|Metadata|History.*Shape|SecretClassification' -count=1`,
|
||||
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -count=1`,
|
||||
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./...`, and `git diff --check`.
|
||||
|
||||
1. Derive historical membership and enumeration from all model observations and
|
||||
registered payload roots, including inventory-only checkpoints and controls.
|
||||
|
||||
@@ -58,6 +58,8 @@ type historyModel struct {
|
||||
observations []historyObservation
|
||||
attributions []historyAttribution
|
||||
baseline *document.Node
|
||||
final *document.Node
|
||||
inventoryRoots []*document.Node
|
||||
sourcePayloads []historySourcePayload
|
||||
inheritancePayloads []historyInheritancePayload
|
||||
cliPayloads []historySourcePayload
|
||||
@@ -69,6 +71,7 @@ type historyModel struct {
|
||||
shapeOrder []Path
|
||||
observationsByPointer map[string][]int
|
||||
reportedPathIndex pathPrefixIndex
|
||||
documentLookup *documentLookup
|
||||
|
||||
frozen bool
|
||||
}
|
||||
@@ -134,21 +137,33 @@ func (builder *historyBuilder) addObservation(observation historyObservation) in
|
||||
}
|
||||
|
||||
func (builder *historyBuilder) observeDocument(root *document.Node, phase historyPhase) {
|
||||
builder.observeDocumentAt(root, Path{}, phase)
|
||||
}
|
||||
|
||||
func (builder *historyBuilder) observeDocumentAt(root *document.Node, prefix Path, phase historyPhase) {
|
||||
builder.requireOpen()
|
||||
if root == nil {
|
||||
return
|
||||
}
|
||||
builder.registerDocumentRoot(root)
|
||||
payload := builder.nextPayload(phase)
|
||||
walkDocument(root, Path{}, func(path Path, node *document.Node) {
|
||||
builder.addObservation(historyObservation{
|
||||
path: path,
|
||||
phase: phase,
|
||||
payload: payload,
|
||||
reportedPath: path,
|
||||
role: historyRoleInventory,
|
||||
sensitive: node.Sensitive,
|
||||
attributionRef: noHistoryAttribution,
|
||||
})
|
||||
walkDocument(root, prefix, func(path Path, node *document.Node) {
|
||||
builder.addInventoryObservation(path, path, payload, phase, node.Sensitive)
|
||||
})
|
||||
}
|
||||
|
||||
func (builder *historyBuilder) registerDocumentRoot(root *document.Node) {
|
||||
builder.requireOpen()
|
||||
if root != nil {
|
||||
builder.model.inventoryRoots = append(builder.model.inventoryRoots, root)
|
||||
}
|
||||
}
|
||||
|
||||
func (builder *historyBuilder) addInventoryObservation(path, reportedPath Path, payload historyPayloadRef, phase historyPhase, sensitive bool) int {
|
||||
return builder.addObservation(historyObservation{
|
||||
path: path, phase: phase, payload: payload,
|
||||
reportedPath: reportedPath, role: historyRoleInventory,
|
||||
sensitive: sensitive, attributionRef: noHistoryAttribution,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -173,6 +188,7 @@ func (builder *historyBuilder) advanceScratch(root *document.Node) {
|
||||
func (builder *historyBuilder) freeze() *historyModel {
|
||||
builder.requireOpen()
|
||||
builder.scratch = nil
|
||||
builder.model.documentLookup = buildDocumentLookup(builder.model.documentRoots()...)
|
||||
builder.model.frozen = true
|
||||
model := builder.model
|
||||
builder.model = nil
|
||||
@@ -180,6 +196,48 @@ func (builder *historyBuilder) freeze() *historyModel {
|
||||
return model
|
||||
}
|
||||
|
||||
func (model *historyModel) documentRoots() []*document.Node {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
roots := append([]*document.Node(nil), model.inventoryRoots...)
|
||||
roots = append(roots, model.baseline, model.final)
|
||||
for _, payload := range model.sourcePayloads {
|
||||
if payload.kind == historySourceApplicationPayload {
|
||||
roots = append(roots, payload.application.value)
|
||||
}
|
||||
}
|
||||
for _, payload := range model.inheritancePayloads {
|
||||
for _, operation := range payload.operations {
|
||||
roots = append(roots, operation.parent, operation.effective)
|
||||
}
|
||||
}
|
||||
for _, payload := range model.cliPayloads {
|
||||
if payload.kind == historySourceApplicationPayload {
|
||||
roots = append(roots, payload.application.value)
|
||||
}
|
||||
}
|
||||
if model.preCLICheckpoint != nil {
|
||||
roots = append(roots, model.preCLICheckpoint.root)
|
||||
}
|
||||
if model.postCLICheckpoint != nil {
|
||||
roots = append(roots, model.postCLICheckpoint.root)
|
||||
}
|
||||
if model.resolutionPayload != nil {
|
||||
for _, snapshot := range model.resolutionPayload.snapshots {
|
||||
roots = append(roots, snapshot.before, snapshot.after)
|
||||
}
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
func (model *historyModel) documentAt(root *document.Node, path Path) (*document.Node, bool) {
|
||||
if model == nil || model.documentLookup == nil {
|
||||
return nil, false
|
||||
}
|
||||
return documentAtPathWithLookup(root, path, model.documentLookup)
|
||||
}
|
||||
|
||||
func (builder *historyBuilder) addShape(path Path) {
|
||||
pointer := path.String()
|
||||
for _, existing := range builder.model.shapesByPointer[pointer] {
|
||||
|
||||
@@ -92,3 +92,59 @@ func TestHistoryResolutionRetainsOriginalShapeAttributionBehindReplayGroup(t *te
|
||||
t.Fatal("original resolution subtree sensitivity was omitted from observations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataFinalizationRegistersResolutionInventoryAndRoots(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
beforeShape := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
||||
Key: "0", Value: &document.Node{Kind: document.String, Text: "old"},
|
||||
}}}
|
||||
afterShape := &document.Node{Kind: document.Sequence, Sequence: []*document.Node{{
|
||||
Kind: document.String, Text: "new", Sensitive: true,
|
||||
}}}
|
||||
before := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "shape", Value: beforeShape}}}
|
||||
final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "shape", Value: afterShape}}}
|
||||
changePath := parseOverrideTestPath(t, "/shape/0")
|
||||
snapshot := resolutionSnapshot{
|
||||
change: Change{
|
||||
Path: changePath, Origin: Origin{Layer: LayerResolution, Operation: ChangeNormalization},
|
||||
Reasons: []string{"changed dynamic shape"}, Dependencies: []Path{Keys("source")},
|
||||
},
|
||||
before: beforeShape.Mapping[0].Value,
|
||||
after: afterShape.Sequence[0],
|
||||
}
|
||||
parentSnapshot := resolutionSnapshot{
|
||||
change: Change{
|
||||
Path: Keys("shape"), Origin: Origin{Layer: LayerResolution, Operation: ChangeNormalization},
|
||||
Reasons: []string{"replaced container"},
|
||||
},
|
||||
before: beforeShape,
|
||||
after: afterShape,
|
||||
}
|
||||
|
||||
var metadata Metadata
|
||||
snapshots := []resolutionSnapshot{parentSnapshot, snapshot}
|
||||
finalizeMetadataPathsWithResolution(&metadata, final, nil, snapshots, before, before)
|
||||
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)
|
||||
}
|
||||
keyPath := Keys("shape", "0")
|
||||
indexPath := Keys("shape").Index(0)
|
||||
if !metadata.hasHistoricalPath(keyPath) || !metadata.hasHistoricalPath(indexPath) {
|
||||
t.Fatalf("resolution shape inventory omitted key or index: %#v", metadata.HistoricalPaths())
|
||||
}
|
||||
if got := metadata.historyModel.selectHistory(keyPath); len(got) != 2 || !got[0].before || got[0].after || !got[1].before || got[1].after {
|
||||
t.Fatalf("removed mapping-key resolution selection = %#v", got)
|
||||
}
|
||||
if got := metadata.historyModel.selectHistory(indexPath); len(got) != 2 || got[0].before || !got[0].after || got[1].before || !got[1].after {
|
||||
t.Fatalf("introduced sequence-index resolution selection = %#v", got)
|
||||
}
|
||||
var retainedBefore, retainedAfter bool
|
||||
for _, root := range metadata.historyModel.documentRoots() {
|
||||
retainedBefore = retainedBefore || root == snapshot.before
|
||||
retainedAfter = retainedAfter || root == snapshot.after
|
||||
}
|
||||
if !retainedBefore || !retainedAfter {
|
||||
t.Fatalf("resolution roots not enumerated: before=%v after=%v", retainedBefore, retainedAfter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ type historySelection struct {
|
||||
previous []Origin
|
||||
}
|
||||
|
||||
type historySensitivityCandidate struct {
|
||||
path Path
|
||||
sensitive bool
|
||||
dependencies []Path
|
||||
}
|
||||
|
||||
func (model *historyModel) selectHistory(path Path) []historySelection {
|
||||
if model == nil || !path.valid() || !model.hasObservedShape(path) {
|
||||
return nil
|
||||
@@ -119,7 +125,7 @@ func (model *historyModel) observedPaths() []Path {
|
||||
|
||||
func (model *historyModel) historicalPaths() []Path {
|
||||
if model == nil {
|
||||
return nil
|
||||
return []Path{}
|
||||
}
|
||||
pointers := make([]string, 0, len(model.shapesByPointer))
|
||||
for pointer := range model.shapesByPointer {
|
||||
@@ -136,6 +142,39 @@ func (model *historyModel) historicalPaths() []Path {
|
||||
return paths
|
||||
}
|
||||
|
||||
func (model *historyModel) latestHistoricalLocation(path Path) (Location, bool) {
|
||||
selections := model.selectHistory(path)
|
||||
for selectionIndex := len(selections) - 1; selectionIndex >= 0; selectionIndex-- {
|
||||
selection := selections[selectionIndex]
|
||||
if selection.hasAttribution {
|
||||
if location, found := latestOriginDocumentLocation(selection.attribution.origin); found {
|
||||
return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
|
||||
}
|
||||
}
|
||||
for previousIndex := len(selection.previous) - 1; previousIndex >= 0; previousIndex-- {
|
||||
if location, found := latestOriginDocumentLocation(selection.previous[previousIndex]); found {
|
||||
return Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return Location{}, false
|
||||
}
|
||||
|
||||
func (model *historyModel) sensitivityCandidates() []historySensitivityCandidate {
|
||||
if model == nil {
|
||||
return nil
|
||||
}
|
||||
candidates := make([]historySensitivityCandidate, 0, len(model.observations))
|
||||
for _, observation := range model.observations {
|
||||
candidate := historySensitivityCandidate{path: observation.path.clone(), sensitive: observation.sensitive}
|
||||
if attribution, found := model.resolveAttribution(observation.attributionRef); found {
|
||||
candidate.dependencies = attribution.dependencies
|
||||
}
|
||||
candidates = append(candidates, candidate)
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func (model *historyModel) resolveAttribution(ref historyAttributionRef) (historyAttribution, bool) {
|
||||
if model == nil || ref < 0 || int(ref) >= len(model.attributions) {
|
||||
return historyAttribution{}, false
|
||||
|
||||
@@ -156,6 +156,68 @@ func TestHistorySelectionMatchesDeepSidesOfAncestorShapeChange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistoryModelFinalizesLocationSensitivityAndDocumentReaders(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
root := parseDocumentForDecode(t, "shape: {'0': {value: old}}\n")
|
||||
value, _ := documentAtPath(root, Keys("shape", "0", "value"))
|
||||
value.Sensitive = true
|
||||
keyPath := Keys("shape", "0", "value")
|
||||
dependency := Keys("credentials", "source")
|
||||
|
||||
builder := newHistoryBuilder()
|
||||
builder.observeDocument(root, historyPhaseSources)
|
||||
definitionAttribution := builder.addAttribution(Origin{
|
||||
Layer: LayerBase, Locations: []Location{{SourceID: "base.yml", Line: 4, Column: 2}},
|
||||
}, nil, nil)
|
||||
definition := builder.nextPayload(historyPhaseSources)
|
||||
builder.addObservation(historyObservation{
|
||||
path: keyPath, phase: historyPhaseSources, payload: definition,
|
||||
reportedPath: keyPath, role: historyRoleDefinition,
|
||||
afterPresent: true, attributionRef: definitionAttribution,
|
||||
})
|
||||
resolutionAttribution := builder.addAttribution(
|
||||
Origin{Layer: LayerResolution, Operation: ChangeNormalization},
|
||||
[]string{"normalized credential"}, []Path{dependency},
|
||||
)
|
||||
resolution := builder.nextPayload(historyPhaseResolution)
|
||||
builder.addObservation(historyObservation{
|
||||
path: keyPath, phase: historyPhaseResolution, payload: resolution,
|
||||
reportedPath: Keys("shape"), role: historyRoleOperation,
|
||||
beforePresent: true, afterPresent: true, sensitive: true,
|
||||
attributionRef: resolutionAttribution,
|
||||
})
|
||||
model := builder.freeze()
|
||||
|
||||
if got := model.documentRoots(); len(got) == 0 || got[0] != root {
|
||||
t.Fatalf("document roots = %#v, want registered root", got)
|
||||
}
|
||||
if got, ok := model.documentAt(root, Keys("shape", "0")); !ok || got.Kind != document.Mapping {
|
||||
t.Fatalf("indexed document lookup = %#v, %v", got, ok)
|
||||
}
|
||||
if got, ok := model.latestHistoricalLocation(keyPath); !ok || got.SourceID != "base.yml" || got.Line != 4 || got.Column != 2 {
|
||||
t.Fatalf("latest historical location = %#v, %v", got, ok)
|
||||
}
|
||||
|
||||
candidates := model.sensitivityCandidates()
|
||||
var sawCandidate bool
|
||||
for _, candidate := range candidates {
|
||||
if !sameConcreteHistoryPath(candidate.path, keyPath) || !candidate.sensitive || len(candidate.dependencies) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(candidate.dependencies) != 1 || candidate.dependencies[0].String() != dependency.String() {
|
||||
t.Fatalf("sensitivity dependencies = %#v", candidate.dependencies)
|
||||
}
|
||||
sawCandidate = true
|
||||
}
|
||||
if !sawCandidate {
|
||||
t.Fatal("sensitivity candidate omitted the concrete historical path")
|
||||
}
|
||||
if got, found := model.documentAt(root, Keys("shape", "missing")); found || got != nil {
|
||||
t.Fatalf("lookup for missing value = %#v, %v", got, found)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistoryBuilderScratchUsesOneDetachedWorkingDocument(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ func TestProjectedSequenceHistoryAgreesAcrossMetadataAndExplanation(t *testing.T
|
||||
var metadata Metadata
|
||||
finalizeMetadataPaths(&metadata, composed.root, composed.applications)
|
||||
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())
|
||||
}
|
||||
|
||||
inspection := &Inspection{
|
||||
|
||||
@@ -339,7 +339,7 @@ func TestIndexedInheritanceHistoryMatchesOperationScan(t *testing.T) {
|
||||
if got := metadata.History(path); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("indexed history for %s differs from operation scan:\n got: %#v\nwant: %#v", path, got, want)
|
||||
}
|
||||
unprepared := Metadata{history: metadata.history, historicalPaths: metadata.historicalPaths, inheritance: metadata.inheritance}
|
||||
unprepared := Metadata{history: metadata.history, historyModel: metadata.historyModel, inheritance: metadata.inheritance}
|
||||
if got := unprepared.History(path); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unprepared history for %s differs from operation scan:\n got: %#v\nwant: %#v", path, got, want)
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ func collectFieldInfo(node *document.Node, path Path, decoded decodedConfigurati
|
||||
}
|
||||
|
||||
func cloneMetadata(metadata Metadata) Metadata {
|
||||
if metadata.fields == nil && metadata.history == nil {
|
||||
if metadata.fields == nil && metadata.history == nil && metadata.historyModel == nil {
|
||||
return Metadata{}
|
||||
}
|
||||
cloned := Metadata{
|
||||
@@ -230,15 +230,12 @@ func cloneMetadata(metadata Metadata) Metadata {
|
||||
history: make(map[string][]Change, len(metadata.history)),
|
||||
inheritance: append([]inheritanceOperation(nil), metadata.inheritance...),
|
||||
inheritanceIndex: metadata.inheritanceIndex,
|
||||
historyModel: metadata.historyModel,
|
||||
}
|
||||
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
|
||||
for pointer, path := range metadata.effectivePaths {
|
||||
cloned.effectivePaths[pointer] = path.clone()
|
||||
}
|
||||
cloned.historicalPaths = make(map[string][]Path, len(metadata.historicalPaths))
|
||||
for pointer, paths := range metadata.historicalPaths {
|
||||
cloned.historicalPaths[pointer] = clonePaths(paths)
|
||||
}
|
||||
for path, info := range metadata.fields {
|
||||
cloned.fields[path] = cloneFieldInfo(info)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T]
|
||||
}
|
||||
decoded := resolved.decoded
|
||||
metadata, final, preCLI := inspection.metadata, inspection.final, inspection.preCLI
|
||||
finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
|
||||
finalizeMetadataPathsWithResolution(&metadata, final, applications, resolved.snapshots, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
|
||||
secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, applications, resolved.versionEvidence, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final)
|
||||
sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false)
|
||||
retainedTrees := make([]*document.Node, 0, len(applications)+4)
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
)
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
|
||||
// Metadata provides detached effective presence/origin and chronological history
|
||||
// queries for a load. Its zero value contains no paths.
|
||||
type Metadata struct {
|
||||
effectivePaths map[string]Path
|
||||
historicalPaths map[string][]Path
|
||||
fields map[string]FieldInfo
|
||||
history map[string][]Change
|
||||
inheritance []inheritanceOperation
|
||||
inheritanceIndex *inheritanceHistoryIndex
|
||||
historyModel *historyModel
|
||||
}
|
||||
|
||||
// Lookup returns information for an effective path. Unknown and invalid paths
|
||||
@@ -77,18 +73,7 @@ func (m Metadata) History(path Path) []Change {
|
||||
// a path remains effective. Enumeration exposes no configuration values and does
|
||||
// not perform another load or resolve secrets.
|
||||
func (m Metadata) HistoricalPaths() []Path {
|
||||
pointers := make([]string, 0, len(m.historicalPaths))
|
||||
for pointer := range m.historicalPaths {
|
||||
pointers = append(pointers, pointer)
|
||||
}
|
||||
sort.Strings(pointers)
|
||||
paths := make([]Path, 0, len(pointers))
|
||||
for _, pointer := range pointers {
|
||||
// The index contains only canonical pointers produced by Path.String.
|
||||
path, _ := ParsePath(pointer)
|
||||
paths = append(paths, path)
|
||||
}
|
||||
return paths
|
||||
return m.historyModel.historicalPaths()
|
||||
}
|
||||
|
||||
// Textual pointers are lookup keys, but explicit key/index kinds remain part of
|
||||
@@ -115,54 +100,75 @@ func matchesPathShape(query, candidate Path) bool {
|
||||
}
|
||||
|
||||
func (m Metadata) hasHistoricalPath(path Path) bool {
|
||||
if !path.valid() {
|
||||
return false
|
||||
}
|
||||
for _, candidate := range m.historicalPaths[path.String()] {
|
||||
if matchesPathShape(path, candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return m.historyModel.hasObservedShape(path)
|
||||
}
|
||||
|
||||
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) {
|
||||
m.effectivePaths = make(map[string]Path)
|
||||
m.historicalPaths = make(map[string][]Path)
|
||||
addHistorical := func(path Path, pointer string) {
|
||||
for _, prior := range m.historicalPaths[pointer] {
|
||||
if matchesPathShape(path, prior) {
|
||||
return
|
||||
}
|
||||
finalizeMetadataPathsWithResolution(m, final, applications, nil, earlier...)
|
||||
}
|
||||
|
||||
func finalizeMetadataPathsWithResolution(m *Metadata, final *document.Node, applications []historyApplication, snapshots []resolutionSnapshot, earlier ...*document.Node) {
|
||||
finalizeEffectiveMetadataPaths(m, final)
|
||||
builder := newHistoryBuilder()
|
||||
if len(earlier) != 0 {
|
||||
builder.model.baseline = earlier[0]
|
||||
}
|
||||
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
|
||||
}
|
||||
m.historicalPaths[pointer] = append(m.historicalPaths[pointer], path.clone())
|
||||
builder.observeDocument(tree, phase)
|
||||
}
|
||||
walkDocument(final, Path{}, func(path Path, _ *document.Node) {
|
||||
pointer := path.String()
|
||||
m.effectivePaths[pointer] = path.clone()
|
||||
addHistorical(path, pointer)
|
||||
})
|
||||
add := func(path Path, _ *document.Node) {
|
||||
addHistorical(path, path.String())
|
||||
}
|
||||
for _, tree := range earlier {
|
||||
walkDocument(tree, Path{}, add)
|
||||
if final != nil {
|
||||
builder.observeDocument(final, historyPhasePostCLI)
|
||||
}
|
||||
for _, application := range applications {
|
||||
walkHistoryApplication(application, func(_ Path, effective Path, _ *document.Node) {
|
||||
add(effective, nil)
|
||||
builder.registerDocumentRoot(application.value)
|
||||
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 {
|
||||
add(operation.control.path, nil)
|
||||
builder.addInventoryObservation(operation.control.path, operation.control.path, payload, historyPhaseInheritance, false)
|
||||
continue
|
||||
}
|
||||
if operation.transfer != nil {
|
||||
walkDocument(operation.parent, operation.transfer.to, add)
|
||||
walkDocument(operation.effective, operation.transfer.to, add)
|
||||
if operation.transfer == nil {
|
||||
continue
|
||||
}
|
||||
builder.registerDocumentRoot(operation.parent)
|
||||
builder.registerDocumentRoot(operation.effective)
|
||||
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.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
||||
}
|
||||
|
||||
func finalizeEffectiveMetadataPaths(m *Metadata, final *document.Node) {
|
||||
m.effectivePaths = effectivePathIndex(final)
|
||||
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestMetadataPathIndexesOwnIndependentSegments(t *testing.T) {
|
||||
finalizeMetadataPaths(&metadata, final, nil)
|
||||
pointer := "/service/workers/0"
|
||||
effective := metadata.effectivePaths[pointer]
|
||||
historical := metadata.historicalPaths[pointer]
|
||||
historical := metadata.historyModel.concreteShapes(pointer)
|
||||
if effective.String() != pointer || len(historical) != 1 || historical[0].String() != pointer {
|
||||
t.Fatalf("path indexes = %#v, %#v", effective, historical)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func TestMetadataPathIndexesRetainDistinctHistoricalShapes(t *testing.T) {
|
||||
if !reflect.DeepEqual(metadata.effectivePaths[pointer], indexPath) {
|
||||
t.Fatal("effective index does not reflect the final sequence")
|
||||
}
|
||||
historical := metadata.historicalPaths[pointer]
|
||||
historical := metadata.historyModel.concreteShapes(pointer)
|
||||
if len(historical) != 2 {
|
||||
t.Fatalf("historical shapes = %#v, want one key and one index", historical)
|
||||
}
|
||||
@@ -99,6 +99,16 @@ func TestMetadataPathIndexesRetainDistinctHistoricalShapes(t *testing.T) {
|
||||
t.Fatalf("historical index lost shape %#v", want)
|
||||
}
|
||||
}
|
||||
paths := metadata.HistoricalPaths()
|
||||
var pointers []string
|
||||
for _, path := range paths {
|
||||
pointers = append(pointers, path.String())
|
||||
}
|
||||
if !reflect.DeepEqual(pointers, []string{"", "/shape", "/shape/0"}) ||
|
||||
len(paths[2].segments) != 2 || paths[2].segments[1].kind != pathSegmentParsed ||
|
||||
metadata.hasHistoricalPath(Keys("shape", "missing")) {
|
||||
t.Fatalf("historical enumeration or membership = %#v", paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistoricalPathsExposeHiddenBaseOwnershipWithoutResolvingBaseSecrets(t *testing.T) {
|
||||
@@ -149,9 +159,11 @@ func TestMetadataQueriesReturnDetachedRecords(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
path := Keys("service", "workers")
|
||||
builder := newHistoryBuilder()
|
||||
builder.observeDocument(parseDocumentForDecode(t, "service: {workers: null}\n"), historyPhaseBaseline)
|
||||
metadata := Metadata{
|
||||
effectivePaths: map[string]Path{path.String(): path},
|
||||
historicalPaths: map[string][]Path{path.String(): {path}},
|
||||
effectivePaths: map[string]Path{path.String(): path},
|
||||
historyModel: builder.freeze(),
|
||||
fields: map[string]FieldInfo{
|
||||
path.String(): {
|
||||
Explicit: true,
|
||||
|
||||
@@ -65,8 +65,7 @@ func preparePostDecodeOverrideContext(input assembledInput, before decodedConfig
|
||||
origin: overlayOrigin(LayerCLI, document.Location{SourceID: override.source.ID}),
|
||||
})
|
||||
}
|
||||
allApplications := append(cloneHistoryApplications(applications), cloneHistoryApplications(postDecodeApps)...)
|
||||
finalizeMetadataPaths(&metadata, assigned.final, allApplications, input.document, before.final)
|
||||
finalizeEffectiveMetadataPaths(&metadata, assigned.final)
|
||||
|
||||
return postDecodeOverrideContext{
|
||||
structuralInput: document.Clone(input.document),
|
||||
|
||||
@@ -83,6 +83,7 @@ func TestPostDecodeOverrideContextOrdersDecodeCLIAndResolutionEvidence(t *testin
|
||||
t.Fatalf("resolution CLI source = %#v, %v", source, ok)
|
||||
}
|
||||
|
||||
finalizeMetadataPaths(&prepared.metadata, assigned.final, prepared.historyApplications(), decoded.baseline, prepared.structuralInput, prepared.preCLI, assigned.final)
|
||||
inspection := Inspection{
|
||||
metadata: prepared.metadata, baseline: decoded.baseline, input: assigned.final, inputOrigins: assigned.origins,
|
||||
preCLI: prepared.preCLI, preCLIOrigins: prepared.preCLIOrigins,
|
||||
|
||||
Reference in New Issue
Block a user