Preserve tier sequence operations in source history

This commit is contained in:
2026-10-24 00:01:46 +00:00
parent e6598810e0
commit a035df9845
5 changed files with 198 additions and 7 deletions

View File

@@ -78,6 +78,71 @@ func TestSequenceHistoryReplaysAppendAtAssignedIndexes(t *testing.T) {
}
}
func TestTierSequenceHistoryReplaysSelectedOperation(t *testing.T) {
t.Parallel()
policy := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{
selector: sequenceIdentitySelector{literalSequenceSelector("records")}, identityFields: []string{"id"},
}})
for _, test := range []struct {
name string
mode sequenceMode
}{{"append", sequenceModeAppend}, {"upsert", sequenceModeUpsert}, {"replace", sequenceModeReplace}} {
t.Run(test.name, func(t *testing.T) {
mode := test.mode
lower, diagnostics := composeDocumentsWithPolicy(policy, parseCompositionDocument(t, "lower", "records: [{id: a, removed: old}, {id: b, value: old, removed: old}]\n"))
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
secondID := "d"
if mode == sequenceModeUpsert {
secondID = "b"
}
declarations := []sequenceDeclarationSpec{{destination: Keys("records"), modePresent: true, mode: mode}}
higher, diagnostics := composeDocumentsWithCollectionPolicy(policy, lower.collectionPolicies, declarations,
parseCompositionDocument(t, "first-peer", "records: [{id: c, value: first}]\n"),
parseCompositionDocument(t, "second-peer", "records: [{id: "+secondID+", value: second}]\n"),
)
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
assembly := newAssemblyAccumulator(lower)
if diagnostics := assembly.applyOverlayWithCollectionPolicy(higher.root, higher.origins, higher.collectionPolicies, higher.applications, LayerBase, policy, declarations); len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
if len(assembly.current.applications) != 3 {
t.Fatalf("source applications = %d, want all three sources", len(assembly.current.applications))
}
if mode == sequenceModeUpsert {
query := Keys("records").Index(1).Key("value")
if _, relevant := applicationOperationPath(assembly.current.applications[1], query, nil); relevant {
t.Fatal("first peer was attributed to another peer's upserted record")
}
origin := historyApplicationOrigin(assembly.current.applications[2], query, nil)
if len(origin.Locations) != 1 || origin.Locations[0].SourceID != "second-peer" {
t.Fatalf("upsert history source = %#v", origin)
}
}
working := parseCompositionDocument(t, "initial", "{}\n")
index := new(mappingIndex)
for _, application := range assembly.current.applications {
applyHistoryApplication(&working, application, index)
}
if !configurationValuesEqual(working, assembly.current.root) {
t.Fatal("tier history differs from structural assembly before decoding")
}
overlay := parseCompositionDocument(t, "overlay", "records: [{id: c, value: overlay}]\n")
declarations[0].mode = sequenceModeUpsert
if diagnostics := assembly.applyOverlayWithCollectionPolicy(overlay, nil, collectionPolicyScope{}, nil, LayerOverlay, policy, declarations); len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
applyHistoryApplication(&working, assembly.current.applications[3], index)
if !configurationValuesEqual(working, assembly.current.root) {
t.Fatal("subsequent overlay history differs from structural assembly")
}
})
}
}
func TestSequenceHistoryPreservesTierReplacementAndEmptyLists(t *testing.T) {
policy := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{
{selector: sequenceIdentitySelector{literalSequenceSelector("records")}, identityFields: []string{"id"}},
@@ -151,6 +216,49 @@ func TestHistoryMergeSupportsSemanticIndexedPrefixes(t *testing.T) {
if !present || value.Text != "new" {
t.Fatalf("indexed helper replay = %#v, %v", value, present)
}
root = parseCompositionDocument(t, "base", "records: [{id: one, helper: {items: [{id: a}]}}]\n")
application.value = parseCompositionDocument(t, "helper", "items: [{id: b}]\n")
application.sequences = []sequenceHistoryContribution{{path: Keys("items"), mode: sequenceHistoryAppend, offset: 1, length: 1}}
application.sequencesByPath = indexSequenceHistoryContributions(application.sequences)
applyHistoryApplication(&root, application, new(mappingIndex))
items, present := documentAtPath(root, Keys("records").Index(0).Key("helper").Key("items"))
if !present || len(items.Sequence) != 2 {
t.Fatalf("indexed helper lost its local sequence operation: %#v", items)
}
}
func TestLayeredUpsertExplanationFollowsStructuralOrder(t *testing.T) {
t.Parallel()
type config struct {
Records []map[string]any `yaml:"records"`
}
loaded, err := testGroupedLoad(loadRequest[config]{
main: loadInput{source: Bytes("main", []byte("configkit:\n sequences: [{destination: [records], key: id}]\nrecords: [{id: b, value: new-b}, {id: c, value: new-c}, {id: a, value: new-a}]\n"))},
underlays: []loadGroup{{inputs: []loadInput{{source: Bytes("lower", []byte("records: [{id: a, value: old-a}, {id: b, value: old-b, removed: old}]\n"))}}}},
})
if err != nil {
t.Fatal(err)
}
explanation, ok := loaded.Explain(Keys("records"))
if !ok || len(explanation.History) == 0 {
t.Fatal("missing collection history")
}
for _, change := range explanation.History {
if change.Origin.Operation == ChangeDecoding {
t.Fatal("structural upsert was misreported as a decoding reconciliation")
}
}
if last := explanation.History[len(explanation.History)-1]; last.After.YAML != explanation.Effective.YAML || last.Origin.Layer != LayerBase {
t.Fatalf("main contribution did not produce the effective collection: %#v", last)
}
removed, ok := loaded.Explain(Keys("records").Index(1).Key("removed"))
if !ok || len(removed.History) == 0 || removed.Effective.Present {
t.Fatalf("missing displaced field history: %#v, %v", removed, ok)
}
if last := removed.History[len(removed.History)-1]; last.After.Present || last.Origin.Layer != LayerBase || last.Origin.Operation == ChangeDecoding {
t.Fatalf("upsert removal attribution = %#v", last)
}
}
func TestProjectedSequenceHistoryAgreesAcrossMetadataAndExplanation(t *testing.T) {