Expose layered configuration loading and inspection

This commit is contained in:
2026-09-29 17:58:15 +00:00
parent 1cdf90d831
commit 5a77728a81
13 changed files with 456 additions and 16 deletions

View File

@@ -38,7 +38,7 @@ func (r *Inspection) WriteExplanation(output io.Writer, path Path) error {
rendered.WriteString(" - representation: ")
if change.Origin.Operation == ChangeDecoding {
rendered.WriteString("input to canonical typed\n")
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerOverlay || change.Origin.Layer == LayerCLI {
} else if change.Origin.Layer == LayerBase || change.Origin.Layer == LayerUnderlay || change.Origin.Layer == LayerOverlay || change.Origin.Layer == LayerCLI {
rendered.WriteString("policy-validated input\n")
} else {
rendered.WriteString("canonical typed\n")

View File

@@ -261,6 +261,9 @@ func (s *importTraversal) visitPath(basePath string, declaration importDeclarati
label = s.access.label + "/" + canonical
}
info := SourceInfo{ID: sourceID, Label: label, Path: canonical, Layer: LayerBase, Kind: kind, ImportedFrom: &location}
if kind == SourceKindFilesystem {
info.namespace = s.access.token
}
s.sources = append(s.sources, info)
data, skipped, diagnostic := s.readImport(canonical, sourceID, declaration.optional)
if diagnostic != nil {

View File

@@ -0,0 +1,50 @@
package engine
import "os"
// Input selects one source and its independent import policy.
type Input struct {
Source Source
Imports bool
Filesystem *FilesystemContext
}
// Underlay contains strictly composed contributions at one precedence tier.
type Underlay struct{ Inputs []Input }
// LayeredOptions selects lower-priority tiers and final application rules.
type LayeredOptions[T any] struct {
Definition Definition[T]
Underlays []Underlay
Overlay string
OverlayFilesystem *FilesystemContext
Overrides []Override
}
// LoadLayered loads a main input and lower-priority grouped inputs through one
// final decode, resolution, and validation lifecycle.
func LoadLayered[T any](main Input, secrets SecretBinding, supplied ...LayeredOptions[T]) (*Loaded[T], error) {
if len(supplied) > 1 {
return nil, (&diagnosticAccumulator{diagnostics: []Diagnostic{optionDiagnostic(CodeOptionsInvalid, "LoadLayered accepts at most one options value")}}).loadError()
}
var options LayeredOptions[T]
if len(supplied) != 0 {
options = supplied[0]
}
request := loadRequest[T]{
main: loadInput{source: main.Source, imports: main.Imports, filesystem: main.Filesystem},
definition: options.Definition, overlay: options.Overlay,
overlayFilesystem: options.OverlayFilesystem,
overrides: append([]Override(nil), options.Overrides...),
underlays: make([]loadGroup, len(options.Underlays)),
}
for index, underlay := range options.Underlays {
request.underlays[index].inputs = make([]loadInput, len(underlay.Inputs))
for inputIndex, input := range underlay.Inputs {
request.underlays[index].inputs[inputIndex] = loadInput{
source: input.Source, imports: input.Imports, filesystem: input.Filesystem,
}
}
}
return loadGroupedWithSecretLookup(request, secrets, os.LookupEnv)
}

View File

@@ -182,6 +182,7 @@ type SourceInfo struct {
Kind SourceKind
UnderlayIndex int
ImportedFrom *Location
namespace *namespaceToken
}
// Diagnostic is a safe structured problem report. Message and source labels must

View File

@@ -84,7 +84,9 @@ func loadSelectedOverlayWithAccess(access *filesystemAccess, base *assemblyAccum
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayOutsideRoot, "selected overlay resolves outside the configuration root", nil)}
}
for _, existing := range sources {
if existing.Path != "" && existing.Path == canonical {
if existing.Path != "" && existing.Path == canonical &&
((access.native == nil && existing.Kind == SourceKindFile) ||
(access.native != nil && existing.Kind == SourceKindFilesystem && existing.namespace == access.token)) {
return cloneSourceInfos(sources), []Diagnostic{importDiagnostic(CodeOverlayRepeated, "selected overlay is already present in the base source graph", nil)}
}
}
@@ -105,6 +107,9 @@ func loadSelectedOverlayWithAccess(access *filesystemAccess, base *assemblyAccum
label = access.label + "/" + canonical
}
overlaySource := SourceInfo{ID: sourceID, Label: label, Path: canonical, Layer: LayerOverlay, Kind: kind}
if kind == SourceKindFilesystem {
overlaySource.namespace = access.token
}
sources = append(cloneSourceInfos(sources), overlaySource)
data, diagnostic := readOverlayFile(access, canonical, sourceID, limits.bytes-base.current.inputBytes)
if diagnostic != nil {

View File

@@ -238,6 +238,7 @@ func readSource(source Source, sourceID string, limits sourceLimits) (sourceRead
case sourceFilesystem:
access := newNativeAccess(source.namespace.filesystem, ".", source.namespace.token)
info.Path = source.path
info.namespace = source.namespace.token
canonical, err := access.canonical(source.path)
if err != nil {
return sourceRead{info: info}, []Diagnostic{classifySourceError(err, sourceID)}