Prepare the ConfigKit v0.10.0 release
This commit is contained in:
@@ -62,7 +62,7 @@ the public symbols. Dynamic bases and file availability are observed when each
|
|||||||
load consumes the description. Applications remain responsible for selecting
|
load consumes the description. Applications remain responsible for selecting
|
||||||
conventions and for their schema and domain requirements.
|
conventions and for their schema and domain requirements.
|
||||||
|
|
||||||
The [configuration contract](../config.md) will own implemented behavior, the
|
The [configuration contract](../config.md) owns implemented behavior, and the
|
||||||
[component overview](../internal/overview.md) will describe implementation
|
[component overview](../internal/overview.md) describes implementation ownership.
|
||||||
ownership, and the [implementation plan](../roadmap/implementation.md) tracks
|
The completed implementation plan and remediation record are preserved in Git
|
||||||
delivery progress.
|
history at `0369111` under `docs/roadmap/`.
|
||||||
|
|||||||
@@ -183,6 +183,12 @@ Current behavior belongs to the configuration contract's
|
|||||||
The completed remediation record, including the algorithm-audit findings, is
|
The completed remediation record, including the algorithm-audit findings, is
|
||||||
preserved at `ea46001:docs/roadmap/remediation.md`.
|
preserved at `ea46001:docs/roadmap/remediation.md`.
|
||||||
|
|
||||||
|
The completed configuration-discovery roadmap, implementation plan, and
|
||||||
|
remediation record are preserved in Git history at `0369111`, for example
|
||||||
|
`git show 0369111:docs/roadmap/remediation.md`. Current behavior belongs to
|
||||||
|
[configuration discovery](config.md#configuration-discovery); the public
|
||||||
|
[discovery declarations](../discovery.go) own the Go API.
|
||||||
|
|
||||||
Potential enhancements are tracked in [future features](roadmap/future.md).
|
Potential enhancements are tracked in [future features](roadmap/future.md).
|
||||||
|
|
||||||
Install the Go version required by `go.mod` or a compatible newer toolchain.
|
Install the Go version required by `go.mod` or a compatible newer toolchain.
|
||||||
|
|||||||
77
docs/releases/v0.10.0.md
Normal file
77
docs/releases/v0.10.0.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# ConfigKit v0.10.0
|
||||||
|
|
||||||
|
This minor release adds explicit, composable discovery of a configuration file.
|
||||||
|
Applications choose the search conventions and their order; ConfigKit selects one
|
||||||
|
file during loading and returns the final configuration with its normal metadata.
|
||||||
|
Optional searches that find no file share the expanded `NoSource()` lifecycle.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Add `SearchConfig`, immutable `Search` and `FilenameGroup` descriptions, and
|
||||||
|
search-wide requiredness through `SearchOptions`. Compose explicit `Paths`,
|
||||||
|
custom `SearchDirectory` descriptions, and built-in Unix system, user-config,
|
||||||
|
home, or working-directory presets with `Searches`. See the
|
||||||
|
[public declarations](../../discovery.go) and
|
||||||
|
[discovery contract](../config.md#configuration-discovery).
|
||||||
|
- Expand searches in supplied order, trying a directory's filename groups before
|
||||||
|
moving to the next directory. Named and generic groups provide conventional
|
||||||
|
YAML filenames; custom groups provide literal names. Directory searches
|
||||||
|
distinguish omitted filename selection from an explicitly empty selection.
|
||||||
|
Constructors perform no filesystem access, and each load observes current
|
||||||
|
directory bases and file availability.
|
||||||
|
- Select the first existing candidate. Access errors and failures while loading
|
||||||
|
the selected file terminate the load rather than trying another configuration.
|
||||||
|
Selected files retain physical source identity, placement, provenance, imports,
|
||||||
|
and relative-resource context. Discovery works with ordinary loading and
|
||||||
|
layered inputs, including underlays, without adding a precedence tier.
|
||||||
|
- Permit enabled imports with an absent main input: there are no main import
|
||||||
|
declarations to traverse, while actual underlay sources retain normal imports.
|
||||||
|
Support ordinary selected overlays with an absent main and explicit filesystem
|
||||||
|
context, including a root-only host context. Layered overlays retain their
|
||||||
|
independent `OverlayFilesystem` requirement. See
|
||||||
|
[single-source loading](../config.md#single-source-loading) and
|
||||||
|
[layered loading](../config.md#layered-loading).
|
||||||
|
- Preserve ordinary file-context behavior after discovery and include the
|
||||||
|
failing candidate's file identity in access diagnostics. Use Go's
|
||||||
|
platform-aware Windows component rules and native drive-root-relative path
|
||||||
|
resolution. Isolate user-config tests according to the host Go conventions.
|
||||||
|
|
||||||
|
## Compatibility and upgrade
|
||||||
|
|
||||||
|
Existing explicit source constructors and loading APIs remain available.
|
||||||
|
`File(path)` remains an exclusive selection that fails for a missing file;
|
||||||
|
discovery is activated only by explicitly supplying `SearchConfig`.
|
||||||
|
|
||||||
|
Searches are optional by default. Set `SearchOptions{Required: true}` when the
|
||||||
|
application must find a file. An exhausted optional search contributes no main
|
||||||
|
document or source record; application version checks, defaults, resolution,
|
||||||
|
and validation still run under their existing rules and may reject incomplete
|
||||||
|
configuration. A selected overlay is still required to exist.
|
||||||
|
|
||||||
|
The former `NoSource()` rejections of enabled imports and explicit filesystem
|
||||||
|
context are relaxed. An active ordinary overlay with no main file now loads
|
||||||
|
when explicit context is supplied. Applications that relied on those combinations
|
||||||
|
being rejected should review their option handling. Selected host files derive
|
||||||
|
their own base directory; do not supply an explicit `BaseDir` or native filesystem
|
||||||
|
context for a search that selects a host file.
|
||||||
|
|
||||||
|
Applications may replace their own fallback-loading loops with the new search
|
||||||
|
descriptions. The maintained [consumer examples](../consumers/README.md#explicit-main-file-discovery)
|
||||||
|
show simple preset selection and custom-first discovery alongside existing
|
||||||
|
configuration tiers. Composition, collection operations, inheritance, CLI
|
||||||
|
precedence, and the complete-on-return ownership model retain their existing
|
||||||
|
semantics.
|
||||||
|
|
||||||
|
Select this version and rebuild:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get gitea.maximumdirect.net/eric/ConfigKit@v0.10.0
|
||||||
|
```
|
||||||
|
|
||||||
|
The consumer minimum remains Go 1.26.0. Release validation uses Go 1.26.10.
|
||||||
|
The YAML dependency remains `go.yaml.in/yaml/v4 v4.0.0-rc.6`, and the maintainer
|
||||||
|
tool remains ReleaseKit v0.4.1. Linux amd64 and arm64 remain release-validated
|
||||||
|
targets; macOS remains best-effort. Publication follows the existing
|
||||||
|
[release procedure](../release.md).
|
||||||
|
|
||||||
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.10.0/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.10.0/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.10.0/docs/oss/attribution.md)
|
||||||
@@ -173,9 +173,9 @@ overlapping work and select its starting revision rather than duplicating the
|
|||||||
remediation plan here. These structural changes do not close a finding merely
|
remediation plan here. These structural changes do not close a finding merely
|
||||||
because its function moved or became smaller.
|
because its function moved or became smaller.
|
||||||
|
|
||||||
The pending [discovery roadmap](discovery.md) remains separate feature work.
|
[Configuration discovery](../config.md#configuration-discovery) is implemented.
|
||||||
Import cleanup should leave clear private boundaries for future use without
|
Import cleanup should preserve its source-acquisition boundary and behavior as
|
||||||
implementing discovery behavior as part of this refactor.
|
part of this refactor.
|
||||||
|
|
||||||
## Work Needed And Validation
|
## Work Needed And Validation
|
||||||
|
|
||||||
|
|||||||
@@ -1,364 +0,0 @@
|
|||||||
# Composable Configuration Discovery Roadmap
|
|
||||||
|
|
||||||
Status: implemented. This document preserves the design rationale and agreed
|
|
||||||
direction. The [configuration contract](../config.md#configuration-discovery)
|
|
||||||
owns current behavior, [ADR 0019](../adr/0019-explicit-configuration-discovery.md)
|
|
||||||
records the decision, and the [implementation plan](implementation.md) records
|
|
||||||
the completed work sequence.
|
|
||||||
|
|
||||||
## Purpose And Ownership
|
|
||||||
|
|
||||||
Make ordinary file-based loading concise for applications that want conventional
|
|
||||||
configuration locations. ConfigKit supplies useful built-in candidate lists and
|
|
||||||
an ordered selection operation. Consumers continue to own main-file search
|
|
||||||
conventions: they choose which lists apply, their order, custom candidates, and
|
|
||||||
whether a configuration file is required.
|
|
||||||
|
|
||||||
A consumer can select one preset for a simple application or compose several
|
|
||||||
presets and custom path lists. The application supplies an explicit slug where
|
|
||||||
needed to construct conventional paths. ConfigKit does not infer application
|
|
||||||
identity from the executable name or enable discovery for existing explicit
|
|
||||||
sources.
|
|
||||||
|
|
||||||
Discovery selects one source for the existing loading lifecycle. It does not
|
|
||||||
create additional configuration tiers or merge every matching file. Existing
|
|
||||||
layered loading remains available when consumers intentionally combine sources.
|
|
||||||
|
|
||||||
## Baseline Before Implementation
|
|
||||||
|
|
||||||
Before this work, sources included explicit host files, native filesystem files,
|
|
||||||
bytes, readers, mappings, and `NoSource()`, with no conventional main-file search.
|
|
||||||
That baseline also restricted `NoSource()` with imports and overlays. The
|
|
||||||
implemented discovery behavior now lets an exhausted optional search follow the
|
|
||||||
absent-main lifecycle, including enabled imports and an active overlay when its
|
|
||||||
required explicit context is supplied. The current [source contract](../config.md#sources),
|
|
||||||
[single-source loading](../config.md#single-source-loading), and
|
|
||||||
[layered loading](../config.md#layered-loading) sections define the behavior;
|
|
||||||
this baseline is retained only to explain the original design problem.
|
|
||||||
|
|
||||||
Applications still choose search conventions and ordering. The architecture
|
|
||||||
policy now distinguishes those consumer choices from explicitly selected search
|
|
||||||
descriptions resolved during loading, while continuing to exclude implicit
|
|
||||||
discovery and network acquisition.
|
|
||||||
|
|
||||||
## Agreed Direction
|
|
||||||
|
|
||||||
### Filename Groups, Directory Searches, And Search Presets
|
|
||||||
|
|
||||||
Organize discovery around three composable descriptions. These are ordered lists:
|
|
||||||
their order determines precedence.
|
|
||||||
|
|
||||||
**Filename groups** provide reusable, ordered filename templates. The built-in
|
|
||||||
groups are:
|
|
||||||
|
|
||||||
| Group | Filenames, in order |
|
|
||||||
| --- | --- |
|
|
||||||
| Named | `<appname>.yml`, `<appname>.yaml` |
|
|
||||||
| Generic | `config.yml`, `config.yaml`, `main.yml`, `main.yaml` |
|
|
||||||
|
|
||||||
`<appname>` is the application slug supplied by the consumer.
|
|
||||||
|
|
||||||
**Directory searches** pair a directory with an ordered list of filename groups.
|
|
||||||
The default selection is `[Named, Generic]`, producing the six filenames above
|
|
||||||
in that order. A directory search may select different groups or reverse their
|
|
||||||
precedence, such as `[Generic, Named]`, while retaining each group's internal
|
|
||||||
order. Consumers can also supply custom filenames, subject to the literal
|
|
||||||
single-component rules under OD-5 below.
|
|
||||||
|
|
||||||
**Search presets** bundle ordered directory searches. Consumers can select a
|
|
||||||
complete preset for the simple case or compose presets, custom directory searches,
|
|
||||||
and explicit file candidates in their desired order. Explicit file candidates
|
|
||||||
are literal paths and receive no filename expansion.
|
|
||||||
|
|
||||||
### Ordered Expansion And Concatenation
|
|
||||||
|
|
||||||
The selection model is **ordered expansion and concatenation**, followed by
|
|
||||||
first-match selection:
|
|
||||||
|
|
||||||
1. Expand the selected presets into directory searches in their supplied order.
|
|
||||||
2. For each directory search, concatenate its selected filename groups in order,
|
|
||||||
substitute the application slug where needed, and join each filename to that
|
|
||||||
directory.
|
|
||||||
3. Concatenate the resulting candidate lists, preserving the positions of custom
|
|
||||||
directory searches and explicit file candidates.
|
|
||||||
4. Try candidates in order, stopping at the first selected file or an error as
|
|
||||||
described below.
|
|
||||||
|
|
||||||
Directory priority comes before filename priority: exhaust one directory's
|
|
||||||
filenames before trying the next directory. When several directories share a
|
|
||||||
filename list, their expansion is a Cartesian product traversed directory first.
|
|
||||||
Different directories may select different lists, so ordered expansion and
|
|
||||||
concatenation describes the complete model. Set union does not express its
|
|
||||||
precedence semantics.
|
|
||||||
|
|
||||||
The [configuration contract](../config.md#configuration-discovery) explains this
|
|
||||||
model using ordered lists, ordered concatenation, and the directory-first
|
|
||||||
Cartesian product where a shared filename list applies. These terms describe
|
|
||||||
ordering without requiring eager construction of every candidate.
|
|
||||||
|
|
||||||
### Unix System Preset
|
|
||||||
|
|
||||||
The agreed Unix system preset is:
|
|
||||||
|
|
||||||
| Directory, in search order | Filename groups, in order |
|
|
||||||
| --- | --- |
|
|
||||||
| `/usr/local/etc/<appname>` | `[Named, Generic]` |
|
|
||||||
| `/etc/<appname>` | `[Named, Generic]` |
|
|
||||||
| `/etc` | `[Named]` |
|
|
||||||
|
|
||||||
This produces 14 candidates: six in each application-specific directory, then
|
|
||||||
`/etc/<appname>.yml` and `/etc/<appname>.yaml`. For example,
|
|
||||||
`/usr/local/etc/<appname>/main.yaml` precedes
|
|
||||||
`/etc/<appname>/<appname>.yml`. Selecting only `Named` for `/etc` is an ordinary
|
|
||||||
directory-search description; the selection algorithm needs no special exception.
|
|
||||||
|
|
||||||
### Additional Presets (OD-1 — Settled)
|
|
||||||
|
|
||||||
Provide the following independently selectable presets alongside Unix system.
|
|
||||||
Within each preset, search directories in the order shown:
|
|
||||||
|
|
||||||
| Preset | Directory | Filename groups, in order |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| User configuration | `<user-config-dir>/<appname>` | `[Named, Generic]` |
|
|
||||||
| User configuration | `<user-config-dir>` | `[Named]` |
|
|
||||||
| Home directory | `<home>/.<appname>` | `[Named, Generic]` |
|
|
||||||
| Home directory | `<home>` | `[Named]` |
|
|
||||||
| Working directory | Current working directory | `[Named]` |
|
|
||||||
|
|
||||||
Resolve the user-configuration base with
|
|
||||||
[`os.UserConfigDir`](https://pkg.go.dev/os#UserConfigDir), the home base with
|
|
||||||
[`os.UserHomeDir`](https://pkg.go.dev/os#UserHomeDir), and the working-directory
|
|
||||||
base with [`os.Getwd`](https://pkg.go.dev/os#Getwd). Adopt the Go helpers'
|
|
||||||
platform-specific directory and environment behavior, including their errors;
|
|
||||||
the settled OD-2 rules below govern how discovery handles resolution errors.
|
|
||||||
|
|
||||||
The goal is to support familiar conventions through idiomatic Go. This feature
|
|
||||||
does not add a separate XDG compatibility layer or XDG system preset. A selected
|
|
||||||
user-configuration base has no additional hard-coded `<home>/.config` fallback.
|
|
||||||
The Home directory preset is a separate, explicit choice.
|
|
||||||
|
|
||||||
The working-directory preset searches only that directory and uses application-named
|
|
||||||
files; it does not walk ancestor directories. The consumer chooses precedence
|
|
||||||
between all presets. Selecting one does not automatically select any others.
|
|
||||||
|
|
||||||
### Illustrative Consumer 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.
|
|
||||||
source := configkit.SearchConfig("myapp", configkit.UnixSystemDirectories())
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
// A consumer-selected working-directory, user, then system search.
|
|
||||||
source := configkit.SearchConfig(
|
|
||||||
"myapp",
|
|
||||||
configkit.Searches(
|
|
||||||
configkit.WorkingDirectory(),
|
|
||||||
configkit.UserConfigDirectory(),
|
|
||||||
configkit.UnixSystemDirectories(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Explicit custom candidates followed by a selected conventional fallback.
|
|
||||||
source := configkit.SearchConfig(
|
|
||||||
"myapp",
|
|
||||||
configkit.Searches(
|
|
||||||
configkit.Paths("/run/myapp/config.yml", "/opt/myapp/config.yaml"),
|
|
||||||
configkit.UnixSystemDirectories(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the resulting source with ordinary loading or as a layered main input. Custom
|
|
||||||
searches and file lists participate at their specified position; they do not
|
|
||||||
silently append unselected presets. Reordering presets or their directory
|
|
||||||
searches changes search priority.
|
|
||||||
|
|
||||||
### Selection And Failure Behavior
|
|
||||||
|
|
||||||
| Situation | Selected behavior |
|
|
||||||
| --- | --- |
|
|
||||||
| Candidate is absent | Continue to the next candidate. |
|
|
||||||
| A candidate file is selected | Stop searching and load that file. |
|
|
||||||
| Candidate is inaccessible, unreadable, or not an accepted file type | Fail using normal source diagnostics. |
|
|
||||||
| Selected file fails parsing, version checks, composition, decoding, or validation | Fail without trying another candidate. |
|
|
||||||
| Entire search has no match | Absence is permitted by default; follow the shared absent-main lifecycle settled under OD-3 below. |
|
|
||||||
| Entire search has no match and is required | Fail with a diagnostic explaining the unsuccessful search. |
|
|
||||||
| Caller explicitly uses `File(path)` | Retain existing exclusive-file behavior, including errors for a missing file. |
|
|
||||||
|
|
||||||
Requiredness applies to the complete search, not to each preset or directory.
|
|
||||||
Permitting an absent file does not waive application version, schema, or domain
|
|
||||||
requirements; the remaining lifecycle may still reject an incomplete configuration.
|
|
||||||
|
|
||||||
Perform discovery before parsing the chosen main file. Do not implement fallback
|
|
||||||
by repeatedly running complete loads. Defaults, application resolution, secret
|
|
||||||
lookup, and final validation retain their existing lifecycle execution rules.
|
|
||||||
Once a file has been selected, later failure does not restart discovery.
|
|
||||||
|
|
||||||
### Construction And Filesystem Behavior
|
|
||||||
|
|
||||||
Constructors describe the search without filesystem access. Resolve dynamic base
|
|
||||||
directories and access candidate files during loading, so reusable descriptions
|
|
||||||
can observe changes on subsequent loads. Do not change the process working
|
|
||||||
directory or environment.
|
|
||||||
|
|
||||||
Reuse the host-file acquisition rules for selected files, including regular-file
|
|
||||||
checks, symlink handling, resource ownership, and source limits. The feature does
|
|
||||||
not require a new filesystem traversal engine, recursive directory search, or
|
|
||||||
new file-size limits. Candidate filenames and groups are bounded by their
|
|
||||||
explicit descriptions, rather than by scanning unrelated directories.
|
|
||||||
|
|
||||||
The exact Go names, constructors, and representation of search-wide options
|
|
||||||
belong in the implementation plan. Keep the public surface root-owned and keep
|
|
||||||
the single-preset case concise.
|
|
||||||
|
|
||||||
### Unavailable Bases And Platform Applicability (OD-2 — Settled)
|
|
||||||
|
|
||||||
Resolve directory bases only as the ordered search reaches them. Once an earlier
|
|
||||||
candidate is selected, later searches do not resolve their bases. Static invalid
|
|
||||||
search arguments still fail ordinary option validation before source acquisition.
|
|
||||||
An explicitly empty filename selection skips directory resolution as specified
|
|
||||||
under OD-4; it has no candidates to locate.
|
|
||||||
|
|
||||||
If a selected directory's base cannot be resolved, stop and report the error.
|
|
||||||
A missing directory or candidate file beneath a resolved base allows the search
|
|
||||||
to continue; a directory-resolution error does not. This distinction also applies
|
|
||||||
to optional searches: permitting no match does not suppress errors encountered
|
|
||||||
while searching. Preserve the agreed errors for inaccessible or unreadable
|
|
||||||
candidates.
|
|
||||||
|
|
||||||
Treat using the Unix system preset on an unsupported platform as an explicit
|
|
||||||
configuration error when that preset is reached. Specify its supported platforms
|
|
||||||
in the implementation plan. Do not translate the Unix preset into a different
|
|
||||||
platform's convention. User, home, and working-directory presets use their native
|
|
||||||
Go directory helpers.
|
|
||||||
|
|
||||||
This keeps search priority predictable when directory resolution fails. Consumers
|
|
||||||
select the presets appropriate to their application and environment; there is no
|
|
||||||
option to skip unavailable bases or unsupported presets.
|
|
||||||
|
|
||||||
### No Match, Imports, And Selected Overlays (OD-3 — Settled)
|
|
||||||
|
|
||||||
Use a shared absent-main lifecycle for optional no-match and explicit `NoSource()`.
|
|
||||||
Allow imports to be enabled when the main file is absent: there are no main-file
|
|
||||||
import declarations to process, and enablement does not introduce automatic
|
|
||||||
directory scanning. Other actual sources retain their normal import behavior.
|
|
||||||
In layered loads, process underlays normally and retain the existing explicit
|
|
||||||
`OverlayFilesystem` requirement for an active overlay.
|
|
||||||
|
|
||||||
For ordinary loads with an active overlay and no main file, require an explicit
|
|
||||||
filesystem context to locate the overlay. Do not infer an overlay root from the
|
|
||||||
first or last searched directory. A missing selected overlay remains an error;
|
|
||||||
absence of a main file does not silently disable a requested overlay.
|
|
||||||
|
|
||||||
For an absent main input, a host filesystem context specifying only `Root` is
|
|
||||||
sufficient to locate the overlay; no main-file `BaseDir` is needed. If discovery
|
|
||||||
selects a host file, retain the normal file-context rules: the selected file
|
|
||||||
determines its base directory, an explicit `BaseDir` is rejected, and the file
|
|
||||||
must be contained within any supplied root. This allows one root-only context
|
|
||||||
to work with both selected and absent main inputs, subject to those existing
|
|
||||||
root and containment checks. An invalid context is an error, not a reason to
|
|
||||||
resume discovery.
|
|
||||||
|
|
||||||
Defaults, CLI overrides, application resolution, and validation continue through
|
|
||||||
the normal lifecycle. Main-version checking receives an absent main input and
|
|
||||||
may reject it under the application's existing policy. Create no synthetic empty
|
|
||||||
document or source record for the absent main file.
|
|
||||||
|
|
||||||
This deliberately relaxes the former `NoSource()` restrictions on imports and
|
|
||||||
on overlays with explicit context. Both ways of obtaining an absent main input
|
|
||||||
have the same behavior, as defined in the [configuration contract](../config.md#configuration-discovery).
|
|
||||||
|
|
||||||
### Empty Composition And Implicit Preset Selection (OD-4 — Settled)
|
|
||||||
|
|
||||||
Require explicit preset or search selection to activate a convention. Supplying
|
|
||||||
no presets or custom searches produces no candidates and activates no implicit
|
|
||||||
fallback. Empty custom path lists and explicitly empty filename-group selections
|
|
||||||
also contribute no candidates. An empty component does not prevent later
|
|
||||||
components from contributing candidates.
|
|
||||||
|
|
||||||
A directory search with an explicitly empty filename selection does not resolve
|
|
||||||
its directory base. Static argument validation still applies. This gives empty
|
|
||||||
selections their no-candidate meaning without introducing directory-resolution
|
|
||||||
errors for a search that cannot select a file.
|
|
||||||
|
|
||||||
If the complete search is empty, an optional search follows the absent-main
|
|
||||||
lifecycle settled under OD-3; a required search fails as exhausted. Empty lists
|
|
||||||
are valid, supporting consumers that construct their searches programmatically.
|
|
||||||
|
|
||||||
Distinguish omission from an explicitly empty filename selection for a directory
|
|
||||||
search. Omitting the selection uses the agreed `[Named, Generic]` default;
|
|
||||||
explicitly selecting an empty list means no filenames. The eventual API must
|
|
||||||
represent both intentions clearly. Presets retain their specified filename groups.
|
|
||||||
|
|
||||||
Selecting a single preset keeps the simple case concise. Search-wide requiredness
|
|
||||||
provides the check for consumers that must find a configuration file.
|
|
||||||
|
|
||||||
### Slugs And Custom Path Interpretation (OD-5 — Settled)
|
|
||||||
|
|
||||||
Require a nonempty single-component application slug whenever a selected search
|
|
||||||
uses it to construct a directory or filename. Reject path separators, `.` and
|
|
||||||
`..`, and forms unusable as that component on the target platform. Preserve
|
|
||||||
spelling and case; do not trim, lowercase, or impose a kebab-case naming scheme.
|
|
||||||
Validation keeps the slug from changing the intended directory structure without
|
|
||||||
imposing a separate naming style.
|
|
||||||
|
|
||||||
Searches that do not use the slug do not require one. Examples include explicit
|
|
||||||
file candidates and a custom directory searched with only Generic filenames.
|
|
||||||
|
|
||||||
Treat custom paths as literal host paths. Resolve relative paths against the
|
|
||||||
working directory at load time. Do not expand shell variables, `~`, or wildcard
|
|
||||||
syntax. Consumers can construct expanded paths themselves; User and Home presets
|
|
||||||
provide the common directory-resolution conveniences. These rules keep custom
|
|
||||||
paths consistent with explicit file loading.
|
|
||||||
|
|
||||||
Custom filenames within directory searches are literal single filename
|
|
||||||
components. They cannot be absolute paths or contain subdirectory components;
|
|
||||||
use custom directory searches or explicit file candidates for those layouts.
|
|
||||||
They receive no application-name placeholder expansion. The Named group supplies
|
|
||||||
application-name expansion, while consumers may construct custom filenames
|
|
||||||
themselves before supplying them.
|
|
||||||
|
|
||||||
### Placement, Provenance, And Existing Features
|
|
||||||
|
|
||||||
The selected file participates as the actual file source. Preserve its physical
|
|
||||||
identity, effective placement, source origin, and passive resource context.
|
|
||||||
Relative imports and relative application resources use the selected file's
|
|
||||||
normal context. Discovery does not relocate overlay or CLI coordinates.
|
|
||||||
|
|
||||||
Missing candidates are not acquired sources and do not create source IDs,
|
|
||||||
configuration history, or explicit input presence. An entirely absent search
|
|
||||||
contributes no synthetic empty configuration file. Diagnostics should distinguish
|
|
||||||
an exhausted required search from a failed candidate. Existing selected-source
|
|
||||||
inspection remains the foundation; a new public per-attempt trace is not part of
|
|
||||||
the implemented API.
|
|
||||||
|
|
||||||
Preserve collection policies, inheritance, source-version rules, CLI ordering,
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Target Integration And Documentation
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
work, provides the implementation baseline.
|
|
||||||
@@ -1,449 +0,0 @@
|
|||||||
# Configuration Discovery Implementation Plan
|
|
||||||
|
|
||||||
Status: complete. Stages 1 through 10 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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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 (Complete)
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
# Configuration Discovery Review Findings
|
|
||||||
|
|
||||||
Status: complete. All five findings have been addressed in separate commits.
|
|
||||||
|
|
||||||
Reviewed commit: `e50fee2`, including the discovery implementation commits after
|
|
||||||
`424c897`. The review compared the implementation with the
|
|
||||||
[discovery roadmap](discovery.md), [implementation plan](implementation.md),
|
|
||||||
[ADR 0019](../adr/0019-explicit-configuration-discovery.md), and the existing
|
|
||||||
configuration and architecture contracts.
|
|
||||||
|
|
||||||
## 1. Defer Absence-Only Context Requirements Until The Search Outcome Is Known
|
|
||||||
|
|
||||||
Priority: P2. Status: complete.
|
|
||||||
|
|
||||||
**Completed:** search prevalidation now defers absence-only root requirements
|
|
||||||
until selection. Ordinary, layered-main, and underlay regression cases compare
|
|
||||||
searched files with explicit files under an empty context and retain errors for
|
|
||||||
absent inputs. The regression failed before the fix. Focused context/absence
|
|
||||||
tests and `./scripts/check.sh` passed with Go 1.26.10 on Linux.
|
|
||||||
|
|
||||||
**Location:** [filesystem.go](../../internal/engine/filesystem.go), lines 77–123,
|
|
||||||
and the outcome-specific validation in
|
|
||||||
[load_input.go](../../internal/engine/load_input.go), lines 103–122.
|
|
||||||
|
|
||||||
The `sourceSearch` branch calls `absentFilesystemOptionDiagnostics` before
|
|
||||||
selection. That helper requires a nonempty host `Root`, even though a selected
|
|
||||||
host file can derive both its base and default root from its own path.
|
|
||||||
|
|
||||||
**Confirmed reproduction:** with an existing file containing `value: selected`
|
|
||||||
and `Options[Config]{Filesystem: &FilesystemContext{}}`, ordinary `File(path)`
|
|
||||||
loading succeeds. Replacing only the source with
|
|
||||||
`SearchConfig("", Paths(path))` fails with `options.invalid` and the message
|
|
||||||
"absent-input filesystem root and base directories must be absolute host paths".
|
|
||||||
No candidate is acquired. The same source distinction affects layered inputs
|
|
||||||
because their validation uses the same helper.
|
|
||||||
|
|
||||||
This applies an absent-input requirement to a selected-file workflow and
|
|
||||||
unintentionally strengthens its contract. The roadmap requires selected files
|
|
||||||
to retain normal file-context behavior; the plan explicitly separates static
|
|
||||||
syntax checks from outcome-dependent compatibility checks.
|
|
||||||
|
|
||||||
**Required correction:** validate only outcome-independent context syntax before
|
|
||||||
discovery. Once the outcome is known, apply normal host-file rules to selection
|
|
||||||
and absent-input rules to exhaustion. Preserve the required explicit root for an
|
|
||||||
absent input with a supplied host context, selected-file rejection of `BaseDir`
|
|
||||||
or native `FS`, containment checks, and terminal context failures.
|
|
||||||
|
|
||||||
**Verification:** add ordinary and layered selected-file equivalence cases with
|
|
||||||
an explicit empty context. Also verify that optional exhaustion with that same
|
|
||||||
context still fails, and that the existing root-only selected/absent overlay
|
|
||||||
cases continue to pass. Do not change `File` to reject its currently valid context
|
|
||||||
or require callers to omit an otherwise valid options value.
|
|
||||||
|
|
||||||
## 2. Preserve The Failing Candidate's Identity In Access Diagnostics
|
|
||||||
|
|
||||||
Priority: P2. Status: complete.
|
|
||||||
|
|
||||||
**Completed:** terminal probe failures retain the candidate descriptor and gain
|
|
||||||
normal file-source evidence at the acquisition boundary. Regression tests compare
|
|
||||||
ordinary and later-underlay failures with explicit-file errors, including source
|
|
||||||
IDs, placement, layers, and skipped missing candidates. A controlled permission
|
|
||||||
probe checks the diagnostic-to-file link. The load regression failed before the
|
|
||||||
fix; focused discovery/failure tests and `./scripts/check.sh` passed on Linux.
|
|
||||||
|
|
||||||
**Location:** [discovery.go](../../internal/engine/discovery.go), lines 517–528,
|
|
||||||
and the early resolver-error return in
|
|
||||||
[load_input.go](../../internal/engine/load_input.go), lines 105–109.
|
|
||||||
|
|
||||||
When symlink resolution or stat fails for a reached candidate with an error other
|
|
||||||
than absence, the resolver returns a diagnostic with an empty source ID and
|
|
||||||
discards the candidate. The acquisition boundary returns no source evidence.
|
|
||||||
Consequently neither `LoadError.Sources()` nor the diagnostic identifies which
|
|
||||||
candidate failed. The permission-probe test in
|
|
||||||
[discovery_test.go](../../internal/engine/discovery_test.go) also asserts a nil
|
|
||||||
primary location, preserving this omission rather than the intended normal
|
|
||||||
source diagnostics.
|
|
||||||
|
|
||||||
**Confirmed reproduction:** create a regular `app.yml`, then search paths
|
|
||||||
`app.yml/child.yml` followed by the valid `app.yml`. The first path fails with
|
|
||||||
`ENOTDIR`. Explicit `File(badPath)` returns `source.read` with a primary source
|
|
||||||
location and a file record identifying `badPath`. The equivalent search correctly
|
|
||||||
stops, but returns `source.read` with no primary location and an empty source
|
|
||||||
list. An operator cannot locate the problem from the structured error.
|
|
||||||
|
|
||||||
The roadmap calls for normal candidate access diagnostics, and the plan requires
|
|
||||||
normal candidate records for file failures. The decision to omit a public attempt
|
|
||||||
trace does not require discarding the one failing file's identity.
|
|
||||||
|
|
||||||
**Required correction:** retain the failing candidate and expose its normal
|
|
||||||
file-source diagnostic identity at the acquisition boundary, with the appropriate
|
|
||||||
source ID, safe path/label, and input placement/layer. Keep missing attempts
|
|
||||||
absent from records and budgets. Required exhaustion and directory-helper errors
|
|
||||||
still have no invented file location. Preserve terminal access errors and avoid
|
|
||||||
adding a public attempt history, raw OS error strings, or YAML values.
|
|
||||||
|
|
||||||
**Verification:** cover a deterministic real candidate-resolution failure and a
|
|
||||||
controlled permission/stat failure. Check the diagnostic's location against the
|
|
||||||
returned file record, including an underlay after earlier acquired sources.
|
|
||||||
Assert that no later candidate is loaded and that skipped missing candidates
|
|
||||||
still produce no records. Revise the existing nil-primary assertion accordingly.
|
|
||||||
|
|
||||||
## 3. Isolate User-Configuration Preset Tests Using The Platform's Go Convention
|
|
||||||
|
|
||||||
Priority: P2. Status: complete.
|
|
||||||
|
|
||||||
**Completed:** the environment-change test selects the environment variable used
|
|
||||||
by Go on Windows, macOS/iOS, Plan 9, or other Unix hosts, and obtains both fixture
|
|
||||||
bases through `os.UserConfigDir`. It remains nonparallel and restores the
|
|
||||||
environment. The redundant relative-XDG failure assumption was removed; existing
|
|
||||||
private-helper tests retain portable failure/short-circuit coverage. Focused
|
|
||||||
tests and `./scripts/check.sh` passed on Linux. The macOS branch was checked
|
|
||||||
against Go's helper implementation; no native macOS gate was added.
|
|
||||||
|
|
||||||
**Location:** [discovery_load_test.go](../../internal/engine/discovery_load_test.go),
|
|
||||||
`TestSearchSourceObservesUserConfigEnvironmentChanges`, lines 231–279.
|
|
||||||
|
|
||||||
The new test treats every non-Windows platform as using `XDG_CONFIG_HOME`, writes
|
|
||||||
both fixtures beneath that variable, and leaves the actual home environment
|
|
||||||
unchanged. On macOS, `os.UserConfigDir` uses
|
|
||||||
`$HOME/Library/Application Support` and ignores `XDG_CONFIG_HOME`. The first load
|
|
||||||
therefore searches the real user's configuration directory instead of the
|
|
||||||
fixture, and the environment change cannot select the second fixture. This makes
|
|
||||||
the test fail on an ordinary macOS checkout and potentially depend on local files.
|
|
||||||
|
|
||||||
This is confirmed by inspection of the selected Go 1.26.10 standard library's
|
|
||||||
`os.UserConfigDir` implementation and the test. A native macOS run was not
|
|
||||||
performed or required. Production preset resolution correctly delegates to Go;
|
|
||||||
the defect is in the test setup.
|
|
||||||
|
|
||||||
**Required correction:** control the environment consumed by the platform's Go
|
|
||||||
helper, then obtain fixture bases through `os.UserConfigDir` rather than assuming
|
|
||||||
the XDG layout. On macOS, use a temporary home and its derived configuration
|
|
||||||
directory; retain appropriate isolation on Linux and Windows. Keep environment
|
|
||||||
changes restored and the test nonparallel. Use the existing private helper seam
|
|
||||||
for portable directory-resolution failure tests rather than assuming that a
|
|
||||||
relative environment value fails on every platform.
|
|
||||||
|
|
||||||
**Verification:** the environment-change test must observe both controlled
|
|
||||||
fixtures and avoid the real home/config directory. Review the macOS branch
|
|
||||||
against Go's helper behavior. Keep macOS best-effort support and the existing
|
|
||||||
release procedure; do not introduce native macOS validation as a gate.
|
|
||||||
|
|
||||||
## 4. Use Platform-Aware Windows Component Validation
|
|
||||||
|
|
||||||
Priority: P3. Status: complete. Algorithmic audit: correctness finding.
|
|
||||||
|
|
||||||
**Completed:** Windows validation now delegates reserved-name decisions to
|
|
||||||
`filepath.IsLocal` while preserving the selected component/character checks. The
|
|
||||||
manual device table was removed. Tests cover console names, platform-dependent
|
|
||||||
extension behavior, and invalid later search components; Unix tests retain their
|
|
||||||
own filename rules. Focused tests and `./scripts/check.sh` passed on Linux. The
|
|
||||||
Windows engine test binary cross-compiled successfully; it was not run natively.
|
|
||||||
|
|
||||||
**Location:** [discovery.go](../../internal/engine/discovery.go),
|
|
||||||
`validWindowsFilenameComponent`, lines 266–281.
|
|
||||||
|
|
||||||
The hand-maintained device-name switch omits `CONIN$` and `CONOUT$`. Both are
|
|
||||||
Windows console device names, not ordinary filename components. The helper
|
|
||||||
accepts them, so on Windows a slug or custom filename using one can pass static
|
|
||||||
validation. That conflicts with the selected usable-host-component rule and can
|
|
||||||
defer an invalid description until acquisition or hide it behind an earlier
|
|
||||||
successful candidate.
|
|
||||||
|
|
||||||
**Evidence:** an external Go test overlay calling the current private helper
|
|
||||||
failed for both names: `validWindowsFilenameComponent` returned true. The Go
|
|
||||||
1.26.10 Windows implementation of `filepath.IsLocal` recognizes both as reserved,
|
|
||||||
and its own tests include both spellings. The overlay ran on Linux and exercised
|
|
||||||
the helper directly; it was not a native Windows load test. No repository test
|
|
||||||
file was added.
|
|
||||||
|
|
||||||
**Complexity and refactoring opportunity:** the current predicate takes O(L)
|
|
||||||
time for a component of length L, with O(L) temporary storage for case conversion.
|
|
||||||
No asymptotic optimization is needed. Prefer the standard library's
|
|
||||||
platform-aware component/local-path checks within the Windows branch, retaining
|
|
||||||
the selected single-component and invalid-character checks as needed. This
|
|
||||||
avoids maintaining an incomplete device table. Go's Windows reserved-name logic
|
|
||||||
also accounts for OS-dependent extension behavior; do not replace it with a
|
|
||||||
broader blanket naming prohibition.
|
|
||||||
|
|
||||||
**Verification:** add appropriate Windows-specific cases for console names and
|
|
||||||
ordinary names, including invalid later components behind an earlier match.
|
|
||||||
Keep those tests consistent with the host Go helpers rather than imposing their
|
|
||||||
Windows behavior on Unix. This is a correction to existing platform-specific
|
|
||||||
code, not a new Windows support promise or native-platform release gate.
|
|
||||||
|
|
||||||
## 5. Preserve Drive-Root-Relative Windows Path Meaning
|
|
||||||
|
|
||||||
Priority: P3. Status: complete. Algorithmic audit: correctness finding.
|
|
||||||
|
|
||||||
**Completed:** drive-root-relative Windows paths now use `filepath.Abs` alongside
|
|
||||||
drive-relative paths, preserving native host interpretation. Windows regression
|
|
||||||
tests compare candidates and loaded files with explicit `File` for relative,
|
|
||||||
drive-root-relative (both separators), drive-relative, and absolute paths, through
|
|
||||||
both `Paths` and `SearchDirectory`. Existing Unix path/short-circuit tests and
|
|
||||||
`./scripts/check.sh` passed. The Windows engine test binary cross-compiled; no
|
|
||||||
native Windows execution or new release gate was introduced.
|
|
||||||
|
|
||||||
**Location:** [discovery.go](../../internal/engine/discovery.go),
|
|
||||||
`absoluteCandidate`, lines 380–391; also used for literal directory searches.
|
|
||||||
|
|
||||||
On Windows, a path such as `\config.yml` has no volume name and is not absolute
|
|
||||||
according to `filepath.IsAbs`, but it is rooted at the current drive's root.
|
|
||||||
The helper treats it as an ordinary relative path and joins it to the whole
|
|
||||||
working directory. With a working directory of `C:\work`, discovery therefore
|
|
||||||
probes `C:\work\config.yml` instead of `C:\config.yml`. The same issue affects
|
|
||||||
slash-rooted Windows spellings and custom directory searches.
|
|
||||||
|
|
||||||
This can select a different file from explicit `File` loading, which uses
|
|
||||||
`filepath.Abs`. The roadmap requires literal host paths consistent with explicit
|
|
||||||
file loading; it does not select an alternate interpretation of drive-root paths.
|
|
||||||
|
|
||||||
**Evidence:** static tracing against the selected Go 1.26.10 Windows `IsAbs`,
|
|
||||||
`VolumeName`, `Join`, and `Abs` implementations establishes the branch and result.
|
|
||||||
The first two checks route this path into `Join`, which appends it beneath the
|
|
||||||
working directory; Windows `Abs` delegates to the native full-path operation.
|
|
||||||
A native Windows execution was not performed or required.
|
|
||||||
|
|
||||||
**Complexity and refactoring opportunity:** both current normalization and the
|
|
||||||
correct standard-library route are linear in the path spelling length, apart
|
|
||||||
from OS helper work. This is a correctness refactoring, not a scaling problem.
|
|
||||||
Route Windows drive-root-relative paths through `filepath.Abs`, as the helper
|
|
||||||
already does for volume-qualified relative paths, or use a narrow private
|
|
||||||
absolute-path helper that delegates to Go in production. Preserve lazy base
|
|
||||||
resolution and the ordinary-relative-path test seam. No new public API or
|
|
||||||
hand-written Windows path parser is needed.
|
|
||||||
|
|
||||||
**Verification:** on Windows, compare discovery's candidate interpretation with
|
|
||||||
`filepath.Abs` and explicit `File` for ordinary relative, drive-root-relative,
|
|
||||||
drive-relative, and absolute paths. Cover both `Paths` and a literal
|
|
||||||
`SearchDirectory`, and retain the existing Unix literal-path tests. Do not add a
|
|
||||||
new native Windows validation requirement to the release procedure.
|
|
||||||
|
|
||||||
## Algorithmic Audit Scope And Conclusions
|
|
||||||
|
|
||||||
The additional audit reviewed new production code from `424c897` through
|
|
||||||
`e50fee2`; the current code at `68cf5c4` is unchanged from that implementation.
|
|
||||||
It preserved the expressly planned flat snapshots, ordered first-match search,
|
|
||||||
directory-first expansion, whole-description static validation, and reuse of
|
|
||||||
ordinary file acquisition. It did not benchmark or reopen those choices.
|
|
||||||
|
|
||||||
- Let S be the description data processed by an operation, including entries,
|
|
||||||
filename groups, custom names, retained issues, and supplied option lists.
|
|
||||||
Snapshot/concatenation construction is O(S) per construction operation with
|
|
||||||
O(S) owned storage.
|
|
||||||
The nested clone loops visit each stored group/name once; they are not repeated
|
|
||||||
scans of the same list for each candidate. Repeated immutable prefix rebuilding
|
|
||||||
by a caller follows the explicitly planned flat-snapshot representation and
|
|
||||||
was not treated as a new algorithm finding.
|
|
||||||
- Validation is O(S) plus the existing canonical diagnostic ordering cost,
|
|
||||||
O(E log E) comparisons for E errors. Validation occurs a fixed number of times
|
|
||||||
per consumed description, not once per candidate. The audit found no new
|
|
||||||
repeated sorting or quadratic validation pass.
|
|
||||||
- Iteration advances persistent entry/group/name indexes. Across the search it
|
|
||||||
visits each reached group and candidate a constant number of times, with no
|
|
||||||
restart of earlier candidate lists. Traversal is O(S + C) in descriptor and
|
|
||||||
candidate counts, plus generated path bytes and ordinary filesystem work,
|
|
||||||
with O(1) iterator state beyond the description and current path storage.
|
|
||||||
Descriptions are not expanded into a complete candidate product in memory.
|
|
||||||
- Each attempt performs a bounded number of standard path/probe operations;
|
|
||||||
selection stops on success or a terminal error. Selected-file acquisition and
|
|
||||||
the final lifecycle run once. The added absent-context handling checks a fixed
|
|
||||||
number of directories and does not introduce scans over prior configuration
|
|
||||||
contributions.
|
|
||||||
|
|
||||||
No unplanned quadratic algorithm or other actionable asymptotic regression was
|
|
||||||
identified. Findings 4 and 5 record the additional ordinary correctness issues
|
|
||||||
found while examining those algorithms; findings 1–3 were carried forward from
|
|
||||||
the earlier review. Existing configuration-tree algorithms outside the discovery
|
|
||||||
changes were not re-audited as new feature work.
|
|
||||||
|
|
||||||
## Review Validation
|
|
||||||
|
|
||||||
- `./scripts/check.sh` passed with Go 1.26.10 on Linux: formatting/whitespace,
|
|
||||||
all tests, vet, and build.
|
|
||||||
- `go test -race ./...` passed on the same toolchain.
|
|
||||||
- An external temporary Go program reproduced findings 1 and 2 through the
|
|
||||||
public API, comparing explicit file loading with discovery. No production or
|
|
||||||
test files were modified for these reproductions.
|
|
||||||
- Findings 1–3 remained open at the original review despite the suites passing.
|
|
||||||
The broader discovery architecture is implemented: immutable root-owned
|
|
||||||
descriptions, incremental ordered selection during shared source acquisition,
|
|
||||||
concrete selected-source propagation, and shared absent-input handling before
|
|
||||||
the existing composition, inheritance, decoding, resolution, and publication
|
|
||||||
lifecycle. No additional release gates or new composition semantics were found.
|
|
||||||
- The algorithmic audit used source-level complexity analysis and the temporary
|
|
||||||
helper regression overlay described in finding 4. It collected no timing or
|
|
||||||
allocation measurements. Findings 4 and 5 are platform-specific correctness
|
|
||||||
findings, not claims of a native Windows validation run.
|
|
||||||
|
|
||||||
## Completed Remediation Validation
|
|
||||||
|
|
||||||
All five fixes include focused regression coverage and per-issue completion
|
|
||||||
markers above. The final `./scripts/check.sh` and `go test -race ./...` passed
|
|
||||||
with Go 1.26.10 on Linux. The Windows engine tests also cross-compiled with
|
|
||||||
`GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go test -c`, with the binary kept outside
|
|
||||||
the repository. No native Windows or macOS run was performed or made a gate.
|
|
||||||
The implementation preserves the public API, candidate ordering, optional
|
|
||||||
absence, and existing composition/version/ownership contracts.
|
|
||||||
Reference in New Issue
Block a user