diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 4f78788..b2e90aa 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Consolidation Implementation Plan -Status: in progress. Stages 1 through 5 are complete; follow Stage 6 through Stage 20 in order. +Status: in progress. Stages 1 through 6 are complete; follow Stage 7 through Stage 20 in order. The target executor is a Luna coding agent working through one end-to-end sprint. ## Authority, Baseline, And Execution @@ -308,7 +308,7 @@ yet because the remaining ordinary and grouped assembly callers are scheduled for Stage 6; verification is deferred until that integration. `git diff --check` passed. -## Stage 6 — Wire Ordinary And Grouped Lifecycle To Acquisition +## Stage 6 — Wire Ordinary And Grouped Lifecycle To Acquisition (Complete) **Owners:** engine `load.go`, `load_request.go`, `load_input.go`, related tests. @@ -339,6 +339,14 @@ passed. for acquisition. The shared path is wired end to end; context and import-flow extraction continue in later stages. +Progress notes (2026-10-25): `executeLoadRequest` now creates one acquisition +policy/state and acquires the main input through `acquireInput`; underlay groups +reuse those load owners with a fresh tier state. Removed the generic base-input +assembly variants and the acquisition-only fields from `Options`, and adapted +direct tests to construct named requests. A search-source regression was caught +and fixed during verification. The focused engine selection, facade selection, +consumer example tests, and `GOTOOLCHAIN=local GOPROXY=off go test ./...` passed. + ## Stage 7 — Separate Resolved Filesystem Facts From Access **Owners:** new engine `filesystem_context.go`, `filesystem.go`, diff --git a/internal/engine/acquisition.go b/internal/engine/acquisition.go index 958df9e..4240429 100644 --- a/internal/engine/acquisition.go +++ b/internal/engine/acquisition.go @@ -169,6 +169,7 @@ func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, sta versionState.mainProcessing = &processed } } + request.input.source = selectedSource if request.input.imports && selectedSource.kind != sourceNone { importRequest := importAcquisitionRequest{input: request, main: parsed, processing: versionProcessing} input.composition, input.sources, diagnostics = acquireImportedInput(importRequest, &policy, state, tier) @@ -233,3 +234,21 @@ func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, sta } return input, nil } + +func registerAcquiredSource(source Source, context *FilesystemContext, info *SourceInfo, visited map[resourceIdentity]importRoute, hostToken *namespaceToken) *Diagnostic { + if visited == nil || info == nil || info.Path == "" { + return nil + } + identity := resourceIdentity{namespace: hostToken, path: info.Path} + if source.kind == sourceFilesystem { + identity.namespace = source.namespace.token + } else if context != nil && context.FS != nil { + identity.namespace = context.FS.namespace.token + } + if previous, repeated := visited[identity]; repeated { + value := importRepeatedDiagnostic(Location{SourceID: info.ID}, previous, importRoute{}) + return &value + } + visited[identity] = importRoute{canonical: info.Path} + return nil +} diff --git a/internal/engine/destination_accounting_test.go b/internal/engine/destination_accounting_test.go index 97348f1..e25954d 100644 --- a/internal/engine/destination_accounting_test.go +++ b/internal/engine/destination_accounting_test.go @@ -409,7 +409,7 @@ func TestSelectedOverlayAndCLIUsePlacementNodeCharges(t *testing.T) { overlay: "test", overlayFilesystem: &FilesystemContext{Root: ".", BaseDir: ".", FS: &filesystem}, } accounting := &importAccounting{nodes: limits.nodes - baseNodes} - _, diagnostics := assembleLayeredInput(request, assembledInput{}, accounting) + _, diagnostics := testAssembleLayeredInput(request, assembledInput{}, accounting) if len(diagnostics) != 1 || diagnostics[0].Code != CodeOverlayLimitExceeded { t.Fatalf("overlay diagnostics = %#v", diagnostics) } @@ -428,7 +428,7 @@ func TestSelectedOverlayAndCLIUsePlacementNodeCharges(t *testing.T) { overrides: []Override{override}, } accounting := &importAccounting{nodes: limits.nodes - baseNodes - overrideNodes + 1} - input, diagnostics := assembleLayeredInput(request, assembledInput{}, accounting) + input, diagnostics := testAssembleLayeredInput(request, assembledInput{}, accounting) if len(diagnostics) != 0 { t.Fatalf("assembly diagnostics = %#v", diagnostics) } @@ -440,3 +440,15 @@ func TestSelectedOverlayAndCLIUsePlacementNodeCharges(t *testing.T) { } }) } + +func testAssembleLayeredInput[T any](request loadRequest[T], input assembledInput, accounting *importAccounting) (assembledInput, []Diagnostic) { + if accounting == nil { + accounting = &importAccounting{} + } + policy := acquisitionPolicy{ + importDefaults: snapshotImportDefaults(request.importDefaults), + sequencePolicy: request.sequencePolicy, + importLimits: defaultImportLimits(), + } + return assembleLayeredInput(request, input, policy, &acquisitionState{accounting: accounting, versionState: input.versionState}) +} diff --git a/internal/engine/destination_import_test.go b/internal/engine/destination_import_test.go index 6f964f2..64654af 100644 --- a/internal/engine/destination_import_test.go +++ b/internal/engine/destination_import_test.go @@ -163,11 +163,10 @@ func TestEntryDestinationSurvivesImportSetupAndParseFailures(t *testing.T) { t.Run("import context failure", func(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: child.yml}]}\n") - input, diagnostics := assembleBaseInputTracked( - File(mainPath), - Options[map[string]any]{Imports: true, Filesystem: &FilesystemContext{Root: t.TempDir()}}, - Keys("mounted"), &importAccounting{}, nil, nil, - ) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true, filesystem: &FilesystemContext{Root: t.TempDir()}, destination: Keys("mounted")}, + role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, nil) if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot { t.Fatalf("context diagnostics = %#v", diagnostics) } @@ -180,10 +179,10 @@ func TestEntryDestinationSurvivesImportSetupAndParseFailures(t *testing.T) { directory := t.TempDir() mainPath := writeImportFile(t, directory, "main.yml", "configkit: {imports: [{path: broken.yml}]}\n") writeImportFile(t, directory, "broken.yml", "broken: [\n") - input, diagnostics := assembleBaseInputTracked( - File(mainPath), Options[map[string]any]{Imports: true}, - Keys("mounted"), &importAccounting{}, nil, nil, - ) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true, destination: Keys("mounted")}, + role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, nil) if len(diagnostics) != 1 || diagnostics[0].Code != CodeYAMLInvalid { t.Fatalf("parse diagnostics = %#v", diagnostics) } diff --git a/internal/engine/imports_test.go b/internal/engine/imports_test.go index 08ca2d1..6783d57 100644 --- a/internal/engine/imports_test.go +++ b/internal/engine/imports_test.go @@ -113,10 +113,13 @@ tasks: [{id: z-parent}] aIndependentPath := writeImportFile(t, directory, "a-independent.yml", "tasks: [{id: a-independent}]\n") bDependencyPath := writeImportFile(t, directory, "b-dependency.yml", "tasks: [{id: b-dependency}]\n") - input, diagnostics := assembleBaseInput[config](File(mainPath), Options[config]{ - Imports: true, + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, + directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{ sequencePolicy: mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("tasks")}, identityFields: []string{"id"}}}), - }) + importLimits: defaultImportLimits(), + }, nil) if len(diagnostics) != 0 { t.Fatalf("assemble imports: %#v", diagnostics) } @@ -617,9 +620,10 @@ id: item `) helperPath := writeImportFile(t, directory, "helper.yml", "value: acquired\n") accounting := &importAccounting{} - input, diagnostics := assembleBaseInputTracked(File(mainPath), Options[struct{}]{ - Imports: true, Filesystem: &FilesystemContext{Root: directory}, deferAssembly: true, - }, Path{}, accounting, nil, nil) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true, filesystem: &FilesystemContext{Root: directory}}, + role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly, deferAssembly: true, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, accounting) if len(diagnostics) != 0 { t.Fatalf("acquire input: %#v", diagnostics) } @@ -674,7 +678,10 @@ func TestItemImportCanJoinAnAuthoredListUnderRegisteredComposition(t *testing.T) `) writeImportFile(t, directory, "item.yml", "configkit: {imports: [{path: helper.yml}]}\nname: imported\n") writeImportFile(t, directory, "helper.yml", "id: helper-id\n") - input, diagnostics := assembleBaseInput(File(mainPath), Options[config]{Imports: true}) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, + directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, nil) if len(diagnostics) != 0 { t.Fatalf("assemble item import: %#v", diagnostics) } @@ -951,7 +958,10 @@ func TestItemAcquisitionRetainsInclusionGraphAndScopeOwnership(t *testing.T) { itemPath := writeImportFile(t, directory, "item.yml", "configkit: {imports: [{path: helper.yml}]}\nname: item\n") helperPath := writeImportFile(t, directory, "helper.yml", "id: supplied\n") - input, diagnostics := assembleBaseInput[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, + directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, nil) if len(diagnostics) != 0 { t.Fatalf("assemble input: %#v", diagnostics) } @@ -1104,7 +1114,10 @@ func TestItemHelperScopeScheduleInterleavesUnequalBranches(t *testing.T) { writeImportFile(t, directory, "b-parent.yml", "configkit: {imports: [{path: a-dependency.yml}]}\nb: true\n") writeImportFile(t, directory, "a-dependency.yml", "a: true\n") writeImportFile(t, directory, "c-independent.yml", "c: true\n") - input, diagnostics := assembleBaseInput[importTestConfig](File(mainPath), Options[importTestConfig]{Imports: true}) + input, diagnostics := testAcquireInput(inputAcquisitionRequest{ + input: loadInput{source: File(mainPath), imports: true}, role: sourceVersionMain, + directiveContext: rootDirectiveContextImportsOnly, + }, acquisitionPolicy{importLimits: defaultImportLimits()}, nil) if len(diagnostics) != 0 { t.Fatalf("assemble item scope: %#v", diagnostics) } @@ -1420,6 +1433,15 @@ func testImportRequest(source Source, context *FilesystemContext, main parsedSou } } +func testAcquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, accounting *importAccounting) (assembledInput, []Diagnostic) { + if accounting == nil { + accounting = &importAccounting{} + } + state := &acquisitionState{accounting: accounting} + tier := &tierAcquisitionState{visited: make(map[resourceIdentity]importRoute)} + return acquireInput(request, policy, state, tier) +} + func testAcquireImport(request importAcquisitionRequest, policy acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (*composedDocument, []SourceInfo, []Diagnostic) { return acquireImportedInput(request, &policy, state, tier) } diff --git a/internal/engine/load.go b/internal/engine/load.go index 2713458..0089056 100644 --- a/internal/engine/load.go +++ b/internal/engine/load.go @@ -35,9 +35,6 @@ type Options[T any] struct { importDefaults importDefaults destination Path placementMode placementMode - sequencePolicy sequencePolicy - deferAssembly bool - directiveContext rootDirectiveContext } // FilesystemContext supplies filesystem roots for imports and overlays. For an @@ -184,17 +181,10 @@ func loadWithSecretLookup[T any](source Source, secrets SecretBinding, lookup se func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lookup secretLookup) (*Loaded[T], error) { accumulator := diagnosticAccumulator{} accounting := &importAccounting{} - mainOptions := Options[T]{Definition: request.definition, Imports: request.main.imports, Filesystem: request.main.filesystem, destination: request.main.destination.clone(), placementMode: request.main.placementMode} - mainOptions.importDefaults = snapshotImportDefaults(request.importDefaults) - mainOptions.sequencePolicy = request.sequencePolicy - mainOptions.deferAssembly = true - mainOptions.SourceVersionMode = request.sourceVersionMode - mainOptions.Definition.Version = request.protectionVersion() - if request.ordinary { - mainOptions.Overlay = request.overlay - mainOptions.directiveContext = rootDirectiveContextSource - } else { - mainOptions.directiveContext = rootDirectiveContextLayered + policy := acquisitionPolicy{ + importDefaults: snapshotImportDefaults(request.importDefaults), + sequencePolicy: request.sequencePolicy, + importLimits: defaultImportLimits(), } var schema *schemaDescriptor @@ -209,8 +199,19 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo versionState := &sourceVersionState{ localRule: request.definition.Version, effectiveRule: request.protectionVersion(), mode: request.sourceVersionMode, } + state := &acquisitionState{accounting: accounting, versionState: versionState} accumulator.versionState = versionState - input, diagnostics := assembleBaseInputTrackedWithVersion(request.main.source, mainOptions, request.main.destination, accounting, nil, nil, versionState) + directiveContext := rootDirectiveContextLayered + ordinaryOverlay := "" + if request.ordinary { + directiveContext = rootDirectiveContextSource + ordinaryOverlay = request.overlay + } + input, diagnostics := acquireInput(inputAcquisitionRequest{ + input: request.main, role: sourceVersionMain, directiveContext: directiveContext, + protectionRule: request.protectionVersion(), deferAssembly: true, + ordinaryOverlay: ordinaryOverlay, + }, policy, state, &tierAcquisitionState{visited: make(map[resourceIdentity]importRoute)}) request.main.source = input.source accumulator.sources = input.sources accumulator.add(versionState.diagnostics...) @@ -228,7 +229,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo } } if request.ordinary && (input.document != nil || input.composition != nil) { - input.composition, diagnostics = compositionForInputWithPolicy(input, request.sequencePolicy) + input.composition, diagnostics = compositionForInputWithPolicy(input, policy.sequencePolicy) if len(diagnostics) != 0 { accumulator.add(diagnostics...) return nil, accumulator.loadError() @@ -244,10 +245,9 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo } if request.ordinary { versionState.locateMainVersion(input.document) - mainOptions.Definition.Version = versionState.effectiveRule - input, diagnostics = applyInputLayers(request.main.source, mainOptions, input) + input, diagnostics = applyInputLayers(request.main.source, request.main.filesystem, request.overlay, versionState.effectiveRule, policy.sequencePolicy, input) } else { - input, diagnostics = assembleLayeredInput(request, input, accounting) + input, diagnostics = assembleLayeredInput(request, input, policy, state) } accumulator.add(versionState.diagnostics[reportedVersionDiagnostics:]...) accumulator.sources = input.sources @@ -279,7 +279,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo return nil, accumulator.loadError() } expanded, problems := expandInheritanceWithCollectionRules( - composition.root, composition.origins, composition.collectionPolicies, request.sequencePolicy, defaultProjectionLimits(), + composition.root, composition.origins, composition.collectionPolicies, policy.sequencePolicy, defaultProjectionLimits(), ) if len(problems) != 0 { accumulator.add(problems...) @@ -351,7 +351,7 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo if hasErrors(secretResolution.diagnostics) { return nil, accumulator.loadError() } - identityDiagnostics := validateFinalSequenceIdentities(resolved.referenceInput, request.sequencePolicy) + identityDiagnostics := validateFinalSequenceIdentities(resolved.referenceInput, policy.sequencePolicy) accumulator.add(identityDiagnostics...) if hasErrors(identityDiagnostics) { return nil, accumulator.loadError() diff --git a/internal/engine/load_input.go b/internal/engine/load_input.go index c512ef7..a9e3e59 100644 --- a/internal/engine/load_input.go +++ b/internal/engine/load_input.go @@ -88,59 +88,27 @@ func cloneAcquisitionScopes(scopes []acquisitionScope) []acquisitionScope { return cloned } -func assembleBaseInput[T any](source Source, options Options[T]) (assembledInput, []Diagnostic) { - return assembleBaseInputTracked(source, options, Path{}, &importAccounting{}, nil, nil) -} - -func assembleBaseInputTracked[T any](source Source, options Options[T], destination Path, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken) (assembledInput, []Diagnostic) { - return assembleBaseInputTrackedWithVersion(source, options, destination, accounting, visited, hostToken, nil) -} - -func assembleBaseInputTrackedWithVersion[T any](source Source, options Options[T], destination Path, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken, versionState *sourceVersionState) (assembledInput, []Diagnostic) { - return assembleBaseInputTrackedForRole(source, options, destination, accounting, visited, hostToken, versionState, sourceVersionMain) -} - -func registerAcquiredSource(source Source, context *FilesystemContext, info *SourceInfo, visited map[resourceIdentity]importRoute, hostToken *namespaceToken) *Diagnostic { - if visited == nil || info == nil || info.Path == "" { - return nil - } - identity := resourceIdentity{namespace: hostToken, path: info.Path} - if source.kind == sourceFilesystem { - identity.namespace = source.namespace.token - } else if context != nil && context.FS != nil { - identity.namespace = context.FS.namespace.token - } - if previous, repeated := visited[identity]; repeated { - value := importRepeatedDiagnostic(Location{SourceID: info.ID}, previous, importRoute{}) - return &value - } - visited[identity] = importRoute{canonical: info.Path} - return nil -} - -func applyInputLayers[T any](source Source, options Options[T], input assembledInput) (assembledInput, []Diagnostic) { - if !activeOverlay(options.Overlay) { +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, options.sequencePolicy) + composition, diagnostics := compositionForInputWithPolicy(input, policy) if len(diagnostics) != 0 { return input, diagnostics } assembly := newAssemblyAccumulator(composition) - if activeOverlay(options.Overlay) { - input.sources, diagnostics = loadSelectedOverlayIntoWithVersion( - source, options.Filesystem, input.parsed, assembly, input.sources, - options.Overlay, options.Definition.Version, defaultImportLimits(), input.versionState, options.sequencePolicy, - ) - if len(diagnostics) != 0 { - return input, diagnostics - } - contexts, diagnostic := contextsForAcquiredSources(source, options.Filesystem, input.parsed.source, input.sources[len(input.sources)-1:]) - if diagnostic != nil { - return input, []Diagnostic{*diagnostic} - } - mergeResourceContexts(&input.contexts, contexts) + input.sources, diagnostics = loadSelectedOverlayIntoWithVersion( + source, filesystem, input.parsed, assembly, input.sources, + overlay, protectionRule, defaultImportLimits(), input.versionState, policy, + ) + if len(diagnostics) != 0 { + return input, diagnostics } + contexts, diagnostic := contextsForAcquiredSources(source, filesystem, input.parsed.source, 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 diff --git a/internal/engine/load_request.go b/internal/engine/load_request.go index f635dce..5678160 100644 --- a/internal/engine/load_request.go +++ b/internal/engine/load_request.go @@ -79,16 +79,14 @@ func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBi return executeLoadRequest(request, secrets, lookup) } -func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, accounting *importAccounting) (assembledInput, []Diagnostic) { +func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, policy acquisitionPolicy, state *acquisitionState) (assembledInput, []Diagnostic) { var assembly *assemblyAccumulator mainSourceCount := len(input.sources) mainEvidence := input.versionState.evidenceSince(0) projectVersion := sequenceVersionPath(request.protectionVersion()) for index, group := range request.underlays { evidenceStart := len(input.versionState.evidenceSince(0)) - acquired, diagnostics := acquireUnderlayGroupScopes(group, index+1, request.protectionVersion(), accounting, input.versionState, acquisitionPolicy{ - importDefaults: request.importDefaults, sequencePolicy: request.sequencePolicy, importLimits: defaultImportLimits(), - }) + acquired, diagnostics := acquireUnderlayGroupScopes(group, index+1, request.protectionVersion(), state, policy) mergeResourceContexts(&input.contexts, acquired.contexts) if len(diagnostics) != 0 { input.sources = append(input.sources, acquired.sources...) @@ -104,7 +102,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a // Source-local checks still run during acquisition above. protectionRule = nil } - composition, diagnostics := composeUnderlayGroup(acquired, protectionRule, request.sequencePolicy, lower) + composition, diagnostics := composeUnderlayGroup(acquired, protectionRule, policy.sequencePolicy, lower) sourceStart := len(input.sources) input.sources = append(input.sources, acquired.sources...) if len(diagnostics) != 0 { @@ -120,7 +118,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a } operations, diagnostics := assembly.applyOverlayWithCollectionProjection( composition.root, composition.origins, composition.collectionPolicies, composition.applications, LayerUnderlay, - request.sequencePolicy, declarations, + policy.sequencePolicy, declarations, ) if len(diagnostics) != 0 { return input, diagnostics @@ -138,7 +136,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a lower = assembly.current.collectionPolicies } var diagnostics []Diagnostic - mainComposition, diagnostics = compositionForInputWithPolicies(input, lower, request.sequencePolicy) + mainComposition, diagnostics = compositionForInputWithPolicies(input, lower, policy.sequencePolicy) if len(diagnostics) != 0 { return input, diagnostics } @@ -167,7 +165,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a } operations, diagnostics := assembly.applyOverlayWithCollectionProjection( mainComposition.root, mainComposition.origins, mainComposition.collectionPolicies, mainComposition.applications, LayerBase, - request.sequencePolicy, declarations, + policy.sequencePolicy, declarations, ) if len(diagnostics) != 0 { return input, append(versionDiagnostics, diagnostics...) @@ -191,15 +189,15 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a if assembly == nil { return input, versionDiagnostics } - assembly.current.inputBytes = accounting.bytes - assembly.current.inputNodes = accounting.nodes + assembly.current.inputBytes = state.accounting.bytes + assembly.current.inputNodes = state.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 - overlayPolicy := []sequencePolicy{request.sequencePolicy} + overlayPolicy := []sequencePolicy{policy.sequencePolicy} input.sources, diagnostics = loadSelectedOverlayIntoWithVersion(selection, request.overlayFilesystem, selected, assembly, input.sources, request.overlay, request.protectionVersion(), defaultImportLimits(), input.versionState, overlayPolicy...) if len(diagnostics) != 0 { return input, append(versionDiagnostics, diagnostics...) @@ -215,10 +213,9 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a return input, versionDiagnostics } -func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, accounting *importAccounting, versionState *sourceVersionState, policy acquisitionPolicy) (*acquiredUnderlayGroup, []Diagnostic) { - initialBytes, initialNodes := accounting.bytes, accounting.nodes - visited := make(map[resourceIdentity]importRoute) - hostToken := &namespaceToken{} +func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, state *acquisitionState, policy acquisitionPolicy) (*acquiredUnderlayGroup, []Diagnostic) { + initialBytes, initialNodes := state.accounting.bytes, state.accounting.nodes + tier := &tierAcquisitionState{visited: make(map[resourceIdentity]importRoute), hostToken: &namespaceToken{}} nativeAnchors := make(map[*namespaceToken]string) for inputIndex, contribution := range group.inputs { if token := nativeTokenForLoadInput(contribution); token != nil { @@ -230,14 +227,10 @@ func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, a acquiredGroup := &acquiredUnderlayGroup{index: index} inclusionGraph := newImportInclusionGraph() for inputIndex, 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.sequencePolicy, deferAssembly: true, - directiveContext: rootDirectiveContextLayered, - importDefaults: snapshotImportDefaults(policy.importDefaults), - } - acquired, diagnostics := assembleBaseInputTrackedForRole(contribution.source, options, contribution.destination, accounting, visited, hostToken, versionState, sourceVersionContribution) + acquired, diagnostics := acquireInput(inputAcquisitionRequest{ + input: contribution, role: sourceVersionContribution, + directiveContext: rootDirectiveContextLayered, protectionRule: rule, deferAssembly: true, + }, policy, state, tier) for _, source := range acquired.sources { source.Layer = LayerUnderlay source.UnderlayIndex = index @@ -271,8 +264,8 @@ func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, a mergeImportInclusionGraph(&inclusionGraph, inputGraph) } acquiredGroup.importGraph = inclusionGraph.clone() - acquiredGroup.inputBytes = accounting.bytes - initialBytes - acquiredGroup.inputNodes = accounting.nodes - initialNodes + acquiredGroup.inputBytes = state.accounting.bytes - initialBytes + acquiredGroup.inputNodes = state.accounting.nodes - initialNodes return acquiredGroup, nil } diff --git a/internal/engine/load_request_test.go b/internal/engine/load_request_test.go index 7bb7cc5..a0f87f3 100644 --- a/internal/engine/load_request_test.go +++ b/internal/engine/load_request_test.go @@ -80,7 +80,8 @@ func TestUnderlayGroupAcquisitionRetainsScopesUntilComposition(t *testing.T) { }) accounting := &importAccounting{} group := loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}} - acquired, diagnostics := acquireUnderlayGroupScopes(group, 2, nil, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()}) + state := &acquisitionState{accounting: accounting} + acquired, diagnostics := acquireUnderlayGroupScopes(group, 2, nil, state, acquisitionPolicy{importLimits: defaultImportLimits()}) if len(diagnostics) != 0 { t.Fatalf("acquire underlay group: %#v", diagnostics) } @@ -108,7 +109,7 @@ func TestUnderlayGroupAcquisitionRetainsScopesUntilComposition(t *testing.T) { t.Fatalf("composed group handoff = %#v", composition) } - empty, diagnostics := acquireUnderlayGroupScopes(loadGroup{}, 3, nil, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()}) + empty, diagnostics := acquireUnderlayGroupScopes(loadGroup{}, 3, nil, state, acquisitionPolicy{importLimits: defaultImportLimits()}) if len(diagnostics) != 0 || len(empty.scopes) != 0 { t.Fatalf("empty group acquisition = %#v, %#v", empty, diagnostics) } @@ -469,7 +470,8 @@ func TestGroupedLoadStrictConflictsRepetitionAndBudgets(t *testing.T) { } func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { - acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()}) + state := &acquisitionState{accounting: accounting} + acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, state, acquisitionPolicy{importLimits: defaultImportLimits()}) if len(diagnostics) != 0 { return nil, acquired.sources, acquired.contexts, diagnostics }