package engine import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" func compositionTraversal(policy sequencePolicy, rule *VersionRule, versionState *sourceVersionState, accounting *importAccounting, graph importInclusionGraph) importTraversal { return importTraversal{ request: importAcquisitionRequest{input: inputAcquisitionRequest{protectionRule: rule}}, policy: &acquisitionPolicy{sequencePolicy: policy, importLimits: defaultImportLimits()}, state: &acquisitionState{accounting: accounting, versionState: versionState}, tier: &tierAcquisitionState{}, importGraph: graph.clone(), } } // composeAcquiredInputWithPolicy is the per-input adapter from retained // acquisition scopes to the established strict-composition behavior. It uses // the already acquired documents and graph; source handles are closed before // this handoff. func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPolicyScope, policy sequencePolicy) (*composedDocument, []Diagnostic) { if input.composition == nil || input.composition.root != nil || len(input.composition.acquisition) == 0 { return input.composition, nil } acquired := input.composition if len(acquired.acquisition) != 1 { return nil, []Diagnostic{importDiagnostic(CodeImportInvalid, "acquired input has an invalid composition scope", nil)} } scope := cloneAcquisitionScope(acquired.acquisition[0]) var rule *VersionRule if input.versionState != nil { rule = input.versionState.effectiveRule } accounting := input.accounting if accounting == nil { accounting = &importAccounting{sources: len(input.sources), bytes: acquired.inputBytes, nodes: acquired.inputNodes} } traversal := compositionTraversal(policy, rule, input.versionState, accounting, acquired.importGraph) if len(scope.contributions) == 1 && scope.contributions[0].role == importRoleMain && scope.contributions[0].pendingItem != nil { main := scope.contributions[0] localScope := cloneAcquisitionScope(*main.pendingItem) if diagnostics := traversal.composePendingItems(&localScope); len(diagnostics) != 0 { return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0)) } documents := make([]*document.Node, 0, len(localScope.contributions)) for _, contribution := range localScope.contributions { documents = append(documents, contribution.document) } completed, diagnostics := composeDocumentsWithCollectionPolicy( policy, localScope.collectionPolicies, acquiredSequenceDeclarations(localScope), documents..., ) if len(diagnostics) != 0 { return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0)) } if input.versionState != nil { protectionRule := input.versionState.localRule if input.versionState.mode == sourceVersionModeDefault && protectionRule != nil { prefix := main.destination.Index(0) relative, inside := relativeHistoryPath(prefix, protectionRule.Path) if !inside || !matchesPathShape(Path{segments: protectionRule.Path.segments[:len(prefix.segments)]}, prefix) { protectionRule = nil } else { scoped := *protectionRule scoped.Path = relative protectionRule = &scoped } } if diagnostic := importedVersionClaim(documents, localScope, completed, protectionRule); diagnostic != nil { if rule != nil { diagnostic.Path = rule.Path.clone() } return nil, []Diagnostic{*diagnostic} } } completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)} projectNestedEvidence(completed, localScope) _, placed, err := placeDocumentWithMode(completed.root, main.destination, placementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes) if err != nil { return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")} } if !main.placementCharged { placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem) if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes { return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)} } accounting.nodes += placementNodes } composition, diagnostics := composeDocumentsWithPolicy(policy, placed) if len(diagnostics) != 0 { return nil, diagnostics } synthetic := acquisitionScope{owner: main.scopeOwner, contributions: []acquiredContribution{{ source: cloneSourceInfo(main.source), payload: completed.root, document: placed, identity: main.identity, role: importRoleMain, scopeOwner: main.scopeOwner, destination: main.destination.clone(), processing: main.processing, nested: completed, }}} projectNestedEvidence(composition, synthetic) projectAcquiredDestinations(composition, synthetic, input.sources) composition.acquisition = []acquisitionScope{cloneAcquisitionScope(synthetic)} composition.inputBytes = accounting.bytes composition.inputNodes = accounting.nodes composition.importGraph = acquired.importGraph.clone() return composition, nil } return composeAcquiredScopeWithPolicy( &scope, lower, policy, acquired.importGraph, input.sources, rule, input.versionState, accounting, ) } // composeAcquiredScopeWithPolicy is shared by per-input and underlay-group // handoffs. Lower policy is carried separately from registered application // constraints; current strict composition continues to use its existing rules. func composeAcquiredScopeWithPolicy( scope *acquisitionScope, lower collectionPolicyScope, policy sequencePolicy, graph importInclusionGraph, sources []SourceInfo, rule *VersionRule, versionState *sourceVersionState, accounting *importAccounting, ) (*composedDocument, []Diagnostic) { traversal := compositionTraversal(policy, rule, versionState, accounting, graph) if diagnostics := traversal.composePendingItems(scope); len(diagnostics) != 0 { return nil, diagnostics } documents := make([]*document.Node, 0, len(scope.contributions)) for _, contribution := range scope.contributions { documents = append(documents, contribution.document) } composition, diagnostics := composeDocumentsWithCollectionPolicy( policy, lower, acquiredSequenceDeclarations(*scope), documents..., ) if len(diagnostics) != 0 { return nil, diagnostics } if diagnostic := importedVersionClaim(documents, *scope, composition, rule); diagnostic != nil { return nil, []Diagnostic{*diagnostic} } projectNestedEvidence(composition, *scope) projectAcquiredDestinations(composition, *scope, sources) composition.acquisition = []acquisitionScope{cloneAcquisitionScope(*scope)} composition.inputBytes = accounting.bytes composition.inputNodes = accounting.nodes composition.importGraph = graph.clone() return composition, nil } func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits, accounting *importAccounting) *Diagnostic { if scope == nil { return nil } for index := range scope.contributions { contribution := &scope.contributions[index] if contribution.pendingItem == nil { continue } placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem) if accounting.nodes+placementNodes > limits.nodes { diagnostic := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom) return &diagnostic } accounting.nodes += placementNodes contribution.placementCharged = true if diagnostic := reservePendingItemPlacements(contribution.pendingItem, limits, accounting); diagnostic != nil { return diagnostic } } return nil } func (s *importTraversal) visitItemSource(admitted admittedImport, destination, relativeDestination Path, depth int) []Diagnostic { parentScope := s.scope parentItemScope, parentPrefix, parentFallback := s.itemScope, s.itemPrefix, s.itemFallback s.scope = acquisitionScope{owner: admitted.nodeKey} s.itemScope = true if parentItemScope { s.itemPrefix = appendDestination(parentPrefix, destination) } else { s.itemPrefix = destination.clone() s.itemFallback = destination.clone() } s.scope.contributions = append(s.scope.contributions, acquiredContribution{ source: cloneSourceInfo(admitted.source), payload: admitted.payload, document: admitted.payload, identity: admitted.nodeKey, role: importRoleItem, scopeOwner: admitted.nodeKey, processing: admitted.processing, sequenceDeclarations: cloneSequenceDeclarations(admitted.sequenceDeclarations), }) diagnostics := s.walkDeclarations(s.access.dir(admitted.canonical), admitted.declarations, depth, Path{}, admitted.sourceIndex) itemScope := s.scope s.scope, s.itemScope, s.itemPrefix, s.itemFallback = parentScope, parentItemScope, parentPrefix, parentFallback if len(diagnostics) != 0 { return diagnostics } itemScope.destination = Path{} s.scope.contributions = append(s.scope.contributions, acquiredContribution{ source: cloneSourceInfo(admitted.source), payload: admitted.payload, identity: admitted.nodeKey, role: importRoleItem, scopeOwner: parentScope.owner, destination: destination.clone(), relativeDestination: relativeDestination.clone(), processing: admitted.processing, pendingItem: &itemScope, }) return nil } func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnostic { for index := range scope.contributions { contribution := &scope.contributions[index] if contribution.pendingItem == nil { continue } itemScope := contribution.pendingItem if diagnostics := s.composePendingItems(itemScope); len(diagnostics) != 0 { return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination, index) } documents := make([]*document.Node, 0, len(itemScope.contributions)) for _, nested := range itemScope.contributions { documents = append(documents, nested.document) } // An item scope is below a sequence index. Application selectors match // mapping-only effective paths, so none can apply inside this scope. // Its YAML declarations remain relative to the owning record. completed, diagnostics := composeDocumentsWithCollectionPolicy( sequencePolicy{}, itemScope.collectionPolicies, acquiredSequenceDeclarations(*itemScope), documents..., ) if len(diagnostics) != 0 { return projectItemDiagnosticPaths(diagnostics, *scope, contribution.destination, index) } // Version ownership is checked by the enclosing composition once this // item has its effective coordinates, not against its local field names. completed.acquisition = []acquisitionScope{cloneAcquisitionScope(*itemScope)} projectNestedEvidence(completed, *itemScope) _, placed, err := placeDocumentWithMode(completed.root, contribution.destination, placementModeSequenceItem, contribution.payload.Location, maxDocumentDepth, maxDocumentNodes) if err != nil { return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", contribution.source.ImportedFrom)} } placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem) if !contribution.placementCharged { if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes { return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)} } s.state.accounting.nodes += placementNodes } contribution.payload = completed.root contribution.document = placed contribution.nested = completed contribution.pendingItem = nil } return nil } func acquiredSequenceDeclarations(scope acquisitionScope) []sequenceDeclarationSpec { var declarations []sequenceDeclarationSpec for _, contribution := range scope.contributions { projected := cloneSequenceDeclarations(contribution.sequenceDeclarations) for index := range projected { projected[index].destination = appendDestination(contribution.relativeDestination, projected[index].destination) } declarations = append(declarations, projected...) } return declarations } func importedVersionClaim(documents []*document.Node, scope acquisitionScope, composed *composedDocument, rule *VersionRule) *Diagnostic { if composed == nil || rule == nil { return nil } mainIndex := -1 for index, contribution := range scope.contributions { if contribution.role == importRoleMain { mainIndex = index break } } ranges := indexSequenceRanges(composed.sequenceRanges) for index, contribution := range documents { if index == mainIndex { continue } if node := fragmentVersionClaimWithIndex(contribution, rule, index, ranges); node != nil { location := publicLocation(node.Location) return &Diagnostic{ Code: CodeImportVersion, Severity: SeverityError, Path: rule.Path.clone(), Message: "imported fragments cannot contribute to the schema-version path", Primary: &location, } } } return nil }