Document and benchmark sibling inheritance

This commit is contained in:
2026-09-29 23:13:50 +00:00
parent df9a02539c
commit 2ca1f80094
22 changed files with 440 additions and 56 deletions

View File

@@ -54,7 +54,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 overlay
→ apply explicit CLI overrides → create and clone defaults → strictly decode
→ apply explicit CLI overrides → expand sibling inheritance
→ create and clone defaults → strictly decode
→ recorded application resolution
→ resolve typed secret references → semantic validation → classify secrets
→ snapshot configuration and metadata
@@ -152,6 +153,57 @@ The maintained [layered consumer example](consumers/README.md#native-filesystem-
shows a main host file, two underlay tiers, embedded defaults, a mapping source,
and explicit resource access without a second configuration pass.
### Sibling inheritance
Any input mapping may declare `extends: name` to inherit from the mapping named
`name` among its immediate siblings. Names are exact keys, including the empty
string; lookup does not search ancestor or unrelated scopes. The document root
and a sequence-element mapping have no sibling namespace for their own string
relationship, though mappings nested inside them can have one. An `extends`
value of null cancels a relationship without looking up a parent. Other value
types, missing or non-mapping parents, and cycles fail the load with value-free
diagnostics.
`extends` is reserved structural input across ordinary and layered loads, every
source kind, imports, selected overlays, and explicit CLI overrides. It is
consumed before typed decoding and need not appear in the application's Go
types. Applications previously using `extends` as an ordinary YAML field must
rename that key and its field. No collection registration or keyword setup is
required.
ConfigKit completes strict composition within tiers and precedence assembly
between tiers before resolving relationships. An omitted `extends` retains a
lower-priority relationship; `extends: null` cancels it while preserving the
child's own settings. Superseded references are historical evidence, not active
relationships to validate. Each child recursively overlays its own settings
over its resolved parent: explicit zero, false, empty string, sequence, and
null values replace inherited values, while mappings combine recursively.
The child retains its key. All effective relationships resolve, including
entries the application may never select for execution. Partial parent
definitions undergo normal typed decoding and application-owned validation,
not a special structural exemption.
Enclosing relationships resolve first. Nested instructions carried by a parent
are then resolved separately at each destination, so a child's nested sibling
override can change an inherited nested relationship. Typed defaults and
application-derived values do not start another inheritance pass. Inherited
input counts as explicit presence for `Metadata.IsExplicit`,
`Resolution.IsExplicit`, and absence-based resolver defaults, even for false,
zero, empty, or null input. `Origin.Inheritance` distinguishes it from a direct
declaration with concrete source, destination, and controlling directive paths.
A direct child replacement has its own effective origin; displaced ancestry
remains inspectable in history. An inherited relative resource keeps the
declaring source's passive `SourceContext`, not the child's directory.
The optional schema-version checker still runs once against the main input at
its original point. Inheritance cannot add, replace, or consume the protected
version path; an unrelated relationship inside an enclosing mapping does not
invalidate an unchanged version. Expansion uses the decoded projection
node/depth limits without charging copied values as acquired sources;
retained evidence uses the existing history-node limit. See the
[consumer example](consumers/README.md#named-sibling-inheritance) for one-load
usage.
### Source resource contexts
`Result.SourceContext(id)` and callback-scoped `Resolution.SourceContext(id)`
@@ -222,8 +274,9 @@ successful result. Callback panics remain programming failures.
`Result.Metadata` describes every final canonical path. Queries retain explicit
key/index distinctions using actual effective and historical container shapes;
only parsed numeric tokens are resolved contextually. A descendant that never
existed is not historical merely because an atomic ancestor was replaced. Exact surviving input paths
are explicit and have a base or overlay origin; omitted paths have a default origin.
existed is not historical merely because an atomic ancestor was replaced. Exact
surviving direct or inherited input paths are explicit and retain their source
layer and derivation; omitted paths have a default origin.
An explicit parent mapping does not make omitted children explicit. Null state and
available source coordinates are retained. `History` records the Go-zero or application
default baseline followed by exact base or overlay definitions, including
@@ -238,8 +291,9 @@ domain ownership rules remain application responsibilities. The method's Go
documentation in [metadata.go](../metadata.go) defines enumeration and path-ordering
details.
History is chronological: a path can have a synthetic default record, its exact base
definition, an overlay replacement, and a recorded resolution operation. A descendant
History is chronological: a path can have a synthetic default record, its exact
base definition, an overlay replacement, structural inheritance, and a recorded
resolution operation. A descendant
query uses the ancestor's path for a whole-container or whole-sequence replacement;
the descendant can therefore retain history after `Lookup` stops reporting it as
effective. Equal-valued input is still an explicit base definition, while a derived
@@ -255,6 +309,15 @@ operation. Reasons and dependency paths retain first occurrence order with dupli
removed. Source records remain ordered as selected source, depth-first imports, the
selected overlay, then synthetic CLI records in supplied order.
Inheritance adds no acquired source or precedence layer. `Origin.Inheritance`
lists concrete transfers from contributor paths through destinations, with
each step's controlling `extends` path and location. `History` retains source
applications at their original paths and projects parent contributions, child
replacements, and consumed controls at destination paths after source assembly
and before typed decoding. `HistoricalPaths` includes consumed directives and
displaced inherited descendants. A control can have history even when `Lookup`
reports it absent. Queries return detached records.
### Secret classification and effective rendering
Secret classification combines `configkit:"secret"` field tags with the optional
@@ -270,6 +333,11 @@ Propagation reaches a fixed point, so record/map iteration order does not affect
result and dependency cycles terminate. A dependency on a classified descendant or
on a container that contains one is treated conservatively as sensitive. Applications
must classify secret-producing transformations whose dependencies are not recorded.
Inherited value and control paths link source and destination classifications
through chains and nested copies; unrelated sibling fields are not classified
merely because another field is secret. Displaced historical evidence remains
redacted. Secret references are copied only as unresolved names; credential
lookup stays after decoding and application resolution.
`Result.WriteEffective` writes the frozen resolved snapshot as deterministic redacted
YAML. Mappings sort by decoded key string, sequences retain order, indentation is two
@@ -304,7 +372,10 @@ parser values. `Present=false` with empty YAML means absence or deletion; explic
null is present and renders as `null`. The history starts with the canonical typed
application-default or Go-zero baseline, then includes only relevant base sources in
traversal order, the selected overlay, supplied CLI records, and audited net
resolution operations. Overlapping parent/child resolution records are rendered
resolution operations. 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
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
@@ -556,8 +627,8 @@ input document.
`[a-zA-Z0-9_-]+`; they are not paths. The empty name and exact lowercase name
`default` select no overlay, and `overlays/default.yml` is never loaded implicitly.
Every other name resolves only to `<configuration-root>/overlays/<name>.yml`, with
no extension fallback, case normalization, inheritance, or overlay stack. A selected
missing overlay is an error.
no extension fallback, case normalization, inheritance between overlay files,
or overlay stack. A selected missing overlay is an error.
The overlay is a separately policy-validated YAML mapping. It is loaded after the
base import graph is composed and its schema version is checked, but before defaults