354 lines
21 KiB
Markdown
354 lines
21 KiB
Markdown
# Sibling Inheritance Implementation Plan
|
||
|
||
## Scope and working rules
|
||
|
||
Implement the agreed [named-inheritance roadmap](named-inheritance.md), starting
|
||
from `01e38b5`. The roadmap owns feature policy; this document owns execution and
|
||
the concrete implementation choices below. Follow stages in order. Each stage is
|
||
a focused task for a gpt-5.6-terra coding agent and should end with relevant
|
||
passing checks and a plain-English commit. Do not publish or tag a release during
|
||
this sprint.
|
||
|
||
Read `docs/development.md`, `docs/policy/agents.md`, and task-relevant policies.
|
||
Preserve unrelated work. The root owns public types; the private engine owns
|
||
processing. Reuse the parser-independent document representation and existing
|
||
helpers. Do not add dependencies, configurable keywords, registration APIs,
|
||
merge strategies, background work, or consumer-specific semantics.
|
||
|
||
Stages 1–6 develop private machinery without activating inheritance in public
|
||
loading. Stage 7 connects the complete pipeline. Intermediate commits must not
|
||
publish resolved values with incomplete provenance or redaction. Private names
|
||
and file splits below are guidance, not new public contracts.
|
||
|
||
## Decisions carried into implementation
|
||
|
||
- `extends` is reserved input syntax. A string names an exact sibling mapping;
|
||
null cancels a relationship; omission retains lower contributions. Other types,
|
||
missing/non-mapping parents, self-reference, and cycles are errors. Empty strings
|
||
are exact names. Root and direct sequence-element mappings cannot use string
|
||
references; null needs no lookup.
|
||
- Strict composition still rejects overlapping non-mapping contributions,
|
||
including duplicate `extends` definitions. Complete all source-tier and CLI
|
||
precedence before expanding the effective input. Overwritten earlier references
|
||
are historical evidence, not active relationships to validate.
|
||
- Expand enclosing parent chains before resolving nested relationships in each
|
||
destination. Reusable parent templates retain unresolved nested instructions.
|
||
Sort mapping keys lexicographically, follow parent dependencies as needed, and
|
||
visit sequences by index. Sorting never determines value precedence.
|
||
- Parent-to-child combination uses recursive overlay, including explicit
|
||
zero/false/empty/null replacement and whole-sequence replacement. Consume the
|
||
current mapping's own relationship during enclosing expansion; nested
|
||
relationships travel with nested payload until their scope is resolved.
|
||
- Inherited values count as explicit input for presence/defaulting queries.
|
||
Provenance distinguishes direct input from inherited input.
|
||
- Version checking stays at its existing main-input point. Expansion follows
|
||
source assembly and precedes defaults/decoding, recorded resolution, secret
|
||
lookup, validation, and publication. Invalid effective relationships fail even
|
||
in entries the application does not select for execution.
|
||
- Preserve source records and passive resource contexts. Inheritance is neither
|
||
a new acquired source nor another precedence layer.
|
||
|
||
### Concrete metadata representation
|
||
|
||
Extend engine and root `Origin` with `Inheritance []InheritanceStep`. Define
|
||
root-owned and engine counterpart records with `From Path`, `To Path`,
|
||
`Reference Path`, and `Locations []Location`. A step describes a payload-path
|
||
transfer. `Reference` is the destination's controlling `extends` path; `Locations`
|
||
identifies that directive's contributing source coordinates. Paths are concrete
|
||
semantic paths, not names requiring another lookup. Records contain no values.
|
||
|
||
Keep `Origin.Layer` and `Origin.Locations` for actual value contributors. Order
|
||
steps by derivation; composite origins retain contributing branches rather than
|
||
inventing a single chain. Project concrete descendant paths when queried. Keep
|
||
original contributor paths privately alongside locations, so synthetic sources
|
||
do not depend on file coordinates to identify original semantic paths. Direct
|
||
replacement keeps its own effective origin; displaced inheritance stays in history.
|
||
|
||
Add inspection-only `ChangeInheritance = "inheritance"`, like `ChangeDecoding`;
|
||
do not accept it through `Resolution.Record`. Inheritance events identify
|
||
parent/control dependencies through existing `Change`, `ValueChange`, and
|
||
`Explanation` surfaces. No separate public query family is needed. Effective
|
||
inherited origins retain source layers and transfer evidence; expansion history
|
||
events use the new operation kind.
|
||
|
||
### Budgets and diagnostics
|
||
|
||
Keep acquired-input accounting unchanged. Use `defaultProjectionLimits()` for
|
||
expanded tree depth/nodes, counted by logical materialized nodes rather than
|
||
unique pointers; report `CodeSchemaProjectionLimit`. Check during materialization
|
||
before uncontrolled allocation and typed decoding. Do not charge copies as
|
||
acquired input or add a chain-length cap.
|
||
|
||
Extend `historyNodesWithin` to retained inheritance templates and snapshots,
|
||
counting shared immutable document nodes once under the existing `historyNodeLimit`;
|
||
report `CodeExplainLimitExceeded`. Compact relationship records refer to shared
|
||
evidence instead of copying a full chain/tree per field. Do not redefine node
|
||
limits as byte limits or introduce a new public limit. Check retained growth as
|
||
it is accumulated and release temporary templates. Use private small test budgets.
|
||
|
||
Add mirrored engine/root codes: `CodeInheritanceInvalid` (`inheritance.invalid`)
|
||
for invalid type/position or non-mapping parent; `CodeInheritanceMissing`
|
||
(`inheritance.missing`); `CodeInheritanceCycle` (`inheritance.cycle`); and
|
||
`CodeInheritanceVersion` (`inheritance.version`). Use directive paths/locations
|
||
as primary evidence and related definition locations for dependencies/cycles.
|
||
Messages must not interpolate raw reference strings or payload. Existing syntax,
|
||
composition, decode, and budget codes retain their meanings.
|
||
|
||
## Stage 1 — Prepare shared recursive combination
|
||
|
||
**Completed 2026-09-29.** Recursive mapping overlay now uses a private
|
||
value-combination walk with path-aware add, combine, and replacement callbacks;
|
||
the existing overlay adapter continues to own source origins and history, while
|
||
strict composition remains separate. A focused table covers recursive and
|
||
atomic replacements, empty mappings, event paths, and detached inputs. Focused
|
||
composition/overlay/override/underlay tests and `./scripts/check.sh` passed.
|
||
|
||
Inspect `internal/engine/overlay.go`, `compose.go`, `mapping_index.go`,
|
||
`history.go`, and `internal/document/document.go`. `applyOverlay` already shares
|
||
`mergeOverlayMappings` across underlays, main-over-underlay assembly, and selected
|
||
overlays. Strict composition uses `mergeDisjointMappings` after claim validation;
|
||
history replay has a separate value-only traversal.
|
||
|
||
Extract or adapt the smallest private recursive-overlay primitive needed for
|
||
arbitrary mapping roots, preserving ordered entries and indexed lookup. Keep
|
||
source-tier bookkeeping in its adapter. Provide path/replacement information for
|
||
inheritance's later adapter; do not model inheritance as a fake layer. Reuse
|
||
`document.Clone` and `mappingIndex`. Keep strict composition distinct. Consolidate
|
||
value-only replay only if doing so preserves its behavior without extra machinery;
|
||
do not build a public or general-purpose merge framework.
|
||
|
||
Validation: existing compose/overlay/override and underlay tests, plus a compact
|
||
table covering recursion, atomic sequence/scalar/null replacement, empty mappings,
|
||
and detached ownership. Existing behavioral expectations should not change.
|
||
Run `./scripts/check.sh` before committing.
|
||
|
||
## Stage 2 — Expand one sibling scope
|
||
|
||
**Completed 2026-09-29.** A private, non-activated resolver indexes sibling
|
||
definitions, follows parent dependencies with an explicit stack, memoizes
|
||
unresolved enclosing templates, consumes each mapping's own relationship, and
|
||
retains transfer/control evidence. Safe diagnostics cover missing or non-mapping
|
||
parents, invalid reference types, and cycles. Focused tests include forward and
|
||
backward references, shared chains, exact unusual names, null cancellation,
|
||
independent results, and a deep cycle. `./scripts/check.sh` passed.
|
||
|
||
Add a private inheritance resolver, callable only from engine tests initially.
|
||
Index effective sibling definitions. Resolve single-parent dependencies with
|
||
visiting/resolved states and memoized enclosing-chain templates. Use an explicit
|
||
work stack so graph chain length is not tied to the Go call stack. Overlay parent
|
||
payload with child payload using Stage 1. Consume the current mapping's own
|
||
`extends`, leaving nested instructions intact. Handle null before lookup.
|
||
|
||
Implement internal diagnostic codes and deterministic traversal. Preserve
|
||
locations and collect transfer/control evidence for Stage 4. Keep original input
|
||
unchanged and templates private; a child must not mutate its parent. Failed
|
||
expansion returns diagnostics and no usable expanded result.
|
||
|
||
Validation: forward/backward references, chains, shared parents, child replacement,
|
||
null cancellation, exact unusual/empty names, missing/non-mapping parents, invalid
|
||
types, self/long cycles, and input independence. Assert results and diagnostic
|
||
fields, not stack layout or exact prose. Run focused tests and the everyday checker.
|
||
|
||
## Stage 3 — Resolve nested destination scopes and bound expansion
|
||
|
||
**Completed 2026-09-29.** Private expansion now walks from enclosing mappings
|
||
into destination-local nested scopes, including mappings within sequences.
|
||
Root and direct sequence-element string references fail; null cancellation is
|
||
consumed. Logical materialization uses an early non-directive lower bound and
|
||
final node/depth checks under projection limits, with no global cache. Focused
|
||
tests cover destination-specific nested values, three-generation chains,
|
||
relationship replacement/cancellation, a destination-only cycle, sequence
|
||
scopes, invalid positions, and budget boundaries. `./scripts/check.sh` passed.
|
||
|
||
Extend to the complete effective tree. Distinguish enclosing-chain templates from
|
||
fully resolved destination subtrees. After materializing an enclosing mapping,
|
||
resolve its nested sibling scopes independently. Visit sequence-contained mappings;
|
||
reject string references on root/direct sequence elements without ancestor lookup.
|
||
|
||
Scope memoization to each effective destination namespace. Copying a template must
|
||
not copy its already-resolved nested children. Preserve inherited nested control
|
||
evidence and support cancellation through a child's nested null directive. Whole-
|
||
container replacement removes those active relationships at that destination;
|
||
the parent's own surviving relationships still require validation.
|
||
|
||
Apply the selected expanded-tree budgets, counting logical output from shared
|
||
templates and releasing transient state. No global caches or unrelated-scope lookup.
|
||
|
||
Validation: the roadmap's nested `primary`/`secondary` example; three-generation
|
||
enclosing chains; nested reference replacement/cancellation; destination-only
|
||
cycles; scopes inside lists; root/list-element errors; and parent names sorting
|
||
before or after children. Exercise small node/depth budgets at and beyond their
|
||
boundaries. Run the everyday checker.
|
||
|
||
## Stage 4 — Preserve derivation, presence, and resource origins
|
||
|
||
**Completed 2026-09-29.** Private expansion now returns the effective tree,
|
||
per-path origins, shared linked transfer traces, control records, and displaced
|
||
replacement evidence. Contributor records retain original semantic paths and
|
||
source locations through chains, nested destination scopes, composite mappings,
|
||
and sequence descendants; direct child replacements have independent effective
|
||
origins. Clones detach mutable slices and paths, and origin containment checks
|
||
include derivation. Focused source/context, provenance, replacement, and
|
||
detachment tests and `./scripts/check.sh` passed. Public loading remains unchanged.
|
||
|
||
Implement the selected metadata representation privately in the engine. Carry
|
||
original semantic contributor paths, locations/layers, transfer edges, and control
|
||
evidence through copies and recursive combination. Preserve composite origins and
|
||
sequence-descendant origins, including atomic sequence contributions. Do not infer
|
||
ownership from the last matching layer: multiple sources can share a layer.
|
||
|
||
Return an expanded tree, origins, and inheritance evidence for the decoded metadata
|
||
pipeline. Presence comes from expanded input. Direct replacement removes effective
|
||
inheritance at replaced paths but retains displaced evidence. Extend cloning,
|
||
comparison/deduplication, and private carriers; share immutable internal evidence
|
||
while detaching query outputs.
|
||
|
||
Validation: inherited zero/false/null presence, chains and nested transfers,
|
||
composites, same-layer multiple sources, and replacement evidence. Demonstrate
|
||
that an inherited relative resource points to its original source context rather
|
||
than the child's directory. Inheritance does not open referenced resources.
|
||
Run the everyday checker.
|
||
|
||
## Stage 5 — Integrate historical inspection
|
||
|
||
**Completed 2026-09-29.** Source replay stays at original paths, followed by
|
||
projected structural control/transfer events before typed reconciliation and
|
||
application resolution. History and explanations preserve consumed/cancelled
|
||
directives, equal-valued child replacements, displaced descendants, nested
|
||
destination results, and parent source updates. Shared retained evidence is
|
||
counted with a bounded incremental check; focused tests and `./scripts/check.sh`
|
||
passed. Public loading remains unchanged.
|
||
|
||
Extend `history.go`, `load_metadata.go`, `explain.go`, and explanation rendering.
|
||
Retain original source applications; represent structural expansion separately,
|
||
between source replay and canonical decoding, followed by recorded application
|
||
resolution. Do not attribute inherited values or consumed directives to decoding.
|
||
|
||
Destination history must show parent contribution and child replacement when both
|
||
exist, even if expansion leaves the effective child value unchanged. Include
|
||
derivation dependencies, preserve source history at original paths, and make
|
||
chains navigable through concrete paths. Do not fabricate repeated chronological
|
||
source loading at every child. Explain consumed directives as historical control
|
||
input absent from effective metadata/configuration. Preserve cancellation and
|
||
superseded directive evidence.
|
||
|
||
Extend historical path enumeration, ancestor replacement, origin comparison, and
|
||
retained-node accounting. Keep typed reconciliation separate. Share scope evidence
|
||
and project relevant changes instead of storing a full snapshot per leaf.
|
||
|
||
Validation: inherited values, equal child replacements, displaced inherited
|
||
subtrees, parent source changes, consumed/canceled directives, destination-specific
|
||
nested results, and subsequent application-derived replacement. Check agreement
|
||
among final values, `History`, `Explain`, and historical paths. Exercise small
|
||
history budgets. Run the everyday checker.
|
||
|
||
## Stage 6 — Preserve sensitivity through inheritance
|
||
|
||
**Completed 2026-09-29.** Sensitivity classification now follows corresponding
|
||
source/destination paths through structural transfers and controls to a fixed
|
||
point, while leaving neighboring fields unclassified. Historical replacement
|
||
evidence remains redacted, and inherited secret references remain unresolved
|
||
until the existing typed lookup point. Focused secret/history tests and
|
||
`./scripts/check.sh` passed. Public loading remains unchanged.
|
||
|
||
Connect transfer dependencies to existing secret classification and historical
|
||
redaction. Classify both source and destination paths under schema and
|
||
`Definition.Secret` rules; propagate sensitivity through chains/nested copies.
|
||
Include displaced evidence and control history. Preserve existing conservative
|
||
historical redaction without declaring unrelated siblings secret.
|
||
|
||
Copy unresolved secret references only; lookup remains at the existing lifecycle
|
||
point. Credentials never enter expansion trees/history. Independent replacements
|
||
retain their own effective provenance while previous secrets remain redacted.
|
||
Queries and renderers must agree.
|
||
|
||
Validation: source-only and destination-only classification, shared parents,
|
||
transitive inheritance, replacement/removal, and lookup after expansion. Use
|
||
disposable canaries across affected inspection/failure surfaces. Avoid real
|
||
credentials and parallel environment mutation. Run focused secret/history tests
|
||
and the everyday checker.
|
||
|
||
## Stage 7 — Activate the common lifecycle and facade metadata
|
||
|
||
**Completed 2026-09-29.** Ordinary and layered loads now expand assembled
|
||
input before decoding while retaining original source applications, provenance,
|
||
history, sensitivity, and resource contexts. Main-owned version paths reject
|
||
structural changes without rejecting unrelated inheritance. Root records and
|
||
conversions expose detached transfer evidence; public tests cover precedence,
|
||
presence, resource context, version timing, cancellation, imports, and secrets.
|
||
`./scripts/check.sh` and `go test -race ./...` passed.
|
||
|
||
Insert expansion in `executeLoadRequest` after ordinary `applyInputLayers` or
|
||
layered `assembleLayeredInput`, before `decodeLoadInput` invokes defaults/decoding.
|
||
Include plain single-source loads, creating the minimal composition carrier needed
|
||
to preserve original history. Update `load_result.go` and inspection carriers with
|
||
Stages 4–6 evidence. Preserve no-input behavior and use a no-directive fast path
|
||
where practical. Do not repeat acquisition, version callbacks, or user resolution.
|
||
|
||
Keep the version path main-owned. Detect inherited contributions, replacement, or
|
||
directive removal affecting it, including protected ancestor replacement; report
|
||
`CodeInheritanceVersion`. Do not reject an entire enclosing mapping merely because
|
||
it contains an unchanged version field. A version rule targeting an `extends`
|
||
field cannot silently lose its checked declaration when the directive is consumed.
|
||
Keep the checker at its original point and retain accepted version representations.
|
||
|
||
Mirror records/codes in root `records.go`, conversions, clones, and API comments.
|
||
Ensure root `Origin.Inheritance` and `InheritanceStep` expose only root types and
|
||
detached slices/paths. Keep `ChangeInheritance` inspection-only. Update root/engine
|
||
`IsExplicit` comments, including resolver-entry queries. Preserve `SourceContext`.
|
||
|
||
Do not treat `extends` as the acquisition-time `configkit` directive. Imports,
|
||
selected overlays, Mapping input, and CLI overrides carry it through combination
|
||
until expansion. Keep existing `configkit` validation and CLI path restrictions.
|
||
Typed defaults and values added by application resolution do not initiate another
|
||
structural pass; inheritance processes assembled source input only.
|
||
|
||
Validation: public ordinary/layered loads; imports and strict conflicts; multiple
|
||
underlays; selected overlay and CLI changes to parent/child/reference; resolver
|
||
defaulting using inherited presence; version ownership and callback timing;
|
||
typed unknown-field behavior after consumption; no later hooks on expansion
|
||
failure; partial bases with normal validation; and no-directive regressions.
|
||
Include a filesystem resource-origin example and mapping/bytes equivalence case,
|
||
not a full duplication of every mechanism test. Run `./scripts/check.sh` and
|
||
`go test -race ./...` for ownership changes.
|
||
|
||
## Stage 8 — Validate scaling and complete documentation
|
||
|
||
**Completed 2026-09-29.** Benchmarks cover chains, shared-parent width, nested
|
||
destinations, replacements, and full inspected loads. Skipping repeated scope
|
||
assembly when no immediate relationship exists reduced measured work while a
|
||
single boundary clone preserves caller-input ownership. The maintained preset
|
||
example, configuration contract, consumer guidance, internal navigation,
|
||
architecture/deferred scope, and ADR 0009 now describe the implemented feature.
|
||
`./scripts/check.sh`, `go test -race ./...`, and the focused benchmarks passed.
|
||
No release was tagged or published.
|
||
|
||
Add focused benchmarks beside assembly/underlay benchmarks: long chains, wide
|
||
shared-parent scopes, nested destination expansion, and replacements. Evaluate
|
||
allocations/work against input, materialized output, and retained history,
|
||
including sorting costs. Fix repeated ancestor work or whole-tree copying without
|
||
changing semantics. No unconditional O(n) promise or timing release gate.
|
||
|
||
Add a maintained consumer example showing one-load inheritance, explicit false,
|
||
nested destination behavior, and source/derivation inspection. Update
|
||
`docs/config.md` as behavior owner, root comments as API owners, README/consumer
|
||
guidance as summaries, and internal overview/development test map as navigation.
|
||
Update architecture/deferred-scope descriptions for implemented inheritance.
|
||
Explain reserved-key migration, null cancellation, no registration, sibling lookup,
|
||
source-first ordering, presence versus provenance, and unchanged version/lifecycle
|
||
policy. No release-policy or platform-gate changes are part of this feature.
|
||
|
||
Write the next available ADR (currently 0009) recording ConfigKit's chosen
|
||
architecture and rationale. Keep downstream-specific discussion out; rejected
|
||
choices belong only in Alternatives Considered. Do not edit accepted ADR decision
|
||
bodies or silently accept earlier drafts. Mark the feature roadmap's implementation
|
||
status accurately and link the implemented contract. Release notes/tagging remain
|
||
separate release-preparation work.
|
||
|
||
Run the everyday checker, reuse race evidence if ownership code is unchanged, and
|
||
run benchmarks for evidence without pass/fail thresholds. Review the final diff
|
||
against the roadmap, source equivalence, confidentiality, and public boundary.
|
||
Confirm no independent merge rule, registration API, extra release gate, or
|
||
background/resource lifecycle was introduced. Commit and report validation and
|
||
any remaining limitations.
|