Plan configuration discovery implementation

This commit is contained in:
2026-10-24 15:04:02 +00:00
parent 92309ddcc3
commit 424c89780e
2 changed files with 476 additions and 41 deletions

View File

@@ -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

View 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.