Record agreed inheritance reference and presence rules

This commit is contained in:
2026-09-29 21:18:48 +00:00
parent ac1eec2e30
commit 01e38b5840

View File

@@ -1,8 +1,7 @@
# Built-In Sibling Configuration Inheritance
Status: proposed feature design for agreement; not implemented. This document
defines the intended end state and the remaining architectural decisions. A
separate implementation plan will define execution steps after design agreement.
Status: agreed feature design; not implemented. This document defines the intended
end state. A separate implementation plan will define execution steps.
## Purpose and priorities
@@ -82,8 +81,26 @@ and final schema validation; a referenced parent must be a mapping.
Identity comes from the sibling key, not a copied field in the parent payload.
The child keeps its key. Application fields such as `name` or `id` follow ordinary
combination semantics. Reference-value details and positions without a sibling
namespace are addressed in Open Decisions below.
combination semantics.
### Reference values and positions
A string-valued `extends` names an exact sibling key, including an empty string
if that key exists. Explicit `extends: null` means no parent and cancels a
relationship supplied by a lower-precedence source or inherited nested payload.
Omitting `extends` contributes no change to an existing relationship during
combination; if none remains in the effective mapping, the mapping has no parent.
Other value shapes are reference errors.
Cancellation retains the mapping's own assembled settings but adds no settings
from the canceled parent. Like a string reference, the null control field is
consumed as structural metadata before typed decoding. This follows existing
presence and replacement rules without introducing a deletion operator.
A string-valued `extends` on the document root or directly on a sequence-element
mapping has no enclosing mapping of named siblings and produces a location-bearing
reference error. ConfigKit does not search farther up for a namespace. Mappings
nested within those structures can still use their own sibling namespaces normally.
### Source precedence first, inheritance second
@@ -168,8 +185,8 @@ sort before or after its child without changing the meaning. Breadth-first versu
recursive traversal is an implementation choice subject to these semantics.
Nested mappings inside sequences are also visited, while sequence combination
continues to replace whole sequences. The treatment of a relationship on a
sequence element itself is addressed below.
continues to replace whole sequences. A relationship on a sequence element itself
follows the reference-position rules above.
## Loading and validation
@@ -212,9 +229,17 @@ may load again explicitly using the existing source-reuse contracts.
Consumers must distinguish direct child input, inherited input, typed defaults,
and recorded application-derived values. Explicit zero/false/empty/null settings
remain distinguishable from omission. The representation of inherited presence
in the existing query surface is an open decision below; Go zero-value inference
is not an acceptable substitute.
remain distinguishable from omission. Go zero-value inference is not an acceptable
substitute.
An inherited value counts as present configuration input: `IsExplicit` returns
true at its destination, while derivation metadata distinguishes inherited input
from a direct child declaration. Presence answers whether configuration supplied
the value; provenance explains where it came from and how it reached the destination.
Typed defaults and application resolver defaults based on absence therefore remain
below inherited settings, including inherited zero, false, empty, and null values.
The API documentation must explain this effective-input meaning of explicitness;
it does not mean the value was written directly at the destination path.
Each inherited value retains its originating source and source path, together
with the relationship that placed it at the child's destination path. Chains
@@ -268,12 +293,13 @@ remains the only consumer boundary.
Representative examples and behavioral tests should establish source/inheritance
ordering, chains and shared parents, enclosing-first nested resolution, independence
from parent/child name sort order, failure diagnostics, partial bases, explicit
values versus omission, unchanged sequence/null behavior, origin/resource
values versus omission, cancellation of relationships with null, invalid reference
positions and types, inherited presence during defaulting, unchanged sequence/null behavior, origin/resource
preservation, result independence, and safe secret inspection. Both loading entry
points and supported source representations should share those semantics.
A new ADR should record the selected inheritance architecture after the open
decisions are resolved. Architecture/deferred-scope documentation must recognize
A new ADR should record the selected inheritance architecture.
Architecture/deferred-scope documentation must recognize
the new structural responsibility; public API, configuration, consumer, and
internal documentation should describe implemented behavior when it lands.
Implementation sequencing belongs in a separate future plan. Reserving `extends`
@@ -290,41 +316,3 @@ It can inspect whether each setting was direct, inherited, defaulted, or derived
follow inherited resource origins, and validate final domain meaning. Missing or
cyclic relationships fail before a usable result is returned. The consumer needs
no separate graph resolver, generic merge pass, or provenance reconstruction.
## Open Decisions
### 1. Reference values and positions without a sibling namespace
**Recommendation:** an omitted `extends` means no relationship; explicit null
cancels a relationship supplied by a lower-precedence source or inherited nested
payload. A string names an exact sibling key, including an empty string if that
key exists. Other value shapes are reference errors. This follows existing
presence and replacement rules without introducing a deletion operator.
A string-valued `extends` on the document root or directly on a sequence-element
mapping has no enclosing mapping of named siblings. Report a location-bearing
reference error rather than searching elsewhere. Mappings nested within those
structures can still use their own sibling namespaces normally. This follows
the selected lookup rule, rather than imposing a general ban on nested inheritance.
**Alternative:** require every present `extends` to be a string. This makes the
keyword's value type simpler, but leaves no direct way to cancel an existing
relationship while retaining the mapping's other fields. For root/sequence-element
references, an ancestor-scope fallback could supply a namespace, but would introduce
a positional exception to the agreed immediate-sibling rule; adopting it would
require revisiting that decision rather than treating it as an implementation detail.
### 2. Meaning of inherited presence
**Recommendation:** an inherited value counts as present input for effective
`IsExplicit`/defaulting behavior, with separate derivation metadata distinguishing
it from direct child input. This treats inheritance as input expansion, keeps
typed and resolver defaults below inherited settings, and avoids consumers
reimplementing presence logic. The API must clearly explain that effective
presence differs from direct declaration at the destination path.
**Alternative:** inherited values remain non-explicit at the child path, with
a new effective-input-presence query/defaulting rule. This preserves a narrow
direct-declaration meaning of explicitness, but requires two presence concepts
through the resolver lifecycle and risks defaults overwriting inherited settings
unless callers adopt the new query consistently.