Classify source relatedness as data-quality advisories

This commit is contained in:
2026-08-27 15:49:53 +00:00
parent 1f1967c8d2
commit ccba2ce3f9
25 changed files with 232 additions and 189 deletions

View File

@@ -1,4 +1,4 @@
// Package sourcerelatedness warns when cited source text does not mention a location.
// Package sourcerelatedness reports advisory findings when cited source text does not mention a location.
package sourcerelatedness
import (
@@ -14,10 +14,9 @@ import (
)
const (
Key = "extract/dnd/location-registry/source_relatedness"
WarningReasonCode = "location_not_near_source"
OmittedReasonCode = "location_relatedness_warnings_omitted"
policy = "dnd.location_registry.validator.source_relatedness.v2"
Key = "extract/dnd/location-registry/source_relatedness"
ReasonCode = "location_not_near_source"
policy = "dnd.location_registry.validator.source_relatedness.v2"
)
type Options struct{}
@@ -57,11 +56,11 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
continue
}
warnings = append(warnings, contracts.Warning{
Scope: fmt.Sprintf("locations[%d]", locationIndex), ReasonCode: WarningReasonCode,
Scope: fmt.Sprintf("locations[%d]", locationIndex), ReasonCode: ReasonCode,
Message: fmt.Sprintf("Location %s was not found in cited source text", diagnostics.Quote(location.Name)),
})
}
return contracts.ValidationResult{Approved: true, Warnings: diagnostics.LimitWarnings(warnings, "locations", OmittedReasonCode)}, nil
return diagnostics.DataQualityResult(warnings)
}
func Spec() pipeline.ValidatorSpec {

View File

@@ -10,21 +10,20 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
)
func TestValidatorUsesOnlyCitedTranscriptText(t *testing.T) {
value := dnd.LocationRegistry{Locations: []dnd.Location{{ID: "candidate", Name: "O'Rin's Gate", SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}}}}
doc := &source.SourceDocument{ID: "session", Kind: "transcript", Format: "application/json", Digest: "sha256:session", Units: []source.SourceUnit{{ID: 1, Kind: "message", Text: "The party enters orins gate."}, {ID: 2, Kind: "message", Text: "Unrelated location."}}}
result, err := New(Options{}).Validate(context.Background(), request(doc, value, contracts.ReferenceSet{}))
if err != nil || !result.Approved || len(result.Warnings) != 0 {
if err != nil || !result.Approved || len(result.Diagnostics) != 0 {
t.Fatalf("cited match = %#v, %v; want approval without warnings", result, err)
}
value.Locations[0].Name = "Glossary Keep"
before := value
references := contracts.ReferenceSet{Slots: map[string]contracts.ResolvedReferenceSlot{"glossary": {Items: []contracts.ReferenceItem{{Content: []byte("Glossary Keep")}}}}}
result, err = New(Options{}).Validate(context.Background(), request(doc, value, references))
if err != nil || !result.Approved || len(result.Warnings) != 1 || result.Warnings[0].ReasonCode != WarningReasonCode || !strings.Contains(result.Warnings[0].Message, "Glossary Keep") || !reflect.DeepEqual(value, before) {
if err != nil || !result.Approved || len(result.Warnings) != 0 || len(result.Diagnostics) != 1 || result.Diagnostics[0].ReasonCode != ReasonCode || !strings.Contains(result.Diagnostics[0].Samples[0].Message, "Glossary Keep") || !reflect.DeepEqual(value, before) {
t.Fatalf("reference-only match = %#v, %v; want advisory warning", result, err)
}
}
@@ -32,12 +31,12 @@ func TestValidatorUsesOnlyCitedTranscriptText(t *testing.T) {
func TestValidatorDefersMalformedOrUnreadableCitations(t *testing.T) {
invalid := dnd.LocationRegistry{Locations: []dnd.Location{{Name: "Missing"}}}
result, err := New(Options{}).Validate(context.Background(), request(document(), invalid, contracts.ReferenceSet{}))
if err != nil || !result.Approved || len(result.Warnings) != 0 {
if err != nil || !result.Approved || len(result.Diagnostics) != 0 {
t.Fatalf("shape deferral = %#v, %v", result, err)
}
value := dnd.LocationRegistry{Locations: []dnd.Location{{ID: "candidate", Name: "Missing", SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 99, EndUnitID: 99}}}}}
result, err = New(Options{}).Validate(context.Background(), request(document(), value, contracts.ReferenceSet{}))
if err != nil || !result.Approved || len(result.Warnings) != 0 {
if err != nil || !result.Approved || len(result.Diagnostics) != 0 {
t.Fatalf("unreadable citation = %#v, %v", result, err)
}
}
@@ -56,13 +55,13 @@ func TestValidatorRegisters(t *testing.T) {
}
func TestValidatorBoundsWarnings(t *testing.T) {
locations := make([]dnd.Location, diagnostics.MaxWarnings+1)
locations := make([]dnd.Location, contracts.MaxDiagnosticSamples+2)
for index := range locations {
locations[index] = dnd.Location{ID: "candidate", Name: "Missing", SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}}
}
result, err := New(Options{}).Validate(context.Background(), request(document(), dnd.LocationRegistry{Locations: locations}, contracts.ReferenceSet{}))
if err != nil || !result.Approved || len(result.Warnings) != diagnostics.MaxWarnings || result.Warnings[len(result.Warnings)-1].ReasonCode != OmittedReasonCode {
t.Fatalf("bounded warnings = %#v, %v", result, err)
if err != nil || !result.Approved || len(result.Warnings) != 0 || len(result.Diagnostics) != 1 || result.Diagnostics[0].OccurrenceCount != len(locations) || len(result.Diagnostics[0].Samples) != contracts.MaxDiagnosticSamples {
t.Fatalf("bounded advisories = %#v, %v", result, err)
}
}