Extract imported source admission from traversal
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Consolidation Implementation Plan
|
||||
|
||||
Status: in progress. Stages 1 through 12 are complete; follow Stage 13 through Stage 20 in order.
|
||||
Status: in progress. Stages 1 through 13 are complete; follow Stage 14 through Stage 20 in order.
|
||||
The target executor is a Luna coding agent working through one end-to-end sprint.
|
||||
|
||||
## Authority, Baseline, And Execution
|
||||
@@ -534,7 +534,7 @@ admission into a result-returning helper. The selector retains root handling,
|
||||
stack and active-path mutation, candidate union, sorting, exclusions, and the
|
||||
required-empty decision. Directory-focused tests and `./scripts/check.sh` passed.
|
||||
|
||||
## Stage 13 — Extract Imported-Source Admission From Recursion
|
||||
## Stage 13 — Extract Imported-Source Admission From Recursion (Complete)
|
||||
|
||||
**Owner:** engine `import_acquisition.go` after Stage 10.
|
||||
|
||||
@@ -560,6 +560,12 @@ required-empty decision. Directory-focused tests and `./scripts/check.sh` passed
|
||||
read/process path and the original scheduling/evidence algorithms. Run A again
|
||||
because Stages 10–13 changed the import implementation after the first checkpoint.
|
||||
|
||||
Progress notes (2026-10-25): introduced an explicit admitted-import record and
|
||||
optional-missing outcome for source read/parse/version processing. `visitPath`
|
||||
retains containment, identity/cycle checks, graph updates, and recursive
|
||||
placement coordination; item placement receives the admitted record directly.
|
||||
Validation group A and `./scripts/check.sh` passed.
|
||||
|
||||
## Stage 14 — Extract Inheritance Child And Parent Preparation
|
||||
|
||||
**Owners:** engine `inheritance_schedule.go`, new `inheritance_prepare.go`, and
|
||||
|
||||
@@ -10,6 +10,29 @@ import (
|
||||
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
|
||||
)
|
||||
|
||||
type importAdmissionOutcome uint8
|
||||
|
||||
const (
|
||||
importAdmissionReady importAdmissionOutcome = iota
|
||||
importAdmissionOptionalMissing
|
||||
)
|
||||
|
||||
type admittedImport struct {
|
||||
canonical string
|
||||
identity resourceIdentity
|
||||
nodeKey importNodeKey
|
||||
source SourceInfo
|
||||
payload *document.Node
|
||||
declarations []importDeclarationSpec
|
||||
sequenceDeclarations []sequenceDeclarationSpec
|
||||
processing *sourceVersionProcessingResult
|
||||
}
|
||||
|
||||
type importAdmissionResult struct {
|
||||
outcome importAdmissionOutcome
|
||||
source admittedImport
|
||||
}
|
||||
|
||||
func acquireImportedInput(request importAcquisitionRequest, policy *acquisitionPolicy, state *acquisitionState, tier *tierAcquisitionState) (*composedDocument, []SourceInfo, *resolvedFilesystemContext, []Diagnostic) {
|
||||
source := request.input.input.source
|
||||
context := request.input.input.filesystem
|
||||
@@ -276,6 +299,83 @@ func (s *importTraversal) recordEmptyImportSelection(spec importDeclarationSpec)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *importTraversal) admitImportSource(canonical string, identity resourceIdentity, nodeKey importNodeKey, candidate selectedImportCandidate, policy resolvedImportPolicy, destination Path, location Location) (importAdmissionResult, []Diagnostic) {
|
||||
sourceID := "source-" + strconv.Itoa(s.state.accounting.sources+1)
|
||||
kind := SourceKindFile
|
||||
label := candidate.label
|
||||
if label == "" {
|
||||
label = candidate.path
|
||||
}
|
||||
if s.access.native != nil {
|
||||
kind = SourceKindFilesystem
|
||||
label = s.access.label + "/" + canonical
|
||||
}
|
||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||
if policy.placement == placementModeSequenceItem {
|
||||
info.placementMode = placementModeSequenceItem
|
||||
}
|
||||
// A source's own item placement still has an exact sequence-container
|
||||
// destination. Only an unresolved enclosing item index makes the path a
|
||||
// fallback container for this snapshot.
|
||||
info.destinationUnresolved = s.itemScope
|
||||
if kind == SourceKindFilesystem {
|
||||
info.namespace = s.access.token
|
||||
}
|
||||
s.sources = append(s.sources, info)
|
||||
data, skipped, diagnostic := s.readImport(canonical, sourceID, policy.required)
|
||||
if diagnostic != nil {
|
||||
return importAdmissionResult{}, []Diagnostic{*diagnostic}
|
||||
}
|
||||
if skipped {
|
||||
s.sources = s.sources[:len(s.sources)-1]
|
||||
return importAdmissionResult{outcome: importAdmissionOptionalMissing}, nil
|
||||
}
|
||||
s.state.accounting.sources++
|
||||
|
||||
root, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{
|
||||
Depth: maxDocumentDepth, Nodes: maxDocumentNodes,
|
||||
})
|
||||
if len(issues) != 0 {
|
||||
return importAdmissionResult{}, parserDiagnostics(sourceID, issues)
|
||||
}
|
||||
nodeCount := countInputNodes(root)
|
||||
if s.state.accounting.nodes+nodeCount > s.policy.importLimits.nodes {
|
||||
value := importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)
|
||||
return importAdmissionResult{}, []Diagnostic{value}
|
||||
}
|
||||
s.state.accounting.nodes += nodeCount
|
||||
|
||||
directiveContext := s.request.input.directiveContext
|
||||
if directiveContext == 0 {
|
||||
directiveContext = rootDirectiveContextImportsOnly
|
||||
}
|
||||
versionDestination := destination
|
||||
if s.itemScope {
|
||||
versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
|
||||
}
|
||||
var processing sourceVersionProcessingResult
|
||||
if s.state.versionState != nil && s.state.versionState.mode != sourceVersionModeDefault {
|
||||
processed, failures := s.state.versionState.processWithDirectiveContext(root, sourceVersionContribution, versionDestination, sourceID, policy.placement, directiveContext)
|
||||
if len(failures) != 0 {
|
||||
return importAdmissionResult{}, failures
|
||||
}
|
||||
processing = processed
|
||||
} else {
|
||||
processed := processSourceVersionWithDirectiveContext(root, s.request.input.protectionRule, sourceVersionContribution, versionDestination, sourceVersionModeDefault, directiveContext)
|
||||
if len(processed.diagnostics) != 0 {
|
||||
return importAdmissionResult{}, processed.diagnostics
|
||||
}
|
||||
processing = processed
|
||||
}
|
||||
return importAdmissionResult{source: admittedImport{
|
||||
canonical: canonical, identity: identity, nodeKey: nodeKey,
|
||||
source: cloneSourceInfo(info), payload: processing.payload,
|
||||
declarations: processing.imports,
|
||||
sequenceDeclarations: cloneSequenceDeclarations(processing.sequences),
|
||||
processing: &processing,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec importDeclarationSpec, policy resolvedImportPolicy, destination, relativeDestination Path, depth int) []Diagnostic {
|
||||
location := candidate.site.location
|
||||
if location.SourceID == "" && location.Line == 0 && location.Column == 0 {
|
||||
@@ -328,83 +428,15 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
|
||||
if _, err := s.access.relative(canonical); err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportOutsideRoot, "import target could not be addressed inside the configuration root", &location)}
|
||||
}
|
||||
sourceID := "source-" + strconv.Itoa(s.state.accounting.sources+1)
|
||||
kind := SourceKindFile
|
||||
label := candidate.label
|
||||
if label == "" {
|
||||
label = candidate.path
|
||||
admission, diagnostics := s.admitImportSource(canonical, identity, nodeKey, candidate, policy, destination, location)
|
||||
if len(diagnostics) != 0 {
|
||||
return diagnostics
|
||||
}
|
||||
if s.access.native != nil {
|
||||
kind = SourceKindFilesystem
|
||||
label = s.access.label + "/" + canonical
|
||||
}
|
||||
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Destination: s.sourceDestination(destination), Layer: LayerBase, Kind: kind, ImportedFrom: &location}
|
||||
if policy.placement == placementModeSequenceItem {
|
||||
info.placementMode = placementModeSequenceItem
|
||||
}
|
||||
// A source's own item placement still has an exact sequence-container
|
||||
// destination. Only an unresolved enclosing item index makes the path a
|
||||
// fallback container for this snapshot.
|
||||
info.destinationUnresolved = s.itemScope
|
||||
if kind == SourceKindFilesystem {
|
||||
info.namespace = s.access.token
|
||||
}
|
||||
s.sources = append(s.sources, info)
|
||||
data, skipped, diagnostic := s.readImport(canonical, sourceID, policy.required)
|
||||
if diagnostic != nil {
|
||||
return []Diagnostic{*diagnostic}
|
||||
}
|
||||
if skipped {
|
||||
s.sources = s.sources[:len(s.sources)-1]
|
||||
if admission.outcome == importAdmissionOptionalMissing {
|
||||
return nil
|
||||
}
|
||||
s.state.accounting.sources++
|
||||
admitted := admission.source
|
||||
|
||||
root, issues := yamladapter.Parse(bytes.NewReader(data), sourceID, yamladapter.Limits{
|
||||
Depth: maxDocumentDepth, Nodes: maxDocumentNodes,
|
||||
})
|
||||
if len(issues) != 0 {
|
||||
return parserDiagnostics(sourceID, issues)
|
||||
}
|
||||
nodeCount := countInputNodes(root)
|
||||
if s.state.accounting.nodes+nodeCount > s.policy.importLimits.nodes {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
|
||||
}
|
||||
s.state.accounting.nodes += nodeCount
|
||||
|
||||
var body *document.Node
|
||||
var declarations []importDeclarationSpec
|
||||
var sequenceDeclarations []sequenceDeclarationSpec
|
||||
var diagnostics []Diagnostic
|
||||
var processing *sourceVersionProcessingResult
|
||||
directiveContext := s.request.input.directiveContext
|
||||
if directiveContext == 0 {
|
||||
directiveContext = rootDirectiveContextImportsOnly
|
||||
}
|
||||
if s.state.versionState != nil && s.state.versionState.mode != sourceVersionModeDefault {
|
||||
versionDestination := destination
|
||||
if s.itemScope {
|
||||
versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
|
||||
}
|
||||
processed, failures := s.state.versionState.processWithDirectiveContext(root, sourceVersionContribution, versionDestination, sourceID, policy.placement, directiveContext)
|
||||
if len(failures) != 0 {
|
||||
return failures
|
||||
}
|
||||
processing = &processed
|
||||
body, declarations = processed.payload, processed.imports
|
||||
} else {
|
||||
versionDestination := destination
|
||||
if s.itemScope {
|
||||
versionDestination = appendDestination(s.itemPrefix.Index(0), destination)
|
||||
}
|
||||
processed := processSourceVersionWithDirectiveContext(root, s.request.input.protectionRule, sourceVersionContribution, versionDestination, sourceVersionModeDefault, directiveContext)
|
||||
if len(processed.diagnostics) != 0 {
|
||||
return processed.diagnostics
|
||||
}
|
||||
processing = &processed
|
||||
body, declarations = processed.payload, processed.imports
|
||||
}
|
||||
sequenceDeclarations = cloneSequenceDeclarations(processing.sequences)
|
||||
placementDestination := destination
|
||||
mode := placementModeMapping
|
||||
if policy.placement == placementModeSequenceItem {
|
||||
@@ -414,19 +446,19 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
|
||||
if mode == placementModeSequenceItem {
|
||||
role = importRoleItem
|
||||
}
|
||||
s.importGraph.addNode(importGraphNode{key: nodeKey, canonical: canonical, sourceID: sourceID, role: role})
|
||||
s.importGraph.addEdge(importerKey, nodeKey, location)
|
||||
s.importGraph.addNode(importGraphNode{key: admitted.nodeKey, canonical: admitted.canonical, sourceID: admitted.source.ID, role: role})
|
||||
s.importGraph.addEdge(importerKey, admitted.nodeKey, location)
|
||||
if mode == placementModeSequenceItem {
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
|
||||
s.active[identity] = len(s.stack)
|
||||
s.tier.visited[identity] = route
|
||||
route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey}
|
||||
s.active[admitted.identity] = len(s.stack)
|
||||
s.tier.visited[admitted.identity] = route
|
||||
s.stack = append(s.stack, route)
|
||||
diagnostics := s.visitItemSource(canonical, nodeKey, destination, relativeDestination, depth, info, body, declarations, sequenceDeclarations, processing)
|
||||
delete(s.active, identity)
|
||||
diagnostics := s.visitItemSource(admitted, destination, relativeDestination, depth)
|
||||
delete(s.active, admitted.identity)
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
return diagnostics
|
||||
}
|
||||
_, placed, err := placeDocument(body, placementDestination, body.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
_, placed, err := placeDocument(admitted.payload, placementDestination, admitted.payload.Location, maxDocumentDepth, maxDocumentNodes)
|
||||
if err != nil {
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the source depth or node limit after destination placement", &location)}
|
||||
}
|
||||
@@ -435,18 +467,18 @@ func (s *importTraversal) visitPath(candidate selectedImportCandidate, spec impo
|
||||
return []Diagnostic{importDiagnostic(CodeImportLimitExceeded, "configuration exceeds the aggregate node limit", &location)}
|
||||
}
|
||||
s.state.accounting.nodes += placementNodes
|
||||
route := importRoute{canonical: canonical, locations: currentRoute.locations, key: nodeKey}
|
||||
route := importRoute{canonical: admitted.canonical, locations: currentRoute.locations, key: admitted.nodeKey}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: placed,
|
||||
identity: nodeKey, role: role, scopeOwner: s.scope.owner,
|
||||
destination: placementDestination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
|
||||
source: cloneSourceInfo(admitted.source), payload: admitted.payload, document: placed,
|
||||
identity: admitted.nodeKey, role: role, scopeOwner: s.scope.owner,
|
||||
destination: placementDestination.clone(), relativeDestination: relativeDestination.clone(), processing: admitted.processing,
|
||||
sequenceDeclarations: cloneSequenceDeclarations(admitted.sequenceDeclarations),
|
||||
})
|
||||
s.active[identity] = len(s.stack)
|
||||
s.tier.visited[identity] = route
|
||||
s.active[admitted.identity] = len(s.stack)
|
||||
s.tier.visited[admitted.identity] = route
|
||||
s.stack = append(s.stack, route)
|
||||
diagnostics = s.walkDeclarations(s.access.dir(canonical), declarations, depth, destination)
|
||||
delete(s.active, identity)
|
||||
diagnostics = s.walkDeclarations(s.access.dir(admitted.canonical), admitted.declarations, depth, destination)
|
||||
delete(s.active, admitted.identity)
|
||||
s.stack = s.stack[:len(s.stack)-1]
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
@@ -168,10 +168,10 @@ func reservePendingItemPlacements(scope *acquisitionScope, limits importLimits,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *importTraversal) visitItemSource(canonical string, identity importNodeKey, destination, relativeDestination Path, depth int, info SourceInfo, body *document.Node, declarations []importDeclarationSpec, sequenceDeclarations []sequenceDeclarationSpec, processing *sourceVersionProcessingResult) []Diagnostic {
|
||||
func (s *importTraversal) visitItemSource(admitted admittedImport, destination, relativeDestination Path, depth int) []Diagnostic {
|
||||
parentScope := s.scope
|
||||
parentItemScope, parentPrefix, parentFallback := s.itemScope, s.itemPrefix, s.itemFallback
|
||||
s.scope = acquisitionScope{owner: identity}
|
||||
s.scope = acquisitionScope{owner: admitted.nodeKey}
|
||||
s.itemScope = true
|
||||
if parentItemScope {
|
||||
s.itemPrefix = appendDestination(parentPrefix, destination)
|
||||
@@ -180,11 +180,11 @@ func (s *importTraversal) visitItemSource(canonical string, identity importNodeK
|
||||
s.itemFallback = destination.clone()
|
||||
}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body, document: body,
|
||||
identity: identity, role: importRoleItem, scopeOwner: identity,
|
||||
processing: processing, sequenceDeclarations: cloneSequenceDeclarations(sequenceDeclarations),
|
||||
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),
|
||||
})
|
||||
diagnostics := s.walkDeclarations(s.access.dir(canonical), declarations, depth, Path{})
|
||||
diagnostics := s.walkDeclarations(s.access.dir(admitted.canonical), admitted.declarations, depth, Path{})
|
||||
itemScope := s.scope
|
||||
s.scope, s.itemScope, s.itemPrefix, s.itemFallback = parentScope, parentItemScope, parentPrefix, parentFallback
|
||||
if len(diagnostics) != 0 {
|
||||
@@ -192,9 +192,9 @@ func (s *importTraversal) visitItemSource(canonical string, identity importNodeK
|
||||
}
|
||||
itemScope.destination = Path{}
|
||||
s.scope.contributions = append(s.scope.contributions, acquiredContribution{
|
||||
source: cloneSourceInfo(info), payload: body,
|
||||
identity: identity, role: importRoleItem, scopeOwner: parentScope.owner,
|
||||
destination: destination.clone(), relativeDestination: relativeDestination.clone(), processing: processing,
|
||||
source: cloneSourceInfo(admitted.source), payload: admitted.payload,
|
||||
identity: admitted.nodeKey, role: importRoleItem, scopeOwner: parentScope.owner,
|
||||
destination: destination.clone(), relativeDestination: relativeDestination.clone(), processing: admitted.processing,
|
||||
pendingItem: &itemScope,
|
||||
})
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user