Document source-root configuration controls

This commit is contained in:
2026-11-02 00:45:36 +00:00
parent e5475ea7f8
commit 68ec552b52
8 changed files with 73 additions and 34 deletions

View File

@@ -356,7 +356,7 @@ independent of where the source file lives on disk. Consider this file layout:
With imports enabled, `config.yml` can contain:
```yaml
configkit:
config:
imports:
- path: fragments/prompt.yml
destination: [prompts, welcome]
@@ -383,10 +383,10 @@ existing inputs keep their behavior. Main inputs and underlay inputs use the
same destination model. Destination segments are literal mapping keys: dots,
slashes, and numeric-looking text are not split or interpreted as sequence
indexes. Explicit index segments and paths that introduce the reserved
`configkit` or mapping-valued `extends` structure are invalid input options and
mapping-valued `extends` structure are invalid input options and
produce a structured `LoadError` before source acquisition.
Source-root `configkit.imports` directives are processed before placement. An
Source-root `config.imports` directives are processed before placement. An
import without an explicit destination inherits the effective destination of
its importing document, including through nested imports. An import may instead
declare a relative destination as a sequence of literal mapping keys; its keys
@@ -449,7 +449,7 @@ child uses the parent's settings that remain after that combination.
An identified list uses a mapping reference instead of a sibling name:
```yaml
configkit:
config:
sequences:
- destination: [presets]
key: id
@@ -468,7 +468,7 @@ Any input mapping may declare `extends: name` to inherit from the mapping named
`name` among its immediate siblings. Names are exact keys, including the empty
string; lookup does not search ancestor or unrelated scopes. A mapping inside a
sequence can also participate in record inheritance when that sequence has an
effective identity declaration from `configkit.sequences` or an application
effective identity declaration from `config.sequences` or an application
registration. In that case, `extends` is a mapping containing exactly the
sequence's identity fields, with scalar values; lookup is limited to that final
sequence. The document root and a sequence-element mapping without an effective
@@ -683,7 +683,8 @@ each fragment conforms to a complete application schema. Final assembled
decoding and application validation still run once.
A header that is absent below a structural control follows the same omission
policy. Actual header/control overlap is rejected, and ordinary structural
policy. Actual source-local header/control overlap is rejected; an application
path beginning with `config` alone does not imply overlap. Ordinary structural
validation and main-version ownership protection continue independently.
Accepted non-main declarations are consumed from their owned payload before
@@ -1292,7 +1293,7 @@ tell which records refer to the same entry. Declare the identity and desired
operation at the source root, for example:
```yaml
configkit:
config:
sequences:
- destination: [records]
key: id
@@ -1330,7 +1331,7 @@ lists replace whole lower lists.
mapping-only `Path`; the matching form accepts literal-key and any-mapping-key
segments created by `SequenceLiteralKey` and `SequenceAnyMappingKey`. A literal
`"*"` key is distinct from the wildcard constructor. Parsed numeric path tokens
remain literal mapping keys; explicit index segments and reserved `configkit` or
remain literal mapping keys; explicit index segments and reserved
mapping-valued `extends` segments are invalid. The root selector is harmless under
the mapping-root schema. Selector segments consume one mapping level and match a
complete destination; an any-mapping-key segment matches exactly one key, not an
@@ -1384,7 +1385,7 @@ completed configuration.
### YAML declarations and policy lifetime
Ordinary sources, layered inputs, and selected overlays may declare collection
behavior under `configkit.sequences` at the document root. Each entry has a
behavior under `config.sequences` at the document root. Each entry has a
required literal `destination` and may specify `key` as one string or a nonempty
list of strings, plus a `mode` of `replace`, `append`, or `upsert`. These
controls are removed before application decoding. A declaration destination is
@@ -1450,7 +1451,7 @@ policies follow their owning records: retaining a record retains its rules, whil
whole-record upsert replacement discards the displaced record's rules. Matching
identity or reusing an index does not transfer them. These policies do not change
CLI behavior: CLI sequence assignments replace whole sequences after decoding.
A root `configkit.imports` declaration still requires `Options.Imports`;
A root `config.imports` declaration still requires `Options.Imports`;
`NoSource` has no declaration scope.
An empty mapping contributes no child definitions. Two empty mappings compose, and
@@ -1503,8 +1504,8 @@ origin data; history retains the displaced definitions and identifies the parent
replacement. Surviving overlay paths have overlay origins, while recursively shared
mapping containers retain their contributing locations.
An overlay may contain only root `configkit.sequences` declarations; overlay
imports and nested directives are rejected. Sequence declarations are removed
An overlay may contain only root `config.sequences` declarations; overlay
imports are rejected and nested `config` fields are ordinary data. Sequence declarations are removed
before application decoding. At declared or registered sequence destinations,
`replace`, `append`, and `upsert` use the same identity and policy rules as source
composition. Other overlay sequences remain atomic replacements. An overlay may
@@ -1544,12 +1545,12 @@ values recursively overlay existing mappings. Map keys are sorted canonically an
sequence order is preserved.
All records are detached and validated before any is applied. They cannot create
`configkit` or `extends` controls. Root and explicit
`extends` controls. Root and explicit
sequence-index paths are invalid; parsed numeric tokens remain mapping keys. A path
may create missing mapping ancestors but cannot traverse an existing scalar, null,
or sequence. Duplicate and ancestor/descendant-overlapping records are rejected.
Neither paths nor values may create `configkit`, and no record may overlap the
configured schema-version path. Unknown fields and effective type mismatches remain
Paths and values may contain ordinary `config` and `configkit` data; no record may
overlap the configured schema-version path. Unknown fields and effective type mismatches remain
ordinary strict-decoder errors.
At most 1,024 records are accepted, and each record creates one synthetic CLI
@@ -1582,7 +1583,7 @@ files, not three declarations that must be used together:
Named file:
```yaml
configkit:
config:
imports:
- path: fragments/service.yml
```
@@ -1590,7 +1591,7 @@ configkit:
Directory search, restricted here to YAML filenames:
```yaml
configkit:
config:
imports:
- dir: fragments
pattern: "*.yml"
@@ -1599,7 +1600,7 @@ configkit:
Path pattern:
```yaml
configkit:
config:
imports:
- glob: "fragments/*.yml"
```
@@ -1621,7 +1622,7 @@ rules below explain absence, patterns, placement, and ordering.
### Declaration and selection rules
Imports are opt-in. With `Options.Imports` enabled, a document may use its root
`configkit` mapping for `imports` and `sequences` controls. Ordinary `Load` may use
`config` mapping for `imports` and `sequences` controls. Ordinary `Load` may use
`sequences` without enabling imports, but an `imports` field still requires
`Options.Imports`. ConfigKit removes recognized root controls before application
decoding. Each imports sequence item is a mapping with exactly one `path`,
@@ -1633,9 +1634,26 @@ removed `optional` field is invalid. `destination` is an optional sequence of
strings. Every destination string is one literal mapping key and follows the
input-destination validation rules above; quote numeric-looking keys so YAML
reads them as strings. Null, scalar destinations, and non-string members are
`import.invalid`, as are all other unsupported shapes and fields. A `configkit`
key below the root is invalid. With imports disabled, root imports and nested
`configkit` keys remain `import.disabled`.
`import.invalid`, as are all other unsupported shapes and fields. Nested `config`
keys are ordinary application data. With imports disabled, root imports remain
`import.disabled`.
Each acquired document has its own root, including imported files mounted below
another value or as sequence items. Controls are extracted before placement;
assembled application-root `config` data from placement, defaults, CLI, or
resolution is not reinterpreted. For example:
```yaml
config:
sequences: []
service:
config:
imports: application-data
```
The [mounted-root example](../examples/consumers/config_controls_test.go) shows
an imported payload becoming application-root `config`. Inspection YAML can
contain that data and retains its existing lack of a reloadability guarantee.
Path declarations use slash-separated relative paths. Absolute paths and network
URLs are invalid. Each path resolves from its declaring file's canonical directory.
@@ -1679,8 +1697,8 @@ the supplied pointers and slice before source acquisition.
The Go fields are named `FollowSymlinks` and `IncludeHidden`; the YAML keys
remain `follow` and `hidden`.
A source's `configkit.sequences` declarations apply independently of import
enablement; a `configkit.imports` declaration still requires `Options.Imports`.
A source's `config.sequences` declarations apply independently of import
enablement; a `config.imports` declaration still requires `Options.Imports`.
Sequence identities can be supplied either by these YAML declarations or by
application registrations. The [strict composition contract](#strict-document-composition)
defines their agreement, operation, and lifetime rules.
@@ -1800,11 +1818,13 @@ named `conf.d`, and ConfigKit performs no remote fetching.
### Earlier directive spelling
The directive key is `configkit`, not the `_configkit` spelling used in v0.1.0.
There is no legacy alias: `_configkit` is ordinary application data, subject to
normal schema validation, and does not trigger imports. When upgrading, rename
directive keys in root files and imported fragments, and rename any application
fields previously named `configkit` to avoid the newly reserved key.
The directive key is `config`. Neither `configkit` nor `_configkit` is an alias:
both are ordinary application data subject to normal schema validation and do
not trigger imports. Rename genuine old control blocks in each source; move,
rename, or separately mount source-root application fields named `config`.
Stored source-local declaration paths change from `/configkit/...` to
`/config/...`. Permissive schemas may decode old blocks as data without
executing their imports.
## YAML documents

View File

@@ -7,6 +7,16 @@ Your application still owns CLI parsing, resource reads, remote acquisition, and
runtime initialization.
Import `"go.maximumdirect.net/configkit"`; the package name is `configkit`.
Document controls now use root `config` in every source. Rename genuine old
`configkit` or `_configkit` control blocks and update stored declaration paths
from `/configkit/...` to `/config/...`. Old blocks become ordinary data and a
permissive schema can accept them without executing imports. Move, rename, or
separately mount application fields previously authored as source-root `config`.
Nested and assembled root `config` values remain ordinary application data; the
[mounted-root example](../../examples/consumers/config_controls_test.go) shows
the boundary. See the [configuration contract](../config.md#declaration-and-selection-rules)
for the complete rules.
Start with one file, then choose the tasks your application needs:
| Task | Start here |

View File

@@ -64,6 +64,7 @@ Accepted decisions:
- [Define identity and merge rules for each collection](adr/0017-collection-sequence-policy.md)
- [Apply CLI overrides after inheritance and decoding](adr/0018-cli-after-structural-resolution.md)
- [Keep main-file discovery explicit and composable](adr/0019-explicit-configuration-discovery.md)
- [Scope configuration controls to document roots](adr/0020-document-root-controls.md)
For cross-cutting changes, follow every applicable row. Completed implementation
and remediation plans are preserved in Git history at `cb5b435`; use current

View File

@@ -110,7 +110,9 @@ source, filesystem, destination, and import settings.
[`acquisition.go`](../../internal/engine/acquisition.go) defines acquisition
policy, load-wide accounting, and tier-scoped repetition/namespace state. Each
input is acquired under that policy; grouped requests retain acquired underlay
input is acquired under that policy. Source-root controls are extracted from
each acquired tree before placement; assembled application data is not parsed
again for controls. Grouped requests retain acquired underlay
scopes until their tier is composed in
[`load_request.go`](../../internal/engine/load_request.go). Completed tiers then
share one assembly handoff that applies collection declarations and projects

View File

@@ -207,7 +207,9 @@ records this ordering.
CLI overrides are the highest-priority input to application resolution. Supplied
sequences replace whole sequences, and mappings combine recursively. Inheritance
and typed defaulting do not run again after CLI application. CLI paths and values
must not introduce structural directives; earlier source, inheritance, and
must not introduce inheritance controls; source-document controls are interpreted
only at acquisition, before placement, and later application `config` data is
not reinterpreted. Earlier source, inheritance, and
decoding failures remain errors even if a CLI assignment would replace the value.
Semantic validation assesses the final configuration after overrides and resolution.

View File

@@ -289,6 +289,8 @@ executable example. Existing scenarios still exercise their original features.
## Stage 9 — Update current documentation and public comments
**Status:** Complete. Current documentation, navigation, and public comments reflect the implemented boundary; local links and examples pass.
**Scope:** canonical behavior, local architectural descriptions, consumer
migration guidance, and navigation after the implementation is in place.