Normalize inheritance history evidence

This commit is contained in:
2026-10-26 15:25:02 +00:00
parent 11f7154874
commit da74d4190d
8 changed files with 482 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
# Interface Contract Remediation Implementation Plan
Status: in progress. Stages 1–5 are complete; Stages 6–18 remain pending.
Status: in progress. Stages 1–6 are complete; Stages 7–18 remain pending.
Baseline: `f6d54a094a2b5ada5748686620659399dce7ebf0`.
Audience: a Luna coding agent executing the stages sequentially.
@@ -420,7 +420,11 @@ and correct displacement attribution. Focused check:
## Stage 6 — Normalize Inheritance And Control Evidence
Owners: new `history_inheritance.go`, `inheritance_history.go`,
`inheritance_explain.go`, inheritance operation producers. Status: pending.
`inheritance_explain.go`, inheritance operation producers. Status: complete.
Validation:
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Inheritance.*History|History.*Inheritance|RecordInheritance|NestedInheritance' -count=1`,
`GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -count=1`,
and `git diff --check`.
1. Register one ordered inheritance payload. Produce observations for control
removal/cancellation, parent transfer, effective child contribution, and

View File

@@ -0,0 +1,305 @@
package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
type historyInheritancePayload struct {
operations []inheritanceOperation
}
type inheritanceEvidenceKind uint8
const (
inheritanceEvidenceControl inheritanceEvidenceKind = iota
inheritanceEvidenceParent
inheritanceEvidenceChild
inheritanceEvidenceDisplaced
inheritanceEvidenceRemoval
)
type inheritanceAttributionSelection struct {
kind inheritanceEvidenceKind
path Path
origin Origin
dependencies []Path
}
type inheritanceShapeFacts struct {
child bool
parent bool
effective bool
}
// observeInheritanceEvidence owns the ordered inheritance operation list and
// records its concrete before/after shapes against the builder's one scratch
// projection. Parent and effective-child trees alone cannot establish which
// concrete paths an operation displaced from its child or enclosing tree.
func (builder *historyBuilder) observeInheritanceEvidence(operations []inheritanceOperation) {
builder.requireOpen()
if len(operations) == 0 {
return
}
payload := builder.nextPayload(historyPhaseInheritance)
if payload.id != len(builder.model.inheritancePayloads) {
panic("configkit: inheritance payload identifiers are out of order")
}
owned := cloneInheritanceOperations(operations)
builder.model.inheritancePayloads = append(builder.model.inheritancePayloads, historyInheritancePayload{operations: owned})
for operationIndex, operation := range owned {
if operation.control != nil {
builder.observeInheritanceControl(payload, operationIndex, operation)
continue
}
if operation.transfer == nil {
continue
}
builder.observeInheritanceTransfer(payload, operationIndex, operation)
}
}
func (builder *historyBuilder) observeInheritanceControl(payload historyPayloadRef, operationIndex int, operation inheritanceOperation) {
control := operation.control
if control == nil {
return
}
node, before := builder.scratchAt(control.path)
selections := resolveInheritanceAttribution(operation, control.path)
var attributionRef historyAttributionRef = noHistoryAttribution
var sensitive bool
if before && node != nil {
sensitive = node.Sensitive
}
if len(selections) != 0 {
selection := selections[0]
attributionRef = builder.addAttribution(selection.origin, nil, selection.dependencies)
}
ref := payload
ref.substep = operationIndex * 2
builder.addObservation(historyObservation{
path: control.path, phase: historyPhaseInheritance, payload: ref,
reportedPath: control.path, role: historyRoleControl,
beforePresent: before, sensitive: sensitive,
attributionRef: attributionRef,
})
replaceHistoryPath(&builder.scratch, control.path, nil)
}
func (builder *historyBuilder) observeInheritanceTransfer(payload historyPayloadRef, operationIndex int, operation inheritanceOperation) {
transfer := operation.transfer
if transfer == nil {
return
}
beforeChild, _ := documentAtPath(builder.scratch, transfer.to)
parentPaths := inheritanceTreePaths(operation.parent, transfer.to)
beforePaths := inheritanceTreePaths(beforeChild, transfer.to)
parentSide := unionInheritancePaths(beforePaths, parentPaths)
parentRef := payload
parentRef.substep = operationIndex * 2
for _, path := range parentSide {
if inheritanceOperationIdentity(operation, path) {
continue
}
beforeNode, before := documentAtInheritancePath(beforeChild, transfer.to, path)
parentNode, after := documentAtInheritancePath(operation.parent, transfer.to, path)
selection := selectInheritanceEvidence(resolveInheritanceAttribution(operation, path, inheritanceShapeFacts{
child: before, parent: after,
}), inheritanceEvidenceParent, inheritanceEvidenceDisplaced)
builder.addInheritanceObservation(parentRef, transfer.to, path, before, after, beforeNode, parentNode, selection)
}
parentWorking := document.Clone(operation.parent)
if parentWorking != nil {
replaceHistoryPath(&builder.scratch, transfer.to, parentWorking)
for _, field := range operation.identityFields {
identity, found := documentAtPath(operation.effective, Keys(field))
if found {
replaceHistoryPath(&builder.scratch, transfer.to.Key(field), document.Clone(identity))
}
}
}
parentState, _ := documentAtPath(builder.scratch, transfer.to)
effectivePaths := inheritanceTreePaths(operation.effective, transfer.to)
parentStatePaths := inheritanceTreePaths(parentState, transfer.to)
childSide := unionInheritancePaths(parentStatePaths, effectivePaths)
childRef := payload
childRef.substep = operationIndex*2 + 1
for _, path := range childSide {
if inheritanceOperationIdentity(operation, path) {
continue
}
parentNode, before := documentAtInheritancePath(parentState, transfer.to, path)
effectiveNode, after := documentAtInheritancePath(operation.effective, transfer.to, path)
selection := selectInheritanceEvidence(resolveInheritanceAttribution(operation, path, inheritanceShapeFacts{
parent: before, effective: after,
}), inheritanceEvidenceChild, inheritanceEvidenceRemoval, inheritanceEvidenceDisplaced)
builder.addInheritanceObservation(childRef, transfer.to, path, before, after, parentNode, effectiveNode, selection)
}
if operation.effective != nil {
replaceHistoryPath(&builder.scratch, transfer.to, document.Clone(operation.effective))
}
}
func (builder *historyBuilder) addInheritanceObservation(payload historyPayloadRef, reportedPath, path Path, before, after bool, beforeNode, afterNode *document.Node, selection *inheritanceAttributionSelection) {
ref := noHistoryAttribution
if selection != nil {
ref = builder.addAttribution(selection.origin, nil, selection.dependencies)
}
sensitive := beforeNode != nil && beforeNode.Sensitive || afterNode != nil && afterNode.Sensitive
builder.addObservation(historyObservation{
path: path, phase: historyPhaseInheritance, payload: payload,
reportedPath: reportedPath, role: historyRoleOperation,
beforePresent: before, afterPresent: after, sensitive: sensitive,
attributionRef: ref,
})
}
func selectInheritanceEvidence(selections []inheritanceAttributionSelection, preferred inheritanceEvidenceKind, fallbacks ...inheritanceEvidenceKind) *inheritanceAttributionSelection {
for index := range selections {
if selections[index].kind == preferred {
return &selections[index]
}
}
for _, fallback := range fallbacks {
for index := range selections {
if selections[index].kind == fallback {
return &selections[index]
}
}
}
return nil
}
func inheritanceTreePaths(root *document.Node, destination Path) []Path {
if root == nil {
return nil
}
var paths []Path
walkDocument(root, Path{}, func(local Path, _ *document.Node) {
paths = append(paths, appendInheritancePath(destination, local))
})
return paths
}
func unionInheritancePaths(groups ...[]Path) []Path {
var paths []Path
seen := make(map[string][]Path)
for _, group := range groups {
for _, path := range group {
found := false
for _, existing := range seen[path.String()] {
if sameConcreteHistoryPath(existing, path) {
found = true
break
}
}
if !found {
owned := path.clone()
paths = append(paths, owned)
seen[path.String()] = append(seen[path.String()], owned)
}
}
}
return paths
}
func documentAtInheritancePath(root *document.Node, rootPath, path Path) (*document.Node, bool) {
relative, within := relativeHistoryPath(rootPath, path)
if !within {
return nil, false
}
return documentAtPath(root, relative)
}
func resolveInheritanceAttribution(operation inheritanceOperation, destination Path, shapes ...inheritanceShapeFacts) []inheritanceAttributionSelection {
if operation.control != nil {
if !pathsIntersect(operation.control.path, destination) {
return nil
}
origin := cloneOrigin(operation.controlOrigin)
origin.Operation = ChangeInheritance
origin.Inheritance = projectInheritanceSteps(origin)
return []inheritanceAttributionSelection{{
kind: inheritanceEvidenceControl, path: operation.control.path.clone(), origin: origin,
dependencies: []Path{operation.control.path.clone()},
}}
}
if operation.transfer == nil || !pathsIntersect(operation.transfer.to, destination) || inheritanceOperationIdentity(operation, destination) {
return nil
}
path := destination
if pathContains(destination, operation.transfer.to) {
path = operation.transfer.to
}
var selections []inheritanceAttributionSelection
parent, parentFound := transferredOperationOrigin(operation, path)
effective, effectiveFound := operation.effectiveOrigins[path.String()]
child, childFound := operation.childOrigins[path.String()]
if len(shapes) != 0 {
parentFound = parentFound && shapes[0].parent
effectiveFound = effectiveFound && shapes[0].effective
childFound = childFound && shapes[0].child
}
if parentFound {
parent.Operation = ChangeInheritance
parent.Inheritance = projectInheritanceSteps(parent)
selections = append(selections, inheritanceAttributionSelection{
kind: inheritanceEvidenceParent, path: path.clone(), origin: parent,
dependencies: []Path{operation.transfer.from.clone(), operation.transfer.reference.clone()},
})
}
if effectiveFound {
effective = cloneOrigin(effective)
effective.Operation = ChangeInheritance
}
if effectiveFound && (!parentFound || !originContainedBy(effective, parent)) {
effective.Inheritance = projectInheritanceSteps(effective)
selections = append(selections, inheritanceAttributionSelection{
kind: inheritanceEvidenceChild, path: path.clone(), origin: effective,
dependencies: []Path{operation.transfer.reference.clone()},
})
} else if parentFound && !effectiveFound {
removed, found := operation.effectiveOrigins[operation.transfer.to.String()]
if found {
removed = cloneOrigin(removed)
removed.Operation = ChangeInheritance
removed.Inheritance = projectInheritanceSteps(removed)
selections = append(selections, inheritanceAttributionSelection{
kind: inheritanceEvidenceRemoval, path: operation.transfer.to.clone(), origin: removed,
dependencies: []Path{operation.transfer.reference.clone()},
})
}
}
if childFound && !parentFound {
child = cloneOrigin(child)
child.Operation = ChangeInheritance
child.Inheritance = projectInheritanceSteps(child)
selections = append(selections, inheritanceAttributionSelection{
kind: inheritanceEvidenceDisplaced, path: path.clone(), origin: child,
dependencies: []Path{operation.transfer.reference.clone()},
})
}
return selections
}
func inheritanceChangesForQuery(query Path, operation inheritanceOperation) []Change {
if operation.control != nil {
for _, selection := range resolveInheritanceAttribution(operation, query) {
return []Change{{Path: selection.path.clone(), Origin: cloneOrigin(selection.origin), Dependencies: clonePaths(selection.dependencies)}}
}
return nil
}
if operation.transfer == nil {
return nil
}
path := operationPathForQuery(operation.transfer.to, query)
selections := resolveInheritanceAttribution(operation, path)
changes := make([]Change, 0, len(selections))
for _, selection := range selections {
if selection.kind == inheritanceEvidenceDisplaced {
continue
}
changes = append(changes, Change{Path: selection.path.clone(), Origin: cloneOrigin(selection.origin), Dependencies: clonePaths(selection.dependencies)})
}
return changes
}

