Normalize inheritance history evidence
This commit is contained in:
@@ -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
|
||||
|
||||
305
internal/engine/history_inheritance.go
Normal file
305
internal/engine/history_inheritance.go
Normal 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
|
||||
}
|
||||
141
internal/engine/history_inheritance_test.go
Normal file
141
internal/engine/history_inheritance_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user