Replay explanations through shared history selections
This commit is contained in:
@@ -37,7 +37,15 @@ type Explanation struct {
|
||||
// Explain returns safe effective and chronological values for a known final or
|
||||
// historical path. Unknown and malformed paths return false.
|
||||
func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
||||
if r == nil || r.final == nil || r.baseline == nil || !r.metadata.hasHistoricalPath(path) {
|
||||
if r == nil || r.final == nil || !path.valid() {
|
||||
return Explanation{}, false
|
||||
}
|
||||
model := r.metadata.historyModel
|
||||
if model == nil {
|
||||
return Explanation{}, false
|
||||
}
|
||||
selections := model.selectHistory(path)
|
||||
if len(selections) == 0 && !model.hasObservedShape(path) {
|
||||
return Explanation{}, false
|
||||
}
|
||||
explanation := Explanation{Path: path.clone(), Effective: valueSnapshotWithLookup(r.final, path, r.secrets, r.immutableLookup)}
|
||||
@@ -45,20 +53,22 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
||||
explanation.Origin = cloneOrigin(info.Origin)
|
||||
}
|
||||
|
||||
working := document.Clone(r.baseline)
|
||||
if _, present := documentAtPath(working, path); present {
|
||||
working := document.Clone(model.baseline)
|
||||
if baseline, found := historySelectionForPayload(selections, historyPayloadRef{phase: historyPhaseBaseline, id: 0}); found && baseline.after {
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
After: valueSnapshot(working, path, r.secrets), Origin: Origin{Layer: LayerDefault},
|
||||
After: valueSnapshot(working, path, r.secrets),
|
||||
Origin: cloneOrigin(baseline.attribution.origin),
|
||||
})
|
||||
}
|
||||
replayIndex := new(mappingIndex)
|
||||
for _, application := range r.applications {
|
||||
operationPath, relevant := applicationOperationPath(application, path, r.immutableLookup)
|
||||
var before ValueSnapshot
|
||||
if relevant {
|
||||
before = valueSnapshot(working, path, r.secrets)
|
||||
for payloadID, payload := range model.sourcePayloads {
|
||||
if payload.kind != historySourceApplicationPayload {
|
||||
continue
|
||||
}
|
||||
application := payload.application
|
||||
before := valueSnapshot(working, path, r.secrets)
|
||||
applyHistoryApplication(&working, application, replayIndex)
|
||||
selected, relevant := historySelectionForApplication(model, selections, historyPhaseSources, historyPayloadRef{phase: historyPhaseSources, id: payloadID})
|
||||
if !relevant {
|
||||
continue
|
||||
}
|
||||
@@ -69,37 +79,38 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: before,
|
||||
After: after,
|
||||
Origin: historyApplicationOrigin(application, operationPath, r.immutableLookup),
|
||||
Origin: cloneOrigin(selected.attribution.origin),
|
||||
})
|
||||
}
|
||||
appendInheritanceExplanation(&explanation, &working, path, r.inheritance, r.secrets)
|
||||
appendInheritanceExplanation(&explanation, &working, path, model, selections, r.secrets)
|
||||
|
||||
// Intermediate input replacements do not permanently suppress defaults:
|
||||
// decoding applies them to the final input tree. Show that reconciliation
|
||||
// explicitly before typed CLI contributions and domain resolution.
|
||||
decodedInput := r.preCLI
|
||||
decodedOrigins := r.preCLIOrigins
|
||||
replayed, _ := documentAtPath(working, path)
|
||||
decoded, _ := documentAtPathWithLookup(decodedInput, path, r.immutableLookup)
|
||||
if !configurationValuesEqual(replayed, decoded) {
|
||||
origin := cloneOrigin(decodedOrigins[path.String()])
|
||||
origin.Operation = ChangeDecoding
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: valueSnapshot(working, path, r.secrets),
|
||||
After: valueSnapshotWithLookup(decodedInput, path, r.secrets, r.immutableLookup),
|
||||
Origin: origin,
|
||||
Reasons: []string{"applied retained defaults and canonical typed decoding"},
|
||||
})
|
||||
if checkpoint := model.preCLICheckpoint; checkpoint != nil {
|
||||
selection, selected := historySelectionForPayload(selections, historyPayloadRef{phase: historyPhasePreCLI, id: 0})
|
||||
replayed, _ := documentAtPath(working, path)
|
||||
decoded, _ := documentAtPathWithLookup(checkpoint.root, path, model.documentLookup)
|
||||
if selected && !configurationValuesEqual(replayed, decoded) {
|
||||
origin, reasons := checkpointExplanationAttribution(selection)
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: valueSnapshot(working, path, r.secrets),
|
||||
After: valueSnapshotWithLookup(checkpoint.root, path, r.secrets, model.documentLookup),
|
||||
Origin: origin,
|
||||
Reasons: reasons,
|
||||
})
|
||||
}
|
||||
working = document.Clone(checkpoint.root)
|
||||
}
|
||||
|
||||
working = document.Clone(decodedInput)
|
||||
for _, application := range r.postDecodeApps {
|
||||
operationPath, relevant := applicationOperationPath(application, path, r.immutableLookup)
|
||||
var before ValueSnapshot
|
||||
if relevant {
|
||||
before = valueSnapshot(working, path, r.secrets)
|
||||
for payloadID, payload := range model.cliPayloads {
|
||||
if payload.kind != historySourceApplicationPayload {
|
||||
continue
|
||||
}
|
||||
applyHistoryApplication(&working, application, replayIndex)
|
||||
applicationRef := historyPayloadRef{phase: historyPhaseCLI, id: payloadID}
|
||||
before := valueSnapshot(working, path, r.secrets)
|
||||
applyHistoryApplication(&working, payload.application, replayIndex)
|
||||
selected, relevant := historySelectionForApplication(model, selections, historyPhaseCLI, applicationRef)
|
||||
if !relevant {
|
||||
continue
|
||||
}
|
||||
@@ -110,47 +121,45 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: before,
|
||||
After: after,
|
||||
Origin: historyApplicationOrigin(application, operationPath, r.immutableLookup),
|
||||
Origin: cloneOrigin(selected.attribution.origin),
|
||||
})
|
||||
}
|
||||
replayed, _ = documentAtPath(working, path)
|
||||
effectiveInput, _ := documentAtPathWithLookup(r.input, path, r.immutableLookup)
|
||||
if !configurationValuesEqual(replayed, effectiveInput) {
|
||||
origin := cloneOrigin(r.inputOrigins[path.String()])
|
||||
origin.Operation = ChangeDecoding
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: valueSnapshot(working, path, r.secrets),
|
||||
After: valueSnapshotWithLookup(r.input, path, r.secrets, r.immutableLookup),
|
||||
Origin: origin,
|
||||
Reasons: []string{"applied the typed CLI assignment"},
|
||||
})
|
||||
if checkpoint := model.postCLICheckpoint; checkpoint != nil {
|
||||
selection, selected := historySelectionForPayload(selections, historyPayloadRef{phase: historyPhasePostCLI, id: 0})
|
||||
replayed, _ := documentAtPath(working, path)
|
||||
effectiveInput, _ := documentAtPathWithLookup(checkpoint.root, path, model.documentLookup)
|
||||
if selected && !configurationValuesEqual(replayed, effectiveInput) {
|
||||
origin, reasons := checkpointExplanationAttribution(selection)
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: valueSnapshot(working, path, r.secrets),
|
||||
After: valueSnapshotWithLookup(checkpoint.root, path, r.secrets, model.documentLookup),
|
||||
Origin: origin,
|
||||
Reasons: reasons,
|
||||
})
|
||||
}
|
||||
working = document.Clone(checkpoint.root)
|
||||
}
|
||||
working = document.Clone(r.input)
|
||||
for _, group := range r.resolutionReplay {
|
||||
if group.coveringRecordID < 0 || group.coveringRecordID >= len(r.resolution) {
|
||||
continue
|
||||
if model.resolutionPayload != nil {
|
||||
for _, group := range model.resolutionPayload.replayGroups {
|
||||
selection, selected := historySelectionForResolutionGroup(selections, group)
|
||||
if !selected || group.coveringRecordID < 0 || group.coveringRecordID >= len(model.resolutionPayload.snapshots) {
|
||||
continue
|
||||
}
|
||||
covering := model.resolutionPayload.snapshots[group.coveringRecordID]
|
||||
before := valueSnapshot(working, path, r.secrets)
|
||||
replaceHistoryPath(&working, group.change.Path, covering.after)
|
||||
after := valueSnapshot(working, path, r.secrets)
|
||||
if !before.Present && !after.Present {
|
||||
continue
|
||||
}
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: before,
|
||||
After: after,
|
||||
Origin: cloneOrigin(selection.attribution.origin),
|
||||
Reasons: append([]string(nil), group.change.Reasons...),
|
||||
Dependencies: clonePaths(group.change.Dependencies),
|
||||
})
|
||||
}
|
||||
covering := r.resolution[group.coveringRecordID]
|
||||
relevant := pathsIntersect(group.change.Path, path)
|
||||
var before ValueSnapshot
|
||||
if relevant {
|
||||
before = valueSnapshot(working, path, r.secrets)
|
||||
}
|
||||
replaceHistoryPath(&working, group.change.Path, covering.after)
|
||||
if !relevant {
|
||||
continue
|
||||
}
|
||||
after := valueSnapshot(working, path, r.secrets)
|
||||
if !before.Present && !after.Present {
|
||||
continue
|
||||
}
|
||||
explanation.History = append(explanation.History, ValueChange{
|
||||
Before: before,
|
||||
After: after,
|
||||
Origin: cloneOrigin(group.change.Origin),
|
||||
Reasons: append([]string(nil), group.change.Reasons...),
|
||||
Dependencies: clonePaths(group.change.Dependencies),
|
||||
})
|
||||
}
|
||||
|
||||
if !explanation.Effective.Present && len(explanation.History) == 0 {
|
||||
@@ -161,3 +170,63 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
|
||||
}
|
||||
return explanation, true
|
||||
}
|
||||
|
||||
func historySelectionForPayload(selections []historySelection, ref historyPayloadRef) (historySelection, bool) {
|
||||
for _, selection := range selections {
|
||||
if selection.payload == ref {
|
||||
return selection, true
|
||||
}
|
||||
}
|
||||
return historySelection{}, false
|
||||
}
|
||||
|
||||
func historySelectionForApplication(model *historyModel, selections []historySelection, phase historyPhase, applicationRef historyPayloadRef) (historySelection, bool) {
|
||||
var selected historySelection
|
||||
found := false
|
||||
if direct, ok := historySelectionForPayload(selections, applicationRef); ok {
|
||||
selected, found = direct, true
|
||||
}
|
||||
var payloads []historySourcePayload
|
||||
switch phase {
|
||||
case historyPhaseSources:
|
||||
payloads = model.sourcePayloads
|
||||
case historyPhaseCLI:
|
||||
payloads = model.cliPayloads
|
||||
default:
|
||||
return selected, found
|
||||
}
|
||||
for payloadID, candidate := range payloads {
|
||||
if candidate.kind != historySourceOverlayPayload || !candidate.hasApplicationRef || candidate.applicationRef != applicationRef {
|
||||
continue
|
||||
}
|
||||
if operation, ok := historySelectionForPayload(selections, historyPayloadRef{phase: phase, id: payloadID}); ok {
|
||||
selected, found = operation, true
|
||||
}
|
||||
}
|
||||
return selected, found
|
||||
}
|
||||
|
||||
func checkpointExplanationAttribution(selection historySelection) (Origin, []string) {
|
||||
if selection.hasAttribution {
|
||||
return cloneOrigin(selection.attribution.origin), append([]string(nil), selection.attribution.reasons...)
|
||||
}
|
||||
return Origin{Operation: ChangeDecoding}, nil
|
||||
}
|
||||
|
||||
func historySelectionForResolutionGroup(selections []historySelection, group resolutionReplayGroup) (historySelection, bool) {
|
||||
var fallback historySelection
|
||||
found := false
|
||||
for _, recordID := range group.originalRecordIDs {
|
||||
selection, ok := historySelectionForPayload(selections, historyPayloadRef{phase: historyPhaseResolution, id: recordID})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if recordID == group.coveringRecordID {
|
||||
return selection, true
|
||||
}
|
||||
if !found {
|
||||
fallback, found = selection, true
|
||||
}
|
||||
}
|
||||
return fallback, found
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user