Apply CLI overrides after typed decoding

This commit is contained in:
2026-10-23 21:37:13 +00:00
parent ecfb78d84d
commit 41b5dff69b
20 changed files with 516 additions and 299 deletions

View File

@@ -55,8 +55,8 @@ The current lifecycle is:
validate options → parse the selected source → extract source controls and
traverse/strictly compose enabled imports
→ build schema → check the optional application version → apply a selected overlay
→ apply explicit CLI overrides → expand sibling inheritance
→ create and clone defaults → strictly decode
→ expand sibling inheritance
→ create and clone defaults → strictly decode → apply explicit CLI overrides
→ recorded application resolution
→ resolve typed secret references → semantic validation → classify secrets
→ snapshot configuration and metadata
@@ -137,13 +137,13 @@ Each group is applied once as a precedence tier. The main tier is then composed
with the accumulated underlay policy, and the optional application version is
checked against that composed main tier only. Underlay roots cannot claim that
version path (`underlay.version`); imported fragments retain `import.version`
protection. ConfigKit then applies the selected overlay and explicit CLI
overrides. Scalar, sequence, and null definitions replace lower values between
tiers unless a collection policy defines the sequence operation; mappings
combine recursively. Syntax and strict-composition errors inside a tier cannot
be masked by a higher-priority tier. Defaults, typed decoding, resolution,
secret lookup, validation, and publication run once on the final assembled
input.
protection. ConfigKit then applies the selected overlay and resolves inheritance.
Scalar, sequence, and null definitions replace lower values between tiers unless
a collection policy defines the sequence operation; mappings combine recursively.
After successful defaults and typed decoding, CLI overrides are applied to that
typed configuration before application resolution. Syntax, strict-composition,
inheritance, and decoding errors cannot be masked by a CLI override. Resolution,
secret lookup, validation, and publication run once on the resulting values.
An active layered overlay requires `LayeredOptions.OverlayFilesystem` explicitly,
independent of the main source's namespace. Its name and root-relative lookup
@@ -530,20 +530,20 @@ historical path. Its effective value and each chronological before/after value a
parser values. `Present=false` with empty YAML means absence or deletion; explicit
null is present and renders as `null`. The history starts with the canonical typed
application-default or Go-zero baseline, followed by relevant underlay and main-tier
contributions in precedence order, the selected overlay, supplied CLI records,
and audited net resolution operations. Within each strict tier, replay follows
contributions in precedence order, the selected overlay, and structural inheritance
events. Typed defaulting and decoding reconcile that source history before supplied
CLI records and audited net resolution operations. Within each strict tier, replay follows
[scoped assembly order](#explicit-imports), not source-record acquisition order.
Structural inheritance events appear after source replay
and before typed reconciliation, including consumed or canceled `extends` input
and equal-valued child replacements. Overlapping parent/child resolution records
Inheritance events include consumed or canceled `extends` input and equal-valued
child replacements. Overlapping parent/child resolution records
are rendered
as one net subtree event with combined reasons and dependencies, rather than
pretending that the recorded assignments ran in lexical order. Explicit equal
writes remain visible. When input replay
differs from the final typed input, a `ChangeDecoding` event records the transition
before domain resolution. Its origin retains the decoded path's source/default
attribution; its reason identifies static-default retention and canonical decoding.
This built-in inspection operation is not accepted by `Resolution.Record`.
writes remain visible. When structural replay differs from the pre-CLI typed input,
a `ChangeDecoding` event records retained defaults and canonical decoding. CLI
contributions then retain their raw input representation while effective and
resolution values use the typed projection. This built-in inspection operation is
not accepted by `Resolution.Record`.
Base, overlay, and CLI history preserves the policy-validated input representation,
including a superseded value that could not have decoded into the final Go field.
@@ -640,13 +640,15 @@ must resolve to a nonempty value. Secret references work in typed structs, maps,
pointers, slices, and arrays. A dynamic `any` value does not establish secret
semantics and cannot contain `Secret`; applications must use a typed slot.
ConfigKit resolves only the effective names after recorded application resolution
and before semantic validation. Each unique name is read with `os.LookupEnv` once
per load. Validation can inspect the credential through the deliberately named
`Value()` accessor and distinguish success with `IsResolved()`. Later loads read
the environment again, and cloned defaults are rebuilt from names so a previously
resolved result cannot retain a stale credential. `Load` never writes to the
environment.
ConfigKit resolves only the effective names after CLI assignment and recorded
application resolution, and before semantic validation. CLI overrides may supply
or replace a typed secret reference; lookup uses that effective name and retains
its effective source location for diagnostics. Each unique name is read with
`os.LookupEnv` once per load. Validation can inspect the credential through the
deliberately named `Value()` accessor and distinguish success with `IsResolved()`.
Later loads read the environment again, and cloned defaults are rebuilt from names
so a previously resolved result cannot retain a stale credential. `Load` never
writes to the environment.
Credential bytes are held only in the per-load lookup state and the trusted returned
configuration, and are returned only by `Value`. Secret formatting uses a safe
@@ -710,18 +712,19 @@ files when appropriate; ConfigKit stores no implicit global report.
### Recorded application resolution
An optional `Definition.Resolve` callback runs after successful typed decoding. It
receives the working configuration and a per-load `Resolution` context. The context's
An optional `Definition.Resolve` callback runs after successful defaults, typed
decoding, and explicit CLI assignment. It receives the working configuration and a
per-load `Resolution` context. The context's
`IsExplicit` query uses surviving input presence, so explicit false, zero, empty, and
null are never confused with absence or static defaults.
`Resolution.InputInfo(path)` returns detached effective metadata from the fixed
decoded entry state, before application resolution runs. It includes retained static
defaults, preserves explicit null, and uses the same exact semantic path matching as
`Metadata.Lookup`; missing, malformed, and key/index-shape-mismatched paths return
false without diagnostics. Its `Explicit` value agrees with `IsExplicit` for every
found entry path. Resolution changes, additions, removals, and moves do not alter
this entry view.
post-decode entry state, including explicit CLI overrides, before application
resolution runs. It includes retained static defaults, preserves explicit null, and
uses the same exact semantic path matching as `Metadata.Lookup`; missing,
malformed, and key/index-shape-mismatched paths return false without diagnostics.
Its `Explicit` value agrees with `IsExplicit` for every found entry path. Resolution
changes, additions, removals, and moves do not alter this entry view.
`Resolution.Source(id)` returns a detached record for an already acquired source in
the same load: the main source, imports, selected overlay, and supplied CLI records
@@ -950,10 +953,16 @@ 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 overlay and before typed defaults/decoding, so
the input precedence is application defaults < composed base < selected overlay <
CLI. Recorded resolution, typed secret lookup, and semantic validation follow the
effective override tree. Overrides also work with `NoSource`.
Overrides are prepared after source assembly and inheritance, then assigned after
typed defaults and decoding and before application resolution. The order is:
defaults and decoded sources < CLI < application resolution. A CLI mapping
recursively overlays the decoded mapping; a CLI sequence replaces the complete
decoded sequence regardless of source collection operations. Inheritance and
typed defaulting do not run again after CLI assignment. Earlier parsing,
composition, inheritance, and decoding failures remain errors even if an override
would replace the failing value. Recorded resolution, typed secret lookup, and
semantic validation use the effective post-override values. 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
@@ -964,7 +973,8 @@ false, and zero remain intentional; empty sequences replace whole lists; and map
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
All records are detached and validated before any is applied. They cannot create
`configkit` or `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.
@@ -981,8 +991,12 @@ 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/overlay
definitions and records equal-value replacements as well as structural changes.
Override origins are explicit CLI origins. Resolution receives CLI values with
their explicit-presence and source information. Absence-based defaults preserve
those values, while recorded normalization and derivation may change them. History
retains replaced source definitions and records equal-value replacements as well
as structural changes. Secret lookup and validation use post-resolution values
with their effective source locations.
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

View File

@@ -1,6 +1,6 @@
# Sequence Identity And Combination Implementation Plan
Status: implementation in progress; Stages 1–25 complete.
Status: implementation in progress; Stages 1–26 complete.
Audience: a Luna coding agent implementing one numbered stage per prompt, in
order. This plan starts at commit
@@ -878,6 +878,13 @@ assignment, inspection, and effective-value handoffs.
Validation: override/lifecycle/resolution/inheritance/history/secret tests,
`go test -race ./...`, and the everyday check.
**Completion:** Activated typed CLI assignment after structural expansion and
decoding in the shared load lifecycle, including resolution, history, and
effective secret-reference context. Removed early application and replay fallback
paths, updated contracts and examples, and revised obsolete relationship-change
expectations. Verified focused lifecycle and secret tests, `go test -race ./...`,
and `./scripts/check.sh`.
## Stage 27 — Enforce Final Application Identity Constraints
Scope: the final-result guarantee, not another combination or inheritance phase.