Harden diagnostic handling and warning presentation
This commit is contained in:
@@ -126,6 +126,9 @@ func (candidate ModelCandidate) Validate() error {
|
||||
}
|
||||
|
||||
func ValidateValidationResult(result ValidationResult) error {
|
||||
if err := ValidateProducerDiagnostics(result.Diagnostics); err != nil {
|
||||
return fmt.Errorf("validation diagnostics: %w", err)
|
||||
}
|
||||
if !result.Approved && result.ReasonCode == "" {
|
||||
return errors.New("validation rejection reason code must not be empty")
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ func TestCorrectionContractsRejectInvalidContent(t *testing.T) {
|
||||
{"oversized correction guidance", func() error {
|
||||
return ValidateValidationResult(ValidationResult{ReasonCode: "invalid", CorrectionGuidance: tooLongValidationGuidance})
|
||||
}},
|
||||
{"invalid diagnostics", func() error {
|
||||
return ValidateValidationResult(ValidationResult{Approved: true, Diagnostics: []ProducerDiagnostic{{}}})
|
||||
}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if err := test.call(); err == nil {
|
||||
|
||||
@@ -102,6 +102,16 @@ type DiagnosticCollection struct {
|
||||
UnrepresentedOccurrenceCount int `json:"unrepresented_occurrence_count"`
|
||||
}
|
||||
|
||||
// DiagnosticProjection is the validated warning/non-warning view used by
|
||||
// durable and presentation boundaries. Occurrence totals are checked before
|
||||
// they leave the framework contract.
|
||||
type DiagnosticProjection struct {
|
||||
Warnings []DiagnosticGroup
|
||||
Diagnostics []DiagnosticGroup
|
||||
WarningOccurrenceCount int
|
||||
DiagnosticOccurrenceCount int
|
||||
}
|
||||
|
||||
// Validate checks a producer-local diagnostic against the public safety and
|
||||
// classification contract.
|
||||
func (diagnostic ProducerDiagnostic) Validate() error {
|
||||
@@ -192,6 +202,41 @@ func (collection DiagnosticCollection) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProjectDiagnosticCollection validates, partitions, and totals one finalized
|
||||
// collection. Unrepresented occurrences belong to the non-warning projection.
|
||||
func ProjectDiagnosticCollection(collection DiagnosticCollection) (DiagnosticProjection, error) {
|
||||
if err := collection.Validate(); err != nil {
|
||||
return DiagnosticProjection{}, err
|
||||
}
|
||||
projection := DiagnosticProjection{
|
||||
Warnings: make([]DiagnosticGroup, 0),
|
||||
Diagnostics: make([]DiagnosticGroup, 0),
|
||||
}
|
||||
for _, group := range collection.Groups {
|
||||
if group.Disposition == DiagnosticDispositionWarning {
|
||||
count, err := addDiagnosticOccurrences(projection.WarningOccurrenceCount, group.OccurrenceCount)
|
||||
if err != nil {
|
||||
return DiagnosticProjection{}, fmt.Errorf("warning occurrences: %w", err)
|
||||
}
|
||||
projection.WarningOccurrenceCount = count
|
||||
projection.Warnings = append(projection.Warnings, cloneDiagnosticGroup(group))
|
||||
continue
|
||||
}
|
||||
count, err := addDiagnosticOccurrences(projection.DiagnosticOccurrenceCount, group.OccurrenceCount)
|
||||
if err != nil {
|
||||
return DiagnosticProjection{}, fmt.Errorf("diagnostic occurrences: %w", err)
|
||||
}
|
||||
projection.DiagnosticOccurrenceCount = count
|
||||
projection.Diagnostics = append(projection.Diagnostics, cloneDiagnosticGroup(group))
|
||||
}
|
||||
count, err := addDiagnosticOccurrences(projection.DiagnosticOccurrenceCount, collection.UnrepresentedOccurrenceCount)
|
||||
if err != nil {
|
||||
return DiagnosticProjection{}, fmt.Errorf("diagnostic occurrences: %w", err)
|
||||
}
|
||||
projection.DiagnosticOccurrenceCount = count
|
||||
return projection, nil
|
||||
}
|
||||
|
||||
// CloneProducerDiagnostics returns independent diagnostic slice ownership.
|
||||
func CloneProducerDiagnostics(diagnostics []ProducerDiagnostic) []ProducerDiagnostic {
|
||||
if len(diagnostics) == 0 {
|
||||
@@ -305,6 +350,13 @@ func cloneDiagnosticGroup(group DiagnosticGroup) DiagnosticGroup {
|
||||
return group
|
||||
}
|
||||
|
||||
func addDiagnosticOccurrences(current, incoming int) (int, error) {
|
||||
if incoming > int(^uint(0)>>1)-current {
|
||||
return 0, errors.New("occurrence count overflow")
|
||||
}
|
||||
return current + incoming, nil
|
||||
}
|
||||
|
||||
func cloneDiagnosticSamples(samples []DiagnosticSample) []DiagnosticSample {
|
||||
if len(samples) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -146,6 +146,47 @@ func TestCloneDiagnosticCollectionOwnsGroupsAndChunkIndex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectDiagnosticCollectionPartitionsAndChecksTotals(t *testing.T) {
|
||||
warning := validDiagnosticGroup(DiagnosticDispositionWarning, DiagnosticCategoryFallback, "fallback", 2)
|
||||
diagnostic := validDiagnosticGroup(DiagnosticDispositionAdvisory, DiagnosticCategoryDataQuality, "quality", 3)
|
||||
collection := DiagnosticCollection{
|
||||
Groups: []DiagnosticGroup{warning, diagnostic},
|
||||
Truncated: true,
|
||||
UnrepresentedOccurrenceCount: 4,
|
||||
}
|
||||
projection, err := ProjectDiagnosticCollection(collection)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(projection.Warnings) != 1 || len(projection.Diagnostics) != 1 || projection.WarningOccurrenceCount != 2 || projection.DiagnosticOccurrenceCount != 7 {
|
||||
t.Fatalf("projection = %#v, want partitioned exact totals", projection)
|
||||
}
|
||||
collection.Groups[0].Samples[0].Message = "mutated"
|
||||
if projection.Warnings[0].Samples[0].Message != "message" {
|
||||
t.Fatal("projection retained caller-owned sample storage")
|
||||
}
|
||||
|
||||
overflow := DiagnosticCollection{Groups: []DiagnosticGroup{
|
||||
validDiagnosticGroup(DiagnosticDispositionWarning, DiagnosticCategoryFallback, "first", int(^uint(0)>>1)),
|
||||
validDiagnosticGroup(DiagnosticDispositionWarning, DiagnosticCategoryFallback, "second", 1),
|
||||
}}
|
||||
if _, err := ProjectDiagnosticCollection(overflow); err == nil {
|
||||
t.Fatal("ProjectDiagnosticCollection() overflow error = nil")
|
||||
}
|
||||
}
|
||||
|
||||
func validDiagnosticGroup(disposition DiagnosticDisposition, category DiagnosticCategory, reason string, occurrences int) DiagnosticGroup {
|
||||
return DiagnosticGroup{
|
||||
Disposition: disposition,
|
||||
Category: category,
|
||||
ReasonCode: reason,
|
||||
Origin: DiagnosticOrigin{Stage: DiagnosticOriginStageNormalize, StepID: "step", LaneID: "lane", ModuleKey: "module"},
|
||||
OccurrenceCount: occurrences,
|
||||
Samples: []DiagnosticSample{{Scope: "scope", Message: "message"}},
|
||||
OmittedSampleCount: occurrences - 1,
|
||||
}
|
||||
}
|
||||
|
||||
func validProducerDiagnostic() ProducerDiagnostic {
|
||||
return ProducerDiagnostic{
|
||||
Disposition: DiagnosticDispositionWarning,
|
||||
|
||||
Reference in New Issue
Block a user