2026-09-12 15:58:06 +00:00
package engine
2026-09-29 17:52:20 +00:00
import (
"strconv"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
2026-09-12 15:58:06 +00:00
// assembledInput is the source/document handoff across schema and version checks.
type assembledInput struct {
2026-10-15 15:24:53 +00:00
parsed parsedSource
document * document . Node
composition * composedDocument
inheritance * inheritanceScope
sources [ ] SourceInfo
contexts map [ string ] ResourceContext
versionEvidence * sourceVersionEvidence
versionDiagnostics [ ] Diagnostic
2026-09-12 15:58:06 +00:00
}
func assembleBaseInput [ T any ] ( source Source , options Options [ T ] ) ( assembledInput , [ ] Diagnostic ) {
2026-10-14 13:14:06 +00:00
return assembleBaseInputTracked ( source , options , Path { } , & importAccounting { } , nil , nil )
2026-09-29 17:52:20 +00:00
}
2026-10-14 13:14:06 +00:00
func assembleBaseInputTracked [ T any ] ( source Source , options Options [ T ] , destination Path , accounting * importAccounting , visited map [ resourceIdentity ] importRoute , hostToken * namespaceToken ) ( assembledInput , [ ] Diagnostic ) {
2026-10-15 15:24:53 +00:00
return assembleBaseInputTrackedWithVersion ( source , options , destination , accounting , visited , hostToken , nil , sourceVersionModeDefault )
}
func assembleBaseInputTrackedWithVersion [ T any ] ( source Source , options Options [ T ] , destination Path , accounting * importAccounting , visited map [ resourceIdentity ] importRoute , hostToken * namespaceToken , localVersion * VersionRule , mode sourceVersionMode ) ( assembledInput , [ ] Diagnostic ) {
2026-09-29 17:52:20 +00:00
limits := defaultImportLimits ( )
parseLimits := defaultParseLimits ( )
parseLimits . source . remaining = limits . bytes - accounting . bytes
parsed , diagnostics := parseSource ( source , "source-" + strconv . Itoa ( accounting . sources + 1 ) , parseLimits )
2026-10-14 17:21:07 +00:00
if parsed . source != nil {
parsed . source . Destination = destination . clone ( )
}
2026-09-12 15:58:06 +00:00
input := assembledInput { parsed : parsed , document : parsed . document }
if len ( diagnostics ) != 0 {
input . sources = sourceInfos ( parsed . source )
2026-10-14 15:49:51 +00:00
// Mapping value issues already identify application content. Other early
// failures describe source syntax or access, not a placed value.
if source . kind == sourceMapping && source . mapIssue != nil && len ( destination . segments ) != 0 {
for index := range diagnostics {
relative := diagnostics [ index ] . Path
segments := clonePathSegments ( destination . segments , len ( relative . segments ) )
diagnostics [ index ] . Path = Path { segments : append ( segments , relative . segments ... ) }
}
}
2026-09-12 15:58:06 +00:00
return input , diagnostics
}
2026-10-15 15:24:53 +00:00
if mode != sourceVersionModeDefault {
processed := processSourceVersion ( parsed . document , localVersion , sourceVersionMain , destination , mode )
input . versionEvidence = processed . evidence
input . versionDiagnostics = processed . diagnostics
if hasErrors ( processed . diagnostics ) {
return input , processed . diagnostics
}
}
2026-09-12 15:58:06 +00:00
if options . Imports {
2026-10-14 13:19:31 +00:00
input . composition , input . sources , diagnostics = loadImportedDocumentsTracked ( source , options . Filesystem , parsed , destination , options . Definition . Version , limits , accounting , visited , hostToken )
2026-09-12 15:58:06 +00:00
if len ( diagnostics ) != 0 {
return input , diagnostics
}
input . document = input . composition . root
} else {
input . sources = sourceInfos ( parsed . source )
2026-09-23 18:27:29 +00:00
if options . Filesystem != nil && ! activeOverlay ( options . Overlay ) {
2026-09-12 15:58:06 +00:00
if diagnostic := validateFilesystemContext ( source , options . Filesystem , parsed ) ; diagnostic != nil {
return input , [ ] Diagnostic { * diagnostic }
}
}
}
if ! options . Imports && input . document != nil {
if diagnostics := disabledDirectiveDiagnostics ( input . document , Path { } ) ; len ( diagnostics ) != 0 {
return input , diagnostics
}
2026-10-14 13:14:06 +00:00
_ , placed , err := placeDocument ( input . document , destination , input . document . Location , parseLimits . document . Depth , parseLimits . document . Nodes )
if err != nil {
return input , [ ] Diagnostic { sourceDiagnostic ( CodeSourceLimitExceeded , parsed . source . ID , "configuration exceeds the source depth or node limit after destination placement" ) }
}
input . document = placed
2026-10-14 13:31:32 +00:00
rawNodes := countInputNodes ( parsed . document )
placementNodes := destinationWrapperNodeCount ( destination )
if accounting . sources + 1 > limits . sources || accounting . bytes + parsed . bytes > limits . bytes || accounting . nodes + rawNodes + placementNodes > limits . nodes {
2026-09-29 17:52:20 +00:00
return input , [ ] Diagnostic { sourceDiagnostic ( CodeSourceLimitExceeded , parsed . source . ID , "configuration exceeds the aggregate input limit" ) }
}
if diagnostic := registerAcquiredSource ( source , options . Filesystem , parsed . source , visited , hostToken ) ; diagnostic != nil {
return input , [ ] Diagnostic { * diagnostic }
}
accounting . sources ++
accounting . bytes += parsed . bytes
2026-10-14 13:31:32 +00:00
accounting . nodes += rawNodes + placementNodes
2026-09-12 15:58:06 +00:00
}
2026-09-29 18:08:27 +00:00
if ! activeOverlay ( options . Overlay ) || options . Imports {
contexts , diagnostic := contextsForAcquiredSources ( source , options . Filesystem , parsed . source , input . sources )
if diagnostic != nil {
return input , [ ] Diagnostic { * diagnostic }
}
input . contexts = contexts
}
2026-09-12 15:58:06 +00:00
return input , nil
}
2026-09-29 17:52:20 +00:00
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
}
2026-09-12 15:58:06 +00:00
func applyInputLayers [ T any ] ( source Source , options Options [ T ] , input assembledInput ) ( assembledInput , [ ] Diagnostic ) {
2026-09-23 18:27:29 +00:00
if ! activeOverlay ( options . Overlay ) && len ( options . Overrides ) == 0 {
2026-09-12 15:58:06 +00:00
return input , nil
}
composition , diagnostics := compositionForInput ( input )
if len ( diagnostics ) != 0 {
return input , diagnostics
}
2026-09-29 17:13:14 +00:00
assembly := newAssemblyAccumulator ( composition )
2026-09-23 18:27:29 +00:00
if activeOverlay ( options . Overlay ) {
2026-09-29 17:13:14 +00:00
input . sources , diagnostics = loadSelectedOverlayInto (
source , options . Filesystem , input . parsed , assembly , input . sources ,
2026-09-23 18:27:29 +00:00
options . Overlay , options . Definition . Version , defaultImportLimits ( ) ,
2026-09-12 15:58:06 +00:00
)
if len ( diagnostics ) != 0 {
return input , diagnostics
}
2026-09-29 18:08:27 +00:00
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 )
2026-09-12 15:58:06 +00:00
}
if len ( options . Overrides ) != 0 {
2026-09-29 17:13:14 +00:00
input . sources , diagnostics = assembly . applyOverrides (
input . sources , options . Overrides , options . Definition . Version , defaultImportLimits ( ) ,
2026-09-12 15:58:06 +00:00
)
if len ( diagnostics ) != 0 {
return input , diagnostics
}
}
2026-09-29 17:13:14 +00:00
input . composition = assembly . current
input . document = assembly . current . root
2026-09-12 15:58:06 +00:00
return input , nil
}
2026-09-23 18:27:29 +00:00
// compositionForInput initializes one handoff for overlays and CLI, including
2026-09-12 15:58:06 +00:00
// defaults-only CLI input. Raw source bytes and mapping keys retain their budgets.
func compositionForInput ( input assembledInput ) ( * composedDocument , [ ] Diagnostic ) {
if input . composition != nil {
return input . composition , nil
}
if input . document == nil {
return & composedDocument { root : & document . Node { Kind : document . Mapping } , origins : make ( map [ string ] Origin ) } , nil
}
composition , diagnostics := composeDocuments ( input . document )
if len ( diagnostics ) != 0 {
return nil , diagnostics
}
composition . inputBytes = input . parsed . bytes
composition . inputNodes = countInputNodes ( input . document )
return composition , nil
}