diff --git a/docs/internal/modules.md b/docs/internal/modules.md index 5765011..76bf209 100644 --- a/docs/internal/modules.md +++ b/docs/internal/modules.md @@ -64,12 +64,13 @@ lanes. The canonical ordering and cache-boundary policy is documented in contracts expose only Notarius structured-completion types, not Scriptorium public types. -The shared `ChunkPromptMaterial` helper owns common transcript material -preparation for the spell, NPC, combat-turn, and NPC-interaction extractors. It clones supplied -source metadata, falls back to the materialized chunk when content is absent, -checks that content remains chunk-identical, and fills only the common default -fields. Extractors retain their request validation and wrap helper errors with -their module context. +The shared `PrepareChunkExtraction` helper owns common extraction preflight and +transcript material preparation for the spell, NPC, combat-turn, +NPC-interaction, and scene-description extractors. It validates common request +state, clones supplied source metadata, falls back to the materialized chunk +when content is absent, checks that content remains chunk-identical, and fills +only the common default fields. Extractors retain receiver, dependency, and +lane-specific checks locally and wrap helper errors with their module context. Reference material may inform a module or prompt but must not become source evidence. The resolver and materializer behavior is described in @@ -575,6 +576,37 @@ When adding a production module or validator: Do not add the extension to `docs/development.md`; that file routes by task and does not inventory implementations. +### D&D Extractor Contract + +New D&D extractors preserve these package-owned responsibilities: + +- Reject unknown options unless an option namespace is intentionally + extensible, and use shared common preflight while retaining receiver, + dependency, and lane-specific checks locally. +- Return independently owned results and exposed metadata that callers may + safely mutate. +- Keep the private response DTO, structural response schema and its identity, + provider-response mapping, durable artifact conversion, and lane diagnostics + in the owning package. +- Include every stable semantic input that can change durable output in + checkpoint identity. Consider prompt, schema, mapping, canonicalization, + prepared reference projections, identity, normalization, and trimming where + applicable. +- Add focused behavioral coverage where the lane's risks warrant it, including + construction and registration, option rejection, preflight, provider + failures, structured decoding, mapping and ownership, prompt + role/input/cache order, and checkpoint invalidation. + +Prompt ordering, shared-asset ownership, cache boundaries, and private-schema +rules are defined in [LLM Runtime](llm.md#dd-extraction-prompt-ordering-and-cache-boundaries). +[Pipeline Internals](pipeline.md#reference-materialization) owns reference +materialization, and its [checkpoint hooks](pipeline.md#checkpoint-and-debug-hooks) +define checkpoint behavior. Follow [Architecture](../policy/architecture.md#source-and-domain-boundaries) +for ownership boundaries and the [Testing Policy](../policy/testing.md) when +selecting durable coverage. This contract intentionally does not prescribe +prompt prose or length, hashes, test counts, filenames, fixture layouts, or +generic implementation builders. + ## Tests To Inspect - Package-local `*_test.go` files under the module or validator being changed. diff --git a/internal/modules/dnd/extract/combatturns/extractor.go b/internal/modules/dnd/extract/combatturns/extractor.go index 2436943..4184425 100644 --- a/internal/modules/dnd/extract/combatturns/extractor.go +++ b/internal/modules/dnd/extract/combatturns/extractor.go @@ -14,7 +14,6 @@ import ( const ( Key = "dnd/combat-turns" - ArtifactType = "dnd.combat_turn" mappingPolicy = "dnd.combat_turns.extract_mapping.v2" ) diff --git a/internal/modules/dnd/extract/spells/extractor.go b/internal/modules/dnd/extract/spells/extractor.go index 422bbfa..11fbc92 100644 --- a/internal/modules/dnd/extract/spells/extractor.go +++ b/internal/modules/dnd/extract/spells/extractor.go @@ -14,7 +14,6 @@ import ( ) const Key = "dnd/spells" -const ArtifactType = "dnd.spell_cast" const SchemaVersion = "v1" const mappingPolicy = "dnd.spells.extract_mapping.v2" @@ -122,6 +121,9 @@ func (e *Extractor) ReferenceSlots() []contracts.ReferenceSlot { } func (e *Extractor) ManifestMetadata() map[string]any { + if e == nil { + return nil + } metadata := map[string]any{ "prompt_id": PromptID, "prompt_version": SchemaVersion, diff --git a/internal/modules/dnd/extract/spells/extractor_test.go b/internal/modules/dnd/extract/spells/extractor_test.go index 8d0702d..9942933 100644 --- a/internal/modules/dnd/extract/spells/extractor_test.go +++ b/internal/modules/dnd/extract/spells/extractor_test.go @@ -174,6 +174,13 @@ func TestExtractorManifestMetadataIncludesLLMSchemaProvenance(t *testing.T) { } } +func TestNilExtractorManifestMetadata(t *testing.T) { + var extractor *Extractor + if metadata := extractor.ManifestMetadata(); metadata != nil { + t.Fatalf("nil extractor metadata = %#v, want nil", metadata) + } +} + func TestExtractPassesReferencesAsPromptInputs(t *testing.T) { client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{}}} req := extractionRequest()