Separate input acquisition from composition

This commit is contained in:
2026-10-23 18:15:01 +00:00
parent f6b02dec03
commit 5fec16a768
5 changed files with 182 additions and 71 deletions

View File

@@ -235,9 +235,6 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
}
if len(diagnostics) == 0 {
state.scheduleAcquisitionScopes(&state.scope)
if !deferAssembly {
diagnostics = state.composePendingItems(&state.scope)
}
}
closeErr := access.close()
@@ -250,19 +247,19 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
if closeErr != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportRead, "configuration root could not be closed", nil)}
}
if deferAssembly {
acquisition := state.scope
if mainIsItem {
localScope := state.scope
acquisition = acquisitionScope{
owner: mainNodeKey, destination: destination.clone(),
contributions: []acquiredContribution{{
source: cloneSourceInfo(mainInfo), payload: body, identity: mainNodeKey,
role: importRoleMain, scopeOwner: mainNodeKey, destination: destination.clone(),
processing: processing, pendingItem: &localScope,
}},
}
acquisition := state.scope
if mainIsItem {
localScope := state.scope
acquisition = acquisitionScope{
owner: mainNodeKey, destination: destination.clone(),
contributions: []acquiredContribution{{
source: cloneSourceInfo(mainInfo), payload: body, identity: mainNodeKey,
role: importRoleMain, scopeOwner: mainNodeKey, destination: destination.clone(),
processing: processing, pendingItem: &localScope,
}},
}
}
if deferAssembly {
if diagnostic := reservePendingItemPlacements(&acquisition, limits, accounting); diagnostic != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
}
@@ -271,20 +268,62 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
importGraph: state.importGraph.clone(), inputBytes: accounting.bytes, inputNodes: accounting.nodes,
}, cloneSourceInfos(state.sources), nil
}
if mainIsItem {
localScope := state.scope
acquired := &composedDocument{
acquisition: []acquisitionScope{cloneAcquisitionScope(acquisition)},
importGraph: state.importGraph.clone(), inputBytes: accounting.bytes, inputNodes: accounting.nodes,
}
input := assembledInput{
parsed: main, composition: acquired, acquisition: cloneAcquisitionScopes(acquired.acquisition),
sources: state.sources, versionState: versionState, accounting: accounting,
}
composed, diagnostics := composeAcquiredInputWithPolicy(input, policy)
return composed, cloneSourceInfos(state.sources), diagnostics
}
// composeAcquiredInputWithPolicy is the per-input adapter from retained
// acquisition scopes to the established strict-composition behavior. It uses
// the already acquired documents and graph; source handles are closed before
// this handoff.
func composeAcquiredInputWithPolicy(input assembledInput, policy sequencePolicy) (*composedDocument, []Diagnostic) {
if input.composition == nil || input.composition.root != nil || len(input.composition.acquisition) == 0 {
return input.composition, nil
}
acquired := input.composition
if len(acquired.acquisition) != 1 {
return nil, []Diagnostic{importDiagnostic(CodeImportInvalid, "acquired input has an invalid composition scope", nil)}
}
scope := cloneAcquisitionScope(acquired.acquisition[0])
var rule *VersionRule
if input.versionState != nil {
rule = input.versionState.effectiveRule
}
accounting := input.accounting
if accounting == nil {
accounting = &importAccounting{sources: len(input.sources), bytes: acquired.inputBytes, nodes: acquired.inputNodes}
}
traversal := importTraversal{
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 {
main := scope.contributions[0]
localScope := cloneAcquisitionScope(*main.pendingItem)
if diagnostics := traversal.composePendingItems(&localScope); len(diagnostics) != 0 {
return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0))
}
documents := make([]*document.Node, 0, len(localScope.contributions))
for _, contribution := range localScope.contributions {
documents = append(documents, contribution.document)
}
completed, failures := composeDocuments(documents...)
if len(failures) != 0 {
return nil, cloneSourceInfos(state.sources), prependCompositionDiagnosticPaths(failures, destination.Index(0))
completed, diagnostics := composeDocuments(documents...)
if len(diagnostics) != 0 {
return nil, prependCompositionDiagnosticPaths(diagnostics, main.destination.Index(0))
}
if versionState != nil {
protectionRule := versionState.localRule
if versionState.mode == sourceVersionModeDefault && protectionRule != nil {
prefix := destination.Index(0)
if input.versionState != nil {
protectionRule := input.versionState.localRule
if input.versionState.mode == sourceVersionModeDefault && protectionRule != nil {
prefix := main.destination.Index(0)
relative, inside := relativeHistoryPath(prefix, protectionRule.Path)
if !inside || !matchesPathShape(Path{segments: protectionRule.Path.segments[:len(prefix.segments)]}, prefix) {
protectionRule = nil
@@ -298,56 +337,60 @@ func loadImportedWithAccessTrackedAndProcessingPolicyDeferred(access *filesystem
if rule != nil {
diagnostic.Path = rule.Path.clone()
}
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
return nil, []Diagnostic{*diagnostic}
}
}
completed.acquisition = []acquisitionScope{cloneAcquisitionScope(localScope)}
projectNestedEvidence(completed, localScope)
_, placed, err := placeDocumentWithMode(completed.root, destination, placementModeSequenceItem, body.Location, maxDocumentDepth, maxDocumentNodes)
_, placed, err := placeDocumentWithMode(completed.root, main.destination, placementModeSequenceItem, main.payload.Location, maxDocumentDepth, maxDocumentNodes)
if err != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
return nil, []Diagnostic{sourceDiagnostic(CodeSourceLimitExceeded, main.source.ID, "configuration exceeds the source depth or node limit after destination placement")}
}
placementNodes := destinationWrapperNodeCountForMode(destination, placementModeSequenceItem)
if accounting.nodes+placementNodes > limits.nodes {
return nil, cloneSourceInfos(state.sources), []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", nil)}
if !main.placementCharged {
placementNodes := destinationWrapperNodeCountForMode(main.destination, placementModeSequenceItem)
if accounting.nodes+placementNodes > traversal.limits.nodes {
return nil, []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", main.source.ImportedFrom)}
}
accounting.nodes += placementNodes
}
accounting.nodes += placementNodes
composed, failures := composeDocumentsWithPolicy(policy, placed)
if composed != nil {
synthetic := acquisitionScope{contributions: []acquiredContribution{{
source: cloneSourceInfo(mainInfo), payload: completed.root, document: placed,
identity: mainNodeKey, role: importRoleMain, scopeOwner: mainNodeKey,
destination: destination.clone(), processing: processing, nested: completed,
}}}
synthetic.owner = mainNodeKey
projectNestedEvidence(composed, synthetic)
projectAcquiredDestinations(composed, synthetic, state.sources)
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(synthetic)}
composed.inputBytes = accounting.bytes
composed.inputNodes = accounting.nodes
composed.importGraph = state.importGraph.clone()
composition, diagnostics := composeDocumentsWithPolicy(policy, placed)
if len(diagnostics) != 0 {
return nil, diagnostics
}
return composed, cloneSourceInfos(state.sources), failures
synthetic := acquisitionScope{owner: main.scopeOwner, contributions: []acquiredContribution{{
source: cloneSourceInfo(main.source), payload: completed.root, document: placed,
identity: main.identity, role: importRoleMain, scopeOwner: main.scopeOwner,
destination: main.destination.clone(), processing: main.processing, nested: completed,
}}}
projectNestedEvidence(composition, synthetic)
projectAcquiredDestinations(composition, synthetic, input.sources)
composition.acquisition = []acquisitionScope{cloneAcquisitionScope(synthetic)}
composition.inputBytes = accounting.bytes
composition.inputNodes = accounting.nodes
composition.importGraph = acquired.importGraph.clone()
return composition, nil
}
documents := make([]*document.Node, 0, len(state.scope.contributions))
for _, contribution := range state.scope.contributions {
if diagnostics := traversal.composePendingItems(&scope); len(diagnostics) != 0 {
return nil, diagnostics
}
documents := make([]*document.Node, 0, len(scope.contributions))
for _, contribution := range scope.contributions {
documents = append(documents, contribution.document)
}
composed, diagnostics := composeDocumentsWithPolicy(policy, documents...)
if composed != nil {
if diagnostic := importedVersionClaim(documents, state.scope, composed, rule); diagnostic != nil {
return nil, cloneSourceInfos(state.sources), []Diagnostic{*diagnostic}
}
composition, diagnostics := composeDocumentsWithPolicy(policy, documents...)
if len(diagnostics) != 0 {
return nil, diagnostics
}
if composed != nil {
projectNestedEvidence(composed, state.scope)
projectAcquiredDestinations(composed, state.scope, state.sources)
composed.acquisition = []acquisitionScope{cloneAcquisitionScope(state.scope)}
composed.inputBytes = accounting.bytes
composed.inputNodes = accounting.nodes
composed.importGraph = state.importGraph.clone()
if diagnostic := importedVersionClaim(documents, scope, composition, rule); diagnostic != nil {
return nil, []Diagnostic{*diagnostic}
}
return composed, cloneSourceInfos(state.sources), diagnostics
projectNestedEvidence(composition, scope)
projectAcquiredDestinations(composition, scope, input.sources)
composition.acquisition = []acquisitionScope{cloneAcquisitionScope(scope)}
composition.inputBytes = accounting.bytes
composition.inputNodes = accounting.nodes
composition.importGraph = acquired.importGraph.clone()
return composition, nil
}
func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits, accounting *importAccounting) *Diagnostic {