Implement canonical projection and detached copies

This commit is contained in:
2026-09-06 19:44:36 +00:00
parent 2b65876a7d
commit 15172294b5
8 changed files with 1059 additions and 23 deletions

View File

@@ -101,8 +101,39 @@ tokens become indices only in a sequence. Sequence indices cannot have leading
zeroes; fixed-array indices are bounds checked. Dynamic `any` and inline-map entries
remain schema-valid for application-defined descendants.
Descriptors do not yet decode documents, clone defaults, or project application
values. Those operations will reuse this graph as their implementation lands.
Descriptors do not yet decode documents. Document decoding will reuse this graph as
its implementation lands.
## Typed projection and ownership
ConfigKit can internally project a schema-valid application value directly into its
owned canonical tree without YAML serialization. Projection includes every bound
field, including ordinary zero values; `omitempty` has no effect. Struct fields keep
binding order, dynamic map keys sort by decoded string, and sequences keep their
order. Integers retain exact base-10 values, finite floats use their destination
precision, durations use `time.Duration.String`, and paired text scalars use their
canonical marshaled text.
Nil pointers, maps, slices, and dynamic values project as null. Non-nil empty maps
and slices remain empty containers, preserving the distinction. Recursive acyclic
objects are supported. Cyclic values, non-finite floats, unsupported dynamic concrete
types, invalid codec output, and depth/node-limit violations fail with safe schema
classifications that omit codec error text.
The same descriptor graph creates detached typed copies. Pointers, maps, slices,
arrays, nested dynamic values, and shared references are recursively copied; shared
pointer identity is not preserved as configuration meaning. Paired text scalars are
rebuilt by unmarshaling their canonical text into fresh values, detaching private
backing storage. Ignored and unexported struct fields are zeroed rather than copied.
Private structural comparison operates on canonical trees. Mapping additions,
removals, and descendant replacements retain semantic paths. A sequence is atomic:
any sequence change reports the sequence path and never invents correspondence
between old and new indices. Cross-kind and null/container changes replace the
affected path.
These mechanisms currently support later decoding, resolution auditing, metadata,
and rendering. They are not a public generic conversion or cloning API.
## Safe failures

View File

@@ -11,10 +11,13 @@ module and package documentation boundary. Its implemented public primitives are
- the sole upstream YAML integration in `internal/yaml`, which translates validated
input into that owned tree and exposes no parser types outside its boundary; and
- a per-operation private reflection descriptor graph in `schema.go` for supported
types, YAML field binding, inlining, secret tags, recursion, and semantic paths.
types, YAML field binding, inlining, secret tags, recursion, and semantic paths;
and
- descriptor-driven canonical projection, detached typed cloning, cycle/limit
enforcement, and structural comparison in `projection.go`.
The [configuration input contract](../config.md) owns implemented source and YAML
behavior. ConfigKit intentionally exposes no typed loading or decoding API yet.
The [configuration contract](../config.md) owns implemented input, schema, and
ownership behavior. ConfigKit intentionally exposes no typed loading or decoding API yet.
Private metadata population will be integrated when that lifecycle exists.
The source-extraction inventory records the application foundations assigned to

View File

@@ -2,7 +2,7 @@
## Status and ownership
Status: implementation in progress; Stages 1–4 are complete and later stages are
Status: implementation in progress; Stages 1–5 are complete and later stages are
pending.
Audience: a `gpt-5.6-terra` coding agent implementing one stage per prompt, in
@@ -512,7 +512,30 @@ Acceptance:
## Stage 5 — Implement canonical projection and detached copies
Status: pending.
Status: complete.
Completion evidence (2026-09-06):
- Added descriptor-driven canonical typed projection and independently owned cloning
in [`projection.go`](../../projection.go), without YAML serialization or a second
binding model. Zero values, nil/empty distinctions, exact integers, destination-
precision finite floats, durations, text scalars, dynamic concrete values, and
recursive acyclic objects retain their configuration meaning.
- Added active-graph cycle detection, private depth/node enforcement, safe custom
codec and dynamic-type failures, zeroing of ignored state, reconstruction of text
scalars into fresh instances, and non-preservation of shared pointer identity.
- Added canonical-tree structural comparison with semantic mapping additions,
removals and replacements, cross-kind replacement, and whole-sequence atomicity.
The [current ownership contract](../config.md#typed-projection-and-ownership) and
component inventory now own the implemented behavior.
- Focused tests mutate every source alias after cloning, cover nil versus empty,
wide integers, duration/text canonicalization, recursive acyclic values and cycles,
unsupported dynamic types, non-finite floats, value-free codec failures, inline
physical members/conflicts, projection bounds, ignored state, and meaningful
comparison paths without index-wise sequence matching. 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; defaults and typed decoding
remain later work.
Read: feature [ownership](initial.md#recorded-resolution-and-validation) and
[type/projection contract](initial.md#supported-types-and-projection).

View File

@@ -645,6 +645,11 @@ subsequently mutate, not language-enforced immutability of exported Go values.
Callers should treat returned configuration as read-only; later caller mutations
are outside the captured metadata snapshot.
Canonical typed projection, detached cloning, and structural comparison are now
implemented as private foundations. The [current ownership contract](../config.md#typed-projection-and-ownership)
owns those mechanics; publishing independently captured results remains lifecycle
work.
Application semantic validation runs after successful resolution and collects
independent errors in a standard diagnostic form. Validators are read-only and
own domain requirements; checking live S3 buckets or starting runtime clients
@@ -1304,20 +1309,12 @@ sequence length. Ignored and unexported fields stay outside configuration projec
hooks must not use them to carry runtime resources or configuration needing
inspection. Non-configuration runtime state belongs in separate application types.
One field-binding model supplies defaults, decoding, path lookup, change auditing,
and rendering. `omitempty` never removes projected zero values. Preserve nil
versus empty maps/slices where represented by the type. Shared pointer identity
inside application values is not a configuration semantic; cyclic values are
unsupported and independent load/snapshot ownership must still hold.
`time.Duration` projects to a canonical duration string. A custom scalar's text
marshal method supplies its deterministic canonical string; unmarshalling must
accept that representation and preserve its configuration meaning. These methods
must be side-effect-free with respect to shared state. Custom scalars are atomic
for comparison/history; their internal fields are not traversed. Reject types
with only a text decoder and no supported inspection representation. Conversion
or projection failures produce safe diagnostics. This is a bounded scalar seam,
not a custom structural decoder or arbitrary reflection extension framework.
Canonical projection, detached copying, cycle and finite-value enforcement, custom
scalar reconstruction, and structural comparison are implemented and owned by the
[current ownership contract](../config.md#typed-projection-and-ownership). The same
field-binding model must still supply defaults, decoding, change auditing, and
rendering. Application text methods must remain side-effect-free with respect to
shared state; this is a bounded scalar seam, not a custom structural decoder.
## Internal organization