5.7 KiB
Sibling Inheritance Review Findings
Reviewed implementation commits 78d4b59 through 2ca1f80 against the
feature roadmap, implementation plan,
and the user's agreed sibling lookup, enclosing-first expansion, presence, and
provenance decisions. Both findings below require remediation. No implementation
fixes are included in this review.
1. Preserve immutable enclosing snapshots during nested resolution
Priority: P2 — incorrect historical explanation despite correct final values.
Locations: retained operation templates (lines 230–235), nested mutation (lines 107–109), and explanation replay (lines 26–38).
inheritanceOperation.parent and .effective refer to the same node trees that
the destination walker later modifies. Replacing nested entry.Value pointers
therefore changes already-recorded enclosing snapshots. Their origin indexes
still describe the earlier template, so historical values and origins can disagree.
Replaying an enclosing transfer prematurely installs already-resolved nested values
and loses the nested control input that should explain those values.
Reproduction: load this configuration through the public loading path (a
map[string]any configuration suffices):
presets:
base:
settings:
primary: {width: 10}
secondary: {extends: primary}
custom:
extends: base
settings:
primary: {width: 20}
The final child width is correctly 20. However:
Explain(/presets/custom/settings/secondary/width)reports its only inheritance value transition as20to20, without showing its introduction.Explain(/presets/custom/settings/secondary/extends)returns two structural events with both before and after absent. It never shows the inheritedprimaryreference before consumption.- Explaining the enclosing child shows fully resolved nested values in its enclosing transfer, before the nested operations that supposedly produce them.
These observations were reproduced with temporary engine tests invoking Load
and Inspection.Explain; the temporary tests were removed after the review.
This violates the agreed inspectable derivation and enclosing-before-nested
history, not a request for additional historical guarantees.
Required correction:
- Keep retained parent/effective templates immutable while destination-local traversal proceeds. Use detached working structure or copy-on-write ownership as appropriate, preserving shared evidence without a snapshot per leaf.
- Keep each operation's values and origins at the same structural point.
- Replay inherited control arrival/consumption and nested value introduction explicitly, with metadata and explanation agreeing.
- Preserve existing retained-history accounting for any additional owned nodes.
Acceptance: add regressions using this nested example that assert meaningful
before/after values and consumed control history, not only the final result or
absence of ChangeDecoding. Cover a further enclosing chain and compare leaf
and container explanations. Existing ownership, redaction, and budget tests must
continue to pass.
2. Remove whole-document budget rescans per sibling scope
Priority: P2 — avoidable superlinear expansion work.
Location: per-scope global lower-bound check (line 95).
Every resolved scope invokes inheritanceLowerBoundWithin(*w.root, ...), traversing
the complete current document again. With N independent constant-sized sibling
scopes, input, output, and retained relationship evidence are all O(N), but this
check alone performs O(N²) visits. Its temporary traversal stacks also create
unnecessary allocation growth. This is independent of legitimate large inherited
output and of key-sorting costs.
A temporary benchmark generated N independent root entries, each containing
a: {x: v} and b: {extends: a}, and invoked expandInheritanceTree. Allocation
measurements were approximately 5.8 MB/op at 256 scopes, 14.1 MB/op at 512, and
49.5 MB/op at 1,024. Timing was noisy and is not proposed as a gate; the repeated
global traversal establishes the complexity defect directly. Existing benchmarks
concentrate on shared parents/chains rather than this independent-scope shape.
Required correction:
- Maintain a document-level logical node total using old/new scope counts, or equivalent incremental accounting, rather than rescanning unrelated branches.
- Keep early bounded expansion and the existing final node/depth verification; do not weaken the existing budgets or add a new cap to work around the cost.
- Include an independent-scope benchmark alongside current shapes and inspect growth relative to output/evidence size. Do not add timing thresholds.
Acceptance: show that independent-scope accounting no longer revisits the whole document per scope; retain at/beyond-budget tests and the existing value/origin semantics. Benchmark evidence should demonstrate the removed allocation/work growth without asserting an unconditional O(N) public guarantee.
Validation and contract review
./scripts/check.shpassed on Linux.go test -race ./...passed on Linux.- Temporary diagnostic tests and the independent-scope benchmark reproduced the findings; no probe files remain in the repository.
- No clear additional public prohibition, strengthened security guarantee, or
release gate was found. Reserved
extends, sibling-only lookup, null cancellation, invalid root/list-element references, inherited explicitness, and preservation of version ownership reflect the agreed design. The performance correction should preserve existing limits rather than introduce stricter ones.