View File

@@ -0,0 +1,141 @@
package engine
import "testing"
func TestInheritanceEvidenceUsesOrderedPayloadAndConcreteTransferShapes(t *testing.T) {
t.Parallel()
controlPath := Keys("child", inheritanceKey)
transfer := inheritanceTransfer{
from: Keys("parent"), to: Keys("child"), reference: Keys("parent"),
locations: []Location{{SourceID: "directive"}},
}
parent := parseDocumentForDecode(t, "id: parent\nbranch: {deep: {leaf: inherited}}\n")
effective := parseDocumentForDecode(t, "id: child\nbranch: replaced\n")
root := parseDocumentForDecode(t, "parent: {unused: true}\nchild: {id: child, extends: parent, branch: {local: stale}}\n")
parentOrigins := map[string]Origin{
Keys("parent", "branch").String(): {Layer: LayerBase, Locations: []Location{{SourceID: "parent"}}},
Keys("parent", "branch", "deep").String(): {Layer: LayerBase, Locations: []Location{{SourceID: "parent"}}},
Keys("parent", "branch", "deep", "leaf").String(): {Layer: LayerBase, Locations: []Location{{SourceID: "parent"}}},
}
effectiveOrigins := map[string]Origin{
Keys("child", "branch").String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}},
}
childOrigins := map[string]Origin{
Keys("child", "id").String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}},
Keys("child", "branch").String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}},
Keys("child", "branch", "local").String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}},
}
operation := inheritanceOperation{
transfer: &transfer, parent: parent, effective: effective,
parentOrigins: parentOrigins, effectiveOrigins: effectiveOrigins,
childOrigins: childOrigins, identityFields: []string{"id"},
}
controlOrigin := Origin{Layer: LayerOverlay, Locations: []Location{{SourceID: "directive"}}}
controlOperation := inheritanceOperation{
control: &inheritanceControl{path: controlPath}, controlOrigin: controlOrigin,
}
builder := newHistoryBuilder()
builder.setScratch(root)
builder.observeInheritanceEvidence([]inheritanceOperation{controlOperation, operation})
model := builder.freeze()
if len(model.inheritancePayloads) != 1 || len(model.inheritancePayloads[0].operations) != 2 {
t.Fatalf("ordered inheritance payload = %#v", model.inheritancePayloads)
}
controlSelections := model.selectHistory(controlPath)
if len(controlSelections) != 1 || controlSelections[0].role != historyRoleControl || !controlSelections[0].before || controlSelections[0].after ||
!controlSelections[0].hasAttribution || len(controlSelections[0].attribution.dependencies) != 1 || controlSelections[0].attribution.dependencies[0].String() != controlPath.String() {
t.Fatalf("consumed control selection = %#v", controlSelections)
}
inheritedPath := Keys("child", "branch", "deep", "leaf")
inherited := model.selectHistory(inheritedPath)
if len(inherited) != 2 || inherited[0].phase != historyPhaseInheritance || inherited[0].payload.id != 0 || inherited[0].payload.substep != 2 ||
inherited[0].before || !inherited[0].after || !inherited[0].hasAttribution || inherited[0].attribution.origin.Locations[0].SourceID != "parent" ||
len(inherited[0].attribution.dependencies) != 2 || inherited[1].payload.substep != 3 || !inherited[1].before || inherited[1].after {
t.Fatalf("parent transfer selection = %#v", inherited)
}
displacedPath := Keys("child", "branch", "local")
displaced := model.selectHistory(displacedPath)
if len(displaced) != 1 || displaced[0].payload.substep != 2 || !displaced[0].before || displaced[0].after ||
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")
}
if identity := model.selectHistory(Keys("child", "id")); len(identity) != 0 {
t.Fatalf("child identity received inheritance evidence: %#v", identity)
}
}
func TestInheritanceResolverSharesProjectionAttribution(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"}}}
operation := inheritanceOperation{
transfer: &inheritanceTransfer{from: from, to: to, reference: reference},
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)
}
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)
}
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)
}
}
func TestInheritanceEvidenceSeparatesCollidingKeyAndIndexShapes(t *testing.T) {
t.Parallel()
from, to := Keys("parent"), Keys("child")
transfer := inheritanceTransfer{from: from, to: to, reference: from}
parent := parseDocumentForDecode(t, "items: [{value: inherited}]\n")
effective := parseDocumentForDecode(t, "items: {'0': {value: child}}\n")
root := parseDocumentForDecode(t, "child: {items: {'0': {value: old}}}\n")
parentPath := from.Key("items").Index(0).Key("value")
childPath := to.Key("items").Key("0").Key("value")
operation := inheritanceOperation{
transfer: &transfer, parent: parent, effective: effective,
parentOrigins: map[string]Origin{parentPath.String(): {Layer: LayerBase, Locations: []Location{{SourceID: "parent"}}}},
effectiveOrigins: map[string]Origin{childPath.String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}}},
childOrigins: map[string]Origin{childPath.String(): {Layer: LayerOverlay, Locations: []Location{{SourceID: "child"}}}},
}
builder := newHistoryBuilder()
builder.setScratch(root)
builder.observeInheritanceEvidence([]inheritanceOperation{operation})
model := builder.freeze()
keyPath := to.Key("items").Key("0").Key("value")
keySelections := model.selectHistory(keyPath)
if len(keySelections) != 2 || keySelections[0].payload.substep != 0 || !keySelections[0].before || keySelections[0].after ||
keySelections[0].attribution.origin.Locations[0].SourceID != "child" || keySelections[1].payload.substep != 1 ||
keySelections[1].before || !keySelections[1].after || keySelections[1].attribution.origin.Locations[0].SourceID != "child" {
t.Fatalf("mapping-key inheritance selections = %#v", keySelections)
}
indexPath := to.Key("items").Index(0).Key("value")
indexSelections := model.selectHistory(indexPath)
if len(indexSelections) != 2 || indexSelections[0].payload.substep != 0 || indexSelections[0].before || !indexSelections[0].after ||
indexSelections[0].attribution.origin.Locations[0].SourceID != "parent" || indexSelections[1].payload.substep != 1 ||
!indexSelections[1].before || indexSelections[1].after {
t.Fatalf("sequence-index inheritance selections = %#v", indexSelections)
}
}

