Move NPC occurrences to their canonical namespace

This commit is contained in:
2026-08-05 19:00:55 +00:00
parent 5e2ccffc0f
commit 2f61118e78
59 changed files with 518 additions and 518 deletions

View File

@@ -1,4 +1,4 @@
// Package invariants validates normalized D&D NPC interaction artifacts.
// Package invariants validates normalized D&D NPC occurrence artifacts.
package invariants
import (
@@ -10,17 +10,17 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcinteractions"
interactionmodel "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcoccurrences"
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
)
const (
Key = "normalize/dnd/npc-interactions/invariants"
Key = "normalize/dnd/npc-occurrences/invariants"
ReasonCode = "invalid_npc_interaction_normalization"
policy = "dnd.npc_interactions.validator.normalized.v1"
policy = "dnd.npc_occurrences.validator.normalized.v1"
)
type Options struct{}
@@ -35,7 +35,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
if len(references) > 1 {
return nil, fmt.Errorf("NPC interaction invariants validator accepts at most one reference set")
return nil, fmt.Errorf("NPC occurrence invariants validator accepts at most one reference set")
}
var referenceSet contracts.ReferenceSet
if len(references) == 1 {
@@ -77,7 +77,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
}
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
if interactionshape.Validate(req.Value) != nil {
if occurrenceshape.Validate(req.Value) != nil {
return contracts.ValidationResult{Approved: true}, nil
}
index := source.NewDocumentIndex(req.Source)
@@ -85,14 +85,14 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
return contracts.ValidationResult{Approved: true}, nil
}
if v == nil || v.npcResolver == nil {
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction invariants validator must not be nil")
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence invariants validator must not be nil")
}
npcRegistry, err := v.npcResolver.Resolve(req.References)
if err != nil {
return contracts.ValidationResult{}, fmt.Errorf("resolve NPC registry: %w", err)
}
if !npcRegistry.Bound() {
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC interaction normalization: NPC registry reference is required"}, nil
return contracts.ValidationResult{Approved: false, ReasonCode: ReasonCode, Message: "invalid NPC occurrence normalization: NPC registry reference is required"}, nil
}
issues := issuesFor(shared.NewSourceRefOrderFromIndex(index), req.Value, npcRegistry)
if len(issues) == 0 {
@@ -101,7 +101,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
return contracts.ValidationResult{
Approved: false,
ReasonCode: ReasonCode,
Message: diagnostics.Aggregate("invalid NPC interaction normalization", issues),
Message: diagnostics.Aggregate("invalid NPC occurrence normalization", issues),
}, nil
}

View File

