From 57edb8ec194ec59bd86b60d4e025dd292aecf318 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 24 Oct 2026 00:15:23 +0000 Subject: [PATCH] Identify conflicting sequence registration positions --- docs/roadmap/remediation.md | 8 +++++- internal/engine/sequence_registration.go | 22 +++++++++++---- internal/engine/sequence_registration_test.go | 28 +++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index 9e4bd60..82c8a64 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -319,7 +319,13 @@ Add ordinary/layered parity and warning-plus-later-error regressions. ## 9. Identify Conflicting Application Registrations In Diagnostics — P2 -Status: [ ] Open. +Status: [x] Complete. + +Completion: definition validation retains original one-based registration +positions and names both positions in each overlap diagnostic. No input path or +new public diagnostic structure is invented. A mixed-selector regression verifies +multiple conflicts even after an earlier invalid registration is skipped. +Verified registration/facade coverage through `./scripts/check.sh`. Owner: [`compileSequenceIdentityPolicy`](../../internal/engine/sequence_registration.go), lines 119–128. diff --git a/internal/engine/sequence_registration.go b/internal/engine/sequence_registration.go index 513c466..cc4f113 100644 --- a/internal/engine/sequence_registration.go +++ b/internal/engine/sequence_registration.go @@ -1,5 +1,7 @@ package engine +import "fmt" + type sequenceSelectorSegmentKind uint8 const ( @@ -87,9 +89,13 @@ func DefinitionWithSequenceIdentityRegistrations[T any](definition Definition[T] func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) (sequencePolicy, []Diagnostic) { policy := sequencePolicy{} - compiled := make([]sequenceIdentityRegistration, 0, len(registrations)) + type positionedRegistration struct { + registration sequenceIdentityRegistration + position int + } + compiled := make([]positionedRegistration, 0, len(registrations)) var diagnostics []Diagnostic - for _, registration := range registrations { + for position, registration := range registrations { selector := cloneSequenceIdentitySelector(registration.selector) if registration.invalidPath != nil { diagnostics = append(diagnostics, Diagnostic{ @@ -113,18 +119,21 @@ func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) }) continue } - compiled = append(compiled, sequenceIdentityRegistration{selector: selector, identityFields: fields}) + compiled = append(compiled, positionedRegistration{ + registration: sequenceIdentityRegistration{selector: selector, identityFields: fields}, + position: position + 1, + }) } for leftIndex := 0; leftIndex < len(compiled); leftIndex++ { for rightIndex := leftIndex + 1; rightIndex < len(compiled); rightIndex++ { - left, right := compiled[leftIndex], compiled[rightIndex] + left, right := compiled[leftIndex].registration, compiled[rightIndex].registration if !sequenceIdentitySelectorsOverlap(left.selector, right.selector) || equalSequenceIdentityFields(left.identityFields, right.identityFields) { continue } diagnostics = append(diagnostics, Diagnostic{ Code: CodeOptionsInvalid, Severity: SeverityError, Path: selectorDiagnosticPath(left.selector), - Message: "overlapping sequence registrations must use the same identity fields", + Message: fmt.Sprintf("sequence registrations %d and %d overlap with conflicting identity fields", compiled[leftIndex].position, compiled[rightIndex].position), }) } } @@ -133,7 +142,8 @@ func compileSequenceIdentityPolicy(registrations []sequenceIdentityRegistration) } unique := make([]sequenceIdentityRegistration, 0, len(compiled)) - for _, registration := range compiled { + for _, positioned := range compiled { + registration := positioned.registration duplicate := false for _, existing := range unique { if sequenceIdentitySelectorsEqual(existing.selector, registration.selector) && equalSequenceIdentityFields(existing.identityFields, registration.identityFields) { diff --git a/internal/engine/sequence_registration_test.go b/internal/engine/sequence_registration_test.go index ebfbec2..8daebbb 100644 --- a/internal/engine/sequence_registration_test.go +++ b/internal/engine/sequence_registration_test.go @@ -178,6 +178,34 @@ func TestCompileSequenceIdentityPolicyRejectsConflictingOverlapsWithoutInput(t * } } +func TestSequenceOverlapDiagnosticsIdentifyOriginalRegistrationPositions(t *testing.T) { + t.Parallel() + _, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{ + {selector: sequenceIdentitySelector{literalSequenceSelector("invalid")}}, + {selector: sequenceIdentitySelector{literalSequenceSelector("unrelated")}, identityFields: []string{"id"}}, + {selector: sequenceIdentitySelector{literalSequenceSelector("collections"), anyMappingSequenceSelector(), literalSequenceSelector("items")}, identityFields: []string{"id"}}, + {selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("foo"), literalSequenceSelector("items")}, identityFields: []string{"name"}}, + {selector: sequenceIdentitySelector{literalSequenceSelector("collections"), literalSequenceSelector("bar"), literalSequenceSelector("items")}, identityFields: []string{"code"}}, + }) + if len(diagnostics) != 3 { + t.Fatalf("diagnostics = %#v, want the invalid registration and two conflicts", diagnostics) + } + for _, pair := range []string{"3 and 4", "3 and 5"} { + found := false + for _, diagnostic := range diagnostics { + if strings.Contains(diagnostic.Message, pair) { + found = true + if diagnostic.Code != CodeOptionsInvalid || diagnostic.Path.String() != "" || len(diagnostic.Related) != 0 { + t.Fatalf("definition overlap invented input coordinates: %#v", diagnostic) + } + } + } + if !found { + t.Errorf("conflicting registration positions %s not identified: %#v", pair, diagnostics) + } + } +} + func TestCompileSequenceIdentityPolicyAllowsDisjointSelectorsAndRoot(t *testing.T) { policy, diagnostics := compileSequenceIdentityPolicy([]sequenceIdentityRegistration{ {selector: sequenceIdentitySelector{}, identityFields: []string{"id"}},