Expose import defaults and selection evidence

This commit is contained in:
2026-10-19 21:06:31 +00:00
parent 05b67cbfcf
commit 24131617c7
12 changed files with 568 additions and 18 deletions

View File

@@ -52,6 +52,40 @@ type importDefaults struct {
pattern []string
}
// ImportDefaults transports root-owned load defaults across the facade
// boundary. Load options snapshot this value before source acquisition.
type ImportDefaults struct {
// Required controls absence handling when non-nil.
Required *bool
// Follow controls symlink following when non-nil.
Follow *bool
// Hidden controls hidden-entry discovery when non-nil.
Hidden *bool
// Recursive controls descendant directory traversal when non-nil.
Recursive *bool
// Pattern filters directory-search filenames; nil and empty differ.
Pattern []string
}
// OptionsWithImportDefaults copies root-owned defaults into ordinary options.
func OptionsWithImportDefaults[T any](options Options[T], value ImportDefaults) Options[T] {
options.importDefaults = privateImportDefaults(value)
return options
}
// LayeredOptionsWithImportDefaults copies root-owned defaults into grouped options.
func LayeredOptionsWithImportDefaults[T any](options LayeredOptions[T], value ImportDefaults) LayeredOptions[T] {
options.importDefaults = privateImportDefaults(value)
return options
}
func privateImportDefaults(value ImportDefaults) importDefaults {
return snapshotImportDefaults(importDefaults{
required: value.Required, follow: value.Follow, hidden: value.Hidden,
recursive: value.Recursive, pattern: value.Pattern,
})
}
type resolvedImportPolicy struct {
required bool
follow bool

View File

@@ -24,17 +24,18 @@ type Definition[T any] struct {
// Imports enables explicit import declarations; Overlay selects one fixed overlay;
// Overrides contains only values explicitly supplied by an application CLI.
type Options[T any] struct {
Definition Definition[T]
Imports bool
Overlay string
Filesystem *FilesystemContext
Overrides []Override
SourceVersionMode sourceVersionMode
importDefaults importDefaults
destination Path
placementMode placementMode
sequencePolicy sequencePolicy
deferAssembly bool
Definition Definition[T]
Imports bool
Overlay string
Filesystem *FilesystemContext
Overrides []Override
SourceVersionMode sourceVersionMode
importDefaults importDefaults
destination Path
placementMode placementMode
sequencePolicy sequencePolicy
deferAssembly bool
importDefaultsValidated bool
}
// FilesystemContext supplies filesystem roots for imports and overlays.

View File

@@ -48,7 +48,8 @@ func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBi
request.definition.sequenceRegistrations = nil
mainOptions, mainDiagnostics := selectOptions(request.main.source, []Options[T]{{
Definition: request.definition, Imports: request.main.imports, Filesystem: request.main.filesystem, destination: request.main.destination.clone(),
SourceVersionMode: request.sourceVersionMode, placementMode: request.main.placementMode, importDefaults: request.importDefaults,
SourceVersionMode: request.sourceVersionMode, placementMode: request.main.placementMode,
importDefaults: request.importDefaults, importDefaultsValidated: true,
}})
request.main.destination = mainOptions.destination
diagnostics = append(diagnostics, mainDiagnostics...)
@@ -56,7 +57,10 @@ func loadGroupedWithSecretLookup[T any](request loadRequest[T], secrets SecretBi
for groupIndex := range request.underlays {
for inputIndex := range request.underlays[groupIndex].inputs {
input := &request.underlays[groupIndex].inputs[inputIndex]
inputOptions, inputDiagnostics := selectOptions(input.source, []Options[T]{{Imports: input.imports, Filesystem: input.filesystem, destination: input.destination.clone(), placementMode: input.placementMode, importDefaults: request.importDefaults}})
inputOptions, inputDiagnostics := selectOptions(input.source, []Options[T]{{
Imports: input.imports, Filesystem: input.filesystem, destination: input.destination.clone(), placementMode: input.placementMode,
importDefaults: request.importDefaults, importDefaultsValidated: true,
}})
input.destination = inputOptions.destination
diagnostics = append(diagnostics, inputDiagnostics...)
}

View File

@@ -9,7 +9,10 @@ func selectOptions[T any](source Source, supplied []Options[T]) (Options[T], []D
options = supplied[0]
}
options.importDefaults = snapshotImportDefaults(options.importDefaults)
diagnostics := validateImportDefaults(options.importDefaults)
var diagnostics []Diagnostic
if !options.importDefaultsValidated {
diagnostics = validateImportDefaults(options.importDefaults)
}
if options.placementMode != placementModeMapping && options.placementMode != placementModeSequenceItem {
diagnostics = append(diagnostics, optionDiagnostic(CodeOptionsInvalid, "input placement mode is invalid"))
}

View File

@@ -207,6 +207,90 @@ type importSelectionEvidence struct {
outcome importSelectionOutcome
}
// ImportSelectionForFacade is a root-converted source-selection transport
// record. Its paths and enum spellings are converted by the facade.
type ImportSelectionForFacade struct {
Path Path // source-local import declaration path
Location Location // authored location and declaring source ID
Selector string // path, dir, or glob
Outcome string // none for a completed empty optional selection
}
// SourceInfoImportSelectionsForFacade returns detached selection records for
// explicit root-owned conversion.
func SourceInfoImportSelectionsForFacade(source SourceInfo) []ImportSelectionForFacade {
if source.importSelections == nil {
return nil
}
result := make([]ImportSelectionForFacade, len(source.importSelections))
for index, selection := range source.importSelections {
result[index] = ImportSelectionForFacade{
Path: selection.path.clone(), Location: selection.location,
Selector: importSelectorString(selection.selector),
Outcome: importSelectionOutcomeString(selection.outcome),
}
}
return result
}
// SourceInfoWithImportSelections copies converted root-owned records into
// private engine source evidence.
func SourceInfoWithImportSelections(source SourceInfo, selections []ImportSelectionForFacade) SourceInfo {
if selections == nil {
source.importSelections = nil
return source
}
source.importSelections = make([]importSelectionEvidence, len(selections))
for index, selection := range selections {
source.importSelections[index] = importSelectionEvidence{
path: selection.Path.clone(), location: selection.Location,
selector: importSelectorFromString(selection.Selector),
outcome: importSelectionOutcomeFromString(selection.Outcome),
}
}
return source
}
func importSelectorString(value importSelector) string {
switch value {
case importSelectorPath:
return "path"
case importSelectorDirectory:
return "dir"
case importSelectorGlob:
return "glob"
default:
return ""
}
}
func importSelectorFromString(value string) importSelector {
switch value {
case "path":
return importSelectorPath
case "dir":
return importSelectorDirectory
case "glob":
return importSelectorGlob
default:
return 0
}
}
func importSelectionOutcomeString(value importSelectionOutcome) string {
if value == importSelectionNone {
return "none"
}
return ""
}
func importSelectionOutcomeFromString(value string) importSelectionOutcome {
if value == "none" {
return importSelectionNone
}
return 0
}
// SourceInfoPlacementModeForFacade converts private placement evidence to its
// root-owned numeric representation.
func SourceInfoPlacementModeForFacade(source SourceInfo) uint8 {