2026-08-05 20:17:20 +00:00
// Package identity validates the stable identity invariants of D&D NPC registries.
2026-07-21 02:36:59 +00:00
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/npcs/identity"
2026-07-21 04:34:56 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
2026-08-05 18:38:22 +00:00
npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcregistry/shape"
2026-07-21 02:36:59 +00:00
)
const (
2026-08-05 18:38:22 +00:00
Key = "normalize/dnd/npc-registry/identity"
2026-07-21 02:36:59 +00:00
ReasonCode = "invalid_npc_identity"
policy = domainidentity . Policy
)
type Options struct { }
type Validator struct { }
2026-08-05 18:25:04 +00:00
var _ contracts . TypedValidator [ dnd . NPCRegistry ] = ( * Validator ) ( nil )
2026-07-21 02:36:59 +00:00
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 } }
}
2026-08-05 18:25:04 +00:00
func ( v * Validator ) Validate ( _ context . Context , req contracts . TypedValidationRequest [ dnd . NPCRegistry ] ) ( contracts . ValidationResult , error ) {
2026-07-21 02:36:59 +00:00
if err := npcshape . Validate ( req . Value ) ; err != nil {
return contracts . ValidationResult { Approved : true } , nil
}
identityIssues := domainidentity . ValidateRegistry ( req . Value . NPCs )
if len ( identityIssues ) == 0 {
return contracts . ValidationResult { Approved : true } , nil
}
issues := make ( [ ] string , len ( identityIssues ) )
for index , issue := range identityIssues {
location := fmt . Sprintf ( "npcs[%d]" , issue . RecordIndex )
issues [ index ] = fmt . Sprintf ( "%s %s: %s" , location , issue . Code , diagnostics . Quote ( issue . Value ) )
}
return contracts . ValidationResult {
2026-08-27 11:50:37 +00:00
Approved : false ,
ReasonCode : ReasonCode ,
Message : diagnostics . Aggregate ( "invalid NPC identity" , issues ) ,
CorrectionGuidance : "Return one canonical registry entry per distinct properly named NPC, combining duplicate mentions under the same transcript-supported name." ,
2026-07-21 02:36:59 +00:00
} , nil
}
func Spec ( ) pipeline . ValidatorSpec {
return pipeline . ValidatorSpec { Key : Key , ExecutionClass : contracts . ExecutionClassDeterministic }
}
func Register ( registry * pipeline . ValidatorRegistry ) error {
2026-08-05 18:25:04 +00:00
return pipeline . RegisterTypedValidatorBuilder ( registry , dnd . NPCRegistryKind , Spec ( ) , validateOptions , func ( request pipeline . BuildRequest ) ( contracts . TypedValidator [ dnd . NPCRegistry ] , error ) {
2026-07-21 02:36:59 +00:00
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 }