Deduplicate parsed sensitivity paths correctly

This commit is contained in:
2026-10-27 03:12:21 +00:00
parent 731149fc6d
commit 1448f9b19d
3 changed files with 46 additions and 3 deletions

View File

@@ -308,7 +308,13 @@ coarsen semantic identity.
## 6. Sensitivity Candidate Deduplication Cannot Match Parsed Paths
Priority: P3. Status: open.
Priority: P3. Status: complete.
Completion: candidate and dependency deduplication uses structural segment
equality, including parsed tokens, without conflating explicit keys and indexes.
`TestSensitivityCandidatesMergeRepeatedParsedPaths` failed before the fix and
now verifies unique semantic/dependency candidates and merged sensitivity facts.
The full ordinary suite and `git diff --check` passed.
Relevant code: `sensitivityCandidates` and `appendDistinctHistoryPath` in
[history_select.go](../../internal/engine/history_select.go), especially

View File

@@ -210,7 +210,7 @@ func (model *historyModel) sensitivityCandidates() []historySensitivityCandidate
}
pointer := path.String()
for _, index := range indices[pointer] {
if sameConcreteHistoryPath(candidates[index].path, path) {
if slices.Equal(candidates[index].path.segments, path.segments) {
candidates[index].sensitive = candidates[index].sensitive || sensitive
candidates[index].classify = candidates[index].classify || classify
return index
@@ -254,7 +254,7 @@ func (model *historyModel) sensitivityCandidates() []historySensitivityCandidate
func appendDistinctHistoryPath(paths *[]Path, path Path) {
for _, existing := range *paths {
if sameConcreteHistoryPath(existing, path) {
if slices.Equal(existing.segments, path.segments) {
return
}
}

View File

@@ -2,11 +2,48 @@ package engine
import (
"reflect"
"slices"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestSensitivityCandidatesMergeRepeatedParsedPaths(t *testing.T) {
t.Parallel()
builder := newHistoryBuilder()
key := Keys("shape", "0")
index := Keys("shape").Index(0)
parsed := parseOverrideTestPath(t, "/shape/0")
dependency := parseOverrideTestPath(t, "/dependency")
attribution := builder.addAttribution(Origin{Layer: LayerResolution}, nil, []Path{dependency})
payload := builder.nextPayload(historyPhaseResolution)
for repeat := 0; repeat < 3; repeat++ {
for _, path := range []Path{key, index} {
builder.addObservation(historyObservation{
path: path, phase: historyPhaseResolution, payload: payload,
reportedPath: Keys("shape"), role: historyRoleOperation,
afterPresent: true, sensitive: repeat == 2,
attributionRef: attribution,
})
}
}
candidates := builder.freeze().sensitivityCandidates()
for _, path := range []Path{key, index, parsed, dependency} {
count := 0
for _, candidate := range candidates {
if slices.Equal(candidate.path.segments, path.segments) {
count++
if path.String() == "/shape/0" && !candidate.sensitive {
t.Fatal("merged candidate lost a later sensitivity fact")
}
}
}
if count != 1 {
t.Fatalf("semantic candidate %#v occurs %d times; want one", path, count)
}
}
}
func TestHistorySelectionSeparatesNumericShapesAndGroupsParsedSides(t *testing.T) {
t.Parallel()