Consolidate import request ownership

This commit is contained in:
2026-10-25 13:02:46 +00:00
parent d05426d8a2
commit bd51b9c3f2
14 changed files with 178 additions and 157 deletions

View File

@@ -1,6 +1,6 @@
# Consolidation Implementation Plan # Consolidation Implementation Plan
Status: in progress. Stages 1 through 4 are complete; follow Stage 5 through Stage 20 in order. Status: in progress. Stages 1 through 5 are complete; follow Stage 6 through Stage 20 in order.
The target executor is a Luna coding agent working through one end-to-end sprint. The target executor is a Luna coding agent working through one end-to-end sprint.
## Authority, Baseline, And Execution ## Authority, Baseline, And Execution
@@ -270,7 +270,7 @@ test command currently cannot compile because the old assembly callers in
`load_input.go` and `load_request.go` are scheduled to migrate in Stage 6; test `load_input.go` and `load_request.go` are scheduled to migrate in Stage 6; test
verification is deferred until that integration. verification is deferred until that integration.
## Stage 5 — Consolidate Import Requests And Default Ownership ## Stage 5 — Consolidate Import Requests And Default Ownership (Complete)
**Owners:** engine import entry points, `importTraversal`, `source_version.go`, **Owners:** engine import entry points, `importTraversal`, `source_version.go`,
and tests that directly invoke the old import variants. and tests that directly invoke the old import variants.
@@ -301,6 +301,13 @@ and tests that directly invoke the old import variants.
longer travel through version processing; no forwarding ladder remains merely longer travel through version processing; no forwarding ladder remains merely
to supply another nil/default positional parameter. to supply another nil/default positional parameter.
Progress notes (2026-10-25): replaced the import entry ladders with named
requests and one access worker, moved defaults to acquisition policy, and
retargeted direct import tests. The focused engine test command cannot compile
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
**Owners:** engine `load.go`, `load_request.go`, `load_input.go`, related tests. **Owners:** engine `load.go`, `load_request.go`, `load_input.go`, related tests.

View File

@@ -170,11 +170,8 @@ func acquireInput(request inputAcquisitionRequest, policy acquisitionPolicy, sta
} }
} }
if request.input.imports && selectedSource.kind != sourceNone { if request.input.imports && selectedSource.kind != sourceNone {
if request.deferAssembly { importRequest := importAcquisitionRequest{input: request, main: parsed, processing: versionProcessing}
input.composition, input.sources, diagnostics = loadImportedDocumentsTrackedWithProcessingDeferred(selectedSource, request.input.filesystem, parsed, destination, request.protectionRule, limits, accounting, visited, hostToken, versionState, versionProcessing, policy.sequencePolicy, true) input.composition, input.sources, diagnostics = acquireImportedInput(importRequest, &policy, state, tier)
} else {
input.composition, input.sources, diagnostics = loadImportedDocumentsTrackedWithProcessing(selectedSource, request.input.filesystem, parsed, destination, request.protectionRule, limits, accounting, visited, hostToken, versionState, versionProcessing, policy.sequencePolicy)
}
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
return input, diagnostics return input, diagnostics
} }

View File

