From c0bc5e2b73d65ad7d345b19aea69166c890c9a70 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 9 Nov 2026 18:00:31 +0000 Subject: [PATCH] Settle main-source eligibility for relative context binding --- docs/roadmap/main-relative-inputs.md | 120 +++++++++++++++++---------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/docs/roadmap/main-relative-inputs.md b/docs/roadmap/main-relative-inputs.md index 3c6681f..8a4ff58 100644 --- a/docs/roadmap/main-relative-inputs.md +++ b/docs/roadmap/main-relative-inputs.md @@ -1,8 +1,7 @@ # Main-relative companion imports and overlays -Status: accepted direction, public API structure, absent-main behavior, and -containment/symlink convention; main-source eligibility remains open. -Not implemented. Baseline: `191015e`. +Status: all four design decisions settled; not implemented. Final API spelling +and implementation planning remain. Baseline: `191015e`. ## Purpose @@ -14,7 +13,8 @@ boilerplate to the main file or creating a manifest on disk. The central addition is deferred filesystem-context binding for application-owned underlay inputs, including `Mapping` and `Bytes` sources containing native import -declarations. Bind their imports to the selected main directory during loading. +declarations. Bind their imports to the main input's resolved filesystem context +during loading: for file mains, lookup starts beside the selected main file. Reuse existing optional-file, directory, recursion, pattern, destination, and composition behavior. A new file constructor alone would not cover this workflow, and a second fragment-discovery implementation is unnecessary. @@ -32,6 +32,11 @@ root override, `/srv/campaign/audita.yml` supplies the location lookup uses the configuration root, which can differ from the main directory. Optional discovery exhaustion provides no inferred context. +Non-file mains can already carry an explicit filesystem context. Absent inputs, +including optional discovery exhaustion, can also retain a supplied context; +for an absent host input, an explicit root supplies the base when `BaseDir` is +omitted. Reusing these validated contexts requires no filename inference. + The [layered API](../config.md#layered-loading) has the coordination gap: - An active overlay requires `LayeredOptions.OverlayFilesystem`. Preflight @@ -94,10 +99,11 @@ selects main-derived filesystem binding for that input. `Mapping` and `Bytes` are first-class supported sources for this use. OD-1 settles the public API structure below; no new operator-facing YAML syntax is needed. -Once the main is selected, ConfigKit supplies the underlay import base from -that file's resolved directory and inherits its namespace and containment root -under OD-3. Existing import selection then acquires the companion files. Each -imported file uses its own directory for further relative imports. An import of +Once the main context is resolved, ConfigKit inherits its base, namespace, and +containment root under OD-3 and OD-4. File mains supply their resolved directory; +non-file or absent mains may supply an explicit context. Existing import selection +then acquires the companion files. Each imported file uses its own directory for +further relative imports. An import of `extra.yml` from `glossary/terms.yml` resolves to `glossary/extra.yml`. Applications choose underlay groups and their order. A group composes strictly; @@ -118,14 +124,15 @@ native imports. Ordinary `File` paths do not silently gain new relative semantic ### Layered overlays For an active layered overlay, use an explicit `OverlayFilesystem` when supplied. -Otherwise derive the overlay context automatically from the available main-file -information; the common workflow requires no `OverlayFromMain` flag. An invalid -explicit context fails rather than falling back. Inactive overlays need no +Otherwise derive the overlay context automatically from the main input's usable +resolved context; the common workflow requires no `OverlayFromMain` flag. An +invalid explicit context fails rather than falling back. Inactive overlays need no context. Missing-main and fallback cases are addressed below and in OD-2. -For this newly derived layered context, the lookup location is the main file's -directory, independently of a wider containment root. If the main is -`/srv/campaign/audita.yml` and its permitted root is `/srv`, the selected file +For this newly derived layered context, lookup uses the main context's resolved +base, independently of a wider containment root. For file mains this is the +main file's directory. If the main is `/srv/campaign/audita.yml` and its permitted +root is `/srv`, the selected file is `/srv/campaign/overlays/production.yml`, not `/srv/overlays/production.yml`. Widening containment must not relocate the automatically selected companions. @@ -138,7 +145,8 @@ precedence, and validation retain their existing semantics. ### Absent main and optional companions Optional discovery may find no main file while defaults and CLI settings still -permit a successful application load. When no usable base is available, skip +permit a successful application load. An explicit context on that absent main +can still supply a usable base under OD-4. When no usable base is available, skip optional companion import expansion while retaining the declaring input's ordinary payload and validation. Required imports still fail. Applications can require a base or supply an explicit fallback context on each derived binding. @@ -188,21 +196,22 @@ No release version or additional release gate is selected here. ## Work from the baseline -1. **Finalize context declarations and absence policy.** Resolve the remaining - choices below and prepare Go examples for Audita's mapping/bytes workflow, - explicit context overrides, and independently located embedded inputs. +1. **Finalize API spelling and examples.** Translate the settled decisions below + into concrete declarations and prepare Go examples for Audita's mapping/bytes + workflow, explicit overrides, non-file main contexts, and independently located + embedded inputs. 2. **Carry binding intent through the public facade and load request.** Update the relevant input/options declarations and conversions in `layered.go` and `lifecycle.go`, retaining root-owned public types and normal ownership rules. Distinguish a deferred relationship from a concrete filesystem context. -3. **Bind underlay import contexts after main selection.** Extend preparation - and acquisition in `internal/engine/load_request.go`, `load_input.go`, and - `acquisition.go` as needed. Reuse the acquired main's directory and namespace, - apply the chosen missing-base policy, and delegate selection to existing import +3. **Bind underlay import contexts after main context resolution.** Extend + preparation and acquisition in `internal/engine/load_request.go`, `load_input.go`, and + `acquisition.go` as needed. Reuse the main's resolved base, root, and namespace, + apply the settled missing-base policy, and delegate selection to existing import machinery. Preserve independently supplied contexts. 4. **Derive overlay context and separate lookup from containment.** Remove the unconditional explicit-context requirement for the new layered default. - Preserve explicit and ordinary lookup while enabling a main-directory lookup + Preserve explicit and ordinary lookup while using the main context's resolved base inside a potentially wider root. Reuse selected-overlay loading and its failure and resource-ownership behavior. 5. **Verify the complete workflow and document it.** Add maintained consumer @@ -219,6 +228,9 @@ Validate behavior through the public loading boundary, including: - Explicit and discovered main files, early and later search candidates, and an unrelated invocation directory. Companions follow the same selected main. +- Native file mains, bytes/readers/mappings with explicit host or native contexts, + and absent mains with explicit contexts. Cover absent host root-only contexts, + missing and invalid contexts, and context reuse without enabling main imports. - Mapping and bytes underlays containing optional named-file and recursive directory imports with `*.yml`/`*.yaml` filtering and mounted destinations. - File only, directory only, both, neither, empty directories, and invalid or @@ -308,6 +320,9 @@ context in this order: A fallback can satisfy require-base: the option requires a usable context, not an actual main file. Consumers requiring a main file can make discovery required. +Under OD-4, an explicit context on a non-file or absent main can also satisfy it; +the absence policy applies when no usable base exists, not merely when no main +content exists. An invalid selected context fails; it does not activate a lower-priority choice. Fallback applies only when the main cannot provide a base, never after a failure reading or processing a selected main, or after missing or invalid companion @@ -326,10 +341,11 @@ Companion absence settings do not make a selected overlay optional. ### OD-3: Containment inheritance and symlink convention — settled -Derived companion imports and derived layered overlays inherit the selected -main's resolved filesystem namespace and containment root. Lookup starts at the -main file's resolved directory, even when its configured root is wider. Keep -existing explicit overlay context and ordinary `Load` lookup behavior. +Derived companion imports and derived layered overlays inherit the main input's +resolved filesystem namespace and containment root. Lookup uses its resolved +base under OD-4, which for file mains is the file's resolved directory, even when +its configured root is wider. Keep existing explicit overlay context and ordinary +`Load` lookup behavior. For a host main at `/srv/campaign/audita.yml` with root `/srv`, companion `glossary.yml` resolves to `/srv/campaign/glossary.yml`, while an import of @@ -352,27 +368,41 @@ under the normal rules when they choose another location or scope. Each imported file continues to resolve further imports from its own directory, within the inherited root. Existing import `follow` controls still govern discovered symlink entries; they do not select the base of a symlinked main. -Preserve existing containment checks and traversal controls. For any eligible -native main under OD-4, retain that filesystem's namespace and link-handling +Preserve existing containment checks and traversal controls. For native main +contexts under OD-4, retain that filesystem's namespace and link-handling capabilities without imposing host-filesystem guarantees on every `fs.FS`. -## Open Decisions +### OD-4: Main-source eligibility beyond discovered host files — settled -### OD-4: Main-source eligibility beyond discovered host files +Derive from any usable, validated main filesystem context, regardless of the +main source's format. Document `MainRelative` as using the main input's resolved +filesystem context. For the common file workflow, this means lookup beside the +selected main file. Reuse existing context resolution rather than adding another +acquisition mechanism or requiring main imports to be enabled. -**Settled:** both explicitly selected and discovered host mains support the -workflow. Non-file companion inputs, especially mappings and bytes, are in scope; -this decision concerns the main input that supplies their base. +- Explicit host files and successful discovery supply the canonical file directory + and resolved containment root. +- `Filesystem.File` mains supply their directory and root within their existing + namespace. Dependent loading stays in that namespace. +- `Bytes`, `Reader`, and `Mapping` mains can supply an explicit context with a + usable base, root, and namespace under their existing validation rules. +- `NoSource` and optional discovery exhaustion can retain an explicit main + context. Reuse its resolved base: existing absent-host rules allow an explicit + root to serve as the base when `BaseDir` is omitted; native contexts retain + their existing requirements. -**Recommendation:** also support native `Filesystem.File` mains using their -namespace's path and ownership rules. For an initial file-based scope, non-file -mains use independently supplied or fallback contexts rather than implicitly -providing a main-file directory. This covers the requested workflow and avoids -making discovery a prerequisite for equivalent file inputs. +For example, a bytes main with host `BaseDir: /srv/campaign` and `Root: /srv` +can supply the same companion lookup and containment as a file main in that +directory with the same root. An absent main with a usable explicit context +can likewise supply companion and overlay lookup without creating main content. -**Alternative:** derive from any usable main context, including `BaseDir` on a -byte, reader, or mapping main. This is more uniform with context-based loading -and avoids excluding a known base, but requires explaining main-relative as -main-context-relative and deciding how root-only or absent-main contexts behave. -A host-only initial scope is smaller but leaves native-file consumers with the -same coordination gap. +Without a usable main context, apply OD-2's fallback, optional-import skipping, +or error behavior. Invalid supplied contexts fail rather than selecting a +fallback. Never infer filesystem locations from diagnostic labels or unsuccessful +search candidates, manufacture a main source, or change source evidence to make +a non-file input appear file-backed. + +This preserves one context-reuse rule when applications change how they supply +main content. OD-1's concrete overrides and OD-3's containment, namespace, and +link-handling rules continue to apply. Existing ordinary `Load` and explicit +overlay context lookup semantics remain unchanged.