287 lines
16 KiB
Markdown
287 lines
16 KiB
Markdown
# Main-relative underlays and overlays
|
|
|
|
Status: accepted direction; API and filesystem-policy decisions remain open.
|
|
Not implemented. Planning baseline: `191015e`.
|
|
|
|
## Purpose
|
|
|
|
Allow an application to select its main configuration through ConfigKit's
|
|
existing discovery descriptions, then load explicitly declared underlay files
|
|
and a selected overlay relative to that main file. Applications should not need
|
|
to repeat discovery themselves or run a preliminary configuration load to learn
|
|
the selected directory.
|
|
|
|
The application continues to choose its search conventions, underlay tiers, and
|
|
overlay name. ConfigKit resolves their filesystem relationship within the same
|
|
load. Independently located inputs remain useful and can be combined with
|
|
main-relative inputs.
|
|
|
|
## Current baseline
|
|
|
|
The [discovery contract](../config.md#configuration-discovery) resolves a
|
|
`SearchConfig` source inside loading. It selects one host file or absent input;
|
|
errors processing the selected file do not resume the search. The public
|
|
constructor returns a source description, not a selected filename.
|
|
|
|
Ordinary `Load` already derives an overlay context from a selected file. Without
|
|
an explicit root override, selecting `/etc/example/config.yml` permits overlay
|
|
`production` to load from `/etc/example/overlays/production.yml`. Optional search
|
|
exhaustion supplies no inferred root; ordinary loading then needs an explicit
|
|
context for an active overlay.
|
|
|
|
The [layered API](../config.md#layered-loading) differs:
|
|
|
|
- An active overlay requires `LayeredOptions.OverlayFilesystem` independently of
|
|
the main source. Grouped preflight rejects its absence before discovery runs.
|
|
- `Underlay.Inputs` contains independently specified sources and contexts. A
|
|
file source can establish its own context, but no declaration binds an
|
|
underlay filename to the main file that discovery will select.
|
|
- Main acquisition already precedes underlay acquisition. Underlay groups are
|
|
subsequently composed from lowest to highest priority, beneath the main tier.
|
|
- Ordinary relative imports are peers of their declaring input. They do not
|
|
express lower-priority underlay tiers.
|
|
|
|
The extension can therefore reuse the selected main's acquired filesystem
|
|
information and the existing grouped lifecycle. It needs a way to express the
|
|
relationship publicly and bind it before dependent acquisition.
|
|
|
|
## Targeted end state
|
|
|
|
### Application declarations
|
|
|
|
An application can declare, before loading:
|
|
|
|
1. A main `Input` using `SearchConfig`, with any supported combination of search
|
|
presets and custom candidates.
|
|
2. One or more underlay files whose paths are relative to the selected main
|
|
file's directory, grouped and ordered through the existing underlay API.
|
|
3. A selected overlay whose context is derived from the selected main file.
|
|
|
|
The relationship is explicit and declarative. It does not require a new
|
|
application callback, a second load, or a public intermediate configuration.
|
|
Exact API spelling and context selection are subject to OD-1. Missing-main
|
|
behavior, filesystem policy, and eligible main sources are subject to OD-2
|
|
through OD-4; the examples below assume a successfully selected host file.
|
|
|
|
Existing input destinations, placement, import opt-in, and load-wide policies
|
|
remain applicable to underlay inputs. The new relationship determines where an
|
|
input is acquired; it does not change its precedence or composition rules.
|
|
An underlay's own relative imports continue to resolve from that underlay's
|
|
declaring directory.
|
|
|
|
### Examples
|
|
|
|
These describe intended behavior, not implemented Go syntax. Assume there is no
|
|
explicit configuration-root override and that the selected main file is:
|
|
|
|
```text
|
|
/etc/example/config.yml
|
|
```
|
|
|
|
| Application declaration | Selected input |
|
|
| --- | --- |
|
|
| Main-relative underlay `defaults/base.yml` | `/etc/example/defaults/base.yml` |
|
|
| Main-relative underlay `defaults/site.yml` | `/etc/example/defaults/site.yml` |
|
|
| Main-relative overlay context, overlay name `production` | `/etc/example/overlays/production.yml` |
|
|
|
|
If the underlays are placed in separate groups in that order, effective priority
|
|
is typed defaults, base underlay, site underlay, main, overlay, then CLI.
|
|
Putting both underlay inputs in one group instead composes them strictly as
|
|
peers. A declaration of `extras.yml` inside `defaults/base.yml` resolves beside
|
|
that file, at `/etc/example/defaults/extras.yml`.
|
|
|
|
If discovery instead selects a file in the user's configuration directory, the
|
|
same relative declarations follow that file's directory. An embedded or otherwise
|
|
independent underlay may coexist with these inputs and retains its own namespace
|
|
and context. No directory is searched for underlays merely because it exists.
|
|
|
|
### Loading and inspection
|
|
|
|
Resolve the main-relative descriptions using the selected main's acquired
|
|
filesystem information, before acquiring the inputs that depend on it. Keep
|
|
discovery within the existing single loading lifecycle and preserve main-first
|
|
acquisition, underlay tier order, version processing, selected-overlay timing,
|
|
and the existing defaults, resolution, secret, and validation phases.
|
|
|
|
Static declaration errors can still be checked before source acquisition;
|
|
checks that need the selected directory must run after it is available. The
|
|
implementation plan should identify this division without imposing a new global
|
|
ordering guarantee for unrelated diagnostics or filesystem checks.
|
|
|
|
After binding, use ordinary source acquisition, parsing, import handling, and
|
|
overlay loading. A missing required input, unreadable selected file, or invalid
|
|
configuration follows the applicable existing failure rules; it does not cause
|
|
main discovery to try another candidate. This feature does not itself introduce
|
|
optional-file syntax, underlay globbing, additional overlay filenames, or a new
|
|
overlay stack.
|
|
|
|
Acquired files retain their actual source identity, path, source-local locations,
|
|
layer, underlay index, and passive resource context. Do not create an extra
|
|
source or precedence tier for the relative declaration itself. Preserve existing
|
|
namespace identity, repetition scope, shared acquisition budgets, detached
|
|
inspection, and resource cleanup. Use ordinary diagnostic classifications where
|
|
they fit; decide whether any new classification is needed during API design.
|
|
|
|
### Compatibility and scope
|
|
|
|
Preserve successful existing ordinary and layered loading behavior. Main-relative
|
|
selection should be additive and opt-in, with independently supplied sources and
|
|
contexts continuing to work. Preserve existing named-overlay lookup semantics;
|
|
OD-3 determines the root used by the new context selection.
|
|
|
|
The work does not add general resource-path resolution, changes to process working
|
|
directory, remote acquisition, or a generic source-provider callback framework.
|
|
These are scope boundaries for this feature, not new architectural exclusions.
|
|
There is no new filesystem race-protection or sandbox guarantee.
|
|
|
|
The design follows [ADR 0019](../adr/0019-explicit-configuration-discovery.md):
|
|
applications select conventions while ConfigKit performs selection and loading.
|
|
Review whether the finalized context-ownership decision needs an additional ADR;
|
|
do not rewrite an accepted ADR's decision content. No release version or new
|
|
release gate is selected by this roadmap.
|
|
|
|
## Work from the baseline
|
|
|
|
1. **Settle the public declaration and filesystem policies.** Resolve the open
|
|
decisions below, document combinations with explicit contexts, and prepare
|
|
representative API examples. Keep public types owned by the root package.
|
|
2. **Represent the relationship through the facade and request preparation.**
|
|
Update the relevant declarations and conversions in `layered.go`,
|
|
`lifecycle.go`, or `source.go` as the selected API requires. Carry the intent
|
|
through the private request without prematurely interpreting paths against
|
|
the working directory. Preserve reusable-description and input-ownership
|
|
conventions.
|
|
3. **Bind dependent inputs during acquisition.** Extend the request preparation
|
|
and acquisition boundary in `internal/engine/load_request.go`,
|
|
`load_input.go`, and `acquisition.go`. Reuse discovery's selected concrete
|
|
source and filesystem-context resolution. Establish which checks remain in
|
|
preflight and which require resolved main information.
|
|
4. **Integrate underlays and overlays.** Resolve relative underlay files into
|
|
ordinary acquisitions with their own import bases; derive the selected
|
|
overlay context and reuse its loader. Preserve independent input contexts,
|
|
namespace handling, cleanup on failure, and source/error evidence.
|
|
5. **Verify observable behavior and update documentation.** Add focused public
|
|
coverage and maintained consumer examples, then update the discovery,
|
|
layered-loading, and selected-overlay contracts, declaration comments, and
|
|
relevant internal ownership documentation. Explain the difference between a
|
|
file's directory and the configured root when they differ.
|
|
|
|
This is dependency order for a later implementation plan, not a required commit
|
|
layout or a requirement to expose intermediate stages to consumers. Reuse the
|
|
existing lifecycle; a new discovery pass or a second configuration load is not
|
|
needed.
|
|
|
|
## Validation and completion
|
|
|
|
Exercise the behavior through the public API, supported by focused engine tests:
|
|
|
|
- Main discovery selects an early or later candidate; the dependent files follow
|
|
the actual selection, including when it is outside the working directory.
|
|
- Relative and independent underlays coexist; multiple groups retain precedence,
|
|
and peers in one group retain strict-composition conflicts.
|
|
- Underlay imports use their own file's directory. Placement and source-version
|
|
policies remain effective after binding.
|
|
- Active and inactive overlays, explicit contexts, optional search exhaustion,
|
|
and required-search failures follow the settled policies.
|
|
- Symlinked main files, explicit root overrides, parent-relative paths, and
|
|
namespace boundaries follow OD-3 and OD-4 without broadening existing access
|
|
guarantees.
|
|
- Selected-file failures and dependent acquisition failures are terminal; no
|
|
repeated full load or duplicate defaults/resolution/validation callbacks are
|
|
introduced.
|
|
- Source records, underlay indexes, origins, passive contexts, and failed-load
|
|
evidence identify the actual acquired inputs. Existing handle cleanup and
|
|
budget accounting still cover dependent acquisitions.
|
|
- Existing ordinary discovery and overlay loading, independent layered inputs,
|
|
and reuse of descriptions across loads retain their behavior.
|
|
|
|
Run focused checks during implementation and the repository's applicable normal
|
|
checks before completion. No benchmark campaign or additional release gate is
|
|
required for this feature. Completion means applications can demonstrate the
|
|
discovery-plus-relative-input workflow in one load, with documented public
|
|
declarations and verified failure behavior.
|
|
|
|
## Open Decisions
|
|
|
|
### OD-1: Public declaration shape and explicit-context combinations
|
|
|
|
**Recommendation:** add declarative, explicit choices for a main-relative
|
|
underlay file and a main-derived overlay context. A dedicated underlay source
|
|
constructor and a small overlay context-selection option are plausible shapes;
|
|
names remain to be chosen. Keep the existing independent-source/context path as
|
|
the default. For overlays, an explicitly supplied `OverlayFilesystem` should
|
|
take precedence over a request to derive context, allowing application overrides
|
|
without changing shared setup. Specify analogous precedence for any permitted
|
|
underlay context overrides. This keeps common use short without making nil
|
|
contexts change meaning or introducing callback lifetime concerns.
|
|
|
|
**Alternative:** add a general deferred filesystem-context descriptor shared by
|
|
inputs and overlays. This could give context selection one uniform representation
|
|
and accommodate later relationships, but risks expanding a small feature into
|
|
a general context API. It is attractive if concrete API sketches show less
|
|
duplication than separate declarations. Rejecting simultaneous explicit and
|
|
derived overlay choices is also viable: it catches ambiguous setup, but requires
|
|
callers to clear the derived choice when supplying an override.
|
|
|
|
### OD-2: No main file is selected
|
|
|
|
**Recommendation:** when an active dependent declaration needs a main-file base
|
|
and none exists, fail with a clear diagnostic. Do not use an attempted candidate
|
|
directory or the working directory as an implicit substitute. This makes an
|
|
unresolvable relationship explicit without making discovery globally required.
|
|
Applications can require the main search or use independent inputs and an
|
|
explicit overlay context when absent-main operation is intended. Inactive
|
|
overlays do not require a derived context.
|
|
|
|
**Alternative:** treat main-relative inputs as conditional on main presence and
|
|
skip them after optional exhaustion. This supports defaults-only startup and is
|
|
consistent with a permissive search, but could silently omit an explicitly
|
|
selected overlay or expected underlay. A selectable skip policy could support
|
|
that use case, at the cost of another option; adopting it would require defining
|
|
its effect on tier evidence and distinguishing absent main from a missing file
|
|
beside an existing main.
|
|
|
|
### OD-3: Relative base, configuration root, and symlinks
|
|
|
|
**Recommendation:** anchor underlay filenames to the canonical selected main
|
|
file's directory, consistent with existing file/import behavior. Derive overlay
|
|
context from the main's resolved context, so overlay lookup remains relative to
|
|
its configuration root. When an explicit main root is wider than the main
|
|
directory, underlay filenames still use the directory as their base and overlays
|
|
use that root. Inherit the main's root for dependent underlay acquisition and
|
|
imports unless an explicitly supported context override selects another root.
|
|
Each acquired underlay supplies its own import base. Apply existing canonical
|
|
containment checks; allow parent-relative traversal when it stays within the
|
|
selected root rather than adding a blanket prohibition on `..`.
|
|
|
|
This distinguishes lookup from containment, reuses existing machinery, and
|
|
supports sibling directories under an explicitly wider root. It also means a
|
|
symlinked main selects resources beside its target, which must be documented.
|
|
|
|
**Alternative:** always create a fresh root at the main file's directory for
|
|
both kinds of input. This makes every main-relative lookup visually local,
|
|
including with a wider main root, but discards intentional root configuration
|
|
and prevents some sibling-directory layouts. Using the main file's lexical
|
|
symlink directory instead is another viable convention for deployment bundles;
|
|
it would differ from current canonical import behavior and require separate
|
|
handling for lookup and containment.
|
|
|
|
### OD-4: Eligible main sources and namespaces
|
|
|
|
**Recommendation:** support discovered and explicit host files, and native
|
|
`Filesystem.File` mains, using each namespace's existing path rules and resource
|
|
ownership. Discovery currently selects host files; a native file should not
|
|
require discovery merely to provide the same directory relationship. Bind paths
|
|
within the selected namespace and reuse its identity. For this initial feature,
|
|
byte, reader, mapping, and absent mains do not supply a main-file directory;
|
|
independently specified contexts remain available for those workflows.
|
|
|
|
**Alternative:** derive from any resolved main filesystem context, including an
|
|
explicit `BaseDir` for non-file inputs. This is more uniform for context-based
|
|
loading and avoids an artificial file requirement when a base is already known,
|
|
but changes the concept from relative-to-main-file to relative-to-main-context.
|
|
It needs explicit rules for absent mains and root-only contexts, where root and
|
|
base are not interchangeable. Limiting the first implementation to host files
|
|
is also viable and smaller, but would leave native-filesystem consumers with
|
|
the same coordination gap.
|