diff --git a/docs/roadmap/discovery.md b/docs/roadmap/discovery.md index 0853def..95fa51d 100644 --- a/docs/roadmap/discovery.md +++ b/docs/roadmap/discovery.md @@ -1,7 +1,8 @@ # Composable Configuration Discovery Roadmap -Status: proposal with the agreed direction captured below. Preset contents and -the remaining behavioral choices are under [Open Decisions](#open-decisions). +Status: proposal with the agreed direction, filename groups, and Unix system +preset captured below. Remaining preset and behavioral choices are under +[Open Decisions](#open-decisions). This document describes future work, not an implemented contract. A staged implementation plan will follow once the decisions are settled. @@ -43,13 +44,76 @@ library-provided search conventions. Consumer ownership remains unchanged. ## Agreed Direction -### Ordered Candidate Groups +### Filename Groups, Directory Searches, And Search Presets -Provide reusable descriptions of built-in search lists and custom ordered paths. -Consumers select one or more groups in the desired order. Conceptually, concatenate -the groups into one ordered candidate sequence, preserving order within each -group. This is ordered concatenation followed by first-match selection, not a -set union or a precedence merge. +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 | `.yml`, `.yaml` | +| Generic | `config.yml`, `config.yaml`, `main.yml`, `main.yaml` | + +`` 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. + +**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 eventual documentation should explain this model using the applicable +mathematical nomenclature: ordered lists, ordered concatenation, and Cartesian +product where a shared filename list applies. Retain these names in relevant +implementation comments where they help readers understand the operations. The +model describes 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/` | `[Named, Generic]` | +| `/etc/` | `[Named, Generic]` | +| `/etc` | `[Named]` | + +This produces 14 candidates: six in each application-specific directory, then +`/etc/.yml` and `/etc/.yaml`. For example, +`/usr/local/etc//main.yaml` precedes +`/etc//.yml`. Selecting only `Named` for `/etc` is an ordinary +directory-search description; the selection algorithm needs no special exception. + +### Illustrative Consumer API Names below are illustrative and are not implemented Go API: @@ -78,8 +142,9 @@ source := configkit.SearchConfig( ``` Use the resulting source with ordinary loading or as a layered main input. Custom -lists participate at their specified position; they do not silently append -unselected built-in defaults. Reordering groups changes search priority. +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 @@ -93,9 +158,9 @@ unselected built-in defaults. Reordering groups changes search priority. | 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 group. Permitting an -absent file does not waive application version, schema, or domain requirements; -the remaining lifecycle may still reject an incomplete configuration. +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 @@ -142,25 +207,31 @@ An explicitly selected overlay is not another main-file fallback candidate. The subsequent implementation plan should cover: -- Define candidate-group and search-source representations, slug validation, and - a search-wide requiredness option. Keep construction separate from acquisition. -- Implement the selected preset expansions, ordered custom paths, and first-match - selection through existing file access. Resolve each source once per consumption - before entering its parse and composition lifecycle. +- 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. +- 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. 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 candidate and group ordering, custom-list composition, both filename - extensions, no-match behavior, required searches, unavailable preset bases, - errors that must stop fallback, repeated loads, and untouched explicit `File` +- 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, + 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. + 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. This roadmap does not change implementation, policy, or ADRs by itself. Coordinate implementation with the ongoing sequence integration audit rather than assuming @@ -168,54 +239,48 @@ its in-progress changes are part of a fixed implementation baseline. ## Open Decisions -### OD-1 — Preset Contents And Filename Order +### OD-1 — Additional Preset Locations -**Recommendation:** begin with three explicitly selected presets. Try `.yml` -before `.yaml` at each location, completing both alternatives there before moving -to the next location: +The filename groups, default `[Named, Generic]` order, and Unix system preset +are settled above. The remaining choice is which additional directory conventions +to provide as presets. -| Preset | Candidate locations, in order | -| --- | --- | -| Unix system | `/usr/local/etc//config.yml`, `/usr/local/etc//config.yaml`, `/etc//config.yml`, `/etc//config.yaml`, `/etc/.yml`, `/etc/.yaml` | -| User configuration | `//config.yml`, then `config.yaml` in the same directory | -| Working directory | `./.yml`, then `./.yaml` | +**Recommendation:** also provide user-configuration and working-directory presets: + +| Preset | Directory | Filename groups, in order | +| --- | --- | --- | +| User configuration | `/` | `[Named, Generic]` | +| Working directory | Current working directory | `[Named, Generic]` | For the user preset, use Go's platform-specific [`os.UserConfigDir`](https://pkg.go.dev/os#UserConfigDir) as the base. This supplies a conventional user location without inventing separate home-directory rules. -The system list retains the originally proposed location order. Application-named -working-directory files make the selected application's convention explicit. -No preset walks ancestor directories. +The working-directory preset searches that directory without walking ancestors. +Both reuse the agreed default filename groups. -**Alternative:** use `./config.yml` and `./config.yaml` for the working-directory -preset, or add them after the application-named files. Generic names are concise -for project-local applications, while application-named files distinguish -applications sharing a working directory. Adding both is convenient but expands -the candidate list and makes a generic file a fallback even when the consumer -intended an application-specific convention. - -An additional viable user convention is a home-directory dotfile or directory, -such as `~/..yml` or `~/./config.yml`. It is familiar for some tools, -but would add a second user convention and ordering to explain. Prefer the -user-config-directory preset initially; custom paths can express other layouts. +**Alternative:** provide a home-directory convention, such as +`~/./` with `[Named, Generic]`, instead of or alongside the +user-config-directory preset. It is familiar for some tools, but providing both +adds another convention for consumers to select and order. Prefer the +user-config-directory preset initially; custom searches can express other layouts. ### OD-2 — Unavailable Bases And Platform Applicability -**Recommendation:** resolve group bases as the ordered search reaches them. -If a selected group's base cannot be resolved, report an error. Treat using the +**Recommendation:** resolve directory bases as the ordered search reaches them. +If a selected directory's base cannot be resolved, report an error. Treat using the Unix system preset on a platform where it is unsupported as an explicit -configuration error when that group is reached. Specify the supported platforms +configuration error when that preset is reached. Specify the supported platforms in the implementation plan. Do not silently translate the Unix preset into a different platform's convention. User and working-directory presets use their platform-native bases. An unavailable base is different from a candidate file being absent. Failure to determine the user's configuration directory should not silently redirect loading -to a different group. Once an earlier candidate is selected, later groups need +to a different directory. Once an earlier candidate is selected, later searches need not resolve their bases. Static invalid search arguments still fail ordinary option validation before source acquisition. -**Alternative:** treat unsupported presets or unavailable bases as empty groups +**Alternative:** treat unsupported presets or unavailable bases as empty searches and continue. This makes one preset composition usable across more environments, but can unexpectedly select a lower-priority configuration. A selectable skip-unavailable behavior could serve that need later; it adds a distinct policy @@ -248,14 +313,15 @@ case could avoid changing `NoSource()`, at the cost of two different absence rul ### OD-4 — Empty Composition And Implicit Preset Selection -**Recommendation:** require explicit group selection to activate a convention. -An empty group composition has no candidates: optional search contributes no -source, and required search fails as exhausted. Empty custom lists follow the -same rule. This supports programmatic list construction and introduces no hidden +**Recommendation:** require explicit preset or search selection to activate a +convention. An empty search composition has no candidates: optional search contributes no +source, and required search fails as exhausted. Empty custom lists, including +empty filename-group selections, follow the same rule. This supports programmatic +list construction and introduces no hidden system, user, or working-directory fallback. One explicitly selected preset still gives the simple application a short call. -**Alternative:** let omission of groups select a documented default preset. +**Alternative:** let omission of searches select a documented default preset. This shortens the simplest call, but omission and an intentionally empty computed list would need distinguishable representations. It also requires choosing a default convention across platforms and application styles. Another alternative