From c5275df1fc2907efa2ce4ae5e4affa305b883bb2 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 25 Oct 2026 13:41:41 +0000 Subject: [PATCH] Return inheritance combination evidence together --- docs/roadmap/implementation.md | 11 +- internal/engine/inheritance_collection.go | 11 +- internal/engine/inheritance_combine.go | 127 ++++++++++++++++++++++ internal/engine/inheritance_schedule.go | 126 ++++----------------- 4 files changed, 164 insertions(+), 111 deletions(-) create mode 100644 internal/engine/inheritance_combine.go diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index e4a1ecd..aa11654 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Consolidation Implementation Plan -Status: in progress. Stages 1 through 14 are complete; follow Stage 15 through Stage 20 in order. +Status: in progress. Stages 1 through 15 are complete; follow Stage 16 through Stage 20 in order. The target executor is a Luna coding agent working through one end-to-end sprint. ## Authority, Baseline, And Execution @@ -595,7 +595,7 @@ preliminary budget checks, scheduler, and combination path, while history and transfer inputs stay available to combination. Engine inheritance coverage and facade inheritance tests passed. -## Stage 15 — Return One Inheritance Combination Result +## Stage 15 — Return One Inheritance Combination Result (Complete) **Owners:** new engine `inheritance_combine.go`, `inheritance_collection.go`, and the shared executor. @@ -620,6 +620,13 @@ and the shared executor. **Finish:** merge callbacks no longer mutate the executor accumulator. The resolved template and all evidence travel together for Stage 16 integration. +Progress notes (2026-10-25): added `inheritanceTemplateResult` and moved the +parent/child merge and evidence construction into `combineInheritanceTemplate`. +Callbacks now update the local origins, policies, and replacement evidence; +sequence-origin handling returns replacement records to the template result. +Parentless templates use the same result shape. Engine and facade inheritance +tests passed. + ## Stage 16 — Finish The Inheritance Executor And Accounting **Owner:** engine `inheritance_schedule.go` and focused finalization helpers. diff --git a/internal/engine/inheritance_collection.go b/internal/engine/inheritance_collection.go index 6d12ed0..6af19bf 100644 --- a/internal/engine/inheritance_collection.go +++ b/internal/engine/inheritance_collection.go @@ -228,17 +228,17 @@ func applyInheritanceSequencePolicy( } func applyInheritanceSequenceOrigins( - result *inheritanceScope, origins, incomingOrigins map[string]Origin, path Path, lower, higher *document.Node, combined sequenceCombinationResult, operation sequenceMode, -) { +) []inheritanceReplacement { + var replacements []inheritanceReplacement if operation == sequenceModeReplace { if lower != nil { displaced := displacedOrigins(lower, origins, path) - result.replacements = append(result.replacements, inheritanceReplacement{ + replacements = append(replacements, inheritanceReplacement{ path: path.clone(), before: lower, after: higher, previous: displaced, effective: cloneOrigin(incomingOrigins[path.String()]), }) @@ -253,7 +253,7 @@ func applyInheritanceSequenceOrigins( copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.inputIndex), path.Index(projection.outputIndex)) } } - return + return replacements } mergeInheritanceOrigin(origins, path, incomingOrigins[path.String()]) @@ -268,7 +268,7 @@ func applyInheritanceSequenceOrigins( previous := displacedOrigins(lower.Sequence[replacement.lowerIndex], origins, lowerPath) deleteOriginSubtree(origins, lowerPath, lower.Sequence[replacement.lowerIndex]) copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[replacement.higherIndex], higherPath, outputPath) - result.replacements = append(result.replacements, inheritanceReplacement{ + replacements = append(replacements, inheritanceReplacement{ path: outputPath, before: lower.Sequence[replacement.lowerIndex], after: higher.Sequence[replacement.higherIndex], previous: previous, effective: cloneOrigin(incomingOrigins[higherPath.String()]), }) @@ -278,6 +278,7 @@ func applyInheritanceSequenceOrigins( copyInheritanceSubtreeAt(origins, incomingOrigins, higher.Sequence[projection.inputIndex], path.Index(projection.inputIndex), path.Index(projection.outputIndex)) } } + return replacements } func validateRegisteredInheritanceSequences(root *document.Node, rootPath Path, applicationPolicy sequencePolicy) []Diagnostic { diff --git a/internal/engine/inheritance_combine.go b/internal/engine/inheritance_combine.go new file mode 100644 index 0000000..33271b1 --- /dev/null +++ b/internal/engine/inheritance_combine.go @@ -0,0 +1,127 @@ +package engine + +import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" + +type inheritanceTemplateResult struct { + value *document.Node + origins map[string]Origin + collectionPolicies collectionPolicyScope + policyTemplates []collectionPolicyScope + controls []inheritanceControl + transfers []inheritanceTransfer + replacements []inheritanceReplacement + operations []inheritanceOperation +} + +func combineInheritanceTemplate( + template inheritanceExecutionTemplate, + child preparedInheritanceChild, + parent *preparedInheritanceParent, + parentOrigins map[string]Origin, + applicationPolicy sequencePolicy, +) (inheritanceTemplateResult, []Diagnostic) { + result := inheritanceTemplateResult{} + if child.control != nil { + result.controls = append(result.controls, *child.control) + result.operations = append(result.operations, inheritanceOperation{ + control: child.control, controlOrigin: child.controlOrigin, + }) + } + if parent == nil { + result.value = document.Clone(child.payload) + result.origins = child.origins + result.collectionPolicies = cloneCollectionPolicyScopeForInheritance(child.policyTemplates) + result.policyTemplates = []collectionPolicyScope{child.policyTemplates} + return result, nil + } + + mergedPolicyScope, diagnostics := mergeInheritanceCollectionPolicies( + parent.parentPolicyScope, parent.childPolicyScope, + template.path, parent.payload, child.payload, + applicationPolicy, parent.replaced, + ) + if len(diagnostics) != 0 { + return inheritanceTemplateResult{}, diagnostics + } + mergedOrigins := transferredInheritanceOrigins(parentOrigins, parent.transfer.from, template.path, parent.transfer) + mergeInheritanceOrigin(mergedOrigins, template.path, child.origins[template.path.String()]) + var sequenceContributions []sequenceHistoryContribution + var combinationDiagnostics []Diagnostic + mergeRecursiveMappings(parent.payload, child.payload, template.path, new(mappingIndex), recursiveMergeHooks{ + added: func(childPath Path, incoming *document.Node) { + copyInheritanceSubtree(mergedOrigins, child.origins, incoming, childPath) + }, + combined: func(childPath Path, _ *document.Node) { + mergeInheritanceOrigin(mergedOrigins, childPath, child.origins[childPath.String()]) + }, + replaced: func(childPath Path, existing, incoming *document.Node) { + previous := displacedOrigins(existing, mergedOrigins, childPath) + result.replacements = append(result.replacements, inheritanceReplacement{ + path: childPath.clone(), before: existing, after: incoming, + previous: previous, effective: cloneOrigin(child.origins[childPath.String()]), + }) + deleteOriginSubtree(mergedOrigins, childPath, existing) + copyInheritanceSubtree(mergedOrigins, child.origins, incoming, childPath) + }, + merge: func(childPath Path, existing, incoming *document.Node) (*document.Node, bool) { + resolution, _, _, active, issues := inheritanceCollectionResolution( + childPath, parent.parentPolicyScope, parent.childPolicyScope, applicationPolicy, incoming, + ) + if len(issues) != 0 { + combinationDiagnostics = append(combinationDiagnostics, issues...) + return existing, false + } + if !active { + return nil, false + } + lowerValue := existing + combined, problems := combineSequence(lowerValue, incoming, resolution, sequenceCombinationContext{destination: childPath}) + if len(problems) != 0 { + combinationDiagnostics = append(combinationDiagnostics, problems...) + return existing, false + } + policy := applyInheritanceSequencePolicy( + childPath, combined, resolution, parent.parentPolicyScope, parent.childPolicyScope, + ) + mergedPolicyScope.set(childPath, policy) + result.replacements = append(result.replacements, applyInheritanceSequenceOrigins( + mergedOrigins, child.origins, childPath, lowerValue, incoming, combined, resolution.operation, + )...) + history := sequenceHistoryContribution{path: childPath.clone(), mode: sequenceHistoryMapped} + if resolution.operation == sequenceModeReplace { + history.mode = sequenceHistoryReplace + if incoming != nil && incoming.Kind == document.Sequence { + history.length = len(incoming.Sequence) + } + } else { + history.items = append([]sequenceItemProjection(nil), combined.higherItems...) + history.replacements = append([]sequenceItemReplacement(nil), combined.replacements...) + } + sequenceContributions = append(sequenceContributions, history) + return combined.value, true + }, + }) + preserveInheritanceIdentityFields(parent.payload, child.payload, template.identityFields) + if len(combinationDiagnostics) != 0 { + return inheritanceTemplateResult{}, canonicalDiagnostics(combinationDiagnostics) + } + if problems := validateRegisteredInheritanceSequences(parent.payload, template.path, applicationPolicy); len(problems) != 0 { + return inheritanceTemplateResult{}, problems + } + parent.payload.Location = template.entry.Value.Location + result.value = parent.payload + result.origins = mergedOrigins + result.collectionPolicies = inheritancePolicyScopeAtPath(mergedPolicyScope, template.path) + result.policyTemplates = parent.effectivePolicyTemplates + result.transfers = append(result.transfers, parent.transfer) + result.operations = append(result.operations, inheritanceOperation{ + transfer: &parent.transfer, parent: parent.historyParent, effective: parent.payload, + parentOrigins: parentOrigins, effectiveOrigins: mergedOrigins, + parentPolicies: cloneInheritancePolicyTemplates(parent.policyTemplates), + templatePolicies: cloneCollectionPolicyScopeForInheritance(child.policyTemplates), + effectivePolicies: cloneInheritancePolicyTemplates(parent.effectivePolicyTemplates), + sequences: cloneSequenceHistoryContributions(sequenceContributions), + identityFields: cloneStrings(template.identityFields), + }) + return result, nil +} diff --git a/internal/engine/inheritance_schedule.go b/internal/engine/inheritance_schedule.go index 5ed8f24..957f200 100644 --- a/internal/engine/inheritance_schedule.go +++ b/internal/engine/inheritance_schedule.go @@ -188,14 +188,7 @@ func applyInheritanceScopeTemplates(adapter inheritanceScopeAdapter, order []int for _, index := range order { node := templates[index] - operationStart, replacementStart := len(result.operations), len(result.replacements) child := prepareInheritanceChild(node, inputOrigins) - if child.control != nil { - result.controls = append(result.controls, *child.control) - result.operations = append(result.operations, inheritanceOperation{ - control: child.control, controlOrigin: child.controlOrigin, - }) - } if limits.nodes >= 0 { remaining := limits.nodes - retained - adapter.slotOverhead() within := inheritanceLowerBoundWithin(child.payload, remaining) @@ -207,114 +200,39 @@ func applyInheritanceScopeTemplates(adapter inheritanceScopeAdapter, order []int } } + var parent *preparedInheritanceParent + var parentOrigins map[string]Origin if node.hasParent { - parent := resolved[node.parent] - preparedParent := prepareInheritanceParent( - node, templates[node.parent].path, parent, resolvedPolicyTemplates[node.parent], + prepared := prepareInheritanceParent( + node, templates[node.parent].path, resolved[node.parent], resolvedPolicyTemplates[node.parent], resolvedPolicies[node.parent], child, adapter.applicationPolicy(), ) - mergedPolicyScope, policyDiagnostics := mergeInheritanceCollectionPolicies( - preparedParent.parentPolicyScope, preparedParent.childPolicyScope, - node.path, preparedParent.payload, child.payload, - adapter.applicationPolicy(), preparedParent.replaced, - ) - if len(policyDiagnostics) != 0 { - return nil, policyDiagnostics - } - mergedOrigins := transferredInheritanceOrigins(resolvedOrigins[node.parent], preparedParent.transfer.from, node.path, preparedParent.transfer) - mergeInheritanceOrigin(mergedOrigins, node.path, child.origins[node.path.String()]) - var sequenceContributions []sequenceHistoryContribution - var combinationDiagnostics []Diagnostic - mergeRecursiveMappings(preparedParent.payload, child.payload, node.path, new(mappingIndex), recursiveMergeHooks{ - added: func(childPath Path, incoming *document.Node) { - copyInheritanceSubtree(mergedOrigins, child.origins, incoming, childPath) - }, - combined: func(childPath Path, _ *document.Node) { - mergeInheritanceOrigin(mergedOrigins, childPath, child.origins[childPath.String()]) - }, - replaced: func(childPath Path, existing, incoming *document.Node) { - previous := displacedOrigins(existing, mergedOrigins, childPath) - result.replacements = append(result.replacements, inheritanceReplacement{ - path: childPath.clone(), before: existing, after: incoming, - previous: previous, effective: cloneOrigin(child.origins[childPath.String()]), - }) - deleteOriginSubtree(mergedOrigins, childPath, existing) - copyInheritanceSubtree(mergedOrigins, child.origins, incoming, childPath) - }, - merge: func(childPath Path, existing, incoming *document.Node) (*document.Node, bool) { - resolution, _, _, active, issues := inheritanceCollectionResolution( - childPath, preparedParent.parentPolicyScope, preparedParent.childPolicyScope, adapter.applicationPolicy(), incoming, - ) - if len(issues) != 0 { - combinationDiagnostics = append(combinationDiagnostics, issues...) - return existing, false - } - if !active { - return nil, false - } - lowerValue := existing - combined, problems := combineSequence(lowerValue, incoming, resolution, sequenceCombinationContext{destination: childPath}) - if len(problems) != 0 { - combinationDiagnostics = append(combinationDiagnostics, problems...) - return existing, false - } - policy := applyInheritanceSequencePolicy( - childPath, combined, resolution, preparedParent.parentPolicyScope, preparedParent.childPolicyScope, - ) - mergedPolicyScope.set(childPath, policy) - applyInheritanceSequenceOrigins( - result, mergedOrigins, child.origins, childPath, lowerValue, incoming, combined, resolution.operation, - ) - history := sequenceHistoryContribution{path: childPath.clone(), mode: sequenceHistoryMapped} - if resolution.operation == sequenceModeReplace { - history.mode = sequenceHistoryReplace - if incoming != nil && incoming.Kind == document.Sequence { - history.length = len(incoming.Sequence) - } - } else { - history.items = append([]sequenceItemProjection(nil), combined.higherItems...) - history.replacements = append([]sequenceItemReplacement(nil), combined.replacements...) - } - sequenceContributions = append(sequenceContributions, history) - return combined.value, true - }, - }) - preserveInheritanceIdentityFields(preparedParent.payload, child.payload, node.identityFields) - if len(combinationDiagnostics) != 0 { - return nil, canonicalDiagnostics(combinationDiagnostics) - } - if problems := validateRegisteredInheritanceSequences(preparedParent.payload, node.path, adapter.applicationPolicy()); len(problems) != 0 { - return nil, problems - } - preparedParent.payload.Location = node.entry.Value.Location - resolved[index], resolvedOrigins[index] = preparedParent.payload, mergedOrigins - resolvedPolicies[index] = inheritancePolicyScopeAtPath(mergedPolicyScope, node.path) - result.transfers = append(result.transfers, preparedParent.transfer) - result.operations = append(result.operations, inheritanceOperation{ - transfer: &preparedParent.transfer, parent: preparedParent.historyParent, effective: preparedParent.payload, - parentOrigins: resolvedOrigins[node.parent], effectiveOrigins: mergedOrigins, - parentPolicies: cloneInheritancePolicyTemplates(preparedParent.policyTemplates), - templatePolicies: cloneCollectionPolicyScopeForInheritance(child.policyTemplates), - effectivePolicies: cloneInheritancePolicyTemplates(preparedParent.effectivePolicyTemplates), - sequences: cloneSequenceHistoryContributions(sequenceContributions), - identityFields: cloneStrings(node.identityFields), - }) - resolvedPolicyTemplates[index] = preparedParent.effectivePolicyTemplates - } else { - resolved[index] = document.Clone(child.payload) - resolvedOrigins[index] = child.origins - resolvedPolicyTemplates[index] = []collectionPolicyScope{child.policyTemplates} - resolvedPolicies[index] = cloneCollectionPolicyScopeForInheritance(child.policyTemplates) + parent = &prepared + parentOrigins = resolvedOrigins[node.parent] } + templateResult, diagnostics := combineInheritanceTemplate( + node, child, parent, parentOrigins, adapter.applicationPolicy(), + ) + if len(diagnostics) != 0 { + return nil, diagnostics + } + resolved[index] = templateResult.value + resolvedOrigins[index] = templateResult.origins + resolvedPolicies[index] = templateResult.collectionPolicies + resolvedPolicyTemplates[index] = templateResult.policyTemplates + result.controls = append(result.controls, templateResult.controls...) + result.transfers = append(result.transfers, templateResult.transfers...) + result.replacements = append(result.replacements, templateResult.replacements...) + result.operations = append(result.operations, templateResult.operations...) count, _ := inheritanceLowerBoundCount(resolved[index], limits.nodes) retained += adapter.slotOverhead() + count - for _, operation := range result.operations[operationStart:] { + for _, operation := range templateResult.operations { if !counter.add(operation.parent) || !counter.add(operation.effective) { return nil, []Diagnostic{inheritanceHistoryBudgetDiagnostic(node.path, node.entry.Value.Location)} } } - for _, replacement := range result.replacements[replacementStart:] { + for _, replacement := range templateResult.replacements { if !counter.add(replacement.before) || !counter.add(replacement.after) { return nil, []Diagnostic{inheritanceHistoryBudgetDiagnostic(node.path, node.entry.Value.Location)} }