Replay explanations through shared history selections

This commit is contained in:
2026-10-26 17:01:32 +00:00
parent 4b4e15fc58
commit 738c8a9cc7
12 changed files with 386 additions and 135 deletions

View File

@@ -2,64 +2,61 @@ package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
func appendInheritanceExplanation(explanation *Explanation, working **document.Node, query Path, operations []inheritanceOperation, secrets map[string]bool) {
for _, operation := range operations {
selectionPath := query
if operation.transfer != nil {
selectionPath = operationPathForQuery(operation.transfer.to, query)
}
selections := resolveInheritanceAttribution(operation, selectionPath)
if operation.control != nil {
control := selectInheritanceEvidence(selections, inheritanceEvidenceControl, inheritanceEvidenceControl)
if control == nil || !pathsIntersect(operation.control.path, query) {
func appendInheritanceExplanation(explanation *Explanation, working **document.Node, query Path, model *historyModel, selections []historySelection, secrets map[string]bool) {
if model == nil {
return
}
for _, payload := range model.inheritancePayloads {
for operationIndex, operation := range payload.operations {
if operation.control != nil {
ref := historyPayloadRef{phase: historyPhaseInheritance, id: 0, substep: operationIndex * 2}
selection, selected := historySelectionForPayload(selections, ref)
if !selected {
continue
}
before := valueSnapshot(*working, query, secrets)
replaceHistoryPath(working, operation.control.path, nil)
after := valueSnapshot(*working, query, secrets)
appendInheritanceChange(explanation, selection, before, after)
continue
}
if operation.transfer == nil {
continue
}
parentRef := historyPayloadRef{phase: historyPhaseInheritance, id: 0, substep: operationIndex * 2}
childRef := historyPayloadRef{phase: historyPhaseInheritance, id: 0, substep: operationIndex*2 + 1}
parentSelection, hasParent := historySelectionForPayload(selections, parentRef)
childSelection, hasChild := historySelectionForPayload(selections, childRef)
before := valueSnapshot(*working, query, secrets)
replaceHistoryPath(working, operation.control.path, nil)
after := valueSnapshot(*working, query, secrets)
explanation.History = append(explanation.History, ValueChange{
Before: before, After: after, Origin: cloneOrigin(control.origin),
Dependencies: clonePaths(control.dependencies),
})
continue
}
if operation.transfer == nil {
continue
}
before := valueSnapshot(*working, query, secrets)
replaceHistoryPath(working, operation.transfer.to, operation.parent)
// A record keeps its own identity throughout inheritance. Restore it
// before taking enclosing snapshots as well as direct field snapshots.
for _, field := range operation.identityFields {
identity, _ := documentAtPath(operation.effective, Keys(field))
replaceHistoryPath(working, operation.transfer.to.Key(field), identity)
}
afterParent := valueSnapshot(*working, query, secrets)
parent := selectInheritanceEvidence(selections, inheritanceEvidenceParent, inheritanceEvidenceParent)
if pathsIntersect(operation.transfer.to, query) && parent != nil {
explanation.History = append(explanation.History, ValueChange{
Before: before, After: afterParent, Origin: cloneOrigin(parent.origin),
Dependencies: clonePaths(parent.dependencies),
})
}
replaceHistoryPath(working, operation.transfer.to, operation.effective)
if !pathsIntersect(operation.transfer.to, query) {
continue
}
afterChild := valueSnapshot(*working, query, secrets)
child := selectInheritanceEvidence(selections, inheritanceEvidenceChild, inheritanceEvidenceRemoval, inheritanceEvidenceDisplaced)
if child != nil && (parent == nil || len(selections) > 1) {
explanation.History = append(explanation.History, ValueChange{
Before: afterParent, After: afterChild, Origin: cloneOrigin(child.origin),
Dependencies: clonePaths(child.dependencies),
})
replaceHistoryPath(working, operation.transfer.to, operation.parent)
// A record keeps its own identity throughout inheritance. Restore it
// before taking enclosing snapshots as well as direct field snapshots.
for _, field := range operation.identityFields {
identity, _ := documentAtPath(operation.effective, Keys(field))
replaceHistoryPath(working, operation.transfer.to.Key(field), identity)
}
afterParent := valueSnapshot(*working, query, secrets)
if hasParent {
appendInheritanceChange(explanation, parentSelection, before, afterParent)
}
replaceHistoryPath(working, operation.transfer.to, operation.effective)
afterChild := valueSnapshot(*working, query, secrets)
if hasChild {
appendInheritanceChange(explanation, childSelection, afterParent, afterChild)
}
}
}
}
func operationPathForQuery(root, query Path) Path {
if pathContains(query, root) {
return root
func appendInheritanceChange(explanation *Explanation, selection historySelection, before, after ValueSnapshot) {
if !selection.hasAttribution {
return
}
return query
explanation.History = append(explanation.History, ValueChange{
Before: before,
After: after,
Origin: cloneOrigin(selection.attribution.origin),
Reasons: append([]string(nil), selection.attribution.reasons...),
Dependencies: clonePaths(selection.attribution.dependencies),
})
}