Define layered config discovery as explicit source plans and preserve config provenance in parsed field history, either by resolving files yourself or by loading plans directly through source middlewares.
Glazed now supports a declarative config-plan API for applications that want to express config discovery as explicit data instead of hidden helper logic. A config plan lets you define which config sources exist, which semantic layer each source belongs to, and in what order those layers should be applied. The result is easier to read in code, easier to debug, and easier to reuse across multiple applications.
This page explains the generic Glazed API, not any one application’s policy. It is the right reference when you want to build your own layered config discovery model and feed the result into Glazed’s field/value system with parse-step provenance intact.
Traditional config discovery helpers are convenient for simple cases, but they hide important policy decisions in control flow. A reviewer often has to reverse-engineer the answers to questions like:
A config plan makes those decisions explicit.
Instead of “ask a helper for a path,” the application builds a plan such as:
plan := config.NewPlan(
config.WithLayerOrder(
config.LayerSystem,
config.LayerUser,
config.LayerRepo,
config.LayerCWD,
config.LayerExplicit,
),
config.WithDedupePaths(),
).Add(
config.SystemAppConfig("myapp").Named("system-app-config"),
config.XDGAppConfig("myapp").Named("xdg-app-config"),
config.HomeAppConfig("myapp").Named("home-app-config"),
config.GitRootFile(".myapp.local.yaml").Named("git-root-local"),
config.WorkingDirFile(".myapp.local.yaml").Named("cwd-local"),
config.ExplicitFile(explicitPath).Named("explicit-config-file"),
)
That one block tells the reader almost everything they need to know.
The declarative config-plan API lives in glazed/pkg/config.
ConfigLayerA config layer is a semantic precedence bucket.
Built-in layers currently include:
LayerSystemLayerUserLayerRepoLayerCWDLayerExplicitThese are more readable than raw priority numbers because they communicate intent directly.
SourceSpecA source spec describes one config discovery rule.
It includes:
You usually construct source specs through helper functions and then customize them fluently:
config.GitRootFile(".myapp.local.yaml").
Named("git-root-local").
InLayer(config.LayerRepo).
Kind("project-config")
ResolvedConfigFileA resolved config file is the output of the plan after discovery.
It carries:
This is the object you pass to sources.FromResolvedFiles(...) when you want provenance-aware loading and also want to inspect or reuse the explicit resolved file list.
PlanReportA plan report summarizes what happened during resolution:
This is useful for debugging, tests, and CLI explain output.
Glazed ships with a small set of generic source constructors that cover common application patterns.
SystemAppConfig(appName)HomeAppConfig(appName)XDGAppConfig(appName)ExplicitFile(path)These cover the common “system / user / explicit override” flow.
GitRootFile(name)WorkingDirFile(name)These are especially useful for tools that want repository-local or working-directory-local config conventions.
Once you have a plan, you can either resolve it explicitly and feed the result into Glazed sources, or let the middleware do that for you.
files, report, err := plan.Resolve(context.Background())
if err != nil {
return err
}
fmt.Println(report.String())
parsed := values.New()
err = sources.Execute(
schema_,
parsed,
sources.FromResolvedFiles(files),
sources.FromDefaults(fields.WithSource(fields.SourceDefaults)),
)
This gives you both:
parsedPlanReport and []ResolvedConfigFile values before loadingIf you do not need to inspect the resolved file list first, use the higher-level source middlewares:
parsed := values.New()
err := sources.Execute(
schema_,
parsed,
sources.FromConfigPlan(plan),
sources.FromDefaults(fields.WithSource(fields.SourceDefaults)),
)
For dynamic plans that depend on already-parsed lower-precedence values, use FromConfigPlanBuilder(...):
err := sources.Execute(
schema_,
parsed,
sources.FromDefaults(fields.WithSource(fields.SourceDefaults)),
sources.FromConfigPlanBuilder(func(ctx context.Context, parsed *values.Values) (*config.Plan, error) {
settings := &MySelectorSettings{}
if err := parsed.DecodeSectionInto("selector", settings); err != nil {
return nil, err
}
return config.NewPlan(
config.WithLayerOrder(config.LayerExplicit),
).Add(
config.ExplicitFile(settings.ConfigFile).Named("selected-config"),
), nil
}),
)
Use FromConfigPlan(...) / FromConfigPlanBuilder(...) when you want the middleware pipeline to own resolution and loading. Use FromResolvedFiles(...) when you want to debug, print, test, or otherwise reuse the explicit resolved results.
The main reason to use FromResolvedFiles(...) instead of only FromFiles(...) is provenance.
When Glazed loads resolved config files, it attaches metadata like:
config_fileconfig_indexconfig_layerconfig_source_nameconfig_source_kindThat means a parsed field history can show not only that a value came from a config file, but also which layer and which source rule produced it.
Example shape:
demo:
threshold:
value: 22
log:
- source: config
value: 11
metadata:
config_file: /repo/repo.yaml
config_layer: repo
config_source_name: repo-example-config
- source: config
value: 22
metadata:
config_file: /repo/cmd/examples/config-plan/local.yaml
config_layer: cwd
config_source_name: cwd-example-config
That is much more useful than only seeing a final value with no history.
FromFiles(...) and FromResolvedFiles(...)FromFiles(...) still exists and is still appropriate when you already have a simple ordered list of file paths.
Use FromFiles(...) when:
Use FromResolvedFiles(...) when:
Use FromConfigPlan(...) or FromConfigPlanBuilder(...) when:
plan.Resolve(...)The plan API is intentionally generic. Glazed does not decide what files your application should load. It provides reusable primitives so higher-level systems can define policy cleanly.
A common pattern looks like this:
That lets multiple tools share the same underlying config machinery without sharing the same policy.
| Problem | Cause | Solution |
|---|---|---|
| A later file did not override an earlier one | The file order in the resolved plan is wrong | Print report.String() and verify the layer order and final path list |
A config value shows source: config but no layer metadata | The loader used FromFiles(...) instead of a provenance-aware plan loader | Resolve the plan to []ResolvedConfigFile and load through FromResolvedFiles(...), or load the plan directly with FromConfigPlan(...) / FromConfigPlanBuilder(...) |
| The same file appears twice | Two sources discovered the same path | Enable WithDedupePaths() and inspect the deduped paths in the plan report |
| A git-root source never finds anything | The current process is not inside a git repository or the target file path is wrong | Verify the repo root and the path passed to GitRootFile(...) |
cmd/examples/config-plan