Add selected profile overlays
This commit is contained in:
@@ -20,28 +20,29 @@ The zero `Source` is invalid. Constructor argument failures are retained inside
|
||||
opaque source and become safe structured diagnostics when the source is consumed.
|
||||
File sources can observe file changes between consumptions; byte sources cannot.
|
||||
|
||||
Each source is limited to 8 MiB. An imported load is limited to 64 MiB across all
|
||||
accepted sources, 1,024 accepted sources, 64 active import edges, and 1,000,000
|
||||
input nodes in aggregate. Glob expansion separately permits 1,000,000 visited
|
||||
directory entries, including nonmatches. Reads request at most the remaining
|
||||
allowance plus one byte so excess data is rejected rather than silently truncated.
|
||||
Data returned together with a read error is not accepted as partial input.
|
||||
Each source is limited to 8 MiB. A load using imports or an active profile is limited
|
||||
to 64 MiB across all accepted sources, 1,024 accepted sources, 64 active import
|
||||
edges, and 1,000,000 input nodes in aggregate. Glob expansion separately permits
|
||||
1,000,000 visited directory entries, including nonmatches. Reads request at most the
|
||||
remaining allowance plus one byte so excess data is rejected rather than silently
|
||||
truncated. Data returned together with a read error is not accepted as partial input.
|
||||
|
||||
## Single-source loading
|
||||
|
||||
`Load[T](source, options...)` accepts zero or one `Options[T]`. The configuration
|
||||
root `T` must satisfy the application schema contract below. With no options it
|
||||
starts from the Go zero value. A `Definition[T].Defaults` factory, when supplied,
|
||||
runs exactly once after successful source parsing, schema construction, and optional
|
||||
schema-version checking. Its result is detached before input is applied.
|
||||
runs exactly once after successful source parsing, schema construction, optional
|
||||
schema-version checking, and selected-profile loading. Its result is detached before
|
||||
input is applied.
|
||||
|
||||
The current lifecycle is:
|
||||
|
||||
```text
|
||||
validate options → parse the selected source → traverse/compose enabled imports
|
||||
→ build schema → check the optional application version → create and clone defaults
|
||||
→ strictly decode → recorded application resolution → semantic validation
|
||||
→ snapshot configuration and metadata
|
||||
→ build schema → check the optional application version → apply a selected profile
|
||||
→ create and clone defaults → strictly decode → recorded application resolution
|
||||
→ semantic validation → snapshot configuration and metadata
|
||||
```
|
||||
|
||||
`File`, `Bytes`, and `Reader` each contribute the first base source with deterministic
|
||||
@@ -51,13 +52,12 @@ contributes no source, and continues through defaults, decode, and projection. A
|
||||
missing file remains an error; it is never interpreted as no input.
|
||||
|
||||
The current `Options` boundary accepts a defaults factory, `VersionRule`, resolver,
|
||||
semantic validator, explicit path and glob imports, and filesystem context. Active
|
||||
profiles, overrides, and dynamic secret classification return `options.unavailable`
|
||||
because their lifecycle stages are not implemented. The reserved profile spelling
|
||||
`default` selects no active profile. `NoSource`
|
||||
additionally rejects imports, active profiles, and filesystem context as invalid
|
||||
combinations. Input containing a reserved `_configkit` mapping key is rejected while
|
||||
imports are disabled.
|
||||
semantic validator, explicit path and glob imports, one selected profile, and
|
||||
filesystem context. Overrides and dynamic secret classification return
|
||||
`options.unavailable` because their lifecycle stages are not implemented. `NoSource`
|
||||
rejects imports, active profiles, and filesystem context as invalid combinations.
|
||||
Input containing a reserved `_configkit` mapping key is rejected while imports are
|
||||
disabled.
|
||||
|
||||
Every source, options, schema, version, defaults, decode, resolution, or validation
|
||||
failure returns a nil result and a `*LoadError`. Successful results contain caller-owned `Config`, detached
|
||||
@@ -79,13 +79,16 @@ error without reporting one, ConfigKit emits a generic `version.hook_failed`
|
||||
diagnostic and suppresses the returned error text. Warnings alone are retained on a
|
||||
successful result. Callback panics remain programming failures.
|
||||
|
||||
### Base/default metadata
|
||||
### Layered input/default metadata
|
||||
|
||||
`Result.Metadata` describes every final canonical path. Exact surviving input paths
|
||||
are explicit and have a base origin; omitted paths have a default origin. An explicit
|
||||
parent mapping does not make omitted children explicit. Null state and available
|
||||
source coordinates are retained. `History` records the Go-zero or application
|
||||
default baseline followed by exact base definitions, including equal-valued input.
|
||||
are explicit and have a base or profile origin; omitted paths have a default origin.
|
||||
An explicit parent mapping does not make omitted children explicit. Null state and
|
||||
available source coordinates are retained. `History` records the Go-zero or application
|
||||
default baseline followed by exact base or profile definitions, including
|
||||
equal-valued input. Profile replacement removes displaced descendants from effective
|
||||
metadata while retaining their base definition and the parent replacement operation
|
||||
in history.
|
||||
All metadata queries and returned nested records are detached.
|
||||
|
||||
### Recorded application resolution
|
||||
@@ -181,6 +184,43 @@ traversal order. Traversal order never authorizes replacement. Composition clone
|
||||
the resulting tree and its ordered claim records rather than mutating or aliasing an
|
||||
input document.
|
||||
|
||||
## Selected profiles
|
||||
|
||||
`Options.Profile` selects at most one overlay by name. Names must match
|
||||
`[a-zA-Z0-9_-]+`; they are not paths. The empty name and exact lowercase name
|
||||
`default` select no profile, and `profiles/default.yml` is never loaded implicitly.
|
||||
Every other name resolves only to `<configuration-root>/profiles/<name>.yml`, with
|
||||
no extension fallback, case normalization, inheritance, or profile stack. A selected
|
||||
missing profile is an error.
|
||||
|
||||
The profile is a separately policy-validated YAML mapping. It is loaded after the
|
||||
base import graph is composed and its schema version is checked, but before defaults
|
||||
and typed decoding. Profile bytes, nodes, and source records count toward the same
|
||||
aggregate limits as base inputs. Its canonical path must remain within the configured
|
||||
root and must not duplicate a file already present in the base graph.
|
||||
|
||||
Overlay is intentionally distinct from strict composition:
|
||||
|
||||
| Existing lower node | Incoming profile node | Result |
|
||||
| --- | --- | --- |
|
||||
| Absent | Value | Retain the profile value |
|
||||
| Value | Absent | Retain the lower value |
|
||||
| Mapping | Mapping | Recurse by mapping key |
|
||||
| Any other pair of present nodes | Any other pair of present nodes | Replace the entire lower node |
|
||||
|
||||
Scalars, sequences, and null are atomic. Thus a profile sequence replaces rather
|
||||
than extends a base sequence, `[]` clears it, null is an explicit replacement, and
|
||||
cross-kind replacement is allowed when the final application schema accepts it.
|
||||
Replacing a subtree removes all displaced descendants from effective presence and
|
||||
origin data; history retains the displaced definitions and identifies the parent
|
||||
replacement. Surviving profile paths have profile origins, while recursively shared
|
||||
mapping containers retain their contributing locations.
|
||||
|
||||
A profile may not contain `_configkit` anywhere, contribute the configured schema
|
||||
version, or replace an ancestor of that version with a scalar or null. These rules
|
||||
apply even if the supplied version text would be unchanged. Profiles cannot mask a
|
||||
base parse, composition, or version failure because those checks run first.
|
||||
|
||||
## Explicit imports
|
||||
|
||||
Imports are opt-in. With `Options.Imports` enabled, a document may reserve its root
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
This is the contributor entry point for ConfigKit. Read the task-specific owners
|
||||
below before making changes. The repository is in early implementation: its public
|
||||
loader currently covers strict selected sources, explicit path/glob import graphs,
|
||||
composition, typed defaults, schema-version checks, recorded application resolution,
|
||||
composition, selected profile overlays, typed defaults, schema-version checks,
|
||||
recorded application resolution,
|
||||
semantic validation, and metadata. The
|
||||
[component overview](internal/overview.md) owns the implementation inventory.
|
||||
|
||||
|
||||
@@ -155,6 +155,17 @@ Assigned to selected-profile overlay:
|
||||
declared defaults, inheritance, arbitrary profile paths, or validating every
|
||||
unselected overlay.
|
||||
|
||||
Implementation record (2026-09-06): `overlayDocuments`, `mergeOverlayMappings`,
|
||||
`loadProfileDocument`, and `profileDirectiveDiagnostics` adapt the assigned merge,
|
||||
selection, loading, and ownership mechanics. `TestOverlayDocumentsRecursesMappingsAndReplacesAtomicSubtrees`,
|
||||
`TestLoadAppliesSelectedProfileAfterBaseAndBeforeDefaults`, profile selection/error,
|
||||
directive/version, containment, and budget cases adapt the assigned tests. ConfigKit
|
||||
uses exactly one fixed selected `.yml` source, permits null and cross-kind replacement,
|
||||
retains displaced descendant history, rejects any profile directive or version-path
|
||||
contribution, and counts the profile in aggregate budgets. Narratio's declared
|
||||
default, multi-overlay validation, inheritance-like application conventions, and
|
||||
null/kind restrictions were not retained.
|
||||
|
||||
### Effective inspection and provenance
|
||||
|
||||
Assigned to layered metadata, redaction, rendering, and explanation:
|
||||
|
||||
@@ -13,6 +13,8 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
- a private strict composer in `compose.go` that joins only disjoint document-tree
|
||||
definitions, retains ordered claims and composite mapping origins, reports every
|
||||
conflicting location, and never aliases an input tree;
|
||||
- fixed selected-profile loading in `profile.go` and recursive, non-aliasing overlay
|
||||
with displacement records in `overlay.go`;
|
||||
- a per-operation private reflection descriptor graph in `schema.go` for supported
|
||||
types, YAML field binding, inlining, secret tags, recursion, and semantic paths;
|
||||
- descriptor-driven canonical projection, detached typed cloning, cycle/limit
|
||||
@@ -31,7 +33,7 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
`validation.go`, completing the public callback lifecycle after composed input.
|
||||
|
||||
The [configuration contract](../config.md) owns implemented input, schema, decoding,
|
||||
loading lifecycle, path imports, composition, version checking, recorded resolution,
|
||||
loading lifecycle, path imports, composition, profile overlay, version checking, recorded resolution,
|
||||
semantic validation, metadata, and ownership behavior.
|
||||
|
||||
The source-extraction inventory records the application foundations assigned to
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user