2026-10-25 13:21:29 +00:00
package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
func compositionTraversal ( policy sequencePolicy , rule * VersionRule , versionState * sourceVersionState , accounting * importAccounting , graph importInclusionGraph ) importTraversal {
return importTraversal {
request : importAcquisitionRequest { input : inputAcquisitionRequest { protectionRule : rule } } ,
policy : & acquisitionPolicy { sequencePolicy : policy , importLimits : defaultImportLimits ( ) } ,
state : & acquisitionState { accounting : accounting , versionState : versionState } ,
tier : & tierAcquisitionState { } ,
importGraph : graph . clone ( ) ,
}
}
// 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 composeAcquiredInputWithPolicies ( input assembledInput , lower collectionPolicyScope , 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 := compositionTraversal ( policy , rule , input . versionState , accounting , acquired . importGraph )
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 , diagnostics := composeDocumentsWithCollectionPolicy (
policy , localScope . collectionPolicies ,
acquiredSequenceDeclarations ( localScope ) , documents ... ,
)
if len ( diagnostics ) != 0 {
return nil , prependCompositionDiagnosticPaths ( diagnostics , main . 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
} else {
scoped := * protectionRule
scoped . Path = relative
protectionRule = & scoped
}
}
if diagnostic := importedVersionClaim ( documents , localScope , completed , protectionRule ) ; diagnostic != nil {
if rule != nil {
diagnostic . Path = rule . Path . clone ( )
}
return nil , [ ] Diagnostic { * diagnostic }
}
}
completed . acquisition = [ ] acquisitionScope { cloneAcquisitionScope ( localScope ) }
projectNestedEvidence ( completed , localScope )
2026-10-26 00:18:12 +00:00
_ , placed , err := placeDocumentWithMode ( completed . root , main . destination , PlacementModeSequenceItem , main . payload . Location , maxDocumentDepth , maxDocumentNodes )
2026-10-25 13:21:29 +00:00
if err != nil {
return nil , [ ] Diagnostic { sourceDiagnostic ( CodeSourceLimitExceeded , main . source . ID , "configuration exceeds the source depth or node limit after destination placement" ) }
}
if ! main . placementCharged {
2026-10-26 00:18:12 +00:00
placementNodes := destinationWrapperNodeCountForMode ( main . destination , PlacementModeSequenceItem )
2026-10-25 13:21:29 +00:00
if accounting . nodes + placementNodes > traversal . policy . importLimits . nodes {
return nil , [ ] Diagnostic { importDiagnostic ( CodeImportLimitExceeded , "configuration exceeds the aggregate node limit" , main . source . ImportedFrom ) }
}
accounting . nodes += placementNodes
}
composition , diagnostics := composeDocumentsWithPolicy ( policy , placed )
if len ( diagnostics ) != 0 {
return nil , diagnostics
}
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
}
return composeAcquiredScopeWithPolicy (
& scope , lower , policy , acquired . importGraph , input . sources , rule , input . versionState , accounting ,
)
}
// composeAcquiredScopeWithPolicy is shared by per-input and underlay-group
// handoffs. Lower policy is carried separately from registered application
// constraints; current strict composition continues to use its existing rules.
func composeAcquiredScopeWithPolicy (
scope * acquisitionScope ,
lower collectionPolicyScope ,
policy sequencePolicy ,
graph importInclusionGraph ,
sources [ ] SourceInfo ,
rule * VersionRule ,
versionState * sourceVersionState ,
accounting * importAccounting ,
) ( * composedDocument , [ ] Diagnostic ) {
traversal := compositionTraversal ( policy , rule , versionState , accounting , graph )
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 )
}
composition , diagnostics := composeDocumentsWithCollectionPolicy (
policy , lower , acquiredSequenceDeclarations ( * scope ) , documents ... ,
)
if len ( diagnostics ) != 0 {
return nil , diagnostics
}
if diagnostic := importedVersionClaim ( documents , * scope , composition , rule ) ; diagnostic != nil {
return nil , [ ] Diagnostic { * diagnostic }
}
projectNestedEvidence ( composition , * scope )
projectAcquiredDestinations ( composition , * scope , sources )
composition . acquisition = [ ] acquisitionScope { cloneAcquisitionScope ( * scope ) }
composition . inputBytes = accounting . bytes
composition . inputNodes = accounting . nodes
composition . importGraph = graph . clone ( )
return composition , nil
}
func reservePendingItemPlacements ( scope * acquisitionScope , limits importLimits , accounting * importAccounting ) * Diagnostic {
if scope == nil {
return nil
}
for index := range scope . contributions {
contribution := & scope . contributions [ index ]
if contribution . pendingItem == nil {
continue
}
2026-10-26 00:18:12 +00:00
placementNodes := destinationWrapperNodeCountForMode ( contribution . destination , PlacementModeSequenceItem )
2026-10-25 13:21:29 +00:00
if accounting . nodes + placementNodes > limits . nodes {
diagnostic := importDiagnostic ( CodeImportLimitExceeded , "configuration exceeds the aggregate node limit" , contribution . source . ImportedFrom )
return & diagnostic
}
accounting . nodes += placementNodes
contribution . placementCharged = true
if diagnostic := reservePendingItemPlacements ( contribution . pendingItem , limits , accounting ) ; diagnostic != nil {
return diagnostic
}
}
return nil
}
2026-10-25 13:28:43 +00:00
func ( s * importTraversal ) visitItemSource ( admitted admittedImport , destination , relativeDestination Path , depth int ) [ ] Diagnostic {
2026-10-25 13:21:29 +00:00
parentScope := s . scope
parentItemScope , parentPrefix , parentFallback := s . itemScope , s . itemPrefix , s . itemFallback
2026-10-25 13:28:43 +00:00
s . scope = acquisitionScope { owner : admitted . nodeKey }
2026-10-25 13:21:29 +00:00
s . itemScope = true
if parentItemScope {
s . itemPrefix = appendDestination ( parentPrefix , destination )
} else {
s . itemPrefix = destination . clone ( )
s . itemFallback = destination . clone ( )
}
s . scope . contributions = append ( s . scope . contributions , acquiredContribution {
2026-10-25 13:28:43 +00:00
source : cloneSourceInfo ( admitted . source ) , payload : admitted . payload , document : admitted . payload ,
identity : admitted . nodeKey , role : importRoleItem , scopeOwner : admitted . nodeKey ,
processing : admitted . processing , sequenceDeclarations : cloneSequenceDeclarations ( admitted . sequenceDeclarations ) ,
2026-10-25 13:21:29 +00:00
} )
2026-10-25 14:36:39 +00:00
diagnostics := s . walkDeclarations ( s . access . dir ( admitted . canonical ) , admitted . declarations , depth , Path { } , admitted . sourceIndex )
2026-10-25 13:21:29 +00:00
itemScope := s . scope
s . scope , s . itemScope , s . itemPrefix , s . itemFallback = parentScope , parentItemScope , parentPrefix , parentFallback
if len ( diagnostics ) != 0 {
return diagnostics
}
itemScope . destination = Path { }
s . scope . contributions = append ( s . scope . contributions , acquiredContribution {
2026-10-25 13:28:43 +00:00
source : cloneSourceInfo ( admitted . source ) , payload : admitted . payload ,
identity : admitted . nodeKey , role : importRoleItem , scopeOwner : parentScope . owner ,
destination : destination . clone ( ) , relativeDestination : relativeDestination . clone ( ) , processing : admitted . processing ,
2026-10-25 13:21:29 +00:00
pendingItem : & itemScope ,
} )
return nil
}
func ( s * importTraversal ) composePendingItems ( scope * acquisitionScope ) [ ] Diagnostic {
for index := range scope . contributions {
contribution := & scope . contributions [ index ]
if contribution . pendingItem == nil {
continue
}
itemScope := contribution . pendingItem
if diagnostics := s . composePendingItems ( itemScope ) ; len ( diagnostics ) != 0 {
return projectItemDiagnosticPaths ( diagnostics , * scope , contribution . destination , index )
}
documents := make ( [ ] * document . Node , 0 , len ( itemScope . contributions ) )
for _ , nested := range itemScope . contributions {
documents = append ( documents , nested . document )
}
// An item scope is below a sequence index. Application selectors match
// mapping-only effective paths, so none can apply inside this scope.
// Its YAML declarations remain relative to the owning record.
completed , diagnostics := composeDocumentsWithCollectionPolicy (
sequencePolicy { } , itemScope . collectionPolicies ,
acquiredSequenceDeclarations ( * itemScope ) , documents ... ,
)
if len ( diagnostics ) != 0 {
return projectItemDiagnosticPaths ( diagnostics , * scope , contribution . destination , index )
}
// Version ownership is checked by the enclosing composition once this
// item has its effective coordinates, not against its local field names.
completed . acquisition = [ ] acquisitionScope { cloneAcquisitionScope ( * itemScope ) }
projectNestedEvidence ( completed , * itemScope )
2026-10-26 00:18:12 +00:00
_ , placed , err := placeDocumentWithMode ( completed . root , contribution . destination , PlacementModeSequenceItem , contribution . payload . Location , maxDocumentDepth , maxDocumentNodes )
2026-10-25 13:21:29 +00:00
if err != nil {
return [ ] Diagnostic { importDiagnostic ( CodeImportLimitExceeded , "configuration exceeds the source depth or node limit after destination placement" , contribution . source . ImportedFrom ) }
}
2026-10-26 00:18:12 +00:00
placementNodes := destinationWrapperNodeCountForMode ( contribution . destination , PlacementModeSequenceItem )
2026-10-25 13:21:29 +00:00
if ! contribution . placementCharged {
if s . state . accounting . nodes + placementNodes > s . policy . importLimits . nodes {
return [ ] Diagnostic { importDiagnostic ( CodeImportLimitExceeded , "configuration exceeds the aggregate node limit" , contribution . source . ImportedFrom ) }
}
s . state . accounting . nodes += placementNodes
}
contribution . payload = completed . root
contribution . document = placed
contribution . nested = completed
contribution . pendingItem = nil
}
return nil
}
func acquiredSequenceDeclarations ( scope acquisitionScope ) [ ] sequenceDeclarationSpec {
var declarations [ ] sequenceDeclarationSpec
for _ , contribution := range scope . contributions {
projected := cloneSequenceDeclarations ( contribution . sequenceDeclarations )
for index := range projected {
projected [ index ] . destination = appendDestination ( contribution . relativeDestination , projected [ index ] . destination )
}
declarations = append ( declarations , projected ... )
}
return declarations
}
func importedVersionClaim ( documents [ ] * document . Node , scope acquisitionScope , composed * composedDocument , rule * VersionRule ) * Diagnostic {
if composed == nil || rule == nil {
return nil
}
mainIndex := - 1
for index , contribution := range scope . contributions {
if contribution . role == importRoleMain {
mainIndex = index
break
}
}
ranges := indexSequenceRanges ( composed . sequenceRanges )
for index , contribution := range documents {
if index == mainIndex {
continue
}
if node := fragmentVersionClaimWithIndex ( contribution , rule , index , ranges ) ; node != nil {
location := publicLocation ( node . Location )
return & Diagnostic {
Code : CodeImportVersion , Severity : SeverityError , Path : rule . Path . clone ( ) ,
Message : "imported fragments cannot contribute to the schema-version path" , Primary : & location ,
}
}
}
return nil
}