@@ -216,9 +216,8 @@ func TestImportedEmptyBodiesChargeFullRelativeDestinations(t *testing.T) {
currentLimits := limits currentLimits := limits
currentLimits.nodes = wantNodes + test.delta currentLimits.nodes = wantNodes + test.delta
accounting := &importAccounting{} accounting := &importAccounting{}
composed, sources, diagnostics := loadImportedDocumentsTracked( request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil)
File(mainPath), nil, main, entryDestination, nil, currentLimits, accounting, nil, nil, composed, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting)
)
if test.wantError { if test.wantError {
if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("below-bound relative placement = %#v, %#v", composed, diagnostics) t.Fatalf("below-bound relative placement = %#v, %#v", composed, diagnostics)
@@ -279,9 +278,8 @@ func TestGlobMatchesChargeTheirSharedRelativeDestination(t *testing.T) {
currentLimits := limits currentLimits := limits
currentLimits.nodes = wantNodes + test.delta currentLimits.nodes = wantNodes + test.delta
accounting := &importAccounting{} accounting := &importAccounting{}
composed, sources, diagnostics := loadImportedDocumentsTracked( request := testImportRequest(File(mainPath), nil, main, entryDestination, nil, nil)
File(mainPath), nil, main, entryDestination, nil, currentLimits, accounting, nil, nil, composed, sources, diagnostics := testAcquireImportWithAccounting(request, currentLimits, accounting)
)
if test.wantError { if test.wantError {
if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if composed != nil || len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("below-bound glob placement = %#v, %#v", composed, diagnostics) t.Fatalf("below-bound glob placement = %#v, %#v", composed, diagnostics)
@@ -366,9 +364,8 @@ func TestAbsentOptionalImportsDoNotChargeUnusedDeepDestinations(t *testing.T) {
main := parseImportMain(t, mainPath) main := parseImportMain(t, mainPath)
limits := defaultImportLimits() limits := defaultImportLimits()
accounting := &importAccounting{} accounting := &importAccounting{}
composed, sources, diagnostics := loadImportedDocumentsTracked( request := testImportRequest(File(mainPath), nil, main, destination, nil, nil)
File(mainPath), nil, main, destination, nil, limits, accounting, nil, nil, composed, sources, diagnostics := testAcquireImportWithAccounting(request, limits, accounting)
)
if len(diagnostics) != 0 || composed == nil || len(sources) != 1 { if len(diagnostics) != 0 || composed == nil || len(sources) != 1 {
t.Fatalf("absent optional import = %#v, %#v, %#v", composed, sources, diagnostics) t.Fatalf("absent optional import = %#v, %#v, %#v", composed, sources, diagnostics)
} }

View File

@@ -108,8 +108,11 @@ parent: true
} }
defer func() { _ = access.close() }() defer func() { _ = access.close() }()
state := importTraversal{ state := importTraversal{
access: access, accounting: &importAccounting{}, limits: defaultImportLimits(), access: access,
active: make(map[resourceIdentity]int), visited: make(map[resourceIdentity]importRoute), policy: &acquisitionPolicy{importLimits: defaultImportLimits()},
state: &acquisitionState{accounting: &importAccounting{}},
tier: &tierAcquisitionState{visited: make(map[resourceIdentity]importRoute)},
active: make(map[resourceIdentity]int),
} }
if diagnostics := state.walkDeclarations(directory, declarations, 0, Keys("entry")); len(diagnostics) != 0 { if diagnostics := state.walkDeclarations(directory, declarations, 0, Keys("entry")); len(diagnostics) != 0 {
t.Fatalf("walk diagnostics = %#v", diagnostics) t.Fatalf("walk diagnostics = %#v", diagnostics)

View File

@@ -216,7 +216,8 @@ func TestGlobTraversalBoundsNonmatchesAndAcceptedCandidates(t *testing.T) {
main := parseImportMain(t, mainPath) main := parseImportMain(t, mainPath)
limits := defaultImportLimits() limits := defaultImportLimits()
limits.entries = 2 limits.entries = 2
_, _, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, limits) request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
_, _, diagnostics := testAcquireImportWithLimits(request, limits)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("entry-budget diagnostics = %#v", diagnostics) t.Fatalf("entry-budget diagnostics = %#v", diagnostics)
} }
@@ -228,7 +229,8 @@ func TestGlobTraversalBoundsNonmatchesAndAcceptedCandidates(t *testing.T) {
main = parseImportMain(t, mainPath) main = parseImportMain(t, mainPath)
limits = defaultImportLimits() limits = defaultImportLimits()
limits.sources = 2 limits.sources = 2
_, _, diagnostics = loadImportedDocuments(File(mainPath), nil, main, nil, limits) request = testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
_, _, diagnostics = testAcquireImportWithLimits(request, limits)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("candidate-budget diagnostics = %#v", diagnostics) t.Fatalf("candidate-budget diagnostics = %#v", diagnostics)
} }

View File

@@ -46,13 +46,11 @@ type importAccounting struct {
} }
type importTraversal struct { type importTraversal struct {
access *filesystemAccess access *filesystemAccess
accounting *importAccounting request importAcquisitionRequest
limits importLimits policy *acquisitionPolicy
rule *VersionRule state *acquisitionState
versionState *sourceVersionState tier *tierAcquisitionState
directiveContext rootDirectiveContext
defaults importDefaults
scope acquisitionScope scope acquisitionScope
itemScope bool itemScope bool
@@ -60,38 +58,37 @@ type importTraversal struct {
itemFallback Path itemFallback Path
sources []SourceInfo sources []SourceInfo
active map[resourceIdentity]int active map[resourceIdentity]int
visited map[resourceIdentity]importRoute
stack []importRoute stack []importRoute
importGraph importInclusionGraph importGraph importInclusionGraph
} }
func loadImportedDocuments(source Source, context *FilesystemContext, main parsedSource, rule *VersionRule, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) { func compositionTraversal(policy sequencePolicy, rule *VersionRule, versionState *sourceVersionState, accounting *importAccounting, graph importInclusionGraph) importTraversal {
return loadImportedDocumentsTracked(source, context, main, Path{}, rule, limits, &importAccounting{}, nil, nil) return importTraversal{
} request: importAcquisitionRequest{input: inputAcquisitionRequest{protectionRule: rule}},
policy: &acquisitionPolicy{sequencePolicy: policy, importLimits: defaultImportLimits()},
func loadImportedDocumentsTracked(source Source, context *FilesystemContext, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken) (*composedDocument, []SourceInfo, []Diagnostic) { state: &acquisitionState{accounting: accounting, versionState: versionState},
return loadImportedDocumentsTrackedWithVersion(source, context, main, destination, rule, limits, accounting, visited, hostToken, nil) tier: &tierAcquisitionState{},
} importGraph: graph.clone(),
func loadImportedDocumentsTrackedWithVersion(source Source, context *FilesystemContext, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken, versionState *sourceVersionState) (*composedDocument, []SourceInfo, []Diagnostic) {
var processing *sourceVersionProcessingResult
if versionState != nil && versionState.mode != sourceVersionModeDefault {
processing = versionState.mainProcessing
} }
return loadImportedDocumentsTrackedWithProcessing(source, context, main, destination, rule, limits, accounting, visited, hostToken, versionState, processing, sequencePolicy{})
} }
func loadImportedDocumentsTrackedWithProcessing(source Source, context *FilesystemContext, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken, versionState *sourceVersionState, processing *sourceVersionProcessingResult, policy sequencePolicy) (*composedDocument, []SourceInfo, []Diagnostic) { func acquireImportedInput(request importAcquisitionRequest, policy *acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (*composedDocument, []SourceInfo, []Diagnostic) {
return loadImportedDocumentsTrackedWithProcessingDeferred(source, context, main, destination, rule, limits, accounting, visited, hostToken, versionState, processing, policy, false) source := request.input.input.source
} context := request.input.input.filesystem
main := request.main
func loadImportedDocumentsTrackedWithProcessingDeferred(source Source, context *FilesystemContext, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, hostToken *namespaceToken, versionState *sourceVersionState, processing *sourceVersionProcessingResult, policy sequencePolicy, deferAssembly bool) (*composedDocument, []SourceInfo, []Diagnostic) {
if nativeContext(source, context) { if nativeContext(source, context) {
access, basePath, mainPath, diagnostic := nativeFilesystemAccess(source, context) access, basePath, mainPath, diagnostic := nativeFilesystemAccess(source, context)
if diagnostic != nil { if diagnostic != nil {
return nil, sourceInfos(main.source), []Diagnostic{*diagnostic} return nil, sourceInfos(main.source), []Diagnostic{*diagnostic}
} }
return loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, versionState, processing, policy, deferAssembly) native := source.namespace
if native == nil && context != nil && context.FS != nil {
native = context.FS.namespace
}
return acquireImportedWithAccess(request, policy, state, tier, access, resolvedFilesystemContext{
native: native, hostToken: tier.hostToken, rootPath: access.rootPath,
basePath: basePath, mainPath: mainPath,
})
} }
rootPath, basePath, mainPath, diagnostic := importFilesystem(source, context, main) rootPath, basePath, mainPath, diagnostic := importFilesystem(source, context, main)
if diagnostic != nil { if diagnostic != nil {
@@ -103,37 +100,26 @@ func loadImportedDocumentsTrackedWithProcessingDeferred(source Source, context *
diagnostic.Primary = nil diagnostic.Primary = nil
return nil, sourceInfos(main.source), []Diagnostic{diagnostic} return nil, sourceInfos(main.source), []Diagnostic{diagnostic}
} }
if hostToken != nil { if tier.hostToken != nil {
access.token = hostToken access.token = tier.hostToken
} }
return loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, versionState, processing, policy, deferAssembly) return acquireImportedWithAccess(request, policy, state, tier, access, resolvedFilesystemContext{
hostToken: tier.hostToken, rootPath: rootPath, basePath: basePath, mainPath: mainPath,
})
} }
func loadImportedWithAccess(access *filesystemAccess, basePath, mainPath string, main parsedSource, rule *VersionRule, limits importLimits, accounting *importAccounting) (*composedDocument, []SourceInfo, []Diagnostic) { func acquireImportedWithAccess(request importAcquisitionRequest, policy *acquisitionPolicy, loadState *acquisitionState, tier *tierAcquisitionState, access *filesystemAccess, resolved resolvedFilesystemContext) (*composedDocument, []SourceInfo, []Diagnostic) {
return loadImportedWithAccessTracked(access, basePath, mainPath, main, Path{}, rule, limits, accounting, nil) main := request.main
} destination := request.input.input.destination
limits := policy.importLimits
func loadImportedWithAccessTracked(access *filesystemAccess, basePath, mainPath string, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute) (*composedDocument, []SourceInfo, []Diagnostic) { accounting := loadState.accounting
return loadImportedWithAccessTrackedWithVersion(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, nil) versionState := loadState.versionState
} processing := request.processing
basePath, mainPath := resolved.basePath, resolved.mainPath
func loadImportedWithAccessTrackedWithVersion(access *filesystemAccess, basePath, mainPath string, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, versionState *sourceVersionState) (*composedDocument, []SourceInfo, []Diagnostic) { if tier.visited == nil {
var processing *sourceVersionProcessingResult tier.visited = make(map[resourceIdentity]importRoute)
if versionState != nil && versionState.mode != sourceVersionModeDefault {
processing = versionState.mainProcessing
} }
return loadImportedWithAccessTrackedAndProcessing(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, versionState, processing) visited := tier.visited
}
func loadImportedWithAccessTrackedAndProcessing(access *filesystemAccess, basePath, mainPath string, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, versionState *sourceVersionState, processing *sourceVersionProcessingResult) (*composedDocument, []SourceInfo, []Diagnostic) {
return loadImportedWithAccessTrackedAndProcessingPolicy(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, versionState, processing, sequencePolicy{})
}
func loadImportedWithAccessTrackedAndProcessingPolicy(access *filesystemAccess, basePath, mainPath string, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, versionState *sourceVersionState, processing *sourceVersionProcessingResult, policy sequencePolicy) (*composedDocument, []SourceInfo, []Diagnostic) {
return loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access, basePath, mainPath, main, destination, rule, limits, accounting, visited, versionState, processing, policy, false)
}
func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystemAccess, basePath, mainPath string, main parsedSource, destination Path, rule *VersionRule, limits importLimits, accounting *importAccounting, visited map[resourceIdentity]importRoute, versionState *sourceVersionState, processing *sourceVersionProcessingResult, policy sequencePolicy, deferAssembly bool) (*composedDocument, []SourceInfo, []Diagnostic) {
if !access.contains(basePath) || (mainPath != "" && !access.contains(mainPath)) { if !access.contains(basePath) || (mainPath != "" && !access.contains(mainPath)) {
_ = access.close() _ = access.close()
return nil, sourceInfos(main.source), []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil)} return nil, sourceInfos(main.source), []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "selected source and import base must stay inside the configuration root", nil)}
@@ -163,21 +149,19 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
accounting.sources++ accounting.sources++
accounting.bytes += main.bytes accounting.bytes += main.bytes
accounting.nodes += countInputNodes(main.document) accounting.nodes += countInputNodes(main.document)
defaults := importDefaults{} directiveContext := request.input.directiveContext
if versionState != nil { if directiveContext == 0 {
defaults = snapshotImportDefaults(versionState.importDefaults) directiveContext = rootDirectiveContextImportsOnly
} }
directiveContext := rootDirectiveContextImportsOnly
if processing != nil && processing.directiveContext != 0 { if processing != nil && processing.directiveContext != 0 {
directiveContext = processing.directiveContext directiveContext = processing.directiveContext
} }
request.input.directiveContext = directiveContext
state := importTraversal{ state := importTraversal{
access: access, accounting: accounting, limits: limits, rule: rule, versionState: versionState, access: access, request: request, policy: policy, state: loadState, tier: tier,
directiveContext: directiveContext, scope: acquisitionScope{owner: mainNodeKey, destination: destination.clone()},
defaults: defaults, sources: []SourceInfo{mainInfo},
scope: acquisitionScope{owner: mainNodeKey, destination: destination.clone()}, active: make(map[resourceIdentity]int),
sources: []SourceInfo{mainInfo},
active: make(map[resourceIdentity]int), visited: visited,
importGraph: newImportInclusionGraph(), importGraph: newImportInclusionGraph(),
} }
if mainIsItem { if mainIsItem {
@@ -187,9 +171,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
state.scope.destination = Path{} state.scope.destination = Path{}
state.sources[0] = cloneSourceInfo(mainInfo) state.sources[0] = cloneSourceInfo(mainInfo)
} }
if state.visited == nil {
state.visited = make(map[resourceIdentity]importRoute)
}
if diagnostic := state.checkInitialLimits(); diagnostic != nil { if diagnostic := state.checkInitialLimits(); diagnostic != nil {
_ = access.close() _ = access.close()
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic} return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
@@ -203,7 +185,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
state.importGraph.addNode(importGraphNode{key: mainNodeKey, canonical: mainIdentity, sourceID: mainInfo.ID, role: importRoleMain}) state.importGraph.addNode(importGraphNode{key: mainNodeKey, canonical: mainIdentity, sourceID: mainInfo.ID, role: importRoleMain})
mainRoute.key = mainNodeKey mainRoute.key = mainNodeKey
state.active[mainResource] = 0 state.active[mainResource] = 0
state.visited[mainResource] = mainRoute tier.visited[mainResource] = mainRoute
state.stack = append(state.stack, mainRoute) state.stack = append(state.stack, mainRoute)
var body *document.Node var body *document.Node
var declarations []importDeclarationSpec var declarations []importDeclarationSpec
@@ -269,7 +251,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
}}, }},
} }
} }
if deferAssembly { if request.input.deferAssembly {
if diagnostic := reservePendingItemPlacements(&acquisition, limits, accounting); diagnostic != nil { if diagnostic := reservePendingItemPlacements(&acquisition, limits, accounting); diagnostic != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic} return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
} }
@@ -286,7 +268,7 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
parsed: main, composition: acquired, acquisition: cloneAcquisitionScopes(acquired.acquisition), parsed: main, composition: acquired, acquisition: cloneAcquisitionScopes(acquired.acquisition),
sources: state.sources, versionState: versionState, accounting: accounting, sources: state.sources, versionState: versionState, accounting: accounting,
} }
composed, diagnostics := composeAcquiredInputWithPolicies(input, collectionPolicyScope{}, policy) composed, diagnostics := composeAcquiredInputWithPolicies(input, collectionPolicyScope{}, policy.sequencePolicy)
return composed, cloneSourceInfos(state.sources), diagnostics return composed, cloneSourceInfos(state.sources), diagnostics
} }
@@ -311,11 +293,7 @@ func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPoli
if accounting == nil { if accounting == nil {
accounting = &importAccounting{sources: len(input.sources), bytes: acquired.inputBytes, nodes: acquired.inputNodes} accounting = &importAccounting{sources: len(input.sources), bytes: acquired.inputBytes, nodes: acquired.inputNodes}
} }
traversal := importTraversal{ traversal := compositionTraversal(policy, rule, input.versionState, accounting, acquired.importGraph)
accounting: accounting,
limits: defaultImportLimits(), rule: rule, versionState: input.versionState,
importGraph: acquired.importGraph.clone(),
}
if len(scope.contributions) == 1 && scope.contributions[0].role == importRoleMain && scope.contributions[0].pendingItem != nil { if len(scope.contributions) == 1 && scope.contributions[0].role == importRoleMain && scope.contributions[0].pendingItem != nil {
main := scope.contributions[0] main := scope.contributions[0]
localScope := cloneAcquisitionScope(*main.pendingItem) localScope := cloneAcquisitionScope(*main.pendingItem)
@@ -361,7 +339,7 @@ func composeAcquiredInputWithPolicies(input assembledInput, lower collectionPoli
} }
if !main.placementCharged { if !main.placementCharged {
placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem) placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem)
if accounting.nodes+placementNodes > traversal.limits.nodes { if accounting.nodes+placementNodes > traversal.policy.importLimits.nodes {
return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)} return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)}
} }
accounting.nodes += placementNodes accounting.nodes += placementNodes
@@ -401,10 +379,7 @@ func composeAcquiredScopeWithPolicy(
versionState *sourceVersionState, versionState *sourceVersionState,
accounting *importAccounting, accounting *importAccounting,
) (*composedDocument, []Diagnostic) { ) (*composedDocument, []Diagnostic) {
traversal := importTraversal{ traversal := compositionTraversal(policy, rule, versionState, accounting, graph)
accounting: accounting, limits: defaultImportLimits(), rule: rule,
versionState: versionState, importGraph: graph.clone(),
}
if diagnostics := traversal.composePendingItems(scope); len(diagnostics) != 0 { if diagnostics := traversal.composePendingItems(scope); len(diagnostics) != 0 {
return nil, diagnostics return nil, diagnostics
} }
@@ -454,15 +429,15 @@ func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits,
} }
func (s *importTraversal) checkInitialLimits() *Diagnostic { func (s *importTraversal) checkInitialLimits() *Diagnostic {
if s.accounting.sources > s.limits.sources { if s.state.accounting.sources > s.policy.importLimits.sources {
value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source-count limit", nil) value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source-count limit", nil)
return &value return &value
} }
if s.accounting.bytes > s.limits.bytes { if s.state.accounting.bytes > s.policy.importLimits.bytes {
value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate byte limit", nil) value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate byte limit", nil)
return &value return &value
} }
if s.accounting.nodes > s.limits.nodes { if s.state.accounting.nodes > s.policy.importLimits.nodes {
value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil) value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)
return &value return &value
} }
@@ -472,7 +447,7 @@ func (s *importTraversal) checkInitialLimits() *Diagnostic {
func (s *importTraversal) walkDeclarations(basePath string, declarations []importDeclarationSpec, depth int, parentDestination Path) []Diagnostic { func (s *importTraversal) walkDeclarations(basePath string, declarations []importDeclarationSpec, depth int, parentDestination Path) []Diagnostic {
policies := make([]resolvedImportPolicy, len(declarations)) policies := make([]resolvedImportPolicy, len(declarations))
for index, spec := range declarations { for index, spec := range declarations {
policy, diagnostics := resolveImportPolicy(spec, s.defaults) policy, diagnostics := resolveImportPolicy(spec, s.policy.importDefaults)
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
return diagnostics return diagnostics
} }
@@ -490,9 +465,9 @@ func (s *importTraversal) walkDeclarations(basePath string, declarations []impor
case importSelectorPath: case importSelectorPath:
selection, diagnostics = selectNamedImportCandidates(s.access, basePath, spec, policy) selection, diagnostics = selectNamedImportCandidates(s.access, basePath, spec, policy)
case importSelectorDirectory: case importSelectorDirectory:
selection, diagnostics = selectDirectoryImportCandidates(s.access, basePath, spec, policy, s.limits, s.accounting) selection, diagnostics = selectDirectoryImportCandidates(s.access, basePath, spec, policy, s.policy.importLimits, s.state.accounting)
case importSelectorGlob: case importSelectorGlob:
selection, diagnostics = selectGlobImportCandidates(s.access, basePath, spec, policy, s.limits, s.accounting) selection, diagnostics = selectGlobImportCandidates(s.access, basePath, spec, policy, s.policy.importLimits, s.state.accounting)
default: default:
location := spec.location location := spec.location
return []Diagnostic{importDiagnostic(CodeImportInvalid, "import selector is invalid", &location)} return []Diagnostic{importDiagnostic(CodeImportInvalid, "import selector is invalid", &location)}
@@ -554,7 +529,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
if !s.access.contains(canonical) { if !s.access.contains(canonical) {
return []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "import target resolves outside the configuration root", &location)} return []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "import target resolves outside the configuration root", &location)}
} }
if depth > s.limits.depth { if depth > s.policy.importLimits.depth {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the active import-depth limit", &location)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the active import-depth limit", &location)}
} }
@@ -570,18 +545,18 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
cycle := append([]importRoute(nil), s.stack[activeIndex:]...) cycle := append([]importRoute(nil), s.stack[activeIndex:]...)
return []Diagnostic{importCycleDiagnostic(canonical, location, cycle)} return []Diagnostic{importCycleDiagnostic(canonical, location, cycle)}
} }
if previous, ok := s.visited[identity]; ok { if previous, ok := s.tier.visited[identity]; ok {
s.importGraph.addEdge(importerKey, nodeKey, location) s.importGraph.addEdge(importerKey, nodeKey, location)
return []Diagnostic{importRepeatedDiagnostic(location, previous, currentRoute)} return []Diagnostic{importRepeatedDiagnostic(location, previous, currentRoute)}
} }
if s.accounting.sources+1 > s.limits.sources { if s.state.accounting.sources+1 > s.policy.importLimits.sources {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source-count limit", &location)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source-count limit", &location)}
} }
if _, err := s.access.relative(canonical); err != nil { if _, err := s.access.relative(canonical); err != nil {
return []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "import target could not be addressed inside the configuration root", &location)} return []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "import target could not be addressed inside the configuration root", &location)}
} }
sourceID := "source-" + strconv.Itoa(s.accounting.sources+1) sourceID := "source-" + strconv.Itoa(s.state.accounting.sources+1)
kind := SourceKindFile kind := SourceKindFile
label := candidate.label label := candidate.label
if label == "" { if label == "" {
@@ -611,7 +586,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
s.sources = s.sources[:len(s.sources)-1] s.sources = s.sources[:len(s.sources)-1]
return nil return nil
} }
s.accounting.sources++ s.state.accounting.sources++
root, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{ root, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{
Depth: maxDocumentDepth, Nodes: maxDocumentNodes, Depth: maxDocumentDepth, Nodes: maxDocumentNodes,
@@ -620,26 +595,26 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
return parserDiagnostics(sourceID, issues) return parserDiagnostics(sourceID, issues)
} }
nodeCount := countInputNodes(root) nodeCount := countInputNodes(root)
if s.accounting.nodes+nodeCount > s.limits.nodes { if s.state.accounting.nodes+nodeCount > s.policy.importLimits.nodes {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
} }
s.accounting.nodes += nodeCount s.state.accounting.nodes += nodeCount
var body *document.Node var body *document.Node
var declarations []importDeclarationSpec var declarations []importDeclarationSpec
var sequenceDeclarations []sequenceDeclarationSpec var sequenceDeclarations []sequenceDeclarationSpec
var diagnostics []Diagnostic var diagnostics []Diagnostic
var processing *sourceVersionProcessingResult var processing *sourceVersionProcessingResult
directiveContext := s.directiveContext directiveContext := s.request.input.directiveContext
if directiveContext == 0 { if directiveContext == 0 {
directiveContext = rootDirectiveContextImportsOnly directiveContext = rootDirectiveContextImportsOnly
} }
if s.versionState != nil && s.versionState.mode != sourceVersionModeDefault { if s.state.versionState != nil && s.state.versionState.mode != sourceVersionModeDefault {
versionDestination := destination versionDestination := destination
if s.itemScope { if s.itemScope {
versionDestination = appendDestination(s.itemPrefix.Index(0), destination) versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
} }
processed, failures := s.versionState.processWithDirectiveContext(root, sourceVersionContribution, versionDestination, sourceID, policy.placement, directiveContext) processed, failures := s.state.versionState.processWithDirectiveContext(root, sourceVersionContribution, versionDestination, sourceID, policy.placement, directiveContext)
if len(failures) != 0 { if len(failures) != 0 {
return failures return failures
} }
@@ -650,7 +625,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
if s.itemScope { if s.itemScope {
versionDestination = appendDestination(s.itemPrefix.Index(0), destination) versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
} }
processed := processSourceVersionWithDirectiveContext(root, s.rule, sourceVersionContribution, versionDestination, sourceVersionModeDefault, directiveContext) processed := processSourceVersionWithDirectiveContext(root, s.request.input.protectionRule, sourceVersionContribution, versionDestination, sourceVersionModeDefault, directiveContext)
if len(processed.diagnostics) != 0 { if len(processed.diagnostics) != 0 {
return processed.diagnostics return processed.diagnostics
} }
@@ -672,7 +647,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
if mode == placementModeSequenceItem { if mode == placementModeSequenceItem {
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey} route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
s.active[identity] = len(s.stack) s.active[identity] = len(s.stack)
s.visited[identity] = route s.tier.visited[identity] = route
s.stack = append(s.stack, route) s.stack = append(s.stack, route)
diagnostics := s.visitItemSource(canonical, nodeKey, destination, relativeDestination, depth, info, body, declarations, sequenceDeclarations, processing) diagnostics := s.visitItemSource(canonical, nodeKey, destination, relativeDestination, depth, info, body, declarations, sequenceDeclarations, processing)
delete(s.active, identity) delete(s.active, identity)
@@ -684,10 +659,10 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &location)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &location)}
} }
placementNodes := destinationWrapperNodeCount(placementDestination) placementNodes := destinationWrapperNodeCount(placementDestination)
if s.accounting.nodes+placementNodes > s.limits.nodes { if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
} }
s.accounting.nodes += placementNodes s.state.accounting.nodes += placementNodes
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey} route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
s.scope.contributions = append(s.scope.contributions, acquiredContribution{ s.scope.contributions = append(s.scope.contributions, acquiredContribution{
source: cloneSourceInfo(info), payload: body, document: placed, source: cloneSourceInfo(info), payload: body, document: placed,
@@ -696,7 +671,7 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations), sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
}) })
s.active[identity] = len(s.stack) s.active[identity] = len(s.stack)
s.visited[identity] = route s.tier.visited[identity] = route
s.stack = append(s.stack, route) s.stack = append(s.stack, route)
diagnostics = s.walkDeclarations(s.access.dir(canonical), declarations, depth, destination) diagnostics = s.walkDeclarations(s.access.dir(canonical), declarations, depth, destination)
delete(s.active, identity) delete(s.active, identity)
@@ -799,10 +774,10 @@ func (s *importTraversal) composePendingItems(scope *acquisitionScope) []Diagnos
} }
placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem) placementNodes := destinationWrapperNodeCountForMode(contribution.destination, placementModeSequenceItem)
if !contribution.placementCharged { if !contribution.placementCharged {
if s.accounting.nodes+placementNodes > s.limits.nodes { if s.state.accounting.nodes+placementNodes > s.policy.importLimits.nodes {
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)} return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", contribution.source.ImportedFrom)}
} }
s.accounting.nodes += placementNodes s.state.accounting.nodes += placementNodes
} }
contribution.payload = completed.root contribution.payload = completed.root
contribution.document = placed contribution.document = placed
@@ -1126,7 +1101,7 @@ func projectSourceVersionEvidence(processing *sourceVersionProcessingResult, des
} }
func (s *importTraversal) readImport(name, sourceID string, required bool) ([]byte, bool, *Diagnostic) { func (s *importTraversal) readImport(name, sourceID string, required bool) ([]byte, bool, *Diagnostic) {
remaining := s.limits.bytes - s.accounting.bytes remaining := s.policy.importLimits.bytes - s.state.accounting.bytes
limit := min(int64(maxSourceBytes), remaining) limit := min(int64(maxSourceBytes), remaining)
if limit < 0 { if limit < 0 {
limit = 0 limit = 0
@@ -1139,7 +1114,7 @@ func (s *importTraversal) readImport(name, sourceID string, required bool) ([]by
value := sourceReadDiagnostic(failure, sourceID, CodeImportLimitExceeded, "configuration exceeds a source or aggregate byte limit") value := sourceReadDiagnostic(failure, sourceID, CodeImportLimitExceeded, "configuration exceeds a source or aggregate byte limit")
return nil, false, &value return nil, false, &value
} }
s.accounting.bytes += int64(len(data)) s.state.accounting.bytes += int64(len(data))
return data, false, nil return data, false, nil
} }

View File

@@ -1268,7 +1268,8 @@ func TestImportTraversalEnforcesPrivateBudgets(t *testing.T) {
{name: "aggregate nodes", limits: importLimits{depth: 8, sources: 8, bytes: maxTotalBytes, nodes: countInputNodes(main.document) + 1}}, {name: "aggregate nodes", limits: importLimits{depth: 8, sources: 8, bytes: maxTotalBytes, nodes: countInputNodes(main.document) + 1}},
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
_, _, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, test.limits) request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
_, _, diagnostics := testAcquireImportWithLimits(request, test.limits)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("budget diagnostics = %#v", diagnostics) t.Fatalf("budget diagnostics = %#v", diagnostics)
} }
@@ -1278,7 +1279,8 @@ func TestImportTraversalEnforcesPrivateBudgets(t *testing.T) {
writeImportFile(t, directory, "one.yml", "configkit: {imports: [{path: nested.yml}]}\n") writeImportFile(t, directory, "one.yml", "configkit: {imports: [{path: nested.yml}]}\n")
writeImportFile(t, directory, "nested.yml", "settings: {nested: nested}\n") writeImportFile(t, directory, "nested.yml", "settings: {nested: nested}\n")
main = parseImportMain(t, mainPath) main = parseImportMain(t, mainPath)
_, _, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, importLimits{depth: 1, sources: 8, bytes: maxTotalBytes, nodes: maxDocumentNodes}) request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
_, _, diagnostics := testAcquireImportWithLimits(request, importLimits{depth: 1, sources: 8, bytes: maxTotalBytes, nodes: maxDocumentNodes})
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded { if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("depth diagnostics = %#v", diagnostics) t.Fatalf("depth diagnostics = %#v", diagnostics)
} }
@@ -1312,7 +1314,9 @@ func TestImportDepthBudgetCountsSelectedEdges(t *testing.T) {
writeImportFile(t, directory, "parts/next.yml", "next: true\n") writeImportFile(t, directory, "parts/next.yml", "next: true\n")
limits := defaultImportLimits() limits := defaultImportLimits()
limits.depth = 1 limits.depth = 1
composed, sources, diagnostics := loadImportedDocuments(File(mainPath), nil, parseImportMain(t, mainPath), nil, limits) main := parseImportMain(t, mainPath)
request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
composed, sources, diagnostics := testAcquireImportWithLimits(request, limits)
if test.code != "" { if test.code != "" {
if len(diagnostics) != 1 || diagnostics[0].Code != test.code { if len(diagnostics) != 1 || diagnostics[0].Code != test.code {
t.Fatalf("diagnostics = %#v, want %s", diagnostics, test.code) t.Fatalf("diagnostics = %#v, want %s", diagnostics, test.code)
@@ -1342,7 +1346,8 @@ func TestImportTraversalAcceptsExactPrivateBudgets(t *testing.T) {
bytes: main.bytes + int64(len(data)), bytes: main.bytes + int64(len(data)),
nodes: countInputNodes(main.document) + countInputNodes(imported.document), nodes: countInputNodes(main.document) + countInputNodes(imported.document),
} }
composed, sources, diagnostics := loadImportedDocuments(File(mainPath), nil, main, nil, limits) request := testImportRequest(File(mainPath), nil, main, Path{}, nil, nil)
composed, sources, diagnostics := testAcquireImportWithLimits(request, limits)
if len(diagnostics) != 0 || composed == nil || len(sources) != 2 { if len(diagnostics) != 0 || composed == nil || len(sources) != 2 {
t.Fatalf("exact-limit composition = (%#v, %#v, %#v)", composed, sources, diagnostics) t.Fatalf("exact-limit composition = (%#v, %#v, %#v)", composed, sources, diagnostics)
} }
@@ -1401,6 +1406,32 @@ func parseImportMain(t *testing.T, filename string) parsedSource {
return parsed return parsed
} }
func testImportRequest(source Source, context *FilesystemContext, main parsedSource, destination Path, rule *VersionRule, processing *sourceVersionProcessingResult) importAcquisitionRequest {
return importAcquisitionRequest{
input: inputAcquisitionRequest{
input: loadInput{
source: source, imports: true, filesystem: context,
destination: destination.clone(), placementMode: main.source.placementMode,
},
role: sourceVersionMain, directiveContext: rootDirectiveContextImportsOnly,
protectionRule: rule,
},
main: main, processing: processing,
}
}
func testAcquireImport(request importAcquisitionRequest, policy acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (*composedDocument, []SourceInfo, []Diagnostic) {
return acquireImportedInput(request, &policy, state, tier)
}
func testAcquireImportWithLimits(request importAcquisitionRequest, limits importLimits) (*composedDocument, []SourceInfo, []Diagnostic) {
return testAcquireImport(request, acquisitionPolicy{importLimits: limits}, &acquisitionState{accounting: &importAccounting{}}, &tierAcquisitionState{})
}
func testAcquireImportWithAccounting(request importAcquisitionRequest, limits importLimits, accounting *importAccounting) (*composedDocument, []SourceInfo, []Diagnostic) {
return testAcquireImport(request, acquisitionPolicy{importLimits: limits}, &acquisitionState{accounting: accounting}, &tierAcquisitionState{})
}
func parseImportSpecificationsYAML(t *testing.T, input string) []importDeclarationSpec { func parseImportSpecificationsYAML(t *testing.T, input string) []importDeclarationSpec {
t.Helper() t.Helper()
declarations, diagnostics := parseImportSpecificationYAML(input) declarations, diagnostics := parseImportSpecificationYAML(input)

View File

@@ -92,7 +92,7 @@ func TestPendingItemCompositionProjectsNestedIdentityDiagnostics(t *testing.T) {
destination: Keys("items"), pendingItem: &itemScope, destination: Keys("items"), pendingItem: &itemScope,
}}} }}}
traversal := importTraversal{accounting: &importAccounting{}, limits: defaultImportLimits()} traversal := compositionTraversal(sequencePolicy{}, nil, nil, &importAccounting{}, newImportInclusionGraph())
diagnostics := traversal.composePendingItems(&outer) diagnostics := traversal.composePendingItems(&outer)
if len(diagnostics) != 1 { if len(diagnostics) != 1 {
t.Fatalf("diagnostics = %#v, want one conflict", diagnostics) t.Fatalf("diagnostics = %#v, want one conflict", diagnostics)

View File

@@ -208,7 +208,6 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo
} }
versionState := &sourceVersionState{ versionState := &sourceVersionState{
localRule: request.definition.Version, effectiveRule: request.protectionVersion(), mode: request.sourceVersionMode, localRule: request.definition.Version, effectiveRule: request.protectionVersion(), mode: request.sourceVersionMode,
importDefaults: snapshotImportDefaults(request.importDefaults),
} }
accumulator.versionState = versionState accumulator.versionState = versionState
input, diagnostics := assembleBaseInputTrackedWithVersion(request.main.source, mainOptions, request.main.destination, accounting, nil, nil, versionState) input, diagnostics := assembleBaseInputTrackedWithVersion(request.main.source, mainOptions, request.main.destination, accounting, nil, nil, versionState)

View File

@@ -86,7 +86,9 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a
projectVersion := sequenceVersionPath(request.protectionVersion()) projectVersion := sequenceVersionPath(request.protectionVersion())
for index, group := range request.underlays { for index, group := range request.underlays {
evidenceStart := len(input.versionState.evidenceSince(0)) evidenceStart := len(input.versionState.evidenceSince(0))
acquired, diagnostics := acquireUnderlayGroupScopes(group, index+1, request.protectionVersion(), accounting, input.versionState, request.sequencePolicy) acquired, diagnostics := acquireUnderlayGroupScopes(group, index+1, request.protectionVersion(), accounting, input.versionState, acquisitionPolicy{
importDefaults: request.importDefaults, sequencePolicy: request.sequencePolicy, importLimits: defaultImportLimits(),
})
mergeResourceContexts(&input.contexts, acquired.contexts) mergeResourceContexts(&input.contexts, acquired.contexts)
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
input.sources = append(input.sources, acquired.sources...) input.sources = append(input.sources, acquired.sources...)
@@ -213,7 +215,7 @@ func assembleLayeredInput[T any](request loadRequest[T], input assembledInput, a
return input, versionDiagnostics return input, versionDiagnostics
} }
func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, accounting *importAccounting, versionState *sourceVersionState, policy sequencePolicy) (*acquiredUnderlayGroup, []Diagnostic) { func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, accounting *importAccounting, versionState *sourceVersionState, policy acquisitionPolicy) (*acquiredUnderlayGroup, []Diagnostic) {
initialBytes, initialNodes := accounting.bytes, accounting.nodes initialBytes, initialNodes := accounting.bytes, accounting.nodes
visited := make(map[resourceIdentity]importRoute) visited := make(map[resourceIdentity]importRoute)
hostToken := &namespaceToken{} hostToken := &namespaceToken{}
@@ -231,14 +233,9 @@ func acquireUnderlayGroupScopes(group loadGroup, index int, rule *VersionRule, a
options := Options[struct{}]{ options := Options[struct{}]{
Imports: contribution.imports, Filesystem: contribution.filesystem, Imports: contribution.imports, Filesystem: contribution.filesystem,
Definition: Definition[struct{}]{Version: rule}, destination: contribution.destination.clone(), Definition: Definition[struct{}]{Version: rule}, destination: contribution.destination.clone(),
placementMode: contribution.placementMode, sequencePolicy: policy, deferAssembly: true, placementMode: contribution.placementMode, sequencePolicy: policy.sequencePolicy, deferAssembly: true,
directiveContext: rootDirectiveContextLayered, directiveContext: rootDirectiveContextLayered,
importDefaults: func() importDefaults { importDefaults: snapshotImportDefaults(policy.importDefaults),
if versionState == nil {
return importDefaults{}
}
return snapshotImportDefaults(versionState.importDefaults)
}(),
} }
acquired, diagnostics := assembleBaseInputTrackedForRole(contribution.source, options, contribution.destination, accounting, visited, hostToken, versionState, sourceVersionContribution) acquired, diagnostics := assembleBaseInputTrackedForRole(contribution.source, options, contribution.destination, accounting, visited, hostToken, versionState, sourceVersionContribution)
for _, source := range acquired.sources { for _, source := range acquired.sources {

View File

@@ -80,7 +80,7 @@ func TestUnderlayGroupAcquisitionRetainsScopesUntilComposition(t *testing.T) {
}) })
accounting := &importAccounting{} accounting := &importAccounting{}
group := loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}} group := loadGroup{inputs: []loadInput{{source: filesystem.File("main.yml"), imports: true}}}
acquired, diagnostics := acquireUnderlayGroupScopes(group, 2, nil, accounting, nil, sequencePolicy{}) acquired, diagnostics := acquireUnderlayGroupScopes(group, 2, nil, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()})
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
t.Fatalf("acquire underlay group: %#v", diagnostics) t.Fatalf("acquire underlay group: %#v", diagnostics)
} }
@@ -108,7 +108,7 @@ func TestUnderlayGroupAcquisitionRetainsScopesUntilComposition(t *testing.T) {
t.Fatalf("composed group handoff = %#v", composition) t.Fatalf("composed group handoff = %#v", composition)
} }
empty, diagnostics := acquireUnderlayGroupScopes(loadGroup{}, 3, nil, accounting, nil, sequencePolicy{}) empty, diagnostics := acquireUnderlayGroupScopes(loadGroup{}, 3, nil, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()})
if len(diagnostics) != 0 || len(empty.scopes) != 0 { if len(diagnostics) != 0 || len(empty.scopes) != 0 {
t.Fatalf("empty group acquisition = %#v, %#v", empty, diagnostics) t.Fatalf("empty group acquisition = %#v, %#v", empty, diagnostics)
} }
@@ -469,7 +469,7 @@ func TestGroupedLoadStrictConflictsRepetitionAndBudgets(t *testing.T) {
} }
func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) { func testAcquireUnderlayGroup(group loadGroup, index int, rule *VersionRule, accounting *importAccounting) (*composedDocument, []SourceInfo, map[string]ResourceContext, []Diagnostic) {
acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, accounting, nil, sequencePolicy{}) acquired, diagnostics := acquireUnderlayGroupScopes(group, index, rule, accounting, nil, acquisitionPolicy{importLimits: defaultImportLimits()})
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
return nil, acquired.sources, acquired.contexts, diagnostics return nil, acquired.sources, acquired.contexts, diagnostics
} }

