Project sequence origins onto appended indexes

This commit is contained in:
2026-10-16 15:29:27 +00:00
parent dc7a70811a
commit bb9bde799a
3 changed files with 147 additions and 9 deletions

View File

@@ -291,6 +291,21 @@ Scope: composition evidence, not import acquisition.
Validation: composition/provenance/path tests and `./scripts/check.sh`.
Completion: correct trees and effective origins share the same record ordering.
Completion evidence: composition origins now traverse each source document with
separate local and effective semantic paths. Stage 2's source ranges shift list
items and descendants to assigned indexes while retaining each field's physical
location and contributor order at sequence containers; conflict claims remain
source-local. Tests cover a later source's local index zero becoming effective
index one, mounted and escaped/numeric-key paths, multiple collections, and
detached origins, claims, ranges, and input trees. These checks passed with Go
`go1.26.9 linux/amd64`:
- `GOTOOLCHAIN=local GOPROXY=off go test ./internal/engine -run 'Compose|Provenance|Path' -count=1`
- `GOTOOLCHAIN=local ./scripts/check.sh`
- `git diff --check`
No environmental limitation affected validation; checks ran on Linux/amd64.
## Stage 4 — Preserve append history and whole-list tier replacement
Scope: private history operations, replay, and precedence handoff.

View File

