2026-08-04 00:33:39 +00:00
// Package sourcerefs validates location occurrence transcript evidence.
package sourcerefs
import (
"context"
"fmt"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"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"
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/locationoccurrences/shape"
)
const (
Key = "extract/dnd/location-occurrences/source_refs"
ReasonCode = "invalid_location_occurrence_source_refs"
policy = "dnd.location_occurrences.validator.source_refs.v1"
)
type Options struct { }
type Validator struct { }
var _ contracts . TypedValidator [ dnd . LocationOccurrenceList ] = ( * Validator ) ( nil )
var _ pipeline . CheckpointFingerprintProvider = ( * Validator ) ( nil )
func New ( Options ) * Validator { return & Validator { } }
func ( v * Validator ) Name ( ) string { return Key }
func ( v * Validator ) ExecutionClass ( ) contracts . ExecutionClass {
return contracts . ExecutionClassDeterministic
}
func ( v * Validator ) CheckpointFingerprints ( ) [ ] pipeline . CheckpointFingerprint {
return [ ] pipeline . CheckpointFingerprint { { Name : "policy" , Value : policy } }
}
func ( v * Validator ) Validate ( _ context . Context , req contracts . TypedValidationRequest [ dnd . LocationOccurrenceList ] ) ( contracts . ValidationResult , error ) {
if req . Stage == string ( pipeline . StageExtract ) && req . Chunk == nil {
return contracts . ValidationResult { } , fmt . Errorf ( "location occurrence source-reference validator requires the current extraction chunk" )
}
if occurrenceshape . Validate ( req . Value ) != nil {
return contracts . ValidationResult { Approved : true } , nil
}
index := source . NewDocumentIndex ( req . Source )
issues := make ( [ ] string , 0 )
for occurrenceIndex , occurrence := range req . Value . Occurrences {
for refIndex , ref := range occurrence . SourceRefs {
if err := index . ValidateRef ( ref ) ; err != nil {
issues = append ( issues , fmt . Sprintf ( "occurrences[%d].source_refs[%d]: %s" , occurrenceIndex , refIndex , diagnostics . Truncate ( err . Error ( ) ) ) )
continue
}
if req . Stage == string ( pipeline . StageExtract ) && ! chunkContainsRef ( req . Chunk , ref ) {
issues = append ( issues , fmt . Sprintf ( "occurrences[%d].source_refs[%d]: source reference is outside the current extraction chunk" , occurrenceIndex , refIndex ) )
}
}
}
if len ( issues ) == 0 {
return contracts . ValidationResult { Approved : true } , nil
}
2026-08-27 11:50:37 +00:00
return contracts . ValidationResult { Approved : false , ReasonCode : ReasonCode , Message : diagnostics . Aggregate ( "invalid location occurrence source references" , issues ) , CorrectionGuidance : "Return location occurrences whose source references identify valid transcript ranges within the supplied extraction chunk and directly support each occurrence." } , nil
2026-08-04 00:33:39 +00:00
}
func chunkContainsRef ( chunk * source . Chunk , ref source . SourceRef ) bool {
if chunk == nil || ref . SourceID != chunk . SourceID {
return false
}
startFound , endFound := false , false
for _ , unit := range chunk . Units {
startFound = startFound || unit . ID == ref . StartUnitID
endFound = endFound || unit . ID == ref . EndUnitID
}
return startFound && endFound
}
func Spec ( ) pipeline . ValidatorSpec {
return pipeline . ValidatorSpec { Key : Key , ExecutionClass : contracts . ExecutionClassDeterministic }
}
func Register ( registry * pipeline . ValidatorRegistry ) error {
return pipeline . RegisterTypedValidatorBuilder ( registry , dnd . LocationOccurrenceListKind , Spec ( ) , validateOptions , func ( request pipeline . BuildRequest ) ( contracts . TypedValidator [ dnd . LocationOccurrenceList ] , error ) {
options , err := DecodeOptions ( request . Options )
if err != nil {
return nil , err
}
return New ( options ) , nil
} )
}
func DecodeOptions ( options map [ string ] any ) ( Options , error ) {
if err := pipeline . RejectUnknownOptions ( options ) ; err != nil {
return Options { } , err
}
return Options { } , nil
}
func validateOptions ( options map [ string ] any ) error { _ , err := DecodeOptions ( options ) ; return err }