Decode typed configuration over defaults

This commit is contained in:
2026-09-06 19:52:45 +00:00
parent 15172294b5
commit 21e3f633bc
8 changed files with 884 additions and 74 deletions

View File

@@ -552,69 +552,21 @@ Applications own flags and commands that select this behavior.
## Static defaults and null
Applications supply a defaults factory that creates a fresh configuration per
load. Mutable maps, slices, and pointers must not be shared between factory
results. ConfigKit must not retain mutable aliases between returned loads or
between the application result and its internal metadata snapshots.
Defaults obey the same recursive mapping/atomic replacement rules as a lower
layer, rather than incidental decoder behavior:
| Configured input | Effect on defaults |
| --- | --- |
| Absent field | Keep the default |
| Explicit false, zero, or empty string | Replace the default |
| Partial mapping, including a string-keyed Go map | Retain omitted default children/entries |
| `{}` over an existing mapping | Retain its default children |
| Sequence, including `[]` | Replace the entire default sequence |
| Partial object represented by a pointer | Retain children of a non-nil default object; otherwise allocate and use ordinary zero values for omitted children |
| Allowed null | Replace with nil; suppress lower defaults |
The factory supplies actual defaults; ConfigKit does not invent per-entry
defaults for newly introduced map entries or sequence elements. Application
resolution may supply those. Go zero values are part of the initial typed
baseline; do not imply that the application explicitly declared every zero as
a domain default. Avoid serialization through `omitempty` that would erase
baseline values or presence information.
Null is valid for pointers, maps, slices, and dynamic `any` values. It is invalid
for ordinary scalar fields, structs, and arrays. Thus `workers: null` cannot
silently leave an integer default intact. Applications can further reject null
through validation. Null never means reset to the application default.
The defaults merge and nullability model is implemented privately. The
[current defaults and typed-decoding contract](../config.md#defaults-and-typed-decoding)
owns recursive mapping, atomic sequence, pointer allocation, explicit-presence,
null, and detached-baseline behavior. The public defaults factory and result
lifecycle remain target work in the sections below.
## Typed decoding
The schema-binding portion of this target is implemented. The [current application
schema contract](../config.md#application-schema-binding) owns root/type support,
field and tag binding, inlining, text-codec pairing, and schema-aware path rules.
The remaining document-to-value behavior below is target decoding work.
Strictness must be enforced by ConfigKit across the effective tree:
- Unknown struct fields, type mismatches, overflow, and invalid null are errors.
- Without an inline map, unmatched keys remain errors. Default inline-map entries
colliding with declared fields are checked when runtime defaults are introduced.
- String-keyed maps accept application-defined keys. Struct values inside them
remain strict. `map[string]any` permits arbitrary keys and recursively accepts
the supported scalar/container model; application validation owns its schema.
- Do not coerce numbers or booleans to strings, or quoted numbers to numeric
fields. Integer targets require integer syntax and range checks. Float targets
accept finite decimal numbers subject to representable range checks.
- Dynamic integer values use checked `int64`, and fractional/exponent values use
checked finite `float64`; values outside those ranges require a typed schema
or a quoted string rather than silent rounding of integer nodes.
- Support `time.Duration` from a duration string. Custom scalar types must
implement both `encoding.TextUnmarshaler` and `encoding.TextMarshaler` so
decoding and inspection have an agreed representation. Duration integers are
rejected. See the [type and projection contract](#supported-types-and-projection).
- Parser-specific unmarshal hooks are not a public extension mechanism. Do not
let them bypass key, null, or type policy. Custom structural decoders are
deferred; unsupported type shapes fail with a clear schema diagnostic.
Keep parser nodes and errors internal under the
[upstream isolation contract](#yaml-dependency-and-public-api-isolation).
The field-binding model must also support defaults, provenance, secret
classification, and rendering consistently.
Schema binding and strict document-to-value decoding are implemented privately.
The [current application schema contract](../config.md#application-schema-binding)
owns root/type support, field and tag binding, inlining, codec pairing, and
schema-aware path rules. The [current defaults and typed-decoding contract](../config.md#defaults-and-typed-decoding)
owns strict conversions, container application, presence, safe failures, and initial
origins. Public lifecycle integration, layered provenance, secret handling, and
rendering remain target work below.
## Recorded resolution and validation