Preserve matched record metadata after resolution

This commit is contained in:
2026-11-02 14:47:52 +00:00
parent c61597d137
commit 4549fbaeef
4 changed files with 92 additions and 3 deletions

View File

@@ -219,6 +219,8 @@ Do not assert an incidental struct layout or a fixed number of internal calls.
## Stage 6 — Refresh effective metadata using the matched entry record
**Status:** Complete. Focused resolution, metadata, validation, and secret checks passed.
**Scope:** presence, origins, and post-resolution diagnostic locations.
1. In `applyResolutionMetadata` and `refreshResolutionFields`, seed final fields

View File

@@ -141,6 +141,15 @@ func cloneMetadata(metadata Metadata) Metadata {
func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult, entry resolutionInput) {
finalPaths := make(map[string]*document.Node)
indexDocument(resolved.final, Path{}, finalPaths)
fields := make(map[string]FieldInfo, len(finalPaths))
walkDocument(resolved.final, Path{}, func(path Path, _ *document.Node) {
if prior, matched := resolved.correspondence.entryPath(path); matched {
if info, found := entry.lookup(prior); found {
fields[path.String()] = cloneFieldInfo(info)
}
}
})
metadata.fields = fields
snapshotCoverage, snapshotPointers := newResolutionCoverageIndexFromSnapshots(resolved.snapshots)
var deltaCoverage pathPrefixIndex
snapshotIDs := make([]int, len(resolved.deltas))
@@ -163,7 +172,7 @@ func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult, entr
}
applyResolutionFieldOrigins(metadata.fields, resolved.snapshots, &deltaCoverage, snapshotIDs)
refreshResolutionFields(metadata.fields, finalPaths, resolved.final, entry, resolved.snapshots, snapshotCoverage)
refreshResolutionFields(metadata.fields, finalPaths, resolved.final, entry, resolved.snapshots, snapshotCoverage, resolved.correspondence)
}
func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolutionSnapshot, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
@@ -192,7 +201,7 @@ func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolu
}
}
func refreshResolutionFields(fields map[string]FieldInfo, finalPaths map[string]*document.Node, final *document.Node, entry resolutionInput, snapshots []resolutionSnapshot, snapshotCoverage *resolutionCoverageIndex) {
func refreshResolutionFields(fields map[string]FieldInfo, finalPaths map[string]*document.Node, final *document.Node, entry resolutionInput, snapshots []resolutionSnapshot, snapshotCoverage *resolutionCoverageIndex, correspondence resolutionCorrespondence) {
for pointer := range fields {
if _, ok := finalPaths[pointer]; !ok {
delete(fields, pointer)
@@ -200,7 +209,9 @@ func refreshResolutionFields(fields map[string]FieldInfo, finalPaths map[string]
}
walkDocument(final, Path{}, func(path Path, node *document.Node) {
pointer := path.String()
entryInfo, entryFound := entry.lookup(path)
prior, matched := correspondence.entryPath(path)
entryInfo, entryFound := entry.lookup(prior)
entryFound = entryFound && matched
if info, ok := fields[pointer]; ok {
info.Explicit = entryFound && entryInfo.Explicit
info.Null = node.Kind == document.Null

View File

@@ -162,6 +162,13 @@ func documentWithMetadataLocations(root *document.Node, metadata Metadata, fallb
}
var lookup *documentLookup
fallbackLocation := func(path Path) (document.Location, bool) {
if model := metadata.historyEvidence(); model != nil && model.resolutionPayload != nil {
var matched bool
path, matched = model.resolutionPayload.correspondence.entryPath(path)
if !matched {
return document.Location{}, false
}
}
if lookup == nil {
lookup = buildDocumentLookup(fallbackRoot)
}
@@ -255,6 +262,13 @@ func metadataFieldLocation(metadata Metadata, path Path, origin Origin) (documen
return location, true
}
if model := metadata.historyEvidence(); model != nil {
if model.resolutionPayload != nil {
var matched bool
path, matched = model.resolutionPayload.correspondence.entryPath(path)
if !matched {
return document.Location{}, false
}
}
location, ok := model.latestHistoricalLocation(path)
if ok {
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true

View File

@@ -203,3 +203,65 @@ func TestRegisteredResolutionFallbackAndHelperTarget(t *testing.T) {
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
}
}
func TestRegisteredResolutionMetadataFollowsEntryIdentity(t *testing.T) {
type item struct {
ID string `yaml:"id"`
Enabled bool `yaml:"enabled"`
Count int `yaml:"count"`
}
type config struct {
Items []item `yaml:"items"`
}
entryFixed := false
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
entryFixed = r.IsExplicit(Keys("items").Index(0).Key("enabled")) && !r.IsExplicit(Keys("items").Index(1).Key("enabled"))
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
value.Items = append(value.Items, item{ID: "new"})
r.RecordNormalization(Keys("items"), "reorder and add")
return nil
}}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
result, err := testLoad(Bytes("records", []byte("items: [{id: a, enabled: false}, {id: b, count: 0}]\n")), Options[config]{Definition: definition})
if err != nil || !entryFixed {
t.Fatalf("load = %#v, fixed entry = %t, error = %v", result, entryFixed, err)
}
for _, check := range []struct {
index int
key string
explicit bool
}{{0, "enabled", false}, {0, "count", true}, {1, "enabled", true}, {1, "count", false}, {2, "id", false}} {
info, ok := result.Metadata().Lookup(Keys("items").Index(check.index).Key(check.key))
if !ok || info.Explicit != check.explicit || info.Origin.Layer != LayerResolution {
t.Errorf("item %d %s = %#v, %t", check.index, check.key, info, ok)
}
}
}
func TestRegisteredResolutionValidationLocationFollowsMovedRecord(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[0], value.Items[1] = value.Items[1], value.Items[0]
r.RecordNormalization(Keys("items"), "reorder")
return nil
},
Validate: func(_ *config, v *Validation) { v.Error(Keys("items").Index(0).Key("value"), "invalid") },
}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
_, err := testLoad(Bytes("records", []byte("items:\n - id: a\n value: first\n - id: b\n value: second\n")), Options[config]{Definition: definition})
var failure *LoadError
if !errors.As(err, &failure) {
t.Fatalf("error = %v", err)
}
for _, diagnostic := range failure.Diagnostics() {
if diagnostic.Path.String() == "/items/0/value" && diagnostic.Primary != nil && diagnostic.Primary.Line == 5 {
return
}
}
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
}