3.9 KiB
ADR 0009: Resolve Sibling Inheritance After Source Assembly
Status
Accepted and implemented. The configuration contract
owns current behavior. The completed implementation plan is preserved in Git
history at 83c59ea.
Date
2026-09-29
Context
Named configuration mappings often share settings while retaining individual overrides. ConfigKit already distinguishes strict composition of independent source contributions from recursive overlay between explicit precedence tiers. Doing another untracked merge in applications would lose omission, source context, and historical explanation information. A shared structural operation must preserve the same presence and provenance model as the rest of loading.
Inheritance also interacts with source precedence, nested namespaces, version ownership, and secrets. Resolving it before source assembly would validate superseded references and copy values that a later tier should replace. Fully resolving nested relationships inside a reusable parent would bind them to the wrong sibling scope when copied into a child.
Decision
Reserve extends as a built-in input key on mappings. A string names one exact
sibling mapping; null cancels the relationship. Complete source acquisition,
strict composition, selected overlay, and CLI precedence first. Then resolve
each effective relationship in the parser-independent document tree, before
typed defaults and decoding. A child recursively overlays its own payload on
its resolved parent using the existing mapping-combination rules. This creates
neither a source tier nor a configurable merge strategy.
Resolve enclosing sibling chains before nested relationships. Reuse memoized parent templates with unresolved nested instructions; evaluate those instructions at each destination. Preserve original source applications separately from structural transfer/control evidence. Effective origins carry concrete source-to-destination derivation, and inspection projects relevant inheritance events between source replay and typed reconciliation. Inherited input counts as explicit presence, while direct replacement retains its own effective origin.
The existing main-input version check remains at its original point. Reject structural contributions or directive consumption affecting its protected path. Classify sensitivity at corresponding source and destination paths and retain historical redaction. Secret references remain unresolved input until the existing lookup point. Keep all machinery behind the root-owned public facade.
Alternatives considered
- Application-owned inheritance after
Load. Leaves every consumer to recreate ordering, presence, provenance, error reporting, and redaction after ConfigKit has already declared a result complete. - Resolve each source before precedence assembly. Makes superseded references active and can copy a parent definition before higher-priority input finishes changing it.
- Resolve nested relationships in a cached parent subtree. Shares more ready-made output but binds nested sibling lookups to the parent's scope, giving incorrect values at a child destination.
- Registered collections, configurable keywords, or multiple-parent merge modes. Increase public setup and semantic branches without a requirement that warrants diverging from one structural rule.
Consequences
Consumers can load a configuration once and inspect direct, inherited,
defaulted, and application-derived values through the existing result surfaces.
extends is no longer an ordinary application field, so existing configurations
using that key as data must migrate. Expanding many children can materialize
substantially more output than the input contains; projection and retained-history
budgets bound that work without charging copies as newly acquired sources.
Applications still own domain validation, entry selection, and runtime resource
use.