// Package register composes the production D&D module family. package register import ( "fmt" "gitea.maximumdirect.net/eric/notarius/internal/core/source" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/chunk/scenes" combatcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/combatturns" npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcs" spellcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/spells" combatextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/combatturns" npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs" "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells" combatnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/combatturns" npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcs" spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells" combatinvariants "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/combatturns/invariants" combatshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/combatturns/shape" combatsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/combatturns/source_refs" combatrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/combatturns/source_relatedness" npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcs/identity" npcshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcs/shape" npcsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcs/source_refs" npcrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/npcs/source_relatedness" spellcatalog "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/spells/catalog" spellshape "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/spells/shape" spellsourcerefs "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/spells/source_refs" spellrelatedness "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/validate/spells/source_relatedness" "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/merge/appendorder" "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/normalize/noop" alwaysaccept "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/validate/always_accept" alwaysreject "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/validate/always_reject" validjson "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/validate/valid_json" validjsonschema "gitea.maximumdirect.net/eric/notarius/internal/modules/generic/validate/valid_json_schema" ) // Register adds all production D&D modules, validators, policy, and assets. func Register(registries pipeline.Registries, assets *llm.AssetRegistry) error { if err := validateRegistries(registries, assets); err != nil { return err } codec := spellcodec.New() registrations := []struct { name string register func() error }{ {name: "spells codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, codec) }}, {name: "npcs codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, npccodec.New()) }}, {name: "combat turns codec", register: func() error { return pipeline.RegisterArtifactCodec(registries.ArtifactCodecs, combatcodec.New()) }}, {name: "scenes chunker", register: func() error { return scenes.Register(registries.Chunkers) }}, {name: "spells extractor", register: func() error { return spells.Register(registries.Extractors) }}, {name: "npcs extractor", register: func() error { return npcextract.Register(registries.Extractors) }}, {name: "combat turns extractor", register: func() error { return combatextract.Register(registries.Extractors) }}, {name: "spell-list appendorder merger", register: func() error { return appendorder.RegisterTyped(registries.Mergers, dnd.SpellListKind, appendSpellLists) }}, {name: "npc-list appendorder merger", register: func() error { return appendorder.RegisterTyped(registries.Mergers, dnd.NPCListKind, appendNPCLists) }}, {name: "combat-turn-list appendorder merger", register: func() error { return appendorder.RegisterTyped(registries.Mergers, dnd.CombatTurnListKind, appendCombatTurnLists) }}, {name: "spells normalizer", register: func() error { return spellnormalize.Register(registries.Normalizers) }}, {name: "npcs normalizer", register: func() error { return npcnormalize.Register(registries.Normalizers) }}, {name: "combat turns normalizer", register: func() error { return combatnormalize.Register(registries.Normalizers) }}, {name: "spell-list noop normalizer", register: func() error { return noop.RegisterTyped[dnd.SpellList](registries.Normalizers, dnd.SpellListKind) }}, {name: "npc-list noop normalizer", register: func() error { return noop.RegisterTyped[dnd.NPCList](registries.Normalizers, dnd.NPCListKind) }}, {name: "combat-turn-list noop normalizer", register: func() error { return noop.RegisterTyped[dnd.CombatTurnList](registries.Normalizers, dnd.CombatTurnListKind) }}, {name: "spell shape validator", register: func() error { return spellshape.Register(registries.Validators) }}, {name: "spell catalog validator", register: func() error { return spellcatalog.Register(registries.Validators) }}, {name: "spell source references validator", register: func() error { return spellsourcerefs.Register(registries.Validators) }}, {name: "spell source relatedness validator", register: func() error { return spellrelatedness.Register(registries.Validators) }}, {name: "npc shape validator", register: func() error { return npcshape.Register(registries.Validators) }}, {name: "npc identity validator", register: func() error { return npcidentity.Register(registries.Validators) }}, {name: "npc source references validator", register: func() error { return npcsourcerefs.Register(registries.Validators) }}, {name: "npc source relatedness validator", register: func() error { return npcrelatedness.Register(registries.Validators) }}, {name: "combat shape validator", register: func() error { return combatshape.Register(registries.Validators) }}, {name: "combat source references validator", register: func() error { return combatsourcerefs.Register(registries.Validators) }}, {name: "combat source relatedness validator", register: func() error { return combatrelatedness.Register(registries.Validators) }}, {name: "combat normalized invariants validator", register: func() error { return combatinvariants.Register(registries.Validators) }}, {name: "spell-list always accept validator", register: func() error { return alwaysaccept.RegisterTyped[dnd.SpellList](registries.Validators, dnd.SpellListKind) }}, {name: "spell-list always reject validator", register: func() error { return alwaysreject.RegisterTyped[dnd.SpellList](registries.Validators, dnd.SpellListKind) }}, {name: "npc-list always accept validator", register: func() error { return alwaysaccept.RegisterTyped[dnd.NPCList](registries.Validators, dnd.NPCListKind) }}, {name: "npc-list always reject validator", register: func() error { return alwaysreject.RegisterTyped[dnd.NPCList](registries.Validators, dnd.NPCListKind) }}, {name: "combat-turn-list always accept validator", register: func() error { return alwaysaccept.RegisterTyped[dnd.CombatTurnList](registries.Validators, dnd.CombatTurnListKind) }}, {name: "combat-turn-list always reject validator", register: func() error { return alwaysreject.RegisterTyped[dnd.CombatTurnList](registries.Validators, dnd.CombatTurnListKind) }}, {name: "scenes prompt assets", register: func() error { return scenes.RegisterPromptAssets(assets) }}, {name: "spells prompt assets", register: func() error { return spells.RegisterPromptAssets(assets) }}, {name: "npcs prompt assets", register: func() error { return npcextract.RegisterPromptAssets(assets) }}, {name: "combat turns prompt assets", register: func() error { return combatextract.RegisterPromptAssets(assets) }}, } for _, registration := range registrations { if err := registration.register(); err != nil { return fmt.Errorf("register dnd %s: %w", registration.name, err) } } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageExtract, Module: spells.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(spellshape.Key), pipeline.Binding(spellcatalog.Key), pipeline.Binding(spellsourcerefs.Key), pipeline.Binding(spellrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd spells validator chain: %w", err) } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageNormalize, Module: spellnormalize.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(spellshape.Key), pipeline.Binding(spellcatalog.Key), pipeline.Binding(spellsourcerefs.Key), pipeline.Binding(spellrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd spells normalize validator chain: %w", err) } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageExtract, Module: npcextract.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(npcshape.Key), pipeline.Binding(npcsourcerefs.Key), pipeline.Binding(npcrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd npcs validator chain: %w", err) } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageNormalize, Module: npcnormalize.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(npcshape.Key), pipeline.Binding(npcidentity.Key), pipeline.Binding(npcsourcerefs.Key), pipeline.Binding(npcrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd npcs normalize validator chain: %w", err) } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageExtract, Module: combatextract.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(combatshape.Key), pipeline.Binding(combatsourcerefs.Key), pipeline.Binding(combatrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd combat turns validator chain: %w", err) } if err := registries.ValidatorChains.Register(pipeline.ValidatorChainMapping{ Stage: pipeline.StageNormalize, Module: combatnormalize.Key, Validators: []pipeline.ModuleBinding{ pipeline.Binding(validjson.Key), pipeline.Binding(validjsonschema.Key), pipeline.Binding(combatshape.Key), pipeline.Binding(combatinvariants.Key), pipeline.Binding(combatsourcerefs.Key), pipeline.Binding(combatrelatedness.Key), }, }); err != nil { return fmt.Errorf("register dnd combat turns normalize validator chain: %w", err) } return nil } func appendSpellLists(values []dnd.SpellList) (dnd.SpellList, error) { count := 0 for _, value := range values { count += len(value.SpellCasts) } combined := dnd.SpellList{SpellCasts: make([]dnd.SpellCast, 0, count)} for _, value := range values { combined.SpellCasts = append(combined.SpellCasts, value.SpellCasts...) } return combined, nil } func appendNPCLists(values []dnd.NPCList) (dnd.NPCList, error) { count := 0 present := false for _, value := range values { if value.NPCs != nil { present = true } count += len(value.NPCs) } if !present { return dnd.NPCList{}, nil } combined := dnd.NPCList{NPCs: make([]dnd.NPC, 0, count)} for _, value := range values { combined.NPCs = append(combined.NPCs, value.NPCs...) } return combined, nil } func appendCombatTurnLists(values []dnd.CombatTurnList) (dnd.CombatTurnList, error) { count := 0 present := false for _, value := range values { if value.CombatTurns != nil { present = true } count += len(value.CombatTurns) } if !present { return dnd.CombatTurnList{}, nil } combined := dnd.CombatTurnList{CombatTurns: make([]dnd.CombatTurn, 0, count)} for _, value := range values { for _, turn := range value.CombatTurns { combined.CombatTurns = append(combined.CombatTurns, cloneCombatTurn(turn)) } } return combined, nil } func cloneCombatTurn(value dnd.CombatTurn) dnd.CombatTurn { clone := value if value.Round != nil { round := *value.Round clone.Round = &round } if value.Actions != nil { clone.Actions = make([]dnd.CombatAction, len(value.Actions)) for index, action := range value.Actions { clone.Actions[index] = action if action.Targets != nil { clone.Actions[index].Targets = append([]string(nil), action.Targets...) } if action.Resolution != nil { resolution := *action.Resolution clone.Actions[index].Resolution = &resolution } } } if value.SourceRefs != nil { clone.SourceRefs = append([]source.SourceRef(nil), value.SourceRefs...) } return clone } func validateRegistries(registries pipeline.Registries, assets *llm.AssetRegistry) error { switch { case registries.Chunkers == nil: return fmt.Errorf("dnd registrar: chunker registry must not be nil") case registries.ArtifactCodecs == nil: return fmt.Errorf("dnd registrar: artifact codec registry must not be nil") case registries.Extractors == nil: return fmt.Errorf("dnd registrar: extractor registry must not be nil") case registries.Mergers == nil: return fmt.Errorf("dnd registrar: merger registry must not be nil") case registries.Normalizers == nil: return fmt.Errorf("dnd registrar: normalizer registry must not be nil") case registries.Validators == nil: return fmt.Errorf("dnd registrar: validator registry must not be nil") case registries.ValidatorChains == nil: return fmt.Errorf("dnd registrar: validator chain registry must not be nil") case assets == nil: return fmt.Errorf("dnd registrar: asset registry must not be nil") default: return nil } }