Finalize structured diagnostic aggregation

This commit is contained in:
2026-08-27 16:40:17 +00:00
parent 480680b257
commit 4dbbf68051
112 changed files with 656 additions and 893 deletions

View File

@@ -122,25 +122,25 @@ type normalizedRecord struct {
type nameCanonicalization struct{ from, to string }
func normalizeList(input dnd.LocationOccurrenceList, documentIndex source.DocumentIndex, order shared.SourceRefOrder, registry *locationregistry.Registry) (dnd.LocationOccurrenceList, []contracts.Warning) {
func normalizeList(input dnd.LocationOccurrenceList, documentIndex source.DocumentIndex, order shared.SourceRefOrder, registry *locationregistry.Registry) (dnd.LocationOccurrenceList, []diagnostics.Finding) {
if input.Occurrences == nil {
return dnd.LocationOccurrenceList{}, nil
}
records := make([]normalizedRecord, len(input.Occurrences))
warnings := make([]contracts.Warning, 0)
warnings := make([]diagnostics.Finding, 0)
for index, inputOccurrence := range input.Occurrences {
occurrence, change, found, refsChanged := normalizeOccurrence(inputOccurrence, order, registry)
records[index] = normalizedRecord{occurrence: occurrence, inputIndex: index}
if change != nil {
warnings = append(warnings, contracts.Warning{Scope: occurrenceScope(index), ReasonCode: ReasonCodeNameCanonicalized,
warnings = append(warnings, diagnostics.Finding{Scope: occurrenceScope(index), ReasonCode: ReasonCodeNameCanonicalized,
Message: fmt.Sprintf("input index %d: location name canonicalized from %s to %s", index, diagnostics.Quote(change.from), diagnostics.Quote(change.to))})
}
if !found {
warnings = append(warnings, contracts.Warning{Scope: occurrenceScope(index), ReasonCode: ReasonCodeUnknownLocationID,
warnings = append(warnings, diagnostics.Finding{Scope: occurrenceScope(index), ReasonCode: ReasonCodeUnknownLocationID,
Message: fmt.Sprintf("input index %d: location ID %s is not in the supplied registry", index, diagnostics.Quote(inputOccurrence.LocationID))})
}
if refsChanged {
warnings = append(warnings, contracts.Warning{Scope: occurrenceScope(index), ReasonCode: ReasonCodeSourceRefsNormalized,
warnings = append(warnings, diagnostics.Finding{Scope: occurrenceScope(index), ReasonCode: ReasonCodeSourceRefsNormalized,
Message: fmt.Sprintf("input index %d: source references normalized (original count %d, final count %d)", index, len(inputOccurrence.SourceRefs), len(occurrence.SourceRefs))})
}
}
@@ -149,7 +149,7 @@ func normalizeList(input dnd.LocationOccurrenceList, documentIndex source.Docume
})
for position, record := range records {
if position != record.inputIndex {
warnings = append(warnings, contracts.Warning{Scope: occurrenceScope(record.inputIndex), ReasonCode: ReasonCodeOccurrencesReordered,
warnings = append(warnings, diagnostics.Finding{Scope: occurrenceScope(record.inputIndex), ReasonCode: ReasonCodeOccurrencesReordered,
Message: fmt.Sprintf("input index %d moved to normalized position %d by canonical occurrence order", record.inputIndex, position)})
}
}
@@ -194,7 +194,7 @@ type duplicateGroup struct {
removed []int
}
func collapseDuplicates(records []normalizedRecord, documentIndex source.DocumentIndex) ([]dnd.LocationOccurrence, []contracts.Warning) {
func collapseDuplicates(records []normalizedRecord, documentIndex source.DocumentIndex) ([]dnd.LocationOccurrence, []diagnostics.Finding) {
if len(records) == 0 {
return make([]dnd.LocationOccurrence, 0), nil
}
@@ -222,7 +222,7 @@ func collapseDuplicates(records []normalizedRecord, documentIndex source.Documen
output = append(output, cloneOccurrence(record.occurrence))
}
}
warnings := make([]contracts.Warning, 0)
warnings := make([]diagnostics.Finding, 0)
for _, group := range groups {
if len(group.removed) > 0 {
warnings = append(warnings, duplicateWarning(group.retainedIndex, group.removed))
@@ -316,12 +316,12 @@ func sourceRefsLess(order shared.SourceRefOrder, left, right []source.SourceRef)
return len(left) < len(right)
}
func duplicateWarning(retainedIndex int, removed []int) contracts.Warning {
func duplicateWarning(retainedIndex int, removed []int) diagnostics.Finding {
issues := make([]string, len(removed))
for index, removedIndex := range removed {
issues[index] = fmt.Sprintf("removed input index %d", removedIndex)
}
return contracts.Warning{Scope: occurrenceScope(retainedIndex), ReasonCode: ReasonCodeDuplicateCollapsed,
return diagnostics.Finding{Scope: occurrenceScope(retainedIndex), ReasonCode: ReasonCodeDuplicateCollapsed,
Message: diagnostics.Aggregate(fmt.Sprintf("duplicate location occurrence collapsed; retained input index %d", retainedIndex), issues)}
}

View File

@@ -12,7 +12,6 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationregistry"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/identity"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
)
func TestNormalizeCanonicalizesNamesByIDAndClonesInputs(t *testing.T) {
@@ -125,7 +124,7 @@ func TestNormalizerContractsRequiredRegistryAndWarningBounds(t *testing.T) {
t.Fatalf("unbound registry error = %v", err)
}
count := diagnostics.MaxWarnings + 5
count := contracts.MaxDiagnosticSamples + 5
doc := &source.SourceDocument{ID: "session", Units: make([]source.SourceUnit, count)}
input := dnd.LocationOccurrenceList{Occurrences: make([]dnd.LocationOccurrence, count)}
location := registryLocations("The Mill").Locations[0]
@@ -134,7 +133,7 @@ func TestNormalizerContractsRequiredRegistryAndWarningBounds(t *testing.T) {
input.Occurrences[index] = dnd.LocationOccurrence{LocationID: location.ID, Name: "not canonical", Kind: dnd.LocationOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: doc.ID, StartUnitID: count - index, EndUnitID: count - index}}}
}
bounded, err := newNormalizer(t, registryReferences(t, dnd.LocationRegistry{Locations: []dnd.Location{location}})).Normalize(context.Background(), normalizeRequest(input, doc, contracts.ReferenceSet{}))
if err != nil || len(bounded.Warnings) != 0 || len(bounded.Diagnostics) == 0 {
if err != nil || len(bounded.Diagnostics) == 0 {
t.Fatalf("bounded diagnostics = %#v, %v", bounded.Diagnostics, err)
}
}