package engine import ( "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) // loadRequest keeps source acquisition separate from precedence assembly. // The ordinary entry point and grouped entry point share the final lifecycle. type loadRequest[T any] struct { main loadInput underlays []loadGroup definition Definition[T] sequencePolicy sequencePolicy sourceVersionMode sourceVersionMode effectiveVersion *VersionRule overlay string overlayFilesystem *FilesystemContext overrides []Override ordinary bool } func (request loadRequest[T]) protectionVersion() *VersionRule { if request.effectiveVersion != nil { return request.effectiveVersion } return request.definition.Version } type loadInput struct { source Source imports bool filesystem *FilesystemContext destination Path placementMode placementMode } type loadGroup struct{ inputs []loadInput } func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBinding, lookup secretLookup) (*Loaded[T], error) { request.ordinary = false var diagnostics []Diagnostic policy, policyDiagnostics := compileSequencePolicy(request.definition.sequenceRegistrations) request.sequencePolicy = policy diagnostics = append(diagnostics, policyDiagnostics...) request.definition.sequenceRegistrations = nil mainOptions, mainDiagnostics := selectOptions(request.main.source, []Options[T]{{ Definition: request.definition, Imports: request.main.imports, Filesystem: request.main.filesystem, destination: request.main.destination.clone(), SourceVersionMode: request.sourceVersionMode, placementMode: request.main.placementMode, }}) request.main.destination = mainOptions.destination diagnostics = append(diagnostics, mainDiagnostics...) request.effectiveVersion = effectiveSourceVersionRuleForPlacement(request.definition.Version, request.main.destination, request.sourceVersionMode, request.main.placementMode) for groupIndex := range request.underlays { for inputIndex := range request.underlays[groupIndex].inputs { input := &request.underlays[groupIndex].inputs[inputIndex] inputOptions, inputDiagnostics := selectOptions(input.source, []Options[T]{{Imports: input.imports, Filesystem: input.filesystem, destination: input.destination.clone(), placementMode: input.placementMode}}) input.destination = inputOptions.destination diagnostics = append(diagnostics, inputDiagnostics...) } } if !validOverlayName(request.overlay) { diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "overlay name must contain only ASCII letters, digits, underscore, or hyphen")) } if activeOverlay(request.overlay) { if request.overlayFilesystem == nil { diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "selected overlay requires explicit filesystem context")) } else { diagnostics = append(diagnostics, filesystemOptionDiagnostics(Source{kind: sourceBytes}, request.overlayFilesystem)...) } } if len(diagnostics) != 0 { return nil, (&diagnosticAccumulator{diagnostics: canonicalDiagnostics(diagnostics)}).loadError() } return executeLoadRequest(request, secrets, lookup) } func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, accounting *importAccounting) (assembledInput, []Diagnostic) { groups := make([]*composedDocument, 0, len(request.underlays)) for index, group := range request.underlays { composition, sources, contexts, diagnostics := acquireUnderlayGroupWithPolicyVersion(group, index+1, request.protectionVersion(), accounting, input.versionState, request.sequencePolicy) input.sources = append(input.sources, sources...) mergeResourceContexts(&input.contexts, contexts) if len(diagnostics) != 0 { return input, diagnostics } if composition != nil { groups = append(groups, composition) } } var assembly *assemblyAccumulator for _, group := range groups { if assembly == nil { assembly = newAssemblyAccumulator(group) continue } if diagnostics := assembly.applyOverlay(group.root, group.origins, group.applications, LayerUnderlay); len(diagnostics) != 0 { return input, diagnostics } } if input.document != nil { mainComposition, diagnostics := compositionForInputWithPolicy(input, request.sequencePolicy) if len(diagnostics) != 0 { return input, diagnostics } if assembly == nil { assembly = newAssemblyAccumulator(mainComposition) } else if diagnostics := assembly.applyOverlay(mainComposition.root, mainComposition.origins, mainComposition.applications, LayerBase); len(diagnostics) != 0 { return input, diagnostics } } if assembly == nil && (activeOverlay(request.overlay) || len(request.overrides) != 0) { composition, _ := compositionForInput(assembledInput{}) assembly = newAssemblyAccumulator(composition) } if assembly == nil { return input, nil } assembly.current.inputBytes = accounting.bytes assembly.current.inputNodes = accounting.nodes if activeOverlay(request.overlay) { // The selected overlay context is independent of the main input. This // synthetic descriptor creates no source record or input contribution. selection := Source{kind: sourceBytes} selected := parsedSource{document: &document.Node{Kind: document.Mapping}, source: &SourceInfo{}} var diagnostics []Diagnostic input.sources, diagnostics = loadSelectedOverlayIntoWithVersion(selection, request.overlayFilesystem, selected, assembly, input.sources, request.overlay, request.protectionVersion(), defaultImportLimits(), input.versionState) if len(diagnostics) != 0 { return input, diagnostics } contexts, diagnostic := contextsForAcquiredSources(selection, request.overlayFilesystem, selected.source, input.sources[len(input.sources)-1:]) if diagnostic != nil { return input, []Diagnostic{*diagnostic} } mergeResourceContexts(&input.contexts, contexts) } if len(request.overrides) != 0 { var diagnostics []Diagnostic input.sources, diagnostics = assembly.applyOverrides(input.sources, request.overrides, request.protectionVersion(), defaultImportLimits()) if len(diagnostics) != 0 { return input, diagnostics } } input.composition = assembly.current input.document = assembly.current.root return input, nil } func acquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { return acquireUnderlayGroupWithVersion(group, index, rule, accounting, nil) } func acquireUnderlayGroupWithVersion(group loadGroup, index int, rule *VersionRule, accounting *importAccounting, versionState *sourceVersionState) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { return acquireUnderlayGroupWithPolicyVersion(group, index, rule, accounting, versionState, sequencePolicy{}) } func acquireUnderlayGroupWithPolicyVersion(group loadGroup, index int, rule *VersionRule, accounting *importAccounting, versionState *sourceVersionState, policy sequencePolicy) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { initialBytes, initialNodes := accounting.bytes, accounting.nodes visited := make(map[resourceIdentity]importRoute) hostToken := &namespaceToken{} var scopes []acquisitionScope var sources []SourceInfo var contexts map[string]ResourceContext for _, contribution := range group.inputs { options := Options[struct{}]{ Imports: contribution.imports, Filesystem: contribution.filesystem, Definition: Definition[struct{}]{Version: rule}, destination: contribution.destination.clone(), placementMode: contribution.placementMode, sequencePolicy: policy, } acquired, diagnostics := assembleBaseInputTrackedForRole(contribution.source, options, contribution.destination, accounting, visited, hostToken, versionState, sourceVersionContribution) for _, source := range acquired.sources { source.Layer = LayerUnderlay source.UnderlayIndex = index sources = append(sources, source) } mergeResourceContexts(&contexts, acquired.contexts) if len(diagnostics) != 0 { return nil, sources, contexts, diagnostics } if acquired.document == nil { continue } scopes = append(scopes, cloneAcquisitionScopes(acquired.acquisition)...) } var documents []*document.Node for _, scope := range scopes { for _, contribution := range scope.contributions { documents = append(documents, contribution.document) } } if len(documents) == 0 { return nil, sources, contexts, nil } composition, diagnostics := composeDocumentsWithPolicy(policy, documents...) if len(diagnostics) != 0 { return nil, sources, contexts, diagnostics } if composition != nil && rule != nil { for sourceIndex, document := range documents { if node := fragmentVersionClaimProjected(document, rule, sourceIndex, composition.sequenceRanges); node != nil { location := publicLocation(node.Location) return nil, sources, contexts, []Diagnostic{{ Code: CodeUnderlayVersion, Severity: SeverityError, Path: rule.Path.clone(), Message: "underlay inputs cannot contribute to the schema-version path", Primary: &location, }} } } } composition.inputBytes = accounting.bytes - initialBytes composition.inputNodes = accounting.nodes - initialNodes composition.acquisition = cloneAcquisitionScopes(scopes) var projectionScope acquisitionScope for _, scope := range scopes { projectionScope.contributions = append(projectionScope.contributions, scope.contributions...) } projectNestedEvidence(composition, projectionScope) projectAcquiredDestinations(composition, projectionScope, sources) for pointer, origin := range composition.origins { origin.Layer = LayerUnderlay composition.origins[pointer] = origin } for index := range composition.applications { composition.applications[index].layer = LayerUnderlay } return composition, sources, contexts, nil }