package engine import ( "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) // assembledInput is the source/document handoff across schema and version checks. type assembledInput struct { source Source parsed parsedSource document *document.Node composition *composedDocument acquisition []acquisitionScope inheritance *inheritanceScope sources []SourceInfo contexts map[string]ResourceContext versionState *sourceVersionState accounting *importAccounting resolvedFilesystem *resolvedFilesystemContext } // acquiredContribution keeps a source-local payload beside its placed document. // The latter is used for composition; the former preserves the acquisition // boundary for later scoped assembly and evidence. type acquiredContribution struct { source SourceInfo identity importNodeKey role importContributionRole scopeOwner importNodeKey payload *document.Node document *document.Node destination Path relativeDestination Path processing *sourceVersionProcessingResult sequenceDeclarations []sequenceDeclarationSpec nested *composedDocument pendingItem *acquisitionScope placementCharged bool } // acquisitionScope is the ordered strict mapping scope produced by one input // and its ordinary imports. type acquisitionScope struct { owner importNodeKey destination Path contributions []acquiredContribution collectionPolicies collectionPolicyScope } func cloneAcquisitionScope(scope acquisitionScope) acquisitionScope { cloned := acquisitionScope{ owner: scope.owner, destination: scope.destination.clone(), collectionPolicies: cloneCollectionPolicyScope(scope.collectionPolicies), } if scope.contributions != nil { cloned.contributions = make([]acquiredContribution, len(scope.contributions)) for index, contribution := range scope.contributions { cloned.contributions[index] = acquiredContribution{ source: cloneSourceInfo(contribution.source), identity: contribution.identity, role: contribution.role, scopeOwner: contribution.scopeOwner, payload: contribution.payload, document: contribution.document, destination: contribution.destination.clone(), relativeDestination: contribution.relativeDestination.clone(), processing: contribution.processing, sequenceDeclarations: cloneSequenceDeclarations(contribution.sequenceDeclarations), nested: cloneComposedDocument(contribution.nested), pendingItem: cloneAcquisitionScopePointer(contribution.pendingItem), placementCharged: contribution.placementCharged, } } } return cloned } func cloneAcquisitionScopePointer(scope *acquisitionScope) *acquisitionScope { if scope == nil { return nil } cloned := cloneAcquisitionScope(*scope) return &cloned } func cloneAcquisitionScopes(scopes []acquisitionScope) []acquisitionScope { if scopes == nil { return nil } cloned := make([]acquisitionScope, len(scopes)) for index, scope := range scopes { cloned[index] = cloneAcquisitionScope(scope) } return cloned } func applyInputLayers(source Source, filesystem *FilesystemContext, overlay string, protectionRule *VersionRule, policy sequencePolicy, input assembledInput) (assembledInput, []Diagnostic) { if !activeOverlay(overlay) { return input, nil } composition, diagnostics := compositionForInputWithPolicy(input, policy) if len(diagnostics) != 0 { return input, diagnostics } assembly := newAssemblyAccumulator(composition) resolved, diagnostic := resolveOrdinaryOverlayFilesystem(source, filesystem, input.parsed, input.resolvedFilesystem) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } if source.kind != sourceNone || nativeContext(source, filesystem) { if diagnostic := validateResolvedFilesystemDirectories(resolved); diagnostic != nil { return input, []Diagnostic{*diagnostic} } } request := selectedOverlayRequest{ name: overlay, protectionRule: protectionRule, sequencePolicy: policy, limits: defaultImportLimits(), versionState: input.versionState, } access, diagnostic := openSelectedOverlayAccess(resolved) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } input.sources, diagnostics = loadSelectedOverlay(request, access, assembly, input.sources) if len(diagnostics) != 0 { return input, diagnostics } input.resolvedFilesystem = &resolved contexts, diagnostic := contextsForResolvedSources(&resolved, input.sources[len(input.sources)-1:]) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } mergeResourceContexts(&input.contexts, contexts) input.composition = assembly.current input.document = assembly.current.root return input, nil } func completeOrdinaryResourceContexts(input assembledInput, main loadInput) (assembledInput, []Diagnostic) { baseSources := make([]SourceInfo, 0, len(input.sources)) for _, source := range input.sources { if source.Layer == LayerBase { baseSources = append(baseSources, source) } } if len(baseSources) == 0 || input.contexts[baseSources[0].ID].FS != nil { return input, nil } if input.resolvedFilesystem == nil && (main.source.kind == sourceFile || main.filesystem != nil || nativeContext(main.source, main.filesystem)) { resolved, diagnostic := resolveFilesystemContext(main.source, main.filesystem, input.parsed, nil) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } input.resolvedFilesystem = &resolved } contexts, diagnostic := contextsForResolvedSources(input.resolvedFilesystem, baseSources) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } mergeResourceContexts(&input.contexts, contexts) return input, nil } func expandStructuralInheritance(input assembledInput, protectionRule *VersionRule, policy sequencePolicy) (assembledInput, *VersionRule, bool, []Diagnostic) { if input.document == nil || !containsInheritanceDirective(input.document) { return input, nil, false, nil } composition, diagnostics := compositionForInput(input) if len(diagnostics) != 0 { return input, nil, false, diagnostics } expanded, diagnostics := expandInheritanceWithCollectionRules( composition.root, composition.origins, composition.collectionPolicies, policy, defaultProjectionLimits(), ) if len(diagnostics) != 0 { return input, nil, false, diagnostics } effectiveVersion, diagnostic := projectInheritanceVersion(protectionRule, expanded) if diagnostic != nil { return input, nil, false, []Diagnostic{*diagnostic} } input.composition = composition input.inheritance = expanded input.document = expanded.root return input, effectiveVersion, true, nil } // compositionForInput initializes the structural handoff used by inheritance // expansion. Raw source bytes and mapping keys retain their budgets. func compositionForInput(input assembledInput) (*composedDocument, []Diagnostic) { return compositionForInputWithPolicy(input, sequencePolicy{}) } func compositionForInputWithPolicy(input assembledInput, policy sequencePolicy) (*composedDocument, []Diagnostic) { return compositionForInputWithPolicies(input, collectionPolicyScope{}, policy) } func compositionForInputWithPolicies(input assembledInput, lower collectionPolicyScope, policy sequencePolicy) (*composedDocument, []Diagnostic) { if input.composition != nil { if input.composition.root == nil && len(input.composition.acquisition) != 0 { return composeAcquiredInputWithPolicies(input, lower, policy) } return input.composition, nil } if len(input.acquisition) != 0 { acquired := &composedDocument{acquisition: cloneAcquisitionScopes(input.acquisition)} if input.accounting != nil { acquired.inputBytes = input.accounting.bytes acquired.inputNodes = input.accounting.nodes } else { acquired.inputBytes = input.parsed.bytes acquired.inputNodes = countInputNodes(input.parsed.document) } input.composition = acquired return composeAcquiredInputWithPolicies(input, lower, policy) } if input.document == nil { return &composedDocument{root: &document.Node{Kind: document.Mapping}, origins: make(map[string]Origin)}, nil } composition, diagnostics := composeDocumentsWithCollectionPolicy(policy, lower, nil, input.document) if len(diagnostics) != 0 { return nil, diagnostics } composition.inputBytes = input.parsed.bytes composition.inputNodes = countInputNodes(input.document) composition.acquisition = cloneAcquisitionScopes(input.acquisition) return composition, nil }