Implement strict document composition
This commit is contained in:
@@ -150,6 +150,36 @@ zero in returned detached values. Applications retain responsibility for domain
|
||||
rules and runtime initialization; validators should not start clients or depend on
|
||||
live services as a substitute for configuration validation.
|
||||
|
||||
## Strict document composition
|
||||
|
||||
ConfigKit's private document layer can strictly compose parsed base documents. The
|
||||
operation is available for file-graph integration but is not yet reachable through
|
||||
the public single-source `Load` lifecycle. Application defaults do not participate
|
||||
in this operation.
|
||||
|
||||
| Existing node | Incoming node | Result |
|
||||
| --- | --- | --- |
|
||||
| Absent | Absent | Absent |
|
||||
| Absent | Present | Retain the incoming node |
|
||||
| Present | Absent | Retain the existing node |
|
||||
| Mapping | Mapping | Recurse by mapping key |
|
||||
| Any other pair of present nodes | Any other pair of present nodes | `compose.conflict` |
|
||||
|
||||
Every non-mapping overlap is a conflict, even when values are equal. Nulls and
|
||||
sequences are present atomic values, so they also conflict when repeated; sequences
|
||||
are never joined by index. Disjoint entries under shared mappings compose, making
|
||||
stable keyed mappings the natural representation for independently contributed
|
||||
entries.
|
||||
|
||||
An empty mapping contributes no child definitions. Two empty mappings compose, and
|
||||
an empty mapping neither clears nor conflicts with another mapping's children.
|
||||
Shared mapping containers retain all contributing value locations as their composite
|
||||
base origin. A conflict reports the first claim as its primary location and every
|
||||
remaining claim as related locations, in deterministic source and declaration
|
||||
traversal order. Traversal order never authorizes replacement. Composition clones
|
||||
the resulting tree and its ordered claim records rather than mutating or aliasing an
|
||||
input document.
|
||||
|
||||
## YAML documents
|
||||
|
||||
The private YAML boundary currently accepts exactly one nonempty document whose root
|
||||
|
||||
@@ -79,6 +79,15 @@ Assigned to document composition:
|
||||
ConfigKit semantic path model; both precise locations and composite origins are
|
||||
retained. Default values never participate in strict composition.
|
||||
|
||||
Implementation record (2026-09-06): `composeDocuments`, its pre-order claim
|
||||
collection, conflict construction, and recursive mapping join in `compose.go` adapt
|
||||
the assigned production symbols. `TestComposeDocumentsJoinsDisjointMappingsAndTracksClaims`,
|
||||
`TestComposeDocumentsRejectsEveryPresentNonMappingOverlap`, and
|
||||
`TestComposeDocumentsReportsAllClaimantsAtExactLocations` adapt the assigned cases.
|
||||
The planned empty-mapping difference is covered separately: empty mappings retain
|
||||
container provenance but contribute no child claims. No unplanned semantic deviation
|
||||
from the extracted additive composer was required.
|
||||
|
||||
### Imports
|
||||
|
||||
Assigned to explicit import traversal and glob handling:
|
||||
|
||||
@@ -10,6 +10,9 @@ module and package documentation boundary. Its implemented public primitives are
|
||||
- a parser-independent ordered tree in `internal/document`;
|
||||
- the sole upstream YAML integration in `internal/yaml`, which translates validated
|
||||
input into that owned tree and exposes no parser types outside its boundary;
|
||||
- a private strict composer in `compose.go` that joins only disjoint document-tree
|
||||
definitions, retains ordered claims and composite mapping origins, reports every
|
||||
conflicting location, and never aliases an input tree;
|
||||
- a per-operation private reflection descriptor graph in `schema.go` for supported
|
||||
types, YAML field binding, inlining, secret tags, recursion, and semantic paths;
|
||||
- descriptor-driven canonical projection, detached typed cloning, cycle/limit
|
||||
|
||||
@@ -802,7 +802,27 @@ Acceptance:
|
||||
|
||||
## Stage 10 — Implement strict document composition
|
||||
|
||||
Status: pending.
|
||||
Status: complete.
|
||||
|
||||
Completion evidence (2026-09-06):
|
||||
|
||||
- Adapted Narratio's additive claim validation and recursive mapping join into the
|
||||
private `composeDocuments` boundary over ConfigKit-owned document trees. Present
|
||||
equal or unequal scalars, nulls, sequences, mapping/value kind changes, and nested
|
||||
duplicate entries return `compose.conflict`; mapping pairs alone recurse.
|
||||
- Changed the extracted empty-map behavior to ConfigKit's operation table: empty
|
||||
mappings add no children, compose with other mappings, and retain their container
|
||||
location in composite base origins. Application defaults never enter the composer.
|
||||
- Preserved source/declaration traversal in detached ordered claim records and used
|
||||
it only for assembled order and exact primary/related diagnostics. Conflicts across
|
||||
three or more inputs report every claimant, while shared containers aggregate all
|
||||
contributing locations instead of selecting an arbitrary owner.
|
||||
- Cloned every retained branch and verified mutations of a composed result cannot
|
||||
affect any input. Focused tests cover disjoint keyed joins, operation categories,
|
||||
ancestor and nested conflicts, equal values, nulls, sequences, empty mappings,
|
||||
exact locations, all claimants, invalid boundaries, and nonmutation. The
|
||||
[current composition contract](../config.md#strict-document-composition) owns the
|
||||
implemented semantics; public import wiring remains deferred to subsequent work.
|
||||
|
||||
Read: feature [composition table](initial.md#composition-and-overlay).
|
||||
|
||||
|
||||
@@ -450,28 +450,24 @@ origin is a resolution operation, with input origins retained separately.
|
||||
|
||||
## Composition and overlay
|
||||
|
||||
Composition combines the main file and explicitly imported fragments as peers.
|
||||
Repeated semantic definitions are errors, even when their values are equal.
|
||||
Both conflicting locations must be reported. Prefer mappings keyed by stable
|
||||
identifiers when entries need to be contributed independently.
|
||||
Strict base composition is implemented and now owned by the
|
||||
[current configuration contract](../config.md#strict-document-composition).
|
||||
Intentional profile overlay remains target behavior. Its exhaustive operation table
|
||||
is below; “value” includes scalar, sequence, mapping, and null, while absent means no
|
||||
node rather than null.
|
||||
|
||||
The following table is exhaustive by node category. “Value” includes scalar,
|
||||
sequence, mapping, and null. Absent means no node, not null.
|
||||
| Existing/lower node | Incoming/higher node | Overlay |
|
||||
| --- | --- | --- |
|
||||
| Absent | Absent | Absent |
|
||||
| Absent | Value | Incoming |
|
||||
| Value | Absent | Existing |
|
||||
| Mapping | Mapping | Recurse by key |
|
||||
| Any other pair of present nodes | Any other pair of present nodes | Replace entire existing node |
|
||||
|
||||
| Existing/lower node | Incoming/higher node | Strict composition | Overlay |
|
||||
| --- | --- | --- | --- |
|
||||
| Absent | Absent | Absent | Absent |
|
||||
| Absent | Value | Incoming | Incoming |
|
||||
| Value | Absent | Existing | Existing |
|
||||
| Mapping | Mapping | Recurse by key | Recurse by key |
|
||||
| Any other pair of present nodes | Any other pair of present nodes | Collision | Replace entire existing node |
|
||||
|
||||
Two empty mappings compose successfully. An empty mapping contributes no child
|
||||
changes to an existing mapping; it does not clear that mapping. A mapping can
|
||||
replace a lower scalar, sequence, or null during overlay, subject to the final
|
||||
schema. Sequences are atomic: two definitions collide in composition and the
|
||||
higher sequence replaces the entire lower sequence in an overlay. `[]` clears a
|
||||
sequence. There is no append, union, index merge, or mapping-clear operator.
|
||||
A mapping can replace a lower scalar, sequence, or null during overlay, subject to
|
||||
the final schema. Sequences are atomic and the higher sequence replaces the entire
|
||||
lower sequence in an overlay. `[]` clears a sequence. There is no append, union,
|
||||
index merge, or mapping-clear operator.
|
||||
|
||||
Replacing a parent must not leave effective metadata for removed children.
|
||||
Sequence histories describe whole-sequence replacement; index origins refer to
|
||||
|
||||
Reference in New Issue
Block a user