View File

@@ -55,10 +55,11 @@ type historyObservation struct {
}
type historyModel struct {
observations []historyObservation
attributions []historyAttribution
baseline *document.Node
sourcePayloads []historySourcePayload
observations []historyObservation
attributions []historyAttribution
baseline *document.Node
sourcePayloads []historySourcePayload
inheritancePayloads []historyInheritancePayload
shapesByPointer map[string][]Path
shapeOrder []Path

View File

@@ -38,6 +38,7 @@ type inheritanceOperation struct {
controlOrigin Origin
parent *document.Node
effective *document.Node
childOrigins map[string]Origin
parentOrigins map[string]Origin
effectiveOrigins map[string]Origin
parentPolicies []collectionPolicyScope
@@ -102,6 +103,7 @@ func cloneInheritanceOperations(operations []inheritanceOperation) []inheritance
}
cloned[index].parent = document.Clone(operation.parent)
cloned[index].effective = document.Clone(operation.effective)
cloned[index].childOrigins = cloneOriginIndex(operation.childOrigins)
cloned[index].parentOrigins = cloneOriginIndex(operation.parentOrigins)
cloned[index].effectiveOrigins = cloneOriginIndex(operation.effectiveOrigins)
cloned[index].parentPolicies = cloneInheritancePolicyTemplates(operation.parentPolicies)

View File

@@ -106,6 +106,7 @@ func combineInheritanceTemplate(
result.transfers = append(result.transfers, parent.transfer)
result.operations = append(result.operations, inheritanceOperation{
transfer: &parent.transfer, parent: parent.historyParent, effective: parent.payload,
childOrigins: child.origins,
parentOrigins: parentOrigins, effectiveOrigins: mergedOrigins,
parentPolicies: cloneInheritancePolicyTemplates(parent.policyTemplates),
templatePolicies: cloneCollectionPolicyScopeForInheritance(child.policyTemplates),

View File

@@ -4,20 +4,23 @@ 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 {
changes := projectedInheritanceChanges(query, operation)
selectionPath := query
if operation.transfer != nil {
selectionPath = operationPathForQuery(operation.transfer.to, query)
}
selections := resolveInheritanceAttribution(operation, selectionPath)
if operation.control != nil {
if !pathsIntersect(operation.control.path, query) {
control := selectInheritanceEvidence(selections, inheritanceEvidenceControl, inheritanceEvidenceControl)
if control == nil || !pathsIntersect(operation.control.path, query) {
continue
}
before := valueSnapshot(*working, query, secrets)
replaceHistoryPath(working, operation.control.path, nil)
after := valueSnapshot(*working, query, secrets)
if len(changes) != 0 {
explanation.History = append(explanation.History, ValueChange{
Before: before, After: after, Origin: cloneOrigin(changes[0].Origin),
Dependencies: clonePaths(changes[0].Dependencies),
})
}
explanation.History = append(explanation.History, ValueChange{
Before: before, After: after, Origin: cloneOrigin(control.origin),
Dependencies: clonePaths(control.dependencies),
})
continue
}
if operation.transfer == nil {
@@ -32,13 +35,11 @@ func appendInheritanceExplanation(explanation *Explanation, working **document.N
replaceHistoryPath(working, operation.transfer.to.Key(field), identity)
}
afterParent := valueSnapshot(*working, query, secrets)
parent, parentPresent := transferredOperationOrigin(operation, operationPathForQuery(operation.transfer.to, query))
if pathsIntersect(operation.transfer.to, query) && parentPresent {
parent.Operation = ChangeInheritance
parent.Inheritance = projectInheritanceSteps(parent)
parent := selectInheritanceEvidence(selections, inheritanceEvidenceParent, inheritanceEvidenceParent)
if pathsIntersect(operation.transfer.to, query) && parent != nil {
explanation.History = append(explanation.History, ValueChange{
Before: before, After: afterParent, Origin: parent,
Dependencies: []Path{operation.transfer.from.clone(), operation.transfer.reference.clone()},
Before: before, After: afterParent, Origin: cloneOrigin(parent.origin),
Dependencies: clonePaths(parent.dependencies),
})
}
replaceHistoryPath(working, operation.transfer.to, operation.effective)
@@ -46,14 +47,12 @@ func appendInheritanceExplanation(explanation *Explanation, working **document.N
continue
}
afterChild := valueSnapshot(*working, query, secrets)
if len(changes) != 0 {
last := changes[len(changes)-1]
if !parentPresent || len(changes) > 1 {
explanation.History = append(explanation.History, ValueChange{
Before: afterParent, After: afterChild, Origin: cloneOrigin(last.Origin),
Dependencies: clonePaths(last.Dependencies),
})
}
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),
})
}
}
}

View File

@@ -104,54 +104,7 @@ func historyWithInheritance(path Path, stored []Change, operations []inheritance
}
func projectedInheritanceChanges(query Path, operation inheritanceOperation) []Change {
if operation.control != nil {
if !pathsIntersect(operation.control.path, query) {
return nil
}
origin := cloneOrigin(operation.controlOrigin)
origin.Operation = ChangeInheritance
origin.Inheritance = projectInheritanceSteps(origin)
return []Change{{Path: operation.control.path.clone(), Origin: origin,
Dependencies: []Path{operation.control.path.clone()}}}
}
if operation.transfer == nil || !pathsIntersect(operation.transfer.to, query) || inheritanceOperationIdentity(operation, query) {
return nil
}
path := query
if pathContains(query, operation.transfer.to) {
path = operation.transfer.to
}
var changes []Change
parent, parentFound := transferredOperationOrigin(operation, path)
if parentFound {
parent.Operation = ChangeInheritance
parent.Inheritance = projectInheritanceSteps(parent)
changes = append(changes, Change{
Path: path.clone(), Origin: parent,
Dependencies: []Path{operation.transfer.from.clone(), operation.transfer.reference.clone()},
})
}
effective, effectiveFound := operation.effectiveOrigins[path.String()]
if effectiveFound {
effective = cloneOrigin(effective)
effective.Operation = ChangeInheritance
}
if effectiveFound && (!parentFound || !originContainedBy(effective, parent)) {
effective.Inheritance = projectInheritanceSteps(effective)
changes = append(changes, Change{
Path: path.clone(), Origin: effective,
Dependencies: []Path{operation.transfer.reference.clone()},
})
} else if parentFound && !effectiveFound {
removed := cloneOrigin(operation.effectiveOrigins[operation.transfer.to.String()])
removed.Operation = ChangeInheritance
removed.Inheritance = projectInheritanceSteps(removed)
changes = append(changes, Change{
Path: operation.transfer.to.clone(), Origin: removed,
Dependencies: []Path{operation.transfer.reference.clone()},
})
}
return changes
return inheritanceChangesForQuery(query, operation)
}
func transferredOperationOrigin(operation inheritanceOperation, destination Path) (Origin, bool) {