Add selected profile overlays

This commit is contained in:
2026-09-06 21:03:45 +00:00
parent 71a73bcfd8
commit ef0e6078f7
17 changed files with 897 additions and 81 deletions

View File

@@ -2,7 +2,7 @@
## Status and ownership
Status: implementation in progress; Stages 1–9 are complete and later stages are
Status: implementation in progress; Stages 1–13 are complete and later stages are
pending.
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
@@ -982,7 +982,31 @@ Acceptance:
## Stage 13 — Implement profile overlays
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added a private non-aliasing recursive overlay that merges mappings by key and
atomically replaces scalars, sequences, empty sequences, nulls, and cross-kind
subtrees. Replacement deletes stale descendant origins and records every displaced
definition plus the parent operation for later history.
- Added exact selected-profile loading from `profiles/<name>.yml`, with constrained
names, inactive empty/`default` selection, canonical root containment, base-graph
duplicate rejection, deterministic profile source ownership, and shared aggregate
source/byte/node limits. No path selection, extension fallback, inheritance, or
implicit default file is supported.
- Applied profiles only after base import composition and version checking and before
defaults and strict decoding. Profiles reject `_configkit` at any depth, direct
version contributions, and scalar/null replacements of version ancestors; base
parse and version failures therefore cannot be masked.
- Tests cover recursive siblings, scalar/full/empty-sequence/null/cross-kind
replacement, lower defaults, profile source and origin attribution, removed-child
and stale-index history, selection errors, fixed extension, directives, version
placement, base aliases, parse ordering, filesystem context, symlink escape, input
budgets, and non-aliasing. The
[current profile contract](../config.md#selected-profiles) owns the behavior.
Verified `go test ./...`, `go test -race ./...`, `go vet ./...`, `go build ./...`,
and `git diff --check` with Go 1.26.7 on linux/amd64.
Read: feature [profiles](initial.md#profiles), composition/overlay table.

View File

@@ -450,28 +450,9 @@ origin is a resolution operation, with input origins retained separately.
## Composition and overlay
Strict base composition is implemented and now owned by the
[current configuration contract](../config.md#strict-document-composition).
Intentional profile overlay remains target behavior. Its exhaustive operation table
is below; “value” includes scalar, sequence, mapping, and null, while absent means no
node rather than null.
| Existing/lower node | Incoming/higher node | Overlay |
| --- | --- | --- |
| Absent | Absent | Absent |
| Absent | Value | Incoming |
| Value | Absent | Existing |
| Mapping | Mapping | Recurse by key |
| Any other pair of present nodes | Any other pair of present nodes | Replace entire existing node |
A mapping can replace a lower scalar, sequence, or null during overlay, subject to
the final schema. Sequences are atomic and the higher sequence replaces the entire
lower sequence in an overlay. `[]` clears a sequence. There is no append, union,
index merge, or mapping-clear operator.
Replacing a parent must not leave effective metadata for removed children.
Sequence histories describe whole-sequence replacement; index origins refer to
the surviving sequence, not an attempted correspondence with previous indices.
Strict base composition and selected-profile overlay are implemented and owned by
the [current composition contract](../config.md#strict-document-composition) and
[profile contract](../config.md#selected-profiles).
## Explicit imports and source identity
@@ -481,18 +462,9 @@ filesystem context, and budgets are implemented and now owned by the
## Profiles
Load only the composed base unless the application selects a profile. The API
accepts a name matching `[a-zA-Z0-9_-]+`, not an arbitrary path. The empty name
and reserved lowercase name `default` both mean no overlay; a literal
`profiles/default.yml` is never loaded. Other names resolve exactly to
`<configuration-root>/profiles/<name>.yml`, without extension fallback or case
normalization. A selected missing profile is an error.
A profile is one policy-validated YAML mapping applied using the overlay table.
It must not contain `_configkit`, introduce imports, or alter the application's
declared schema-version path. Reject these directives even if their values
would be unchanged. There is no profile inheritance or multiple-profile stack.
Applications own flags and commands that select this behavior.
Selected profiles are implemented and owned by the
[current profile contract](../config.md#selected-profiles). Applications continue
to own the flags and commands that choose a profile name.
## Static defaults and null
@@ -597,8 +569,8 @@ Automatic migration is deferred.
## Consumer API design
The core declarations, callback lifecycle, strict composition, and explicit path
imports are implemented; their current contract is owned by
The core declarations, callback lifecycle, strict composition, explicit imports,
and selected profiles are implemented; their current contract is owned by
[configuration input](../config.md) and Go reference comments. Remaining declarations
and behavior in this section are target design, not implemented Go reference
documentation or a frozen naming commitment. Declarations omit method bodies and
@@ -614,7 +586,7 @@ and verification against consumer models.
`Load`, `Definition`, `Options`, `FilesystemContext`, `Override`, `VersionRule`,
`Version`, `Validation`, and `Result` are declared. The current loader supports the
callback and path-import subset described by the [current contract](../config.md);
later profile, override, secret, and rendering behavior below remains target work and
later override, secret, and rendering behavior below remains target work and
unavailable options fail explicitly rather than being ignored.
```go
@@ -660,8 +632,8 @@ ConfigKit does not synchronize application-owned captured state.
`Source`, its constructors, and import filesystem context are implemented;
[`source.go`](../../source.go) and the [configuration input contract](../config.md#sources)
own their current behavior. Applications must use non-secret source labels because
labels appear in diagnostics and source reports. Profile-related filesystem behavior
remains target work. Filesystem options do not change application field path meanings.
labels appear in diagnostics and source reports. Filesystem options do not change
application field path meanings.
`NoSource()` explicitly selects defaults-only loading. It contributes no document
or file source entry and marks no fields explicit by itself; CLI overrides still