View File

@@ -182,7 +182,12 @@ func TestNativeFilesystemRootAndOptionalImports(t *testing.T) {
t.Fatal(diagnostics) t.Fatal(diagnostics)
} }
access := newNativeAccess(filesystem, "config", namespace.token) access := newNativeAccess(filesystem, "config", namespace.token)
composed, sources, diagnostics := loadImportedWithAccess(access, "config/sub", source.path, parsed, nil, defaultImportLimits(), &importAccounting{}) request := testImportRequest(source, nil, parsed, Path{}, nil, nil)
policy := &acquisitionPolicy{importLimits: defaultImportLimits()}
state := &acquisitionState{accounting: &importAccounting{}}
tier := &tierAcquisitionState{}
resolved := resolvedFilesystemContext{native: namespace, rootPath: "config", basePath: "config/sub", mainPath: source.path}
composed, sources, diagnostics := acquireImportedWithAccess(request, policy, state, tier, access, resolved)
if len(diagnostics) != 0 || composed == nil || len(sources) != 2 || sources[1].Path != "config/shared.yml" { if len(diagnostics) != 0 || composed == nil || len(sources) != 2 || sources[1].Path != "config/shared.yml" {
t.Fatalf("contained imports = (%#v, %#v, %#v)", composed, sources, diagnostics) t.Fatalf("contained imports = (%#v, %#v, %#v)", composed, sources, diagnostics)
} }
@@ -196,7 +201,12 @@ func TestNativeFilesystemRootAndOptionalImports(t *testing.T) {
if len(diagnostics) != 0 { if len(diagnostics) != 0 {
t.Fatal(diagnostics) t.Fatal(diagnostics)
} }
_, _, diagnostics = loadImportedWithAccess(newNativeAccess(outside, "config", outsideNamespace.token), "config/sub", "config/sub/main.yml", parsed, nil, defaultImportLimits(), &importAccounting{}) request = testImportRequest(filesystemFile(outsideNamespace, "config/sub/main.yml"), nil, parsed, Path{}, nil, nil)
policy = &acquisitionPolicy{importLimits: defaultImportLimits()}
state = &acquisitionState{accounting: &importAccounting{}}
tier = &tierAcquisitionState{}
resolved = resolvedFilesystemContext{native: outsideNamespace, rootPath: "config", basePath: "config/sub", mainPath: "config/sub/main.yml"}
_, _, diagnostics = acquireImportedWithAccess(request, policy, state, tier, newNativeAccess(outside, "config", outsideNamespace.token), resolved)
if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot { if len(diagnostics) != 1 || diagnostics[0].Code != CodeImportOutsideRoot {
t.Fatalf("escaping import diagnostics = %#v", diagnostics) t.Fatalf("escaping import diagnostics = %#v", diagnostics)
} }

View File

@@ -47,7 +47,6 @@ type sourceVersionState struct {
localRule *VersionRule localRule *VersionRule
effectiveRule *VersionRule effectiveRule *VersionRule
mode sourceVersionMode mode sourceVersionMode
importDefaults importDefaults
mainProcessing *sourceVersionProcessingResult mainProcessing *sourceVersionProcessingResult
evidence []*sourceVersionEvidence evidence []*sourceVersionEvidence
diagnostics []Diagnostic diagnostics []Diagnostic

View File

@@ -1523,9 +1523,11 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T)
limits.nodes = wantNodes limits.nodes = wantNodes
accounting := &importAccounting{} accounting := &importAccounting{}
composed, sources, foundDiagnostics := loadImportedDocumentsTrackedWithVersion( request := testImportRequest(File(mainPath), nil, main, Path{}, rule, nil)
File(mainPath), nil, main, Path{}, rule, limits, accounting, nil, nil, state, policy := acquisitionPolicy{importLimits: limits}
) owners := &acquisitionState{accounting: accounting, versionState: state}
tier := &tierAcquisitionState{}
composed, sources, foundDiagnostics := testAcquireImport(request, policy, owners, tier)
if len(foundDiagnostics) != 0 || composed == nil || len(sources) != 2 || accounting.nodes != wantNodes || accounting.bytes != wantBytes { if len(foundDiagnostics) != 0 || composed == nil || len(sources) != 2 || accounting.nodes != wantNodes || accounting.bytes != wantBytes {
t.Fatalf("exact raw byte/node accounting = %#v, %d/%d, want %d/%d, %#v", composed, accounting.bytes, accounting.nodes, wantBytes, wantNodes, foundDiagnostics) t.Fatalf("exact raw byte/node accounting = %#v, %d/%d, want %d/%d, %#v", composed, accounting.bytes, accounting.nodes, wantBytes, wantNodes, foundDiagnostics)
} }
@@ -1539,10 +1541,11 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T)
below := defaultImportLimits() below := defaultImportLimits()
below.nodes = wantNodes - 1 below.nodes = wantNodes - 1
accounting = &importAccounting{} accounting = &importAccounting{}
composed, _, foundDiagnostics = loadImportedDocumentsTrackedWithVersion( request = testImportRequest(File(mainPath), nil, main, Path{}, rule, nil)
File(mainPath), nil, main, Path{}, rule, below, accounting, nil, nil, policy.importLimits = below
&sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}, owners = &acquisitionState{accounting: accounting, versionState: &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}}
) tier = &tierAcquisitionState{}
composed, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier)
if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("raw header nodes were not charged: %#v, %#v", composed, foundDiagnostics) t.Fatalf("raw header nodes were not charged: %#v, %#v", composed, foundDiagnostics)
} }
@@ -1550,10 +1553,11 @@ func TestSourceVersionImportAccountingIncludesConsumedHeaderNodes(t *testing.T)
below = defaultImportLimits() below = defaultImportLimits()
below.bytes = wantBytes - 1 below.bytes = wantBytes - 1
accounting = &importAccounting{} accounting = &importAccounting{}
composed, _, foundDiagnostics = loadImportedDocumentsTrackedWithVersion( request = testImportRequest(File(mainPath), nil, main, Path{}, rule, nil)
File(mainPath), nil, main, Path{}, rule, below, accounting, nil, nil, policy.importLimits = below
&sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}, owners = &acquisitionState{accounting: accounting, versionState: &sourceVersionState{localRule: rule, effectiveRule: rule, mode: sourceVersionModeRequired}}
) tier = &tierAcquisitionState{}
composed, _, foundDiagnostics = testAcquireImport(request, policy, owners, tier)
if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded { if composed != nil || len(foundDiagnostics) != 1 || foundDiagnostics[0].Code != CodeImportLimitExceeded {
t.Fatalf("raw header bytes were not charged: %#v, %#v", composed, foundDiagnostics) t.Fatalf("raw header bytes were not charged: %#v, %#v", composed, foundDiagnostics)
} }