2026-08-05 19:39:06 +00:00
// Package identity validates stable D&D item-type identity after normalization.
package identity
import (
"context"
"fmt"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
domainidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/items/identity"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
itemshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/itemregistry/shape"
)
const (
2026-08-29 01:24:45 +00:00
Key = "normalize/dnd/item-registry/identity"
ReasonCode = "invalid_item_identity"
policy = domainidentity . Policy
correctionPolicy = "dnd.item_registry.validator.identity.v2"
2026-08-05 19:39:06 +00:00
)
type Options struct { }
type Validator struct { }
var _ contracts . TypedValidator [ dnd . ItemRegistry ] = ( * 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 {
2026-08-29 01:24:45 +00:00
return [ ] pipeline . CheckpointFingerprint { { Name : "policy" , Value : policy } , { Name : "correction_policy" , Value : correctionPolicy } }
2026-08-05 19:39:06 +00:00
}
func ( v * Validator ) Validate ( _ context . Context , req contracts . TypedValidationRequest [ dnd . ItemRegistry ] ) ( contracts . ValidationResult , error ) {
if err := itemshape . Validate ( req . Value ) ; err != nil {
return contracts . ValidationResult { Approved : true } , nil
}
identityIssues := domainidentity . ValidateList ( req . Value )
if len ( identityIssues ) == 0 {
return contracts . ValidationResult { Approved : true } , nil
}
issues := make ( [ ] string , len ( identityIssues ) )
2026-08-29 01:24:45 +00:00
var corrections diagnostics . Corrections
2026-08-05 19:39:06 +00:00
for index , issue := range identityIssues {
issues [ index ] = fmt . Sprintf ( "items[%d] %s: %s" , issue . RecordIndex , issue . Code , diagnostics . Quote ( issue . Value ) )
2026-08-29 01:24:45 +00:00
if issue . RecordIndex < 0 || issue . RecordIndex >= len ( req . Value . Items ) {
continue
}
item := req . Value . Items [ issue . RecordIndex ]
corrections . Add ( string ( issue . Code ) , itemIdentityCorrection ( issue . Code ) , fmt . Sprintf ( "Affected item %s %s." , diagnostics . Quote ( item . Name ) , diagnostics . SourceRange ( item . SourceRefs ) ) )
}
return contracts . ValidationResult { Approved : false , ReasonCode : ReasonCode , Message : diagnostics . Aggregate ( "invalid item identity" , issues ) , CorrectionGuidance : corrections . Guidance ( "Correct the duplicate-group proposals and return the complete replacement proposal response" ) } , nil
}
func itemIdentityCorrection ( code domainidentity . IssueCode ) string {
switch code {
case domainidentity . IssueEmptyCanonicalName :
return "Select a canonical proposal member with a nonblank, transcript-supported item name."
case domainidentity . IssueDuplicateCanonical :
return "Put duplicate mentions of the same item in one proposal group and select one transcript-supported canonical member."
case domainidentity . IssueInvalidID , domainidentity . IssueIDMismatch :
return "Revise the proposal so its canonical item member has a valid transcript-supported name; Notarius derives durable identity without model input."
case domainidentity . IssueDuplicateID :
return "Do not use proposals that collapse distinct items into one canonical identity; group only records that describe the same item."
default :
return "Revise the duplicate-group proposal so every canonical item is transcript-supported and distinct."
2026-08-05 19:39:06 +00:00
}
}
func Spec ( ) pipeline . ValidatorSpec {
return pipeline . ValidatorSpec { Key : Key , ExecutionClass : contracts . ExecutionClassDeterministic }
}
func Register ( registry * pipeline . ValidatorRegistry ) error {
return pipeline . RegisterTypedValidatorBuilder ( registry , dnd . ItemRegistryKind , Spec ( ) , validateOptions , func ( request pipeline . BuildRequest ) ( contracts . TypedValidator [ dnd . ItemRegistry ] , 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 }