Explain matched resolution and removed records

This commit is contained in:
2026-11-02 14:54:47 +00:00
parent 0799e1d18f
commit c296eba6c1
3 changed files with 146 additions and 1 deletions

View File

@@ -307,6 +307,8 @@ every source form across every case. Collection queries keep original list order
## Stage 9 — Explain net resolution and preserve removed evidence
**Status:** Complete. Focused history and explanation checks passed for moves, removals, additions, and reused positions.
**Scope:** resolution replay groups, structural changes, and reused addresses.
1. For matched-record queries, read a group's before side using the entry path

View File

@@ -152,7 +152,7 @@ func (r *Inspection) Explain(path Path) (Explanation, bool) {
continue
}
covering := model.resolutionPayload.snapshots[group.coveringRecordID]
before := valueSnapshot(working, path, r.secrets)
before := valueSnapshot(working, entryPath, r.secrets)
replaceHistoryPath(&working, group.change.Path, covering.after)
after := valueSnapshot(working, path, r.secrets)
if !before.Present && !after.Present {

View File

@@ -2,6 +2,7 @@ package engine
import (
"errors"
"strings"
"testing"
)
@@ -390,3 +391,145 @@ func TestMovedRecordExplanationIncludesCLIValue(t *testing.T) {
}
t.Fatalf("CLI history = %#v", explanation.History)
}
func TestRegisteredResolutionExplainsRemovalAndReusedPositions(t *testing.T) {
type item struct {
ID string `yaml:"id"`
Value string `yaml:"value"`
}
type config struct {
Items []item `yaml:"items"`
}
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
value.Items = value.Items[1:]
r.RecordNormalization(Keys("items"), "remove first")
return nil
}}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
result, err := testLoad(Bytes("records", []byte("items:\n - id: reports\n value: report\n - id: invoices\n value: invoice\n")), Options[config]{Definition: definition})
if err != nil {
t.Fatal(err)
}
path := Keys("items").Index(0).Key("value")
explanation, ok := result.Inspection.Explain(path)
if !ok {
t.Fatal("survivor explanation missing")
}
found := false
for _, change := range explanation.History {
if change.Origin.Layer == LayerResolution {
if change.Before.YAML != "invoice\n" || change.After.YAML != "invoice\n" {
t.Fatalf("survivor operation = %#v", change)
}
found = true
}
if change.Origin.Layer == LayerMain && change.After.YAML == "report\n" {
t.Fatalf("removed occupant leaked into survivor: %#v", change)
}
}
if !found {
t.Fatal("missing collection operation")
}
container, ok := result.Inspection.Explain(Keys("items"))
if !ok {
t.Fatal("collection explanation missing")
}
found = false
for _, change := range container.History {
if change.Origin.Layer == LayerResolution && strings.Contains(change.Before.YAML, "reports") && !strings.Contains(change.After.YAML, "reports") {
found = true
}
}
if !found {
t.Fatalf("missing removed collection evidence: %#v", container.History)
}
mainEvidence := false
for _, change := range result.Metadata().History(Keys("items")) {
if change.Origin.Layer == LayerMain && len(change.Origin.Locations) != 0 {
mainEvidence = true
}
}
if !mainEvidence {
t.Fatal("collection history lost source attribution")
}
if _, ok := result.Inspection.Explain(Keys("items").Index(1)); !ok {
t.Fatal("absent historical position missing")
}
if _, ok := result.Inspection.Explain(Keys("items").Index(8)); ok {
t.Fatal("unknown position reported")
}
}
func TestRegisteredResolutionExplainsAddedAndChangedIdentities(t *testing.T) {
type item struct {
ID string `yaml:"id"`
Value string `yaml:"value"`
}
type config struct {
Items []item `yaml:"items"`
}
for _, replace := range []bool{false, true} {
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
if replace {
value.Items[0] = item{ID: "new", Value: "added"}
} else {
value.Items = append([]item{{ID: "new", Value: "added"}}, value.Items...)
}
r.RecordNormalization(Keys("items"), "update collection")
return nil
}}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
result, err := testLoad(Bytes("records", []byte("items: [{id: old, value: original}]\n")), Options[config]{Definition: definition})
if err != nil {
t.Fatal(err)
}
explanation, ok := result.Inspection.Explain(Keys("items").Index(0).Key("value"))
if !ok {
t.Fatal("added explanation missing")
}
for _, change := range explanation.History {
if change.Origin.Layer == LayerMain || (change.Origin.Layer == LayerResolution && change.Before.Present) {
t.Fatalf("added record inherited previous value: %#v", explanation.History)
}
}
if !replace {
moved, ok := result.Inspection.Explain(Keys("items").Index(1).Key("value"))
if !ok {
t.Fatal("shifted survivor missing")
}
found := false
for _, change := range moved.History {
if change.Origin.Layer == LayerResolution && change.Before.YAML == "original\n" && change.After.YAML == "original\n" {
found = true
}
}
if !found {
t.Fatalf("shifted survivor = %#v", moved.History)
}
}
}
}
func TestRegisteredResolutionExplainsRemovedFieldOnMovedRecord(t *testing.T) {
type config struct {
Items []map[string]any `yaml:"items"`
}
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
delete(value.Items[0], "extra")
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
r.RecordNormalization(Keys("items"), "remove and reorder")
return nil
}}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
result, err := testLoad(Bytes("records", []byte("items: [{id: a, extra: own}, {id: b, extra: other}]\n")), Options[config]{Definition: definition})
if err != nil {
t.Fatal(err)
}
explanation, ok := result.Inspection.Explain(Keys("items").Index(1).Key("extra"))
if !ok {
t.Fatal("removed field explanation missing")
}
for _, change := range explanation.History {
if change.Origin.Layer == LayerResolution && change.Before.YAML == "own\n" && !change.After.Present {
return
}
}
t.Fatalf("removed field history = %#v", explanation.History)
}