Clarify search and secret bootstrap names

This commit is contained in:
2026-10-27 15:09:04 +00:00
parent 9ac0053590
commit eafbd32489
7 changed files with 25 additions and 24 deletions

View File

@@ -52,8 +52,8 @@ func SearchDirectory(directory string, supplied ...DirectorySearchOptions) Searc
return Search{value: engine.SearchDirectory(directory, options...)} return Search{value: engine.SearchDirectory(directory, options...)}
} }
// Paths describes literal host-file candidates in the supplied order. // SearchPaths describes literal host-file candidates in the supplied order.
func Paths(paths ...string) Search { return Search{value: engine.Paths(paths...)} } func SearchPaths(paths ...string) Search { return Search{value: engine.Paths(paths...)} }
// Searches concatenates descriptions in order and snapshots their entries. // Searches concatenates descriptions in order and snapshots their entries.
func Searches(searches ...Search) Search { func Searches(searches ...Search) Search {

View File

@@ -1,6 +1,6 @@
# Pre-v1 Public API Cleanup Implementation Plan # Pre-v1 Public API Cleanup Implementation Plan
Status: in progress; Stages 1–4 are complete and Stages 5–11 are pending. Status: in progress; Stages 1–5 are complete and Stages 6–11 are pending.
Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`. Baseline: `07e4ae5d9236935389e81b207d4a8f00543fafce`.
Audience: a Luna coding agent executing the stages sequentially. Audience: a Luna coding agent executing the stages sequentially.
@@ -225,7 +225,7 @@ ordinary/layered loading.
## Stage 5 — Clarify Discovery And Environment Bootstrap Names ## Stage 5 — Clarify Discovery And Environment Bootstrap Names
Status: pending. Implements N1 and D7. Status: complete. Implements N1 and D7.
1. In root [discovery.go](../../discovery.go), rename `Paths` to `SearchPaths`. 1. In root [discovery.go](../../discovery.go), rename `Paths` to `SearchPaths`.
Forward the same literal candidates to existing engine machinery. Migrate Forward the same literal candidates to existing engine machinery. Migrate

View File

@@ -82,7 +82,7 @@ func Example_weatherStartup() {
defer os.RemoveAll(directory) defer os.RemoveAll(directory)
_ = os.WriteFile(filepath.Join(directory, blockedName), []byte("unused-example-value\n"), 0o600) _ = os.WriteFile(filepath.Join(directory, blockedName), []byte("unused-example-value\n"), 0o600)
_ = os.WriteFile(filepath.Join(directory, tokenName), []byte("disposable-example-credential\n"), 0o600) _ = os.WriteFile(filepath.Join(directory, tokenName), []byte("disposable-example-credential\n"), 0o600)
report, bootstrapErr := configkit.LoadSecretsDir(directory) report, bootstrapErr := configkit.LoadSecretsIntoEnvironment(directory)
var reportOutput bytes.Buffer var reportOutput bytes.Buffer
_, reportWriteErr := report.WriteTo(&reportOutput) _, reportWriteErr := report.WriteTo(&reportOutput)

View File

@@ -43,7 +43,7 @@ func LoadSequenceCatalog(mainPath, overlayName, runLabel string) (*configkit.Res
filesystem := configkit.NewFilesystem("sequence catalog", os.DirFS(root)) filesystem := configkit.NewFilesystem("sequence catalog", os.DirFS(root))
filesystemContext := &configkit.FilesystemContext{BaseDir: ".", Root: ".", FS: &filesystem} filesystemContext := &configkit.FilesystemContext{BaseDir: ".", Root: ".", FS: &filesystem}
mainSearch := configkit.SearchConfig("", configkit.Searches( mainSearch := configkit.SearchConfig("", configkit.Searches(
configkit.Paths(absoluteMain), configkit.SearchPaths(absoluteMain),
configkit.SearchDirectory(filepath.Join(root, "fallback"), configkit.DirectorySearchOptions{ configkit.SearchDirectory(filepath.Join(root, "fallback"), configkit.DirectorySearchOptions{
Filenames: []configkit.FilenameGroup{configkit.CustomFilenames(filepath.Base(absoluteMain))}, Filenames: []configkit.FilenameGroup{configkit.CustomFilenames(filepath.Base(absoluteMain))},
}), }),

View File

@@ -140,7 +140,7 @@ func TestFacadeSecretsBootstrapUsesFacadeRecords(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
report, err := configkit.LoadSecretsDir(directory) report, err := configkit.LoadSecretsIntoEnvironment(directory)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -164,7 +164,7 @@ func TestFacadeSecretsBootstrapUsesFacadeRecords(t *testing.T) {
t.Fatalf("report rendering = %q, %v", rendered.String(), err) t.Fatalf("report rendering = %q, %v", rendered.String(), err)
} }
failedReport, err := configkit.LoadSecretsDir("") failedReport, err := configkit.LoadSecretsIntoEnvironment("")
var secretsError *configkit.SecretsError var secretsError *configkit.SecretsError
if failedReport == nil || !errors.As(err, &secretsError) || len(secretsError.Diagnostics()) != 1 || secretsError.Diagnostics()[0].Code != configkit.CodeSecretsInvalidPath { if failedReport == nil || !errors.As(err, &secretsError) || len(secretsError.Diagnostics()) != 1 || secretsError.Diagnostics()[0].Code != configkit.CodeSecretsInvalidPath {
t.Fatalf("bootstrap failure = %#v / %#v", failedReport, err) t.Fatalf("bootstrap failure = %#v / %#v", failedReport, err)

View File

@@ -93,14 +93,14 @@ func TestFacadeSearchOrderingRequirednessAndSnapshots(t *testing.T) {
names := []string{"config.yml"} names := []string{"config.yml"}
groups := []FilenameGroup{CustomFilenames(names...)} groups := []FilenameGroup{CustomFilenames(names...)}
directoryOptions := []DirectorySearchOptions{{Filenames: groups}} directoryOptions := []DirectorySearchOptions{{Filenames: groups}}
search := Searches(Paths(paths...), SearchDirectory(root, directoryOptions...), Paths(laterPath)) search := Searches(SearchPaths(paths...), SearchDirectory(root, directoryOptions...), SearchPaths(laterPath))
source := SearchConfig("", search, SearchOptions{Required: true}) source := SearchConfig("", search, SearchOptions{Required: true})
paths[0] = laterPath paths[0] = laterPath
names[0] = "later.yml" names[0] = "later.yml"
groups[0] = CustomFilenames("later.yml") groups[0] = CustomFilenames("later.yml")
directoryOptions[0].Filenames[0] = CustomFilenames("later.yml") directoryOptions[0].Filenames[0] = CustomFilenames("later.yml")
search = Paths(laterPath) search = SearchPaths(laterPath)
result, err := Load[facadeDiscoveryConfig](source) result, err := Load[facadeDiscoveryConfig](source)
if err != nil || result.Config.Value != "selected" { if err != nil || result.Config.Value != "selected" {
@@ -110,7 +110,7 @@ func TestFacadeSearchOrderingRequirednessAndSnapshots(t *testing.T) {
t.Fatalf("ordered search sources = %#v", sources) t.Fatalf("ordered search sources = %#v", sources)
} }
_, err = Load[facadeDiscoveryConfig](SearchConfig("", Paths(filepath.Join(root, "missing.yml")), SearchOptions{Required: true})) _, err = Load[facadeDiscoveryConfig](SearchConfig("", SearchPaths(filepath.Join(root, "missing.yml")), SearchOptions{Required: true}))
var loadErr *LoadError var loadErr *LoadError
if !errors.As(err, &loadErr) { if !errors.As(err, &loadErr) {
t.Fatalf("required exhausted search error = %v, want *LoadError", err) t.Fatalf("required exhausted search error = %v, want *LoadError", err)
@@ -131,7 +131,7 @@ func TestFacadeSearchWorksForLayeredMainAndUnderlay(t *testing.T) {
mainPath := writeFacadeImportFile(t, root, "main.yml", "settings: {main: value}\n") mainPath := writeFacadeImportFile(t, root, "main.yml", "settings: {main: value}\n")
underlayPath := writeFacadeImportFile(t, root, "underlay/base.yml", "settings: {underlay: value}\n") underlayPath := writeFacadeImportFile(t, root, "underlay/base.yml", "settings: {underlay: value}\n")
result, err := LoadLayered( result, err := LoadLayered(
Input{Source: SearchConfig("", Paths(mainPath))}, Input{Source: SearchConfig("", SearchPaths(mainPath))},
LayeredOptions[facadeDiscoveryConfig]{Underlays: []Underlay{{Inputs: []Input{{ LayeredOptions[facadeDiscoveryConfig]{Underlays: []Underlay{{Inputs: []Input{{
Source: SearchConfig("", SearchDirectory(filepath.Dir(underlayPath), DirectorySearchOptions{ Source: SearchConfig("", SearchDirectory(filepath.Dir(underlayPath), DirectorySearchOptions{
Filenames: []FilenameGroup{CustomFilenames("base.yml")}, Filenames: []FilenameGroup{CustomFilenames("base.yml")},
@@ -157,7 +157,7 @@ func TestFacadeSearchSelectedAndAbsentUseRootOnlyContext(t *testing.T) {
filesystem := &FilesystemContext{Root: root} filesystem := &FilesystemContext{Root: root}
options := Options[facadeDiscoveryConfig]{Overlay: "testing", Filesystem: filesystem} options := Options[facadeDiscoveryConfig]{Overlay: "testing", Filesystem: filesystem}
selected, err := Load(SearchConfig("", Paths(mainPath)), options) selected, err := Load(SearchConfig("", SearchPaths(mainPath)), options)
if err != nil || selected.Config.Value != "overlay" { if err != nil || selected.Config.Value != "overlay" {
t.Fatalf("selected search with root-only context = %#v, %v", selected, err) t.Fatalf("selected search with root-only context = %#v, %v", selected, err)
} }
@@ -171,7 +171,7 @@ func TestFacadeSearchSelectedAndAbsentUseRootOnlyContext(t *testing.T) {
t.Fatalf("selected file base context = %#v, %v", mainContext, ok) t.Fatalf("selected file base context = %#v, %v", mainContext, ok)
} }
missing := SearchConfig("", Paths(filepath.Join(root, "missing.yml"))) missing := SearchConfig("", SearchPaths(filepath.Join(root, "missing.yml")))
searchedAbsent, err := Load(missing, options) searchedAbsent, err := Load(missing, options)
if err != nil { if err != nil {
t.Fatalf("exhausted search with root-only overlay context: %v", err) t.Fatalf("exhausted search with root-only overlay context: %v", err)
@@ -201,7 +201,7 @@ func TestFacadeSearchRejectsSelectedContextWithoutFallback(t *testing.T) {
inside := writeFacadeImportFile(t, root, "inside.yml", "value: fallback\n") inside := writeFacadeImportFile(t, root, "inside.yml", "value: fallback\n")
outsideRoot := t.TempDir() outsideRoot := t.TempDir()
outside := writeFacadeImportFile(t, outsideRoot, "outside.yml", "value: selected\n") outside := writeFacadeImportFile(t, outsideRoot, "outside.yml", "value: selected\n")
search := SearchConfig("", Paths(outside, inside)) search := SearchConfig("", SearchPaths(outside, inside))
_, err := Load(search, Options[facadeDiscoveryConfig]{Filesystem: &FilesystemContext{Root: root}}) _, err := Load(search, Options[facadeDiscoveryConfig]{Filesystem: &FilesystemContext{Root: root}})
var loadErr *LoadError var loadErr *LoadError
@@ -228,7 +228,7 @@ func TestFacadeSearchRunsCallbacksOnceAndKeepsInspectionWithSelectedFile(t *test
selectedPath := writeFacadeImportFile(t, root, "selected.yml", "value: selected\n") selectedPath := writeFacadeImportFile(t, root, "selected.yml", "value: selected\n")
writeFacadeImportFile(t, root, "later.yml", "value: later\n") writeFacadeImportFile(t, root, "later.yml", "value: later\n")
defaultsCalls, resolveCalls, validateCalls := 0, 0, 0 defaultsCalls, resolveCalls, validateCalls := 0, 0, 0
result, err := Load(SearchConfig("", Paths(selectedPath, filepath.Join(root, "later.yml"))), Options[facadeDiscoveryLifecycleConfig]{ result, err := Load(SearchConfig("", SearchPaths(selectedPath, filepath.Join(root, "later.yml"))), Options[facadeDiscoveryLifecycleConfig]{
Definition: Definition[facadeDiscoveryLifecycleConfig]{ Definition: Definition[facadeDiscoveryLifecycleConfig]{
Defaults: func() facadeDiscoveryLifecycleConfig { Defaults: func() facadeDiscoveryLifecycleConfig {
defaultsCalls++ defaultsCalls++
@@ -272,7 +272,7 @@ func TestFacadeSearchSequenceItemAndAbsentInputPlacement(t *testing.T) {
Destination: Keys("items"), Destination: Keys("items"),
PlacementMode: PlacementModeSequenceItem, PlacementMode: PlacementModeSequenceItem,
} }
selected, err := Load(SearchConfig("", Paths(selectedPath)), options) selected, err := Load(SearchConfig("", SearchPaths(selectedPath)), options)
if err != nil || len(selected.Config.Items) != 1 || selected.Config.Items[0].Name != "selected" { if err != nil || len(selected.Config.Items) != 1 || selected.Config.Items[0].Name != "selected" {
t.Fatalf("selected sequence item = %#v, %v", selected, err) t.Fatalf("selected sequence item = %#v, %v", selected, err)
} }
@@ -283,7 +283,7 @@ func TestFacadeSearchSequenceItemAndAbsentInputPlacement(t *testing.T) {
defaults := func() facadeDiscoveryPlacementConfig { defaults := func() facadeDiscoveryPlacementConfig {
return facadeDiscoveryPlacementConfig{Items: []facadeDiscoveryPlacementItem{}} return facadeDiscoveryPlacementConfig{Items: []facadeDiscoveryPlacementItem{}}
} }
absent, err := Load(SearchConfig("", Paths(filepath.Join(root, "missing.yml"))), Options[facadeDiscoveryPlacementConfig]{ absent, err := Load(SearchConfig("", SearchPaths(filepath.Join(root, "missing.yml"))), Options[facadeDiscoveryPlacementConfig]{
Definition: Definition[facadeDiscoveryPlacementConfig]{Defaults: defaults}, Definition: Definition[facadeDiscoveryPlacementConfig]{Defaults: defaults},
Destination: options.Destination, Destination: options.Destination,
PlacementMode: options.PlacementMode, PlacementMode: options.PlacementMode,

View File

@@ -8,7 +8,7 @@ import (
) )
// SecretFileStatus describes the outcome for one file considered by // SecretFileStatus describes the outcome for one file considered by
// LoadSecretsDir. // LoadSecretsIntoEnvironment.
type SecretFileStatus string type SecretFileStatus string
const ( const (
@@ -85,11 +85,12 @@ func (e *SecretsError) Diagnostics() []Diagnostic {
return result return result
} }
// LoadSecretsDir explicitly loads immediate regular files from directory into // LoadSecretsIntoEnvironment explicitly loads immediate regular files into the
// previously unset process environment variables. Applications should call it // process environment, preserving variables that are already set, including
// once during startup, before concurrent work; Load never calls it. // empty values. Dot-prefixed entries are ignored and omitted from the report.
// Dot-prefixed entries are ignored and omitted from the report. // Load never calls it. The report accompanies any error, and an error does not
func LoadSecretsDir(directory string) (*SecretsReport, error) { // roll back values already applied.
func LoadSecretsIntoEnvironment(directory string) (*SecretsReport, error) {
report, err := engine.LoadSecretsDir(directory) report, err := engine.LoadSecretsDir(directory)
result := &SecretsReport{value: report} result := &SecretsReport{value: report}
if err == nil { if err == nil {