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 scene prose has no lexical grounding.
// Package sourcerelatedness reports advisory findings when scene prose has no lexical grounding.
package sourcerelatedness
import (
@@ -16,10 +16,9 @@ import (
)
const (
Key = "extract/dnd/scene-descriptions/source_relatedness"
WarningReasonCode = "scene_description_not_near_source"
OmittedReasonCode = "scene_description_relatedness_warnings_omitted"
policy = "dnd.scene_descriptions.validator.source_relatedness.v1"
Key = "extract/dnd/scene-descriptions/source_relatedness"
ReasonCode = "scene_description_not_near_source"
policy = "dnd.scene_descriptions.validator.source_relatedness.v1"
)
var stopwords = map[string]struct{}{
@@ -66,10 +65,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
warnings = append(warnings, warning(index, "summary"))
}
}
return contracts.ValidationResult{
Approved: true,
Warnings: diagnostics.LimitWarnings(warnings, "scenes", OmittedReasonCode),
}, nil
return diagnostics.DataQualityResult(warnings)
}
func tokenSet(value string) map[string]struct{} {
@@ -105,7 +101,7 @@ func significant(token string) bool {
func warning(index int, field string) contracts.Warning {
return contracts.Warning{
Scope: fmt.Sprintf("scenes[%d].%s", index, field),
ReasonCode: WarningReasonCode,
ReasonCode: ReasonCode,
Message: "scene description " + field + " has no significant token in cited source text",
}
}

View File

@@ -24,16 +24,16 @@ func TestValidatorWarnsIndependentlyForUngroundedTitleAndSummary(t *testing.T) {
t.Fatalf("Validate() = %#v, %v", result, err)
}
wantScopes := []string{"scenes[1].title", "scenes[1].summary", "scenes[2].title", "scenes[2].summary"}
if len(result.Warnings) != len(wantScopes) {
t.Fatalf("warnings = %#v, want %d", result.Warnings, len(wantScopes))
if len(result.Warnings) != 0 || len(result.Diagnostics) != 1 || result.Diagnostics[0].OccurrenceCount != len(wantScopes) {
t.Fatalf("diagnostics = %#v, want %d occurrences", result.Diagnostics, len(wantScopes))
}
for index, scope := range wantScopes {
item := result.Warnings[index]
if item.Scope != scope || item.ReasonCode != WarningReasonCode {
t.Fatalf("warning[%d] = %#v, want scope %q", index, item, scope)
for index, scope := range wantScopes[:contracts.MaxDiagnosticSamples] {
item := result.Diagnostics[0].Samples[index]
if item.Scope != scope || result.Diagnostics[0].ReasonCode != ReasonCode {
t.Fatalf("sample[%d] = %#v, want scope %q", index, item, scope)
}
if len(item.Message) > 4096 || strings.Contains(item.Message, doc.Units[0].Text) {
t.Fatalf("warning[%d] is not safely bounded: %#v", index, item)
t.Fatalf("sample[%d] is not safely bounded: %#v", index, item)
}
}
}
@@ -47,11 +47,11 @@ func TestValidatorUsesTranscriptOnlyAndDefersInvalidInputs(t *testing.T) {
"glossary": {Items: []contracts.ReferenceItem{{Content: []byte("Greencloak: title")}}},
}},
})
if err != nil || len(result.Warnings) != 2 {
t.Fatalf("Validate() = %#v, %v; want transcript-only warnings", result, err)
if err != nil || len(result.Diagnostics) != 1 || result.Diagnostics[0].OccurrenceCount != 2 {
t.Fatalf("Validate() = %#v, %v; want transcript-only advisories", result, err)
}
malformed, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.SceneDescriptionList]{Source: doc, Value: dnd.SceneDescriptionList{}})
if err != nil || !malformed.Approved || len(malformed.Warnings) != 0 {
if err != nil || !malformed.Approved || len(malformed.Diagnostics) != 0 {
t.Fatalf("malformed Validate() = %#v, %v; want deferral", malformed, err)
}
@@ -59,7 +59,7 @@ func TestValidatorUsesTranscriptOnlyAndDefersInvalidInputs(t *testing.T) {
invalidScene.SourceRef.StartUnitID = 99
invalidSource := dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{invalidScene}}
deferred, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.SceneDescriptionList]{Source: doc, Value: invalidSource})
if err != nil || !deferred.Approved || len(deferred.Warnings) != 0 {
if err != nil || !deferred.Approved || len(deferred.Diagnostics) != 0 {
t.Fatalf("invalid-source Validate() = %#v, %v; want deferral", deferred, err)
}
}