@@ -16,7 +16,7 @@ import (
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
)
func TestValidatorApprovesCanonicalNormalizedInteractions(t *testing.T) {
func TestValidatorApprovesCanonicalNormalizedOccurrences(t *testing.T) {
references := registryReferences(t, "Aria", "Borin")
result, err := newValidator(t, references).Validate(context.Background(), request(references, normalizedList()))
if err != nil || !result.Approved {
@@ -143,9 +143,9 @@ func normalizedList() dnd.NPCOccurrenceList {
func cloneList(value dnd.NPCOccurrenceList) dnd.NPCOccurrenceList {
copyValue := dnd.NPCOccurrenceList{Occurrences: make([]dnd.NPCOccurrence, len(value.Occurrences))}
for index, interaction := range value.Occurrences {
copyValue.Occurrences[index] = interaction
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), interaction.SourceRefs...)
for index, occurrence := range value.Occurrences {
copyValue.Occurrences[index] = occurrence
copyValue.Occurrences[index].SourceRefs = append([]source.SourceRef(nil), occurrence.SourceRefs...)
}
return copyValue
}

View File

@@ -1,4 +1,4 @@
// Package registry validates D&D NPC interaction names against NPC grounding.
// Package registry validates D&D NPC occurrence names against NPC grounding.
package registry
import (
@@ -10,13 +10,13 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/registry"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
)
const (
Key = "extract/dnd/npc-interactions/registry"
Key = "extract/dnd/npc-occurrences/registry"
ReasonCode = "invalid_npc_interaction_registry"
policy = "dnd.npc_interactions.validator.registry.v1"
policy = "dnd.npc_occurrences.validator.registry.v1"
)
type Options struct{}
@@ -31,7 +31,7 @@ var _ pipeline.CheckpointFingerprintProvider = (*Validator)(nil)
func New(_ Options, references ...contracts.ReferenceSet) (*Validator, error) {
if len(references) > 1 {
return nil, fmt.Errorf("NPC interaction registry validator accepts at most one reference set")
return nil, fmt.Errorf("NPC occurrence registry validator accepts at most one reference set")
}
var referenceSet contracts.ReferenceSet
if len(references) == 1 {
@@ -73,11 +73,11 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
}
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
if interactionshape.Validate(req.Value) != nil {
if occurrenceshape.Validate(req.Value) != nil {
return contracts.ValidationResult{Approved: true}, nil
}
if v == nil || v.npcResolver == nil {
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction registry validator must not be nil")
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence registry validator must not be nil")
}
npcRegistry, err := v.npcResolver.Resolve(req.References)
if err != nil {
@@ -107,7 +107,7 @@ func rejection(issues []string) contracts.ValidationResult {
return contracts.ValidationResult{
Approved: false,
ReasonCode: ReasonCode,
Message: diagnostics.Aggregate("invalid NPC interaction registry", issues),
Message: diagnostics.Aggregate("invalid NPC occurrence registry", issues),
}
}

View File

@@ -1,4 +1,4 @@
// Package shape validates required D&D NPC interaction candidate fields.
// Package shape validates required D&D NPC occurrence candidate fields.
package shape
import (
@@ -13,9 +13,9 @@ import (
)
const (
Key = "extract/dnd/npc-interactions/shape"
Key = "extract/dnd/npc-occurrences/shape"
ReasonCode = "invalid_npc_interaction_shape"
policy = "dnd.npc_interactions.validator.shape.v1"
policy = "dnd.npc_occurrences.validator.shape.v1"
)
type Options struct{}
@@ -45,7 +45,7 @@ func Validate(value dnd.NPCOccurrenceList) error {
if len(issues) == 0 {
return nil
}
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC interaction shape", issues))
return fmt.Errorf("%s", diagnostics.Aggregate("invalid NPC occurrence shape", issues))
}
func issuesFor(value dnd.NPCOccurrenceList) []string {

View File

@@ -1,4 +1,4 @@
// Package sourcerefs validates D&D NPC interaction transcript evidence.
// Package sourcerefs validates D&D NPC occurrence transcript evidence.
package sourcerefs
import (
@@ -10,13 +10,13 @@ import (
"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"
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
)
const (
Key = "extract/dnd/npc-interactions/source_refs"
Key = "extract/dnd/npc-occurrences/source_refs"
ReasonCode = "invalid_npc_interaction_source_refs"
policy = "dnd.npc_interactions.validator.source_refs.v2"
policy = "dnd.npc_occurrences.validator.source_refs.v2"
)
type Options struct{}
@@ -36,9 +36,9 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
if req.Stage == string(pipeline.StageExtract) && req.Chunk == nil {
return contracts.ValidationResult{}, fmt.Errorf("NPC interaction source-reference validator requires the current extraction chunk")
return contracts.ValidationResult{}, fmt.Errorf("NPC occurrence source-reference validator requires the current extraction chunk")
}
if interactionshape.Validate(req.Value) != nil {
if occurrenceshape.Validate(req.Value) != nil {
return contracts.ValidationResult{Approved: true}, nil
}
index := source.NewDocumentIndex(req.Source)
@@ -63,7 +63,7 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
return contracts.ValidationResult{
Approved: false,
ReasonCode: ReasonCode,
Message: diagnostics.Aggregate("invalid NPC interaction source references", issues),
Message: diagnostics.Aggregate("invalid NPC occurrence source references", issues),
}, nil
}

View File

@@ -1,4 +1,4 @@
// Package sourcerelatedness warns about NPC interaction evidence unrelated to its NPC.
// Package sourcerelatedness warns about NPC occurrence evidence unrelated to its NPC.
package sourcerelatedness
import (
@@ -10,14 +10,14 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
interactionshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcinteractions/shape"
occurrenceshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcoccurrences/shape"
)
const (
Key = "extract/dnd/npc-interactions/source_relatedness"
Key = "extract/dnd/npc-occurrences/source_relatedness"
WarningReasonCode = "npc_interaction_not_near_source"
OmittedReasonCode = "npc_interaction_relatedness_warnings_omitted"
policy = "dnd.npc_interactions.validator.source_relatedness.v2"
policy = "dnd.npc_occurrences.validator.source_relatedness.v2"
)
type Options struct{}
@@ -36,7 +36,7 @@ func (v *Validator) CheckpointFingerprints() []pipeline.CheckpointFingerprint {
}
func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationRequest[dnd.NPCOccurrenceList]) (contracts.ValidationResult, error) {
if interactionshape.Validate(req.Value) != nil {
if occurrenceshape.Validate(req.Value) != nil {
return contracts.ValidationResult{Approved: true}, nil
}
resolver, err := shared.NewCitationResolver(req.Source)
@@ -59,12 +59,12 @@ func (v *Validator) Validate(_ context.Context, req contracts.TypedValidationReq
warnings = append(warnings, contracts.Warning{
Scope: fmt.Sprintf("occurrences[%d]", index),
ReasonCode: WarningReasonCode,
Message: fmt.Sprintf("NPC interaction name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
Message: fmt.Sprintf("NPC occurrence name %s was not found in cited source text", diagnostics.Quote(occurrence.Name)),
})
}
return contracts.ValidationResult{
Approved: true,
Warnings: diagnostics.LimitWarnings(warnings, "npc_interactions", OmittedReasonCode),
Warnings: diagnostics.LimitWarnings(warnings, "npc_occurrences", OmittedReasonCode),
}, nil
}

View File

@@ -13,7 +13,7 @@ import (
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
)
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerInteraction(t *testing.T) {
func TestValidatorUsesOnlyCurrentTranscriptAndWarnsOncePerOccurrence(t *testing.T) {
value := dnd.NPCOccurrenceList{Occurrences: []dnd.NPCOccurrence{
{NPCID: "npc:test", Name: "O'Rin Thorn", Kind: dnd.NPCOccurrenceKindDialogue, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 1, EndUnitID: 1}}},
{NPCID: "npc:test", Name: "Missing\nNPC", Kind: dnd.NPCOccurrenceKindMentioned, SourceRefs: []source.SourceRef{{SourceID: "session", StartUnitID: 2, EndUnitID: 2}, {SourceID: "session", StartUnitID: 2, EndUnitID: 2}}},
@@ -53,9 +53,9 @@ func TestValidatorDefersMalformedShapeAndInvalidRanges(t *testing.T) {
func TestValidatorBoundsWarnings(t *testing.T) {
count := diagnostics.MaxWarnings + 5
interactions := make([]dnd.NPCOccurrence, count)
for index := range interactions {
interactions[index] = dnd.NPCOccurrence{
occurrences := make([]dnd.NPCOccurrence, count)
for index := range occurrences {
occurrences[index] = dnd.NPCOccurrence{
NPCID: "npc:test",
Name: "Missing NPC",
Kind: dnd.NPCOccurrenceKindMentioned,
@@ -64,7 +64,7 @@ func TestValidatorBoundsWarnings(t *testing.T) {
}
result, err := New(Options{}).Validate(context.Background(), contracts.TypedValidationRequest[dnd.NPCOccurrenceList]{
Source: &source.SourceDocument{ID: "session", Units: []source.SourceUnit{{ID: 1, Text: "The party waits."}}},
Value: dnd.NPCOccurrenceList{Occurrences: interactions},
Value: dnd.NPCOccurrenceList{Occurrences: occurrences},
})
if err != nil || !result.Approved {
t.Fatalf("Validate() = %#v, %v", result, err)