Decode typed configuration over defaults
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
## Status and ownership
|
||||
|
||||
Status: implementation in progress; Stages 1–5 are complete and later stages are
|
||||
Status: implementation in progress; Stages 1–6 are complete and later stages are
|
||||
pending.
|
||||
|
||||
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
|
||||
@@ -564,7 +564,31 @@ Acceptance:
|
||||
|
||||
## Stage 6 — Decode scalar and container values over defaults
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Completion evidence (2026-09-06):
|
||||
|
||||
- Added schema-driven typed decoding in [`decode.go`](../../decode.go), starting
|
||||
from the Stage 5 detached clone and returning only private typed state, input
|
||||
presence, projected baseline/final trees, and per-path default/base origins.
|
||||
- Implemented recursive mapping retention, atomic sequence replacement, exact array
|
||||
lengths, pointer-object zero allocation versus non-nil retention, nullable-shape
|
||||
clearing, strict nested map values, inline-map decoding, and unknown-field errors.
|
||||
- Added checked destination-width integers and floats, dynamic `int64`/`float64`,
|
||||
named scalars, duration strings, paired text codecs, safe value-free failures, and
|
||||
collection of independent decode errors at original semantic paths/coordinates.
|
||||
The [current configuration contract](../config.md#defaults-and-typed-decoding) and
|
||||
component inventory now own this behavior.
|
||||
- Adapted Narratio's fresh-default, presence, strict nested-type, and repeated-load
|
||||
cases, replacing custom YAML hooks and pointer/zero guesses with tree-derived
|
||||
presence; explicit empty and null are retained and equal-valued input is explicit.
|
||||
Focused coverage includes the defaults operation table, new/existing map entries,
|
||||
empty mappings/sequences, nil/non-nil pointer objects, default isolation, numeric
|
||||
range and coercion failures, integer durations, inline collisions, nested unknown
|
||||
fields, coordinates, safe codec failures, and independent repeated results.
|
||||
Verified `go test ./...`, `go vet ./...`, `go build ./...`, `go mod tidy -diff`,
|
||||
and `git diff --check` with Go 1.26.7 on linux/amd64. No deviations were required;
|
||||
public loading and schema-version checks remain later work.
|
||||
|
||||
Read: feature [defaults/null](initial.md#static-defaults-and-null) and typed decoding.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user