@@ -116,7 +116,7 @@ func composeDocumentsWithPolicy(policy sequencePolicy, inputs ...*document.Node)
for _, input := range inputs {
applications = append(applications, historyApplication{layer: LayerBase, value: input})
}
origins := compositionOrigins(collected.ordered)
origins := projectedCompositionOrigins(inputs, ranges)
completeCompositionOrigins(root, Path{}, origins)
return &composedDocument{
root: root,
@@ -337,18 +337,51 @@ func documentEntryIndex(entries []document.Entry, key string) int {
return -1
}
func compositionOrigins(claims []compositionClaim) map[string]Origin {
origins := make(map[string]Origin, len(claims))
for _, claim := range claims {
pointer := claim.path.String()
origin := origins[pointer]
origin.Layer = LayerBase
origin.Locations = append(origin.Locations, claim.location)
origins[pointer] = origin
func projectedCompositionOrigins(inputs []*document.Node, ranges []sequenceRange) map[string]Origin {
origins := make(map[string]Origin)
rangeBySourcePath := make(map[sequenceRangeKey]int, len(ranges))
for index, assigned := range ranges {
rangeBySourcePath[sequenceRangeKey{sourceIndex: assigned.sourceIndex, path: assigned.path.String()}] = index
}
for sourceIndex, input := range inputs {
collectProjectedCompositionOrigins(input, Path{}, Path{}, sourceIndex, ranges, rangeBySourcePath, origins)
}
return origins
}
type sequenceRangeKey struct {
sourceIndex int
path string
}
// Keep local paths for matching Stage 2 ranges and effective paths for emitted
// evidence. The paths are transformed segment by segment; pointer strings are
// only used as map keys after projection is complete.
func collectProjectedCompositionOrigins(node *document.Node, localPath, effectivePath Path, sourceIndex int, ranges []sequenceRange, rangeBySourcePath map[sequenceRangeKey]int, origins map[string]Origin) {
pointer := effectivePath.String()
origin := origins[pointer]
origin.Layer = LayerBase
origin.Locations = append(origin.Locations, publicLocation(node.Location))
origins[pointer] = origin
switch node.Kind {
case document.Mapping:
for _, entry := range node.Mapping {
collectProjectedCompositionOrigins(entry.Value, localPath.Key(entry.Key), effectivePath.Key(entry.Key), sourceIndex, ranges, rangeBySourcePath, origins)
}
case document.Sequence:
start := 0
if rangeIndex, ok := rangeBySourcePath[sequenceRangeKey{sourceIndex: sourceIndex, path: localPath.String()}]; ok {
start = ranges[rangeIndex].start
}
for index, child := range node.Sequence {
localChild := localPath.Index(index)
effectiveChild := effectivePath.Index(start + index)
collectProjectedCompositionOrigins(child, localChild, effectiveChild, sourceIndex, ranges, rangeBySourcePath, origins)
}
}
}
func applyCompositionOrigins(decoded *decodedConfiguration, origins map[string]Origin) {
for pointer, origin := range origins {
if _, present := decoded.presence[pointer]; present {

View File

@@ -220,6 +220,96 @@ func TestComposeDocumentsAppendsRegisteredSequencesAndReturnsRanges(t *testing.T
}
}
func TestComposeDocumentsProjectsSequenceOriginsToAssignedIndexes(t *testing.T) {
first := parseCompositionDocument(t, "source-1", `mounted:
records:
- id: first
name: first-name
escaped/~:
"7":
- id: escaped-first
name: escaped-first-name
`)
second := parseCompositionDocument(t, "source-2", `mounted:
records:
- id: second
name: second-name
escaped/~:
"7":
- id: escaped-second
name: escaped-second-name
`)
firstBefore, secondBefore := document.Clone(first), document.Clone(second)
policy := sequencePolicy{identityFields: map[string]string{
Keys("mounted", "records").String(): "id",
Keys("escaped/~", "7").String(): "id",
}}
composed, diagnostics := composeDocumentsWithPolicy(policy, first, second)
if len(diagnostics) != 0 {
t.Fatalf("composeDocumentsWithPolicy() diagnostics = %#v", diagnostics)
}
for _, test := range []struct {
path Path
want Location
}{
{path: Keys("mounted", "records").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 4, Column: 13}},
{path: Keys("escaped/~", "7").Index(1).Key("name"), want: Location{SourceID: "source-2", Line: 8, Column: 13}},
} {
path, want := test.path, test.want
origin, ok := composed.origins[path.String()]
if !ok || !reflect.DeepEqual(origin.Locations, []Location{want}) {
t.Errorf("origin at %s = %#v, want source-specific location %#v", path, origin, want)
}
}
for _, test := range []struct {
path Path
wantSources []string
}{
{path: Keys("mounted", "records"), wantSources: []string{"source-1", "source-2"}},
{path: Keys("escaped/~", "7"), wantSources: []string{"source-1", "source-2"}},
} {
path, wantSources := test.path, test.wantSources
origin := composed.origins[path.String()]
gotSources := make([]string, len(origin.Locations))
for index, location := range origin.Locations {
gotSources[index] = location.SourceID
}
if !reflect.DeepEqual(gotSources, wantSources) {
t.Errorf("container origin at %s = %#v, want contributors %#v", path, gotSources, wantSources)
}
}
if !reflect.DeepEqual(first, firstBefore) || !reflect.DeepEqual(second, secondBefore) {
t.Fatal("composition projection mutated an input")
}
copy := cloneComposedDocument(composed)
copy.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID = "changed"
for index := range copy.claims {
if copy.claims[index].path.String() == "/mounted/records" {
copy.claims[index].path.segments[0].key = "changed"
break
}
}
copy.sequenceRanges[0].path.segments[0].key = "changed"
if got := composed.origins[Keys("mounted", "records").Index(1).Key("name").String()].Locations[0].SourceID; got != "source-2" {
t.Fatalf("copied origin mutation changed source evidence: %q", got)
}
foundClaim := false
for _, claim := range composed.claims {
if claim.path.String() == "/mounted/records" {
foundClaim = true
break
}
}
if !foundClaim {
t.Fatal("copied claim mutation changed source path")
}
if got := composed.sequenceRanges[0].path.String(); got != "/mounted/records" {
t.Fatalf("copied range mutation changed assigned path: %q", got)
}
}
func TestComposeDocumentsValidatesRegisteredSequenceSingletons(t *testing.T) {
tests := []struct {
name string