Apply explicit CLI configuration overrides

This commit is contained in:
2026-09-06 21:55:35 +00:00
parent d6e32d8807
commit d4ec8607d1
13 changed files with 1049 additions and 72 deletions

View File

@@ -41,7 +41,8 @@ The current lifecycle is:
```text
validate options → parse the selected source → traverse/compose enabled imports
→ build schema → check the optional application version → apply a selected profile
→ create and clone defaults → strictly decode → recorded application resolution
→ apply explicit CLI overrides → create and clone defaults → strictly decode
→ recorded application resolution
→ resolve typed secret references → semantic validation → classify secrets
→ snapshot configuration and metadata
```
@@ -54,11 +55,11 @@ 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, one selected profile, and
filesystem context. It also accepts static secret tags and a dynamic path predicate.
Overrides return `options.unavailable` because their lifecycle stage is 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.
filesystem context. It also accepts explicit CLI override records, static secret
tags, and a dynamic path predicate. `NoSource` rejects imports, active profiles,
and filesystem context as invalid combinations but accepts overrides. 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
@@ -106,8 +107,8 @@ by the profile location; an empty mapping is still an explicit container even th
it contributes no children. Resolution origins are synthetic: they have layer
`resolution`, no fabricated file location, and a `derived_default` or `normalization`
operation. Reasons and dependency paths retain first occurrence order with duplicates
removed. Source records remain ordered as selected source, depth-first imports, then
the selected profile.
removed. Source records remain ordered as selected source, depth-first imports, the
selected profile, then synthetic CLI records in supplied order.
### Secret classification and effective rendering
@@ -354,6 +355,54 @@ version, or replace an ancestor of that version with a scalar or null. These rul
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 CLI overrides
`Options.Overrides` accepts only records the application knows were explicitly set
by its CLI parser. Each `Override` supplies a non-root mapping `Path`, a detached
`Value`, and a required safe display `Flag` such as `--workers`; ConfigKit has no CLI
parser dependency. Parser defaults must not be submitted as overrides, and `Flag`
must never contain the raw argument or configured value. General environment
variable overlays are an explicit non-goal: environment access is reserved for
typed secret references.
Overrides apply after the selected profile and before typed defaults/decoding, so
the input precedence is application defaults < composed base < selected profile <
CLI. Recorded resolution, typed secret lookup, and semantic validation follow the
effective override tree. Overrides also work with `NoSource`.
Values are converted directly into ConfigKit's owned tree without YAML parsing or
string coercion. Supported values are `nil`, built-in strings and booleans, all
built-in signed and unsigned integer widths except `uintptr`, finite `float32` and
`float64`, and recursive `[]any`/`map[string]any`. Strings stay strings; duration and
secret fields therefore receive string values. Nil is explicit null; empty strings,
false, and zero remain intentional; empty sequences replace whole lists; and mapping
values recursively overlay existing mappings. Map keys are sorted canonically and
sequence order is preserved.
All records are detached and validated before any is applied. 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
ordinary strict-decoder errors.
At most 1,024 records are accepted, and each record creates one synthetic CLI
source after file/profile sources in supplied order. Override sources share the
1,024-source ceiling. A record has an 8 MiB string-byte limit; override strings and
source bytes share the 64 MiB aggregate limit. Synthetic mapping keys and values
count toward the existing 1,000,000-node and depth-256 ceilings. Cycles, named or
unsupported types, nonfinite floats, and limit excesses fail with safe `override.*`
diagnostics. Diagnostics and provenance identify the safe `Flag` source, never raw
arguments or values.
Override origins are explicit CLI origins. History retains replaced base/profile
definitions and records equal-value replacements as well as structural changes.
Caller-owned maps and slices are snapshotted during loading and must not be mutated
concurrently with the call. The executable `ExampleOverride` shows translating a
parser's explicit-set state into records while preserving an explicitly supplied
zero.
## Explicit imports
Imports are opt-in. With `Options.Imports` enabled, a document may reserve its root

View File

@@ -4,7 +4,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, selected profile overlays, typed defaults, schema-version checks,
recorded application resolution, semantic validation, layered metadata, secret
explicit CLI overrides, recorded application resolution, semantic validation,
layered metadata, secret
classification, typed environment references, explicit secrets bootstrap, and
redacted effective rendering. The
[component overview](internal/overview.md) owns the implementation inventory.

View File

@@ -15,6 +15,8 @@ module and package documentation boundary. Its implemented public primitives are
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`;
- detached parser-neutral CLI override validation and synthetic-source application
in `override.go`, sharing overlay, source, byte, node, and depth policies;
- 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
@@ -41,7 +43,7 @@ module and package documentation boundary. Its implemented public primitives are
only upstream dumper integration isolated in `internal/yaml/write.go`.
The [configuration contract](../config.md) owns implemented input, schema, decoding,
loading lifecycle, path imports, composition, profile overlay, version checking,
loading lifecycle, path imports, composition, profile/CLI overlay, version checking,
recorded resolution, typed secret references/bootstrap, semantic validation,
metadata, secret classification, redacted effective rendering, and ownership
behavior.

View File

@@ -1301,7 +1301,41 @@ Acceptance:
## Stage 18 — Apply explicit CLI overrides above profiles
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Activated the existing `Override`/`Options.Overrides` surface through the new
parser-neutral implementation in [`override.go`](../../override.go). It accepts
only built-in scalar values, `nil`, `[]any`, and `map[string]any`; preserves
scalar kinds and sequence order; sorts maps; rejects named/unsupported values,
nonfinite floats and cycles; and snapshots all accepted caller-owned state without
YAML reparsing or textual inference.
- Added full-record preflight for safe flag labels, non-root mapping paths,
sequence traversal, existing non-mapping ancestors, duplicates/overlaps,
`_configkit`, schema-version overlap, and fixed record/source/string-byte/node/
depth budgets. Valid records create deterministic synthetic CLI sources after
file and profile sources; failures identify those safe flag sources without raw
arguments or values.
- Generalized the existing non-aliasing overlay machinery across profile and CLI
layers. Overrides apply after the selected profile and before defaults/typed
decode, create missing mapping ancestors, recursively overlay mappings, replace
sequences atomically, and retain detached before/after snapshots plus displaced
origins even for equal-value operations. Layered history reconstruction now
preserves default/base/profile/CLI order and parent operations for descendants.
- Added lifecycle and boundary coverage in
[`override_test.go`](../../override_test.go) for defaults < imports/base < profile
< CLI < recorded normalization, followed by secret lookup and validation; explicit
zero/false/empty/list/null; defaults-only loads; map merge, whole-list replacement,
parsed numeric map keys, caller mutation, equal replacements, safe decode source
attribution, invalid paths/ancestors/overlaps/directives/version writes, value
types/cycles/nonfinite values, and bounds. Added executable `ExampleOverride` and
moved the normative behavior into the
[current override contract](../config.md#explicit-cli-overrides), explicitly
retaining general environment overlays as a non-goal. Verified `go test ./...`,
`go test -race ./...`, `go vet ./...`, `go build ./...`, `go mod tidy -diff`, and
`git diff --check` with Go 1.26.7 on linux/amd64. No deviations were required;
safe historical-value explanation remains later work.
Read: [override contract](initial.md#cli-overrides), overlay/presence/version rules,
source ordering, and the secret-reference boundary.