33 KiB
Filesystem Sources and Underlays Implementation Plan
Purpose and baseline
Implement the feature roadmap in the numbered order
below. Each stage is sized as one focused implementation assignment for a
gpt-5.6-terra coding agent. The baseline is
36172ed20e832b371506d6aceb02dc6c8f50f71b, plus the documentation changes committed
with this plan. No feature implementation is part of this planning pass.
Read docs/development.md and the policies it selects for the stage. The roadmap
owns target semantics; ADR 0007 and
ADR 0008 record the proposed rationale.
Their draft status is independent of implementation progress: do not mark them
accepted automatically. The configuration contract is the
compatibility baseline. Preserve the root-owned facade and private engine/tree
boundary.
At each stage, preserve unrelated work, add focused behavioral coverage with the change, update declaration documentation for public additions, and run the checks specified below. Keep intermediate commits building and tested. Do not expose placeholder public APIs that silently ignore options. Internal scaffolding may precede the public feature. Use plain-English commits under the agent policy.
Settled implementation decisions
Public input surface
Use these names and shapes; bodies and private fields belong to implementation:
// Root-owned opaque filesystem namespace, copying preserves namespace identity.
type Filesystem struct { /* private engine state */ }
func NewFilesystem(label string, filesystem fs.FS) Filesystem
func (f Filesystem) File(name string) Source
func Mapping(label string, values map[string]any) Source
// Extend the existing record; nil FS selects host-filesystem context.
type FilesystemContext struct {
BaseDir string
Root string
FS *Filesystem
}
type Input struct {
Source Source
Imports bool
Filesystem *FilesystemContext
}
type Underlay struct { Inputs []Input }
type LayeredOptions[T any] struct {
Definition Definition[T]
Underlays []Underlay
Overlay string
OverlayFilesystem *FilesystemContext
Overrides []Override
}
func LoadLayered[T any](main Input, supplied ...LayeredOptions[T]) (*Result[T], error)
type ResourceContext struct {
FS fs.FS
BaseDir string
}
func (r *Result[T]) SourceContext(id string) (ResourceContext, bool)
func (r *Resolution) SourceContext(id string) (ResourceContext, bool)
fs above means the standard io/fs package. All named public types are defined
at the root, not aliases of engine types. Existing Load and Options stay
source-compatible apart from the additive field in FilesystemContext. Avoid a
parallel lifecycle implementation: adapt both entry points to one private load
request. No new dependency or minimum-Go-version change is needed.
- Like
Load,LoadLayeredaccepts zero or one options value. A zeroSourcestays invalid. Represent omitted main input asInput{Source: NoSource()}.NoSourcecontributes no document, source ID, or context; preserve its existing invalid import/context combinations. An empty underlay group is a no-op. - Each underlay contains strictly composed inputs, including enabled imports. Groups are ordered lowest to highest. Repeated explicit inputs in different tiers are allowed subject to each source's reuse contract. Within a tier, repeated file identities remain import/repetition errors. Equal scalar claims still conflict. Independent synthetic sources are distinguished by occurrence.
Input.Importsopts in per input and its import graph. The main source and its imports form the main tier. There is no programmatic high-priority escape hatch: a mapping source can be main input or any underlay contribution; CLI overrides retain their explicit-flag contract.- Active named overlays in
LoadLayeredrequireOverlayFilesystemexplicitly. Their names, root-relative lookup, directive prohibition, and version protection follow existing rules. An inactive overlay needs no context; an unused explicit context has no effect. OrdinaryLoadretains implicit selection of its source's filesystem context and existingNoSourcerestrictions.
Filesystem namespaces, paths, and acquisition
NewFilesystem allocates a reusable private namespace token and retains the
caller-owned filesystem without opening it. Copies share the token; separate
constructors are separate namespaces even for the same underlying filesystem.
Never compare or hash arbitrary fs.FS values. Validate nonempty safe labels and
nil filesystem arguments through deferred source diagnostics, following existing
source-constructor conventions. The zero Filesystem yields an invalid source.
Filesystem.File(name) uses a non-root fs.ValidPath file name relative to that
namespace. The default root is . and the base is the file's containing directory.
An optional filesystem context can narrow the root; file sources continue to
derive their own base directory. An explicit FS must identify the same namespace
as the file source; context overrides for native file sources supply that FS.
A host File cannot be rebound to a virtual namespace.
For byte, reader, and mapping inputs, an explicit context supplies their namespace
and base: with FS set, require valid slash-relative Root and BaseDir (use . for
the namespace root), with BaseDir inside Root. With FS nil, preserve the existing
absolute host-directory requirements. Labels never imply a context.
Resolve import paths relative to the declaring document, normalizing joined
paths and rejecting escape from the configured root. Preserve the existing
relative-path/glob language, including contained parent references, optional
missing behavior, sorted matches, bounded traversal, and cycle diagnostics.
Virtual paths use path, not host filepath rules. Imports stay in their namespace;
generic filesystems provide their own symlink behavior. Preserve canonicalization,
regular-file checks, and os.Root containment for host acquisition. Use ordinary
fs.FS operations and fs.ReadDirFile where available; support the standard
fs.ReadDir fallback without claiming control over its internal allocation.
Configuration reads close each file ConfigKit opens, including read/stat/parse failures. Caller readers and caller filesystems are never closed by ConfigKit. Namespace tokens live with source objects; deterministic load-local IDs are assigned by acquisition, not addresses or global counters.
Programmatic representation and diagnostics
Mapping snapshots its input at construction, matching Bytes. Convert directly
to the private document tree using the CLI converter's existing built-in scalar,
[]any, and map[string]any repertoire. Preserve integer precision, finite-float
validation, sorted keys, sequence order, explicit null, and cycle rejection.
Typed nil maps/slices mean null, as in existing overrides. A nil root mapping is
invalid because document roots are mappings; {} is a valid empty mapping.
Constructor failures are stored and surfaced on load. Detach once at construction;
do not retain caller maps or serialize them. Concurrent caller mutation during
construction is outside the supported ownership contract.
Generic conversion returns private issues; adapt them to existing override.*
codes for CLI input, and source.invalid or source.limit_exceeded for mapping
input. Do not leak raw values or conversion errors. Source/path locations use
the label and semantic path, with zero line/column for synthetic input. Apply
reserved configkit directive rules consistently: with imports enabled, a
mapping can contain the same directive structure as YAML. Without context,
relative imports fail under existing context rules.
Ordering, lifecycle, budgets, and metadata
Acquire and strictly compose main input first, construct the schema, and check
the optional version. Only the main source owns the version; main imports and
all underlay documents must satisfy fragment-style version-path protections.
With NoSource main input, pass an absent version to the checker. Do not run the
checker once per tier or validate the final overlaid version instead.
Then acquire/compose underlays in listed order and combine them recursively, apply the composed main tier, selected overlay, and CLI overrides. Execute typed defaulting/decoding, recorded resolution, secret lookup, validation, and final publication once. Syntax/composition/version failures cannot be masked by higher precedence; effective typed validation sees the final assembled input only.
Keep source-record order separate from precedence: main depth-first sources,
underlay tiers in order with each input traversed depth-first, selected overlay,
then CLI. IDs remain source-N. Existing ordinary loads retain their source order.
The history application order follows precedence, not acquisition order.
Extend Layer with LayerUnderlay = "underlay". Add to SourceInfo a Kind SourceKind and UnderlayIndex int (one-based original tier position for underlay
sources, zero otherwise). Define SourceKindFile, SourceKindFilesystem,
SourceKindBytes, SourceKindReader, SourceKindMapping, and SourceKindCLI
with string values file, filesystem, bytes, reader, mapping, and cli.
Imported documents inherit tier role/index and have their actual acquisition kind.
Preserve existing host Path meaning; filesystem-source Path is namespace-relative;
synthetic Path stays empty. Labels for native files combine namespace label and
relative path for display, while identity uses the token and path.
Origins continue to refer to source IDs through locations. Composite origins preserve all contributors; equal-value overrides and displaced descendants stay inspectable. Carry source context separately from serializable source records. Keep existing inspection schema and renderer structure, adding the new layer/kind where appropriate rather than changing existing output gratuitously.
Use one load-wide accounting object for layered loads: existing 64 MiB aggregate
input bytes, 1,024 acquired sources, 1,000,000 input nodes, and glob-entry budget;
existing per-source 8 MiB, depth 256, active import depth 64, and retained-history
limit remain. Mapping key/string bytes use the existing CLI converter's byte
accounting. Charge each accepted occurrence once, including CLI contributions;
do not charge intermediate copies as new input. Explicit empty groups and
NoSource consume no source allowance. Ordinary loads retain their established
limit behavior. Use existing source/import/overlay/override limit codes according
to the acquisition that exceeds a budget. Add CodeUnderlayVersion = "underlay.version" for version protection in underlay roots; imported fragments
retain import.version. Avoid a separate arbitrary tier-count limit.
Passive resource contexts
SourceContext returns false for unknown IDs or sources without a context, and
performs no I/O. The result method is nil-safe; the resolution method follows
existing callback lifetime guards. Return a record copy containing a borrowed
filesystem and slash-relative source BaseDir within its configured root.
Consumers obtain source IDs from ordinary origins; there is no single-context
query that guesses among composite or derived origins. Dependencies continue to
identify the sources of application-derived values through existing metadata.
For native inputs, use a namespace-rooted view over the caller's filesystem and
retain its lifetime semantics. For host input, retain the canonical root path,
not an open os.Root. A private fs.FS adapter validates a relative resource
name, resolves it using existing host containment semantics, opens a fresh root,
and opens the resource. Return a private fs.File wrapper whose Close releases
both file and root, including failed-open cleanup. Expose fs.ReadDirFile when
the underlying directory supports it. Resources themselves are application data:
do not apply configuration YAML parsing or configuration-byte budgets to them.
Helpers such as fs.ReadFile then close the returned handle normally. Errors
follow filesystem access conventions, separately from load diagnostics.
Neither queries nor retained results keep library-owned filesystem handles open. Files/resources can change or disappear after loading; configuration/metadata remain usable. No background work, hidden reopening on metadata queries, filesystem snapshot, universal sandbox, or transaction guarantee is added.
Stage 1: Index mapping assembly and establish scaling baselines
Completed 2026-09-29. Private per-assembly mapping indexes now serve strict composition and recursive overlay lookup while ordered entries and diagnostics remain stable; conflict groups are checked once. Focused Compose/Overlay tests and the everyday checker passed. On Linux amd64, the 512-entry benchmark fixtures measured approximately 4.0 ms/op before and 3.3 ms/op after for wide composition, and 12.9 ms/op before and 12.0 ms/op after for recursive overlay. These are comparative evidence, not timing gates; provenance work still dominates the latter and belongs to the next stage.
Scope: internal/document and engine composition/overlay lookup, with existing
behavior only.
- Add parameterized benchmarks for wide disjoint composition and recursive overlay, recording runtime and allocations at increasing sizes. Keep fixtures within existing budgets and record the baseline in the stage handoff.
- Replace repeated linear mapping-key lookup during bulk assembly with a private indexed builder alongside ordered entries. Build each target index once per assembly session and update it for appended keys. Keep tree traversal and diagnostic order stable; avoid mutable indexes exposed through public APIs.
- Check each strict-composition claim group once while preserving first-conflict ordering and all relevant locations. Preserve original input ownership.
- Extend existing composition tests only for uncovered ordering/conflict risks.
Acceptance: existing Compose/Overlay tests pass unchanged; broad keyed input no longer incurs a full key scan for each insertion. Run the focused composition tests and the everyday checker. Benchmarks are evidence, not timing gates.
Stage 2: Prepare load-owned assembly and incremental provenance
Completed 2026-09-29. Ordinary loads now apply selected overlays and CLI overrides to one owned working document, while copy-preserving helpers remain available for direct use. Acquired history trees stay detached; composite contributors survive overlay application. Origin deletion visits only replaced subtrees, mapping contributors append without copying growing lists, and history replay shares indexed lookup across applications. Focused behavior tests, the everyday checker, and the engine race suite passed. On Linux amd64 at 128 small applications, owned assembly measured about 0.30 ms/op versus 6.0 ms/op for repeated copy-preserving application; alternating subtree/scalar replacement measured about 0.39 versus 7.3 ms/op. These benchmarks are evidence, not gates.
Scope: compose.go, overlay.go, origin indexes, override application, and
history replay in internal/engine.
- Introduce a private load-owned accumulator with explicit ownership. Existing copy-preserving helpers may wrap it, but sequential assembly must mutate one owned working tree rather than clone all accumulated state per tier.
- Retain immutable acquired inputs for history, detached from mutable working nodes. Parameterize internal overlay application by incoming layer and origins so a composed tier's imported contributors are preserved.
- Remove displaced origins by visiting the affected subtree. Accumulate mapping contributors without copying an ever-growing ancestor list on each addition; materialize defensive public records at the existing publication boundary.
- Preserve equal-value replacement events, descendant displacement, redaction, and typed-decoding reconciliation. Extend benchmarks for many small applications and repeated subtree/scalar replacement. Do not add a persistent-tree framework.
Acceptance: existing override/history/rendering/ownership tests pass; benchmark
growth avoids accumulated full-tree copying. Run the everyday checker and
GOPROXY=off go test -race ./internal/engine for ownership changes.
Stage 3: Unify private filesystem acquisition without public changes
Completed 2026-09-29. Imports, glob traversal, and selected-overlay loading
now use a private access boundary with the existing contained host backend and
a native fs.FS backend. Namespace tokens and occurrence identities are separate
from labels and paths; repetition tracking is composition-local and input/glob
accounting can be shared. Native tests cover slash-relative imports, sorted
globs, optional missing paths, root escape, overlays, repeated/cyclic imports,
non-comparable filesystems, and handle closure on success and representative
failures. Existing host tests, focused native tests, and the everyday checker
passed. No public constructor or context field was added.
Scope: engine source/file, filesystem, import, glob, and selected-overlay code.
- Extract a small private namespace/access boundary used by imports and overlays. Keep the host implementation's canonicalization, containment, regular-file checks, safe diagnostics, deterministic traversal, and close behavior.
- Separate namespace identity from source labels and resource paths. Make repeated/cycle tracking belong to an input composition group, and accounting capable of being shared across groups.
- Add a native
fs.FSbackend internally. Resolve paths using slash semantics, preserve glob grammar and optional-missing behavior, and avoid host fallback. - Use real host tests plus
fstest.MapFSand a small tracking/failing filesystem to cover imports, missing paths, directory/read failures, and handle closure. Include a filesystem with a non-comparable dynamic value.
Acceptance: all existing host source/import/glob/overlay tests remain green; native backend tests establish namespace isolation and supported paths. Run the everyday checker. No public constructors land until the next stage.
Stage 4: Expose native filesystem sources through the facade
Completed 2026-09-29. The root API now exposes reusable native filesystem
namespaces, namespace-aware contexts, and source acquisition kinds. Ordinary
loads support native files, opt-in imports, selected overlays, and byte/reader
contexts while retaining host semantics. Public tests cover embedded fixtures,
fstest.MapFS, same-label namespace isolation, wrapper copies and reuse,
constructor/context diagnostics, and host/CLI source kinds. The everyday checker
passed, including public-boundary and documentation checks.
- Add
Filesystem,NewFilesystem,Filesystem.File, and the additive FS context field at root and engine boundaries. Constructors follow deferred diagnostics; copying the wrapper preserves namespace identity. - Integrate native sources with ordinary
Load, including explicitly enabled imports and named overlays. Enforce the path/context rules settled above. - Add
SourceKindand populate it for every current/native acquisition. Preserve host paths and use namespace-relative virtual paths with accurate locations. - Add facade-level tests using embedded fixtures,
fstest.MapFS, byte/reader context, namespace-label collisions, and source reuse. Keep backend error cases at their Stage 3 owner. Update current source/API docs to this implemented scope.
Acceptance: native Load works through the public API and existing sources retain
their contracts. Run the everyday checker, including public-boundary tests.
Stage 5: Add snapshotted mapping sources
Completed 2026-09-29. Mapping now snapshots supported built-in values at
construction, retaining safe deferred diagnostics for invalid roots, values,
cycles, nonfinite floats, and limits. A shared private converter preserves CLI
override behavior through a separate adapter. Mapping inputs participate in
ordinary directives, imports, version checks, decoding, and inspection with
their own source kind and synthetic locations. Public tests cover YAML
equivalence, reuse after caller mutation, null/empty/zero distinctions, exact
integers, explicit filesystem context, and safe failures. The everyday checker
and engine race suite passed.
- Extract reusable value conversion from
override_value.go; keep CLI-specific path, label, and diagnostic handling in its adapter. - Implement
Mappingat root and engine boundaries with constructor-time snapshotting and deferred safe failures. Source-ID assignment at acquisition must not mutate a reusable stored tree shared by another load. - Route converted documents through the same directive processing, strict composition, version checking, decode, and inspection path as YAML sources.
- Test omission/zero/false/null, nil versus empty collections/root, exact integers, invalid/cyclic values, imports with explicit context, and caller mutation after construction. Verify mapping input does not gain CLI origins or restrictions.
Acceptance: equivalent byte/mapping inputs have equivalent semantic results and origin meaning, with genuine location differences allowed. Existing override diagnostics remain stable. Run the everyday checker and engine race tests.
Stage 6: Implement private grouped-underlay orchestration
Completed 2026-09-29. Ordinary and grouped engine loads now use one private request lifecycle. Group-local strict composition and repetition tracking, load-wide source/byte/node/glob accounting, main-owned version checking, underlay layer/index metadata, and precedence-ordered assembly feed one final decode, resolution, validation, and publication. Engine tests cover two tiers, multiple contributions, source/history order, absent main/defaults-only loads, main/import and group conflicts, version protection, shared limits, reusable files across tiers, and replacement of invalid intermediate typed input. The everyday checker and engine race suite passed. No root layered API is exposed yet.
- Add private request/input/group structures matching the settled facade design.
Adapt ordinary
Loadto the shared core while preserving its options and source-order behavior. Keep its existing validation order where observable. - Strictly compose each group with group-scoped repetition tracking and shared aggregate accounting. Apply the lifecycle order defined above, including the single main-owned version check before underlay precedence assembly.
- Add underlay layer/index metadata and version diagnostics. Keep history in precedence order while source IDs follow acquisition order. Apply composed main origins over underlays without relabeling main imports as overlays.
- Reuse Stage 2 assembly and the existing decode/resolution/secret/publication pipeline. Empty groups, omitted main, defaults-only loads, and underlay-only loads must follow the chosen semantics.
- Add engine coverage for two underlay tiers, multiple strict contributions, main/import conflicts, version checks/absent main, limits across groups, and one final decode/resolve/validate lifecycle. Test an invalid intermediate typed value replaced by valid higher input, alongside unmaskable syntax conflicts.
Acceptance: engine layered requests produce correct values and full provenance; ordinary-load tests remain green. Run the everyday checker and engine race tests.
Stage 7: Expose layered loading and complete inspection integration
Completed 2026-09-29. The root facade now offers Input, Underlay,
LayeredOptions, and LoadLayered with copied options/context records. Explicit
host or native selected-overlay contexts work independently of the main source;
repetition uses native namespace identity rather than a display path alone.
Root layer/source conversions and explanation rendering include underlays.
Public tests cover mixed host/native/mapping tiers, recursive precedence,
empty/zero/false/null and sequence replacement, source order and tier indexes,
composite and displaced origins, resolution, redaction, and zero-underlay
equivalence. The everyday checker and full race suite passed.
- Add
Input,Underlay,LayeredOptions, andLoadLayeredroot wrappers and conversions. Copy option slices/context records into private load state while preserving reusable namespace identities and single-consumption readers. - Wire explicit selected-overlay context for layered loads, including host and native namespaces. The selected file may not duplicate an acquired file in that namespace, preserving selected-overlay repetition protection.
- Finish root Layer/SourceInfo conversions, history/explanation rendering, composite mapping attribution, deleted descendants, default and derived origins, and secret redaction. New records must remain detached.
- Add representative public tests for full precedence and same-name recursive mapping combination; retain zero/false/empty/null distinctions and atomic sequence replacement. Test ordinary versus zero-underlay layered equivalence using equivalent explicit overlay context when required.
Acceptance: callers can load native, host, and mapping tiers in one public call;
configuration, metadata, and safe rendering agree. Run the everyday checker and
GOPROXY=off go test -race ./....
Stage 8: Add handle-free source-context queries
Completed 2026-09-29. Results and active resolution callbacks now expose
passive per-source ResourceContext records. Native views borrow caller-owned
filesystems under their configured roots; host views retain only canonical root
paths and open/close fresh roots with each resource file. Main, imported,
selected-overlay, and explicitly contextual synthetic sources have their own
base directories; contextless and CLI sources do not. Public tests verify
origin-based resource access, no I/O on queries, later mutation/deletion,
borrowed-filesystem closure, and callback guards. Tracking tests cover resource
Open failure cleanup, returned file/directory ownership, and loading-handle
closure across success and representative failures. The everyday checker and
full race suite passed.
- Retain context descriptors keyed by acquired source ID, including imported sources, in private inspection and live resolution state. Keep descriptors separate from serializable metadata and preserve callback guards.
- Implement the root query methods and
ResourceContext. Native contexts expose rooted borrowed access; host contexts use the passive adapter described above. Include host main files even when imports are disabled, and explicit contexts on byte/reader/mapping sources. Sources without contexts return false. - Verify each origin's BaseDir points to its actual source. Composite origins require callers to follow individual locations; derived dependencies retain their existing meaning. No arbitrary last-source context is selected.
- Test that queries cause no filesystem operations; loaded values and metadata survive source deletion; later resource access observes mutation/missing files; and caller filesystem closure affects only later resource operations.
- Verify owned files/root handles close on successful loading and representative acquisition, parse, composition, version, and validation failures. Test resource Open failure cleanup and returned-file Close ownership separately. Prefer narrow tracking seams over timing-based goroutine/descriptor-count assertions.
Acceptance: a retained underlay resource can be opened relative to its true origin after load, with no library-owned loading handles retained. Run the everyday checker and full race suite.
Stage 9: Validate cross-feature consistency and scaling
Completed 2026-09-29. A compact public matrix now checks equivalent host, native, byte, reader, and mapping inputs; tier-local repetition versus cross-tier reuse; reader consumption; and safe diagnostics/effective/history output after secret replacement. Engine tests verify exact occurrence accounting across imports, shared glob/source limits, and the mixed-source CLI ceiling. Rendering now indexes source labels once per explanation instead of scanning all records for every location. Loading and passive context ownership were reviewed against ADR 0008; no handles or background work are retained by queries.
On Linux amd64 with -benchmem and 100 ms samples, the 512-entry assembly-only
composition measured about 3.5 ms/op (the earlier post-index baseline was about
3.3 ms), and recursive overlay about 2.2 ms/op (about 12 ms at the first-stage
handoff before subsequent provenance work). The 128-application owned assembly
remained about 0.30 ms/op versus 6.35 ms/op for copy-preserving application.
End-to-end layered measurements were about 7.15 ms/op for 512 broad catalog
entries, 2.59 ms/op for 128 small tiers, 3.27 ms/op for 128 alternating subtree
replacements, 1.86 ms/op for 64 imported native defaults, and 1.36 ms/op for
64 retained-history applications. Allocations grew roughly with these fixture
sizes. I/O, sorting, decoding, callbacks, and retained output contribute to
full-path cost; these observations are not timing gates or an unconditional
complexity guarantee. The everyday checker and full race suite passed.
- Consolidate a small semantic equivalence matrix across host, native, bytes, readers, and mappings, using equivalent logical inputs and contexts. Cover strict conflict versus intentional precedence and version-path preservation; avoid duplicating every lower-level case at every source permutation.
- Exercise load-wide limits across mixed sources/CLI, repeated explicit tiers, shared namespace imports, and source IDs/history order. Protect against double charging and reader re-consumption. Confirm secret canaries stay absent from diagnostics and historical/effective output after replacements.
- Run parameterized benchmarks for broad catalogs, many small tiers, subtree replacement, imported native defaults, and retained history. Compare Stage 1 baselines with the final path. Address demonstrated avoidable quadratic scans or accumulated copying within this feature's scope. Account for sorting, filesystem operations, callbacks, and output/history size explicitly.
- Review source/context ownership and success/failure cleanup against ADR 0008. Keep performance evidence in the stage handoff, not permanent timing gates.
Acceptance: no semantic discrepancy remains among equivalent operations; scaling
results substantiate the engineering target without an unconditional O(n) claim.
Run the everyday checker and full race suite, reusing successful checks for an
unchanged final tree. Benchmarks run with -benchmem and parameterized sizes.
Stage 10: Complete consumer examples and architecture documentation
- Add a maintained simple native-filesystem example and a combined example with embedded defaults, two underlay tiers, independently located main input, programmatic contributions, and a relative resource from a retained origin. Keep fixtures secret-free and offline; include zero/false presence and domain validation. The consumer should perform no generic post-load merge/defaulting or provenance reconstruction. Reuse existing example/test conventions.
- Update
docs/config.md, Go declarations,docs/consumers/, README,docs/internal/overview.md, and applicable architecture/deferred-scope text to describe implemented support. Preserve strict-within-tier and recursive- between-tier terminology, distinguish source kinds from precedence roles, and document context lifetimes and the explicit source-ID query workflow. - Add the relevant tests/commands to the development guide's existing task map. Review ADR links and statuses, preserving draft status pending acceptance and accepted ADR decision bodies. Keep named inheritance separately deferred.
- Mark implementation progress accurately in this plan and the roadmap. Do not retire plans, choose/tag a version, prepare release gates, or publish as part of implementation; those belong to separate release preparation.
Acceptance: examples compile and execute, documentation describes only completed
capabilities, and simple Load usage remains simple. Run the everyday checker
(which includes examples, links, and boundary checks); run the full race suite
if code changed since its last successful run. Report completed stages, checks,
benchmark conclusions, and any remaining limitations honestly.
Validation commands
Use the development guide's focused commands during each stage. The normal
stage-end check is ./scripts/check.sh; it already includes tests, vet, build,
formatting, and documentation links. Ownership stages additionally use the race
commands above. Benchmarks may use:
GOPROXY=off go test ./internal/engine -run '^$' -bench 'Compose|Overlay|Underlay' -benchmem
Name the new benchmarks to match that expression. Select representative sizes under existing limits. No CI wait, human macOS validation, external service, credentials, or new release gate is part of this plan.
Open Questions
None required to implement this plan. The remaining roadmap recommendations have been selected above: labeled snapshotted mappings and separate source-context queries. Private helper names and package-local factoring remain implementation choices within these contracts. If implementation uncovers a genuine conflict with existing behavior, report it rather than silently adding a restriction, weakening compatibility, or broadening a guarantee.