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

@@ -1,5 +1,5 @@
// Package diagnostics provides bounded, safe text for deterministic D&D
// decisions and warnings.
// decisions and findings.
package diagnostics
import (
@@ -13,15 +13,22 @@ import (
const (
MaxIssues = 20
MaxWarnings = 20
MaxDisplayedRunes = 128
MaxMessageBytes = 4096
)
// Finding is a local, ungrouped D&D diagnostic fact. Producers convert these
// to bounded framework diagnostics at their result boundary.
type Finding struct {
Scope string
ReasonCode string
Message string
}
// DataQualityResult converts accepted source-quality findings into bounded,
// locally grouped advisories. These findings do not indicate process
// degradation.
func DataQualityResult(findings []contracts.Warning) (contracts.ValidationResult, error) {
func DataQualityResult(findings []Finding) (contracts.ValidationResult, error) {
diagnostics, err := Collect(findings, contracts.DiagnosticDispositionAdvisory, contracts.DiagnosticCategoryDataQuality)
if err != nil {
return contracts.ValidationResult{}, err
@@ -31,7 +38,7 @@ func DataQualityResult(findings []contracts.Warning) (contracts.ValidationResult
// Collect converts individual deterministic findings into bounded,
// occurrence-grouped producer diagnostics with one consistent classification.
func Collect(findings []contracts.Warning, disposition contracts.DiagnosticDisposition, category contracts.DiagnosticCategory) ([]contracts.ProducerDiagnostic, error) {
func Collect(findings []Finding, disposition contracts.DiagnosticDisposition, category contracts.DiagnosticCategory) ([]contracts.ProducerDiagnostic, error) {
collector := frameworkdiagnostics.NewCollector()
for _, finding := range findings {
if err := collector.Add(contracts.ProducerDiagnostic{
@@ -49,13 +56,13 @@ func Collect(findings []contracts.Warning, disposition contracts.DiagnosticDispo
// NormalizationDiagnostics classifies deterministic normalization findings as
// observations, except for explicitly identified unresolved-quality findings.
func NormalizationDiagnostics(findings []contracts.Warning, advisoryReasonCodes ...string) ([]contracts.ProducerDiagnostic, error) {
func NormalizationDiagnostics(findings []Finding, advisoryReasonCodes ...string) ([]contracts.ProducerDiagnostic, error) {
advisoryReasons := make(map[string]struct{}, len(advisoryReasonCodes))
for _, reasonCode := range advisoryReasonCodes {
advisoryReasons[reasonCode] = struct{}{}
}
observations := make([]contracts.Warning, 0, len(findings))
advisories := make([]contracts.Warning, 0)
observations := make([]Finding, 0, len(findings))
advisories := make([]Finding, 0)
for _, finding := range findings {
if _, advisory := advisoryReasons[finding.ReasonCode]; advisory {
advisories = append(advisories, finding)
@@ -97,28 +104,6 @@ func Aggregate(prefix string, issues []string) string {
return aggregateMessage(prefix, displayed, len(issues)-len(displayed))
}
// LimitWarnings returns at most MaxWarnings warnings, reserving the final
// position for a deterministic omission summary when truncation is required.
func LimitWarnings(warnings []contracts.Warning, scope, reasonCode string) []contracts.Warning {
if warnings == nil {
return nil
}
if len(warnings) <= MaxWarnings {
bounded := make([]contracts.Warning, len(warnings))
copy(bounded, warnings)
return bounded
}
displayed := MaxWarnings - 1
bounded := make([]contracts.Warning, displayed, MaxWarnings)
copy(bounded, warnings[:displayed])
bounded = append(bounded, contracts.Warning{
Scope: scope,
ReasonCode: reasonCode,
Message: fmt.Sprintf("%d additional warning(s) omitted", len(warnings)-displayed),
})
return bounded
}
func aggregateMessage(prefix string, issues []string, omitted int) string {
message := prefix + ": " + strings.Join(issues, ", ")
if omitted > 0 {