Plan configuration discovery implementation
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
|
||||
Status: proposal with OD-1 through OD-5 and the final review clarifications
|
||||
settled and integrated below.
|
||||
This document describes future work, not an implemented contract. A staged
|
||||
implementation plan will follow review of the complete roadmap.
|
||||
This document describes future work, not an implemented contract. The
|
||||
[implementation plan](implementation.md) owns the staged work and concrete API
|
||||
decisions.
|
||||
|
||||
## Purpose And Ownership
|
||||
|
||||
@@ -144,7 +145,9 @@ between all presets. Selecting one does not automatically select any others.
|
||||
|
||||
### Illustrative Consumer API
|
||||
|
||||
Names below are illustrative and are not implemented Go API:
|
||||
Names below are illustrative and are not implemented Go API. The
|
||||
[implementation plan](implementation.md#public-api) fixes the concrete signatures
|
||||
and the `Searches` constructor for concatenating several searches:
|
||||
|
||||
```go
|
||||
// A simple system-wide application.
|
||||
@@ -155,9 +158,11 @@ source := configkit.SearchConfig("myapp", configkit.UnixSystemDirectories())
|
||||
// A consumer-selected working-directory, user, then system search.
|
||||
source := configkit.SearchConfig(
|
||||
"myapp",
|
||||
configkit.WorkingDirectory(),
|
||||
configkit.UserConfigDirectory(),
|
||||
configkit.UnixSystemDirectories(),
|
||||
configkit.Searches(
|
||||
configkit.WorkingDirectory(),
|
||||
configkit.UserConfigDirectory(),
|
||||
configkit.UnixSystemDirectories(),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -165,8 +170,10 @@ source := configkit.SearchConfig(
|
||||
// Explicit custom candidates followed by a selected conventional fallback.
|
||||
source := configkit.SearchConfig(
|
||||
"myapp",
|
||||
configkit.Paths("/run/myapp/config.yml", "/opt/myapp/config.yaml"),
|
||||
configkit.UnixSystemDirectories(),
|
||||
configkit.Searches(
|
||||
configkit.Paths("/run/myapp/config.yml", "/opt/myapp/config.yaml"),
|
||||
configkit.UnixSystemDirectories(),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -340,41 +347,20 @@ final validation, and frozen inspection after selection. Search-list order does
|
||||
not change precedence among underlays, the main input, an overlay, and CLI values.
|
||||
An explicitly selected overlay is not another main-file fallback candidate.
|
||||
|
||||
## Work Needed From The Baseline
|
||||
## Target Integration And Documentation
|
||||
|
||||
The subsequent implementation plan should cover:
|
||||
Discovery belongs at the source-acquisition boundary. A selected file retains
|
||||
the existing file-loading behavior and optional exhaustion shares the absent-main
|
||||
lifecycle above. Public search descriptions remain root-owned while private
|
||||
implementation handles selection and loading. The feature's implemented
|
||||
configuration contract and maintained consumer examples will explain the
|
||||
three-part discovery model, ordering, and context behavior.
|
||||
|
||||
- Define filename-group, directory-search, preset, and search-source representations,
|
||||
slug validation, and a search-wide requiredness option. Keep construction
|
||||
separate from acquisition and the simple preset selection concise. Distinguish
|
||||
omitted and explicitly empty directory-search filename selections.
|
||||
- Implement ordered expansion and concatenation for presets, filename groups,
|
||||
custom searches, and explicit file candidates, followed by first-match selection
|
||||
through existing file access. Resolve each source once per consumption before
|
||||
entering its parse and composition lifecycle.
|
||||
- Integrate selected and absent outcomes with ordinary and layered main inputs,
|
||||
placement, imports, application version checking, and overlay contexts according
|
||||
to OD-3, including root-only host contexts for absent main inputs and normal
|
||||
file-context checks when a file is selected. Preserve ownership and close
|
||||
acquired handles on success and failure.
|
||||
- Preserve real selected-source metadata and safe diagnostics without treating
|
||||
absent candidates as configuration contributions.
|
||||
- Exercise preset, directory, filename-group, and filename ordering, reversed
|
||||
group precedence, the Unix system preset's 14 candidates and named-only `/etc`
|
||||
search, custom-list composition, both filename extensions, no-match behavior,
|
||||
empty components and searches, omitted versus empty filename selections,
|
||||
skipped directory resolution for empty filename selections, slug-dependent and
|
||||
slug-independent searches, literal custom paths and filename components,
|
||||
required searches, unavailable preset bases, errors that must stop fallback,
|
||||
repeated loads, and untouched explicit `File`
|
||||
behavior. Cover imports, overlays, underlays, defaults, CLI, source versions,
|
||||
source contexts, and source-list/inspection evidence at their boundaries.
|
||||
- Clarify the architecture policy's consumer-selected discovery boundary. Record
|
||||
any warranted durable ownership decision in an ADR; keep preset filenames and
|
||||
API mechanics in their canonical contract when implemented. Update consumer
|
||||
examples and the configuration contract when the feature lands. Explain the
|
||||
three-part discovery model and its mathematical terminology in the eventual
|
||||
documentation, with simple preset selection and custom composition examples.
|
||||
The architecture policy should recognize explicitly consumer-selected discovery
|
||||
conventions while preserving consumer ownership of search policy. An ADR records
|
||||
that durable responsibility boundary; preset filenames and API mechanics belong
|
||||
in their canonical contracts once implemented. Execution steps and validation
|
||||
coverage are defined in the [implementation plan](implementation.md).
|
||||
|
||||
This roadmap does not change implementation, policy, or ADRs by itself. The
|
||||
released v0.9.0 code, including the completed sequence integration and remediation
|
||||
|
||||
449
docs/roadmap/implementation.md
Normal file
449
docs/roadmap/implementation.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# Configuration Discovery Implementation Plan
|
||||
|
||||
Status: ready for implementation. No stages are complete.
|
||||
|
||||
Implement the target behavior in [the discovery roadmap](discovery.md). That
|
||||
document owns the selected conventions and OD-1 through OD-5; this plan owns the
|
||||
API decisions, implementation sequence, and validation. Each numbered stage is a
|
||||
separate Luna-sized unit. Complete stages in order and keep each commit buildable.
|
||||
|
||||
## Baseline And Working Rules
|
||||
|
||||
- Start from the released v0.9.0 implementation, with the roadmap clarification
|
||||
at `92309ddcc3495c88c06d3a4dab414912a83acfae`. Inspect the current worktree and
|
||||
preserve unrelated changes. The sequence integration and remediation are done.
|
||||
- Follow [development guidance](../development.md) and the applicable
|
||||
[agent](../policy/agents.md), [architecture](../policy/architecture.md),
|
||||
[testing](../policy/testing.md), and [documentation](../policy/documentation.md)
|
||||
policies. Use graph tools for code discovery. Read only the relevant owners.
|
||||
- Keep the public API root-owned, with explicit delegation to `internal/engine`.
|
||||
Add cohesive discovery files rather than growing the existing large lifecycle
|
||||
functions. Reuse file acquisition, composition, and inspection machinery.
|
||||
- Discovery chooses one source. Keep strict composition within tiers, precedence
|
||||
between tiers, collection rules, inheritance, version policy, CLI ordering,
|
||||
secrets, and complete-on-return ownership unchanged after selection.
|
||||
- The intentional lifecycle extension is shared absent-main behavior for
|
||||
`NoSource()` and optional discovery without a match. Do not invent mandatory
|
||||
files, new limits, retries, background activity, naming styles, alternate merge
|
||||
behavior, or release gates. Do not implement the separate complexity roadmap.
|
||||
- Add tests at the owning boundary. Update current contracts when their behavior
|
||||
lands, rather than leaving contradictory documentation until the last stage.
|
||||
Commit each validated, nonempty stage with a plain English message and mark its
|
||||
completion here. Do not publish a release or update downstream dependencies.
|
||||
|
||||
## Decisions Used Throughout The Stages
|
||||
|
||||
### Public API
|
||||
|
||||
Use these names and signatures. The two opaque types below have private state;
|
||||
their actual representation must satisfy the existing facade boundary tests.
|
||||
|
||||
```go
|
||||
type Search struct { /* private state */ }
|
||||
type FilenameGroup struct { /* private state */ }
|
||||
|
||||
type SearchOptions struct {
|
||||
Required bool
|
||||
}
|
||||
|
||||
type DirectorySearchOptions struct {
|
||||
Filenames []FilenameGroup
|
||||
}
|
||||
|
||||
func NamedFilenames() FilenameGroup
|
||||
func GenericFilenames() FilenameGroup
|
||||
func CustomFilenames(names ...string) FilenameGroup
|
||||
|
||||
func SearchDirectory(directory string, supplied ...DirectorySearchOptions) Search
|
||||
func Paths(paths ...string) Search
|
||||
func Searches(searches ...Search) Search
|
||||
|
||||
func UnixSystemDirectories() Search
|
||||
func UserConfigDirectory() Search
|
||||
func HomeDirectory() Search
|
||||
func WorkingDirectory() Search
|
||||
|
||||
func SearchConfig(application string, search Search, supplied ...SearchOptions) Source
|
||||
```
|
||||
|
||||
`Searches` is ordered concatenation, not set union. It permits the simple
|
||||
`SearchConfig("myapp", UnixSystemDirectories())` and a mixed search such as:
|
||||
|
||||
```go
|
||||
source := configkit.SearchConfig("myapp", configkit.Searches(
|
||||
configkit.Paths("/run/myapp/config.yml"),
|
||||
configkit.WorkingDirectory(),
|
||||
configkit.UserConfigDirectory(),
|
||||
configkit.UnixSystemDirectories(),
|
||||
), configkit.SearchOptions{Required: true})
|
||||
```
|
||||
|
||||
This uses the existing convention of zero or one options value without mixing
|
||||
search-wide controls into the candidate list. More than one options value is a
|
||||
deferred option error. `SearchConfig` returns the existing `Source`, which works
|
||||
through ordinary loading and `Input`; do not introduce a main-only source ban.
|
||||
An underlay input retains its existing tier and contribution-version rules.
|
||||
|
||||
For directory searches, omitted options or `Filenames == nil` use Named followed
|
||||
by Generic. A non-nil empty `Filenames` slice means no names. Groups concatenate
|
||||
in the supplied order; `CustomFilenames` is one group of literal names. An empty
|
||||
custom group contributes none. A directory whose expanded filename list is empty
|
||||
does not resolve its base. Presets retain the roadmap's fixed group selections.
|
||||
|
||||
The zero `Search` and `Searches()` describe an empty candidate list; zero
|
||||
`FilenameGroup` is an empty group. `Paths()` and `CustomFilenames()` are empty.
|
||||
An explicitly supplied empty path or filename string is invalid, as opposed to
|
||||
an empty list. The zero `Source` remains invalid.
|
||||
|
||||
Snapshot caller-owned slices, including nested filename groups and option
|
||||
slices, during construction. Descriptions remain immutable and reusable; dynamic
|
||||
directories and candidate availability are observed again on each consumption.
|
||||
Constructor failures are retained and reported during load, following opaque
|
||||
source conventions. Validate all static search arguments before acquisition,
|
||||
including invalid later components that an earlier match would otherwise hide.
|
||||
|
||||
### Literal Components And Platform Behavior
|
||||
|
||||
Apply the roadmap's component rules to application slugs and custom filenames.
|
||||
A needed slug and each literal filename must be nonempty, distinct from `.` and
|
||||
`..`, and one usable host filename component: no NUL, absolute/volume prefix, or
|
||||
host path separator. Use standard path helpers; account for Windows reserved
|
||||
names and invalid filename characters on Windows. Apply platform-specific rules
|
||||
only on the relevant platform. Preserve case and spelling, including spaces and
|
||||
non-ASCII names that are valid there. Do not impose a cross-platform ASCII or
|
||||
kebab-case subset. Do not add speculative length limits; actual filesystem
|
||||
failures follow normal diagnostics.
|
||||
|
||||
Require a slug only if the description uses it in a directory or a Named group.
|
||||
Explicit paths and Generic-only/custom-only literal directory searches need none.
|
||||
Do not substitute placeholders in custom names or expand shell syntax in paths.
|
||||
Resolve relative custom paths against the working directory at consumption.
|
||||
|
||||
The Unix system preset is available on Go's `unix` platform set: aix, android,
|
||||
darwin, dragonfly, freebsd, hurd, illumos, ios, linux, netbsd, openbsd, and solaris.
|
||||
Use a small private platform predicate, preferably the standard `unix` build
|
||||
constraint with its complement. Reaching it elsewhere reports an option error;
|
||||
an earlier selected candidate prevents reaching it. This describes preset
|
||||
applicability, not an expansion of ConfigKit's platform support or release gates.
|
||||
Other presets call the three Go directory helpers specified in the roadmap.
|
||||
|
||||
### Selection, Diagnostics, And Resource Ownership
|
||||
|
||||
Resolve a search to a concrete host `File` or `NoSource` before parsing it.
|
||||
Visit candidates incrementally in directory-first order. Resolve each reached
|
||||
directory description once for its filenames; do not eagerly resolve later
|
||||
bases, list directories, or globally deduplicate/reorder descriptions.
|
||||
|
||||
Use the same absolute-path and symlink canonicalization rules as host files.
|
||||
During candidate probing, only errors matching `fs.ErrNotExist` mean absence.
|
||||
This includes a missing target reached through ordinary symlink resolution.
|
||||
Other path-resolution/stat errors terminate selection. An existing target is
|
||||
selected; its regular-file preflight, open, read, and parse follow existing file
|
||||
rules. Any failure after selection is terminal, including a file disappearing
|
||||
before open. Do not decide fallback by inspecting a completed load's diagnostics.
|
||||
|
||||
A small stat/canonicalization probe followed by the existing file reader is
|
||||
sufficient. Read and parse the selected file through the ordinary path once;
|
||||
do not read it to probe and then reopen it for parsing. Existing preflight and
|
||||
post-open checks remain. No stronger filesystem snapshot or race guarantee is
|
||||
introduced. Preserve existing symlink and containment contracts.
|
||||
|
||||
Use existing diagnostic codes: `CodeOptionsInvalid` for invalid descriptions and
|
||||
reached unsupported presets, `CodeSourceMissing` for required exhaustion, and
|
||||
normal source access/read codes for candidate and directory-helper failures.
|
||||
Required exhaustion has a search-specific, value-free explanation and no
|
||||
fabricated source location. Directory-helper failures have no acquired source;
|
||||
retain normal selected-candidate records for file failures. Do not expose raw
|
||||
OS errors, environment contents, or YAML values in messages. No new public
|
||||
attempt trace, source kind, or per-candidate history is needed.
|
||||
|
||||
### Absent Input And Filesystem Context
|
||||
|
||||
After resolution, selected files use existing host-file context rules: derive
|
||||
the base from the actual file, reject explicit `BaseDir` or a native `FS`, and
|
||||
enforce any supplied root. Such errors fail without resuming discovery.
|
||||
|
||||
For absence, imports are permitted but have no main declarations to traverse.
|
||||
Retain a nil main document and no main source record. Version checking receives
|
||||
the existing absent-main input; required source-version mode does not fabricate
|
||||
a participating document. Actual underlays, imports, and overlays keep their
|
||||
normal checks and accounting.
|
||||
|
||||
An absent input may have an explicit filesystem context. A host `Root` must be
|
||||
an absolute existing directory; `BaseDir` may be omitted, in which case use that
|
||||
root only as internal filesystem context. If provided, validate `BaseDir` using
|
||||
the existing absolute-directory and containment rules. Complete native contexts
|
||||
continue to use the existing namespace, `BaseDir`, and `Root` rules. The required
|
||||
new shorthand is root-only host context, not a new native-context shorthand.
|
||||
Validate supplied contexts even without imports or an active overlay.
|
||||
|
||||
For a search, validate static context syntax before acquisition where it does
|
||||
not depend on the outcome; apply selected-file versus absent-input compatibility
|
||||
checks after resolution. Do not reject an otherwise valid absent-input context
|
||||
merely because selection could have produced a file. An invalid context never
|
||||
causes fallback.
|
||||
|
||||
An ordinary active overlay with absent main requires explicit context. Load it
|
||||
from that context's normal root-relative overlay location. Never infer context
|
||||
from attempted directories. Layered overlays still require their independent
|
||||
`OverlayFilesystem` with its existing full context rules. Internal empty assembly
|
||||
or context descriptors are acceptable; they must not become an acquired main
|
||||
document, source ID, or explicit-presence contribution.
|
||||
|
||||
## Stage 1 — Define Immutable Search Descriptions And Validation
|
||||
|
||||
Read [the facade ADR](../adr/0006-public-facade.md), the roadmap, and the source,
|
||||
options, filesystem, and lifecycle owners named in the component overview.
|
||||
|
||||
1. Add engine-owned descriptions and search/group constructors corresponding to
|
||||
the chosen API in a focused `internal/engine/discovery.go` file. Leave the
|
||||
source-producing `SearchConfig` constructor for Stage 6. Store flat ordered
|
||||
search entries rather than a recursively interpreted concatenation tree.
|
||||
Snapshot nested lists and retain constructor errors without filesystem access.
|
||||
2. Implement pure description validation, default/empty filename handling, and
|
||||
slug/component validation. Distinguish a zero/empty description from an
|
||||
explicitly invalid supplied argument. Ensure concatenation preserves errors
|
||||
as well as entries; do not validate irrelevant, unused slug text.
|
||||
3. Add engine tests for supplied-option arity, snapshots, zero/empty components,
|
||||
omitted versus empty filenames, reordered groups, slug-independent searches,
|
||||
and platform-appropriate component validation. Do not duplicate standard
|
||||
library path tests or introduce a large filename-validation framework.
|
||||
4. Write ADR 0019, `0019-explicit-configuration-discovery.md`, recording the
|
||||
accepted ownership decision: consumers select conventions and order;
|
||||
ConfigKit selects one real source within synchronous loading; optional
|
||||
exhaustion shares the absent-main lifecycle. Use the repository ADR format.
|
||||
Explain the chosen decision and reasoning directly; keep alternatives in
|
||||
Alternatives Considered. Leave implementation status in these roadmap files
|
||||
and filename inventories/API mechanics in the eventual canonical contract.
|
||||
|
||||
Acceptance: descriptions and validation are testable without acquisition; the
|
||||
public loading behavior is unchanged. Run the new focused engine tests and the
|
||||
everyday check. Mark and commit the stage.
|
||||
|
||||
## Stage 2 — Expand Ordered Candidates And Resolve Preset Bases
|
||||
|
||||
1. Implement incremental ordered expansion for literal paths, directory entries,
|
||||
groups, and presets. Preserve component positions and filename order exactly.
|
||||
Keep requiredness on the whole search. Do not allocate a complete candidate
|
||||
product merely to consume its first member.
|
||||
2. Implement presets exactly as the roadmap tables specify, including all 14
|
||||
Unix system candidates and the Named-only `/etc` search. Implement user,
|
||||
home, and working-directory bases with the Go helpers. Resolve a base only
|
||||
when its nonempty directory search is reached.
|
||||
3. Provide only a narrow private test seam for directory helpers/platform
|
||||
applicability so failure and short-circuit behavior are deterministic. Bind
|
||||
normal loads to the standard library; do not expose environment providers or
|
||||
mutable process-global function replacements as public API.
|
||||
4. Test expansion order and reversed groups, preset composition, custom paths,
|
||||
empty entries followed by nonempty entries, and unsupported/unavailable bases.
|
||||
Assert the contractual preset list at this owner. Use controlled helper
|
||||
results for pure preset tests; do not create files under real `/etc` or home.
|
||||
Demonstrate that stopping consumption prevents later base resolution and
|
||||
that empty filename selections never resolve a base.
|
||||
|
||||
Acceptance: deterministic ordered candidates, with no load integration yet.
|
||||
Work is linear in description size plus candidates visited, excluding OS path
|
||||
operations and selected-file processing. No benchmark gate is needed. Run focused
|
||||
discovery tests and the everyday check; mark and commit.
|
||||
|
||||
## Stage 3 — Select A Concrete File Through Host Acquisition Rules
|
||||
|
||||
1. Add a private resolver taking an immutable search description and returning
|
||||
the concrete source or safe diagnostics. Non-search sources pass through.
|
||||
Keep the selection state local to a consumption, never cached in `Source`.
|
||||
2. Reuse or narrowly extract existing host-path canonicalization/probing from
|
||||
`internal/engine/source.go`. Delegate selected-file reading to `readSource`
|
||||
and `readRegularFile`; do not add a parallel reader or parser.
|
||||
3. Return `NoSource()` on optional exhaustion and a required-search diagnostic on
|
||||
required exhaustion, including a completely empty search. Missing attempts
|
||||
create no source evidence and consume no acquisition budget.
|
||||
4. Test with real temporary files: first match, later match, both extensions,
|
||||
directory/nonregular target, contained symlink, required/optional exhaustion,
|
||||
and a reached error preventing selection of a later good file. Use the
|
||||
existing narrow filesystem failure seams for permission/read failures that
|
||||
cannot reliably be reproduced when tests run as a privileged user. Reuse
|
||||
existing FIFO and bounded-reader tests rather than duplicating them.
|
||||
5. Check relative paths and literal `~`, variable, and wildcard characters in
|
||||
controlled directories. Verify that a later base is untouched after success.
|
||||
|
||||
Acceptance: selection returns one ordinary file or absence, without parsing or
|
||||
running defaults/resolution. A selected source's later read failure is not a
|
||||
fallback signal. Run focused discovery/source tests and the everyday check;
|
||||
mark and commit.
|
||||
|
||||
## Stage 4 — Unify Absent Input Acquisition And Context Validation
|
||||
|
||||
This stage changes `NoSource()` deliberately, before attaching discovery to loads.
|
||||
|
||||
1. In `options.go`, remove the blanket rejection of imports for `sourceNone`.
|
||||
In `filesystem.go`, replace blanket context rejection with the absent-context
|
||||
rules above, separating static checks from directory access as needed.
|
||||
2. In `assembleBaseInputTrackedForRole` and its collaborators, handle absent
|
||||
acquisition without calling the import traversal that requires a main
|
||||
document. Validate any supplied context separately. Preserve nil main state,
|
||||
zero main source IDs, zero input accounting, and existing version callbacks.
|
||||
3. Keep actual underlay and transitive-source imports unchanged. Absence in an
|
||||
underlay contributes nothing and does not add an absent-main version callback.
|
||||
4. Adjust tests that asserted the intentionally retired rejections; add focused
|
||||
cases for `NoSource` with imports, valid root-only context, invalid context,
|
||||
ordinary defaults/explicit zero and false CLI values, and layered underlays
|
||||
whose imports still load. Cover absent-main version acceptance and rejection
|
||||
at the existing version-test owner, including enabled source-version modes.
|
||||
5. Update the `NoSource`/context/import portions of declaration comments and
|
||||
`docs/config.md` for the behavior now implemented. Keep the active ordinary
|
||||
overlay restriction until Stage 5, so this intermediate commit is accurate.
|
||||
|
||||
Acceptance: imports can be enabled on absent input without synthetic sources,
|
||||
while invalid supplied contexts still fail. Run focused load, filesystem,
|
||||
version, and grouped tests, then the everyday check; mark and commit.
|
||||
|
||||
## Stage 5 — Load Ordinary Overlays With An Absent Main
|
||||
|
||||
1. Replace the ordinary `NoSource` active-overlay rejection with the explicit
|
||||
context requirement. Resolve absent-input overlay access from its context;
|
||||
do not invent a selected main file to satisfy `importFilesystem`.
|
||||
2. Adapt the smallest shared context/overlay helpers needed in
|
||||
`filesystem.go`, `selected_overlay.go`, `load_input.go`, and
|
||||
`source_context.go`. Keep layered overlay selection's independent context
|
||||
contract unchanged. Reuse the existing overlay reader and accumulator.
|
||||
3. Preserve source records and contexts for the actual overlay, including its
|
||||
physical path, layer, ID, and resource base. With no other acquired sources
|
||||
it is `source-1`. Missing main contributes no source or presence. Missing
|
||||
selected overlay and invalid/outside-root contexts remain errors.
|
||||
4. Add ordinary tests for absent-main overlay success, missing explicit context,
|
||||
missing overlay, and overlay resource context. Exercise a complete native
|
||||
context through existing filesystem machinery and root-only host context.
|
||||
Reuse layered overlay tests to protect its independent root behavior.
|
||||
5. Update owning declarations and contract text for the completed absent-main
|
||||
overlay behavior. Review cleanup on success and each new failure path;
|
||||
acquired file/root handles remain confined to the load call.
|
||||
|
||||
Acceptance: shared absent-main behavior is complete for explicit `NoSource`.
|
||||
Run focused overlay/context/load tests, `go test -race ./internal/engine`, and
|
||||
the everyday check. Mark and commit.
|
||||
|
||||
## Stage 6 — Integrate Search Resolution Into Engine Loading
|
||||
|
||||
1. Add a private discovery source kind/state to engine `Source` and have the
|
||||
engine `SearchConfig` constructor capture the validated description. Add
|
||||
static search validation to `selectOptions`; layered validation must inspect
|
||||
every supplied input before acquisition, as it does for other options.
|
||||
2. Resolve each discovery source once at the shared input-acquisition boundary,
|
||||
before `parseSource`. Carry the concrete selection in a load-local field of
|
||||
`assembledInput`, initialized for every source kind. Use it for import access,
|
||||
source admission, and contexts within acquisition. Update the ordinary main
|
||||
request to that concrete source before overlay application and context
|
||||
reconstruction in `executeLoadRequest`.
|
||||
3. Apply outcome-specific context checks after selection, without rerunning the
|
||||
search. Audit remaining kind switches and callers so no downstream access
|
||||
helper receives an unresolved discovery descriptor. Keep all existing early
|
||||
validation that is independent of selection; do not change schema/version
|
||||
callback ordering to implement discovery.
|
||||
4. Use the same path for layered main and underlay inputs. Preserve per-tier
|
||||
repetition checks, main-first acquisition order, placement, and budgets. A
|
||||
selected source is `SourceKindFile`, not a new public discovery kind.
|
||||
5. Add engine lifecycle cases comparing a searched source to its explicit `File`
|
||||
counterpart, and exhausted optional search to `NoSource`, with the same
|
||||
options. Compare actual paths, destinations, source IDs/layers, presence, and
|
||||
context rather than internal descriptor shapes. Assert malformed selected
|
||||
YAML and version/decode failures never fall back to a later good file.
|
||||
6. Cover reuse after file/env/working-directory changes using isolated tests;
|
||||
do not run environment-mutating tests in parallel. Verify static invalid
|
||||
later components fail before probing, whereas dynamic later base failures
|
||||
are skipped after an earlier selection.
|
||||
|
||||
Acceptance: engine ordinary and layered loading consume searches through one
|
||||
lifecycle and preserve real-source evidence. Run focused discovery/load/import/
|
||||
version/overlay tests, engine race tests, and the everyday check; mark and commit.
|
||||
|
||||
## Stage 7 — Expose The Root Facade And Verify Public Integration
|
||||
|
||||
1. Add the specified root-owned discovery types, functions, comments, and
|
||||
explicit conversions, preferably in root `discovery.go`. Keep engine types
|
||||
out of exported fields/signatures and do not add bridge methods or aliases.
|
||||
Convert nested lists without losing nil versus non-nil empty filenames.
|
||||
2. Update `Source` constructor guidance. Add the implemented discovery contract
|
||||
to `docs/config.md`: public declarations own symbol details; the contract
|
||||
owns ordered selection, failure behavior, absence, context, and metadata.
|
||||
3. Add root facade tests for the simple preset/custom-directory case, mixed
|
||||
ordered searches with requiredness, absent-main parity, slice ownership,
|
||||
and ordinary/layered use. Exercise custom Generic-only names with an empty
|
||||
slug and explicit empty filenames through the public conversion boundary.
|
||||
4. Test the important selected/absent context pair with one root-only host
|
||||
context: selected file derives its base, absence permits an overlay at the
|
||||
supplied root. Test a selected file outside that root fails without fallback.
|
||||
Retain errors for selected files with explicit `BaseDir` or native `FS`.
|
||||
5. Reuse existing public boundary and dependency tests; update an explicit symbol
|
||||
inventory only if needed to recognize the deliberately added API.
|
||||
|
||||
Acceptance: consumers can use the feature entirely through the root package.
|
||||
Run focused facade discovery/context tests, public/dependency boundary tests,
|
||||
and the everyday check. Mark and commit.
|
||||
|
||||
## Stage 8 — Complete Cross-Feature Coverage And Consumer Examples
|
||||
|
||||
1. Add a small maintained consumer example/test for a simple explicitly selected
|
||||
preset with test-controlled directories, and another for custom-first search
|
||||
used with underlays/imports/overlay/CLI. Keep illustrative snippets short and
|
||||
link to the maintained examples. No example should depend on real host config.
|
||||
2. Review the integration coverage against the roadmap. Add only missing risk
|
||||
cases: selected relative imports/resources; placement and sequence-item
|
||||
placement; absence creating no item; actual collection/inheritance semantics;
|
||||
defaults and callbacks running once; invalid chosen content preventing later
|
||||
fallback; final metadata/rendering agreeing with the selected source.
|
||||
Reuse existing structural, secret, removed-history, and lifetime tests where
|
||||
discovery introduces no new path through them.
|
||||
3. Review all new branches for independently owned descriptions/results and
|
||||
cleanup. Use a narrow controlled acquisition failure test if an introduced
|
||||
failure branch's closure behavior is not already covered. Do not turn this
|
||||
into a cross-platform handle-counting framework or a new support gate.
|
||||
|
||||
Acceptance: representative consumers need no application-side fallback loader
|
||||
or provenance reconstruction. Run maintained consumer tests, relevant executable
|
||||
examples, and the everyday check. Mark and commit.
|
||||
|
||||
## Stage 9 — Align Architecture And Contributor Documentation
|
||||
|
||||
1. Update the architecture policy's responsibility boundary narrowly: applications
|
||||
choose main-file conventions; explicit library-provided searches execute
|
||||
inside loading. Preserve the exclusion of implicit discovery and network
|
||||
acquisition. Link ADR 0019 and the canonical discovery contract.
|
||||
2. Update the internal overview for discovery ownership and resolved-source
|
||||
propagation, the development guide's focused test map/ADR list, and consumer
|
||||
guidance. Add only concise README navigation or an example if useful; retain
|
||||
the current minimal explicit-source example. Explain ordered concatenation
|
||||
and the directory-first Cartesian product where applicable in the contract.
|
||||
3. Review the declaration comments and discovery contract added in earlier
|
||||
stages against the completed feature. Keep one canonical owner per fact and
|
||||
fix stale claims about `NoSource`, implicit discovery, or overlay context.
|
||||
|
||||
Acceptance: documentation describes implemented behavior and links the API,
|
||||
contract, ADR, and maintained examples. Run the documentation link test and
|
||||
whitespace review. Execute any newly changed behavior-bearing examples; reuse
|
||||
Stage 8 results for unchanged examples. Mark and commit.
|
||||
|
||||
## Stage 10 — Review The Complete Feature And Validate The Candidate
|
||||
|
||||
1. Compare the full implementation with the roadmap and this plan. Check every
|
||||
OD, the API/default/empty distinctions, preset lists, lazy failures, context
|
||||
outcome checks, source admission, callback execution, and complete-on-return
|
||||
ownership. Ensure explicit `File` behavior and all existing precedence rules
|
||||
remain intact.
|
||||
2. Look specifically for accidental contract expansion: implicit presets,
|
||||
global canonical deduplication, application-name normalization, extra file
|
||||
restrictions, suppressed access errors, no-match version bypass, fallback
|
||||
after selected-content errors, or new human/CI validation requirements.
|
||||
3. Run `go test -race ./...` and `./scripts/check.sh` under the installed compatible
|
||||
toolchain with dependencies already available, following the development
|
||||
guide. Reuse successful checks for unchanged content; investigate failures
|
||||
rather than weakening assertions. No timing/benchmark threshold, CI wait,
|
||||
native macOS run, or release publication is part of this implementation.
|
||||
4. Review status and staged diff, mark this plan complete only after the
|
||||
acceptance criteria are satisfied, and commit any final nonempty unit.
|
||||
Report validation and remaining limitations. Keep pending roadmap documents;
|
||||
completed-plan retirement belongs to later release preparation.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None. OD-1 through OD-5 are settled in the feature roadmap; this plan fixes the
|
||||
remaining API and integration details without changing those decisions.
|
||||
Reference in New Issue
Block a user