Split import acquisition and projection responsibilities

This commit is contained in:
2026-10-25 13:21:29 +00:00
parent b915dd776a
commit 6bc625b68f
5 changed files with 1074 additions and 1060 deletions

View File

@@ -0,0 +1,277 @@
package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
func prependCompositionDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic {
cloned := append([]Diagnostic(nil), diagnostics...)
for index := range cloned {
if !isCompositionDiagnostic(cloned[index]) {
continue
}
cloned[index].Path = appendDestination(prefix, cloned[index].Path)
cloned[index].Primary = cloneLocation(cloned[index].Primary)
cloned[index].Related = cloneLocations(cloned[index].Related)
}
return cloned
}
func isCompositionDiagnostic(diagnostic Diagnostic) bool {
return diagnostic.Code == CodeComposeConflict || diagnostic.Code == CodeComposeInvalid
}
// On a composition failure only the earlier contributions are needed to locate
// this item. Project one enclosing scope at a time as traversal unwinds; syntax
// and access diagnostics keep their original source-local coordinates.
func projectItemDiagnosticPaths(diagnostics []Diagnostic, parent acquisitionScope, destination Path, beforeContribution int) []Diagnostic {
for _, diagnostic := range diagnostics {
if isCompositionDiagnostic(diagnostic) {
prefix := parent
prefix.contributions = parent.contributions[:beforeContribution]
index := priorSequenceLength([]acquisitionScope{prefix}, destination)
return prependCompositionDiagnosticPaths(diagnostics, destination.Index(index))
}
}
return diagnostics
}
// This scan is confined to failed loads, not the successful construction path.
func priorSequenceLength(scopes []acquisitionScope, path Path) int {
length := 0
for _, scope := range scopes {
for _, contribution := range scope.contributions {
if node, present := documentAtPath(contribution.document, path); present && node.Kind == document.Sequence {
length += len(node.Sequence)
}
}
}
return length
}
func projectPriorInputDiagnosticPaths(diagnostics []Diagnostic, scopes []acquisitionScope) []Diagnostic {
cloned := cloneDiagnostics(diagnostics)
for index := range cloned {
if !isCompositionDiagnostic(cloned[index]) {
continue
}
for position, segment := range cloned[index].Path.segments {
if segment.kind != pathSegmentIndex {
continue
}
container := Path{segments: cloned[index].Path.segments[:position]}
cloned[index].Path.segments[position].index += priorSequenceLength(scopes, container)
// Registration does not address paths through item indexes. A prior
// input shifts the outer collection, not its item-local sequences.
break
}
}
return cloned
}
func projectNestedEvidence(composed *composedDocument, scope acquisitionScope) {
if composed == nil {
return
}
applications := composed.applications
projectedApplications := make([]historyApplication, 0, len(applications))
ranges := indexSequenceRanges(composed.sequenceRanges)
for sourceIndex, contribution := range scope.contributions {
if contribution.nested == nil {
projectedApplications = append(projectedApplications, applications[sourceIndex])
continue
}
itemIndex := 0
if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: contribution.destination.String()}]; found {
if projected, ok := assigned.outputForInput(0); ok {
itemIndex = projected
}
}
prefix := contribution.destination.Index(itemIndex)
if sourceIndex < len(applications) {
outer := applications[sourceIndex]
structural := document.Clone(outer.value)
if node, found := documentAtPath(structural, contribution.destination); found && node.Kind == document.Sequence && len(node.Sequence) > 0 {
// The retained fragment is still a local singleton. Its assigned
// outer index belongs to the projection, not this source tree.
node.Sequence[0] = &document.Node{Kind: document.Mapping, Location: node.Sequence[0].Location}
}
outer.value = structural
projectedApplications = append(projectedApplications, outer)
for _, nested := range contribution.nested.applications {
projected := nested
projected.path = appendDestination(prefix, nested.path)
projected.sequences = cloneSequenceHistoryContributions(nested.sequences)
projected.sequencesByPath = indexSequenceHistoryContributions(projected.sequences)
projectedApplications = append(projectedApplications, projected)
}
}
projectNestedOrigins(composed.origins, contribution.nested.root, contribution.nested.origins, prefix)
}
composed.applications = projectedApplications
projectNestedCollectionPolicies(composed, scope, ranges)
}
func projectNestedCollectionPolicies(composed *composedDocument, scope acquisitionScope, ranges map[sequenceRangeKey]sequenceRange) {
if composed == nil {
return
}
type itemPolicyProjection struct {
destination Path
policy collectionPolicy
base int
}
projected := make(map[string]*itemPolicyProjection)
var prefixLengths map[string]int
for sourceIndex, contribution := range scope.contributions {
if prefixLengths != nil && sourceIndex > 0 {
addSequencePrefixLengths(scope.contributions[sourceIndex-1].document, Path{}, prefixLengths)
}
if contribution.nested == nil || len(contribution.nested.collectionPolicies.collections) == 0 {
continue
}
key := contribution.destination.String()
assigned := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: key}]
itemIndex, found := assigned.outputForInput(0)
if !found {
// Normal composition supplies the assignment directly. For a
// handoff without ranges, scan each preceding tree only once.
if prefixLengths == nil {
prefixLengths = make(map[string]int)
for _, previous := range scope.contributions[:sourceIndex] {
addSequencePrefixLengths(previous.document, Path{}, prefixLengths)
}
}
itemIndex = prefixLengths[key]
}
working, initialized := projected[key]
if !initialized {
// Read one owned copy per destination, then add each incoming
// record without recopying earlier records and their scopes.
policy, _ := composed.collectionPolicies.at(contribution.destination)
working = &itemPolicyProjection{destination: contribution.destination, policy: policy, base: len(policy.recordOrder)}
projected[key] = working
}
working.policy.setRecordPolicyAt(working.base+itemIndex, collectionRecordPolicy{
owner: newCollectionRecordPolicy().owner,
scope: contribution.nested.collectionPolicies,
})
}
for _, working := range projected {
composed.collectionPolicies.set(working.destination, working.policy)
}
}
func addSequencePrefixLengths(node *document.Node, path Path, lengths map[string]int) {
if node == nil {
return
}
if node.Kind == document.Sequence {
lengths[path.String()] += len(node.Sequence)
return
}
// Contribution destinations select mapping paths, not sequence members.
for _, entry := range node.Mapping {
addSequencePrefixLengths(entry.Value, path.Key(entry.Key), lengths)
}
}
func projectNestedOrigins(target map[string]Origin, root *document.Node, origins map[string]Origin, prefix Path) {
var visit func(*document.Node, Path)
visit = func(node *document.Node, path Path) {
if node == nil {
return
}
if origin, ok := origins[path.String()]; ok {
target[appendDestination(prefix, path).String()] = cloneOrigin(origin)
}
for _, entry := range node.Mapping {
visit(entry.Value, path.Key(entry.Key))
}
for index, child := range node.Sequence {
visit(child, path.Index(index))
}
}
visit(root, Path{})
}
func projectAcquiredDestinations(composed *composedDocument, scope acquisitionScope, sources []SourceInfo) {
byID := make(map[string]int, len(sources))
for index := range sources {
byID[sources[index].ID] = index
}
var projectScope func(*composedDocument, acquisitionScope, Path, bool, string)
projectScope = func(currentComposition *composedDocument, current acquisitionScope, prefix Path, insideItem bool, parentSourceID string) {
ranges := indexSequenceRanges(currentComposition.sequenceRanges)
for sourceIndex, contribution := range current.contributions {
if contribution.source.ID == parentSourceID {
continue
}
contributionPrefix := prefix
contributionInsideItem := insideItem
itemIndex := 0
if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: contribution.destination.String()}]; found {
if projected, ok := assigned.outputForInput(0); ok {
itemIndex = projected
}
}
if contribution.source.placementMode == placementModeSequenceItem {
container := appendDestination(contributionPrefix, contribution.destination)
contribution.source.Destination = container.clone()
contribution.source.destinationUnresolved = false
current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source)
if position, ok := byID[contribution.source.ID]; ok {
sources[position].Destination = container
sources[position].destinationUnresolved = false
}
contributionPrefix = appendDestination(container, Path{}.Index(itemIndex))
contributionInsideItem = true
projectSourceVersionEvidence(contribution.processing, contributionPrefix, nil, sourceIndex)
} else if contributionInsideItem {
exactDestination := appendDestination(contributionPrefix, contribution.destination)
contribution.source.Destination = exactDestination.clone()
contribution.source.destinationUnresolved = false
current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source)
if position, ok := byID[contribution.source.ID]; ok {
sources[position].Destination = exactDestination
sources[position].destinationUnresolved = false
}
projectSourceVersionEvidence(contribution.processing, exactDestination, nil, sourceIndex)
} else {
projectSourceVersionEvidence(contribution.processing, contribution.destination, ranges, sourceIndex)
}
if contribution.nested != nil && contribution.nested.acquisition != nil {
for _, nestedScope := range contribution.nested.acquisition {
projectScope(contribution.nested, nestedScope, contributionPrefix, contributionInsideItem, contribution.source.ID)
}
}
}
}
projectScope(composed, scope, Path{}, false, "")
}
func projectSourceVersionEvidence(processing *sourceVersionProcessingResult, destination Path, ranges sequenceRangeIndex, sourceIndex int) {
if processing != nil && processing.evidence != nil {
evidence := processing.evidence
evidence.destination = destination.clone()
// Rebuild from source-local paths on each handoff: an input can be
// composed first on its own and then at a different offset in its tier.
evidence.projectedPaths = nil
paths := evidence.sensitivityPaths()
for index, path := range paths {
for position := range path.segments {
prefix := Path{segments: path.segments[:position]}
assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: prefix.String()}]
if !found {
continue
}
item, ok := schemaIndex(path.segments[position])
if ok && path.segments[position].kind != pathSegmentKey {
if outputIndex, projected := assigned.outputForInput(item); projected {
path.segments[position] = pathSegment{kind: pathSegmentIndex, index: outputIndex}
}
}
}
paths[index] = path
}
evidence.projectedPaths = paths
}
}