diff --git a/docs/roadmap/6-dnd-spells-extractor.md b/docs/roadmap/6-dnd-spells-extractor.md index 0d5f68e..3c4e756 100644 --- a/docs/roadmap/6-dnd-spells-extractor.md +++ b/docs/roadmap/6-dnd-spells-extractor.md @@ -23,7 +23,7 @@ In scope: - module metadata/capability requirements for pipeline validation; - source-reference and schema validators in the extractor chain; - fake LLM tests; -- CLI-level integration test if the CLI path is ready. +- runner-level integration tests with fake infrastructure. Out of scope: @@ -31,89 +31,71 @@ Out of scope: - NPC extraction; - combat extraction; - cross-slice deduplication beyond simple deterministic merging; -- broad D&D rules validation. +- broad D&D rules validation; +- a `notarius run` command. -## Proposed Stages +## Target End State -### Stage 1: Spell Artifact Schema +The repository should contain a real D&D spells extract-stage module at +`internal/modules/extract/dnd/spells`. -Define the D&D spell artifact model. +The spells module should be registered under the stable extractor key +`dnd/spells`. It should be selectable as a named artifact lane in pipeline +configuration, for example a lane named `spells` whose extractor module is +`dnd/spells`. -Initial shape: +The module should translate generic source chunks into spell-cast artifact +candidates with this spell payload: -```go -type SpellCast struct { - Player string `json:"player"` - Spell string `json:"spell"` - Effect string `json:"effect"` - NarrativeDescription string `json:"narrative_description"` - SourceRefs []SourceRef `json:"source_refs"` -} -``` +- `caster`; +- `spell`; +- `effect`; +- `narrative_description`. -Keep this schema inside the D&D spells extract module or a D&D artifact package, -not inside core framework packages. +The LLM structured response should also include source references for each +spell cast. The extractor should copy those references into the generic +artifact envelope rather than duplicating source references inside the durable +spell payload. -### Stage 2: Structured Response Schema - -Add a structured response schema asset for spell extraction. - -The schema should require: - -- spell-cast array; -- non-empty player, spell, effect, and narrative description fields; -- at least one source reference per spell cast. - -### Stage 3: Prompt Assets - -Add embedded prompt assets for D&D spell extraction. +The caster is the in-world character or creature casting the spell, not the +table speaker. Speaker metadata from transcript source units may be used as +optional prompt context when present, but it should not be a required or durable +field in the spell payload. Prompts should: - describe the generic source-unit input format; -- explain that source references must use source-unit IDs; +- explain that source references must use source-unit IDs exactly; - avoid relying on transcript-specific fields except as optional metadata; -- request only spell-cast artifacts. +- request only D&D spell-cast artifacts. -### Stage 4: Process Module Implementation +The extractor should attach deterministic validators by default. Validation +should cover: -Implement `internal/modules/extract/dnd/spells`. +- spell payload shape; +- non-empty required spell fields; +- at least one source reference per spell cast; +- source references that validate against the source document. -The extractor should: +The module should declare flat capabilities for pipeline validation. Initial +capabilities should require chunked transcript source material and provide a +D&D spell-cast artifact capability. -- satisfy the framework `Extractor` contract; -- declare module metadata for pipeline-profile validation; -- build LLM messages from a source document or source chunk; -- call the structured LLM client; -- return artifact candidates with source references; -- attach its validator chain. +Implementation staging belongs in +[`implementation.md`](implementation.md). -### Stage 5: Validators And Tests +## Fixtures And Tests -Wire deterministic validators: +The checkpoint should add synthetic fixtures and focused tests for: -- schema/shape validation; -- source-reference validation; -- required-field validation if not covered by schema handling. - -Add tests using a fake structured LLM client: - -- successful spell extraction; -- empty result; -- invalid source reference rejection; +- successful spell extraction with a fake structured LLM client; +- empty spell-cast results; - malformed structured output handling; -- stable output ordering. - -### Stage 6: CLI Integration - -If the CLI path is ready, add an end-to-end test using: - -```sh -notarius run dnd-session --input ./transcript.json --only spells -``` - -The test should use fake LLM wiring, fixture input, and a named pipeline profile -with a `spells` artifact lane. +- invalid source-reference rejection; +- stable output ordering; +- pipeline-profile selection of a `spells` artifact lane; +- runner integration from Seriatim input through the spells extractor using + fake chunk and output modules. ## Done Criteria @@ -123,8 +105,8 @@ with a `spells` artifact lane. - D&D concepts are contained in extract module/artifact packages and docs. - The spells module can be selected as a named artifact lane in pipeline configuration. -- The first meaningful vertical slice is available through tests, and through - CLI if the CLI path is ready. +- The first meaningful vertical slice is available through tests. +- No CLI `run` behavior is documented or implemented until the CLI path exists. ## Review Questions diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 3f302fd..e73a2e5 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,15 +1,14 @@ -# Implementation Plan: Checkpoint 5 Seriatim Input Module +# Implementation Plan: Checkpoint 6 D&D Spells Extractor ## Status This is a staged implementation plan for -[`5-seriatim-input-module.md`](5-seriatim-input-module.md). It is intended for -an LLM coding agent to follow stage by stage. +[`6-dnd-spells-extractor.md`](6-dnd-spells-extractor.md). It is intended for an +LLM coding agent to follow stage by stage. -This plan implements only checkpoint 5. Do not add D&D extraction, real domain -prompts or schemas, LLM extraction calls, a `notarius run` command, broad -Seriatim schema support, or transcript-specific behavior in core framework -packages in this checkpoint. +This plan implements only checkpoint 6. Do not add D&D item, NPC, combat, or +encounter extraction; do not add broad D&D rules validation; do not add a +`notarius run` command; and do not make core framework packages D&D-specific. ## Policy Context @@ -20,404 +19,564 @@ Follow: Required boundaries: -- keep Seriatim JSON schema details inside `internal/modules/input/seriatim`; -- keep core source, runner, pipeline, extractor, validator, LLM, and config +- keep D&D-specific artifact semantics, prompt data shaping, and response + interpretation inside `internal/modules/extract/dnd/spells`; +- keep source-format details inside input modules and do not depend on concrete + Seriatim package types from the spells extractor; +- keep core source, runner, pipeline, LLM, prompt, config, and validator packages source-agnostic and domain-agnostic; -- do not add transcript-specific typed fields to `SourceDocument`, - `SourceUnit`, runner contracts, or pipeline contracts; -- preserve transcript-specific values only as source metadata conventions; -- register the input module through the existing input adapter registry instead - of adding ad hoc conditionals; +- use embedded prompt and schema assets instead of inline prompt/schema strings; +- register the extractor through the existing extractor registry; - use flat capability strings in module metadata; - keep future or planned behavior in `docs/roadmap/` until implemented. ## Global Implementation Decisions -- Add no new third-party dependency. Use `encoding/json` with - `Decoder.UseNumber` for Seriatim JSON parsing. -- Use `seriatim` as the stable input adapter key. -- Put all concrete Seriatim input code under - `internal/modules/input/seriatim`. -- Expose a small module API: +- Add no new third-party dependency. +- Use `dnd/spells` as the stable extractor module key. +- Put concrete extractor code under `internal/modules/extract/dnd/spells`. +- Use `dnd.spell_cast` as the artifact type and `v1` as the schema version. +- Use `dnd.spell_casts` as the extractor-provided capability. +- The extractor module spec must require `chunks` and `source.transcript`. + It must not require `transcript.speaker` or `transcript.timestamps`; those + metadata values may be included in prompts when present, but extraction must + not depend on Seriatim-specific helper APIs. +- Keep durable artifact payloads focused on spell data: ```go -const Key = "seriatim" - -func New() *Adapter -func ModuleSpec() pipeline.ModuleSpec -func Register(registry *pipeline.InputAdapterRegistry) error +type SpellCast struct { + Caster string `json:"caster"` + Spell string `json:"spell"` + Effect string `json:"effect"` + NarrativeDescription string `json:"narrative_description"` +} ``` -- `ModuleSpec()` must return stage `pipeline.StageInput`, no required - capabilities, and these provided capabilities: - `source.transcript`, `transcript.speaker`, and `transcript.timestamps`. -- The Seriatim adapter should satisfy `contracts.InputAdapter`. -- Use `source.SourceDocument.Kind = "transcript"`. -- Use `source.SourceDocument.Format = - "application/vnd.seriatim.minimal+json"`. -- Use `source.SourceUnit.Kind = "transcript_segment"`. -- Compute `SourceDocument.Digest` from the exact raw input bytes as - `sha256:`. -- Resolve `SourceDocument.ID` in this order: - 1. trimmed `contracts.ParseRequest.SourceID`, if non-empty; - 2. trimmed string `metadata.id`, if present and non-empty; - 3. trimmed string `metadata.source_id`, if present and non-empty; - 4. deterministic fallback `seriatim:`. -- Segment IDs become source unit IDs exactly after validation. Reject segment - IDs with leading or trailing whitespace rather than silently rewriting them. -- Copy top-level Seriatim `metadata` into `SourceDocument.Metadata`. -- Store segment `speaker`, `start`, and `end` in `SourceUnit.Metadata` under - keys with those exact names. -- Store `start` and `end` as `json.Number` values so JSON serialization remains - numeric and the original decimal representation is preserved. -- Require top-level `metadata` to be present and be an object, but do not - require any specific metadata keys in checkpoint 5. -- Require top-level `segments` to be present and contain at least one segment. -- Reject unknown or extra JSON fields only if they prevent parsing the minimal - shape. Otherwise ignore them so the module can tolerate compatible Seriatim - additions. -- Return module-specific errors prefixed with useful Seriatim context, for - example `seriatim input: segment "s1" text must not be empty`. -- Keep examples free of private transcript content. Use synthetic fixture text. +- The LLM response model must include source references so the extractor can + populate `artifacts.ArtifactCandidate.SourceRefs`, but source references must + live in the generic artifact envelope for durable pipeline output rather than + being duplicated inside the `SpellCast` payload. +- `caster` means the in-world character or creature casting the spell. Do not + use table speaker/player as the durable caster field. Source-unit `speaker` + metadata may be rendered as optional prompt context when present. +- Use `source.SourceRef` JSON field names exactly as the core type defines + them: `source_id`, `start_unit_id`, and `end_unit_id`. +- Trim text fields before marshaling candidate payloads. Do not silently trim + source-reference IDs; invalid source refs must be rejected by validation. +- `Extractor.Validators()` must return module-owned deterministic validators by + default. Do not require pipeline profiles to configure validators explicitly + for the default checkpoint behavior. +- Treat an empty `spell_casts` response array as a successful empty extraction. + Treat a missing or null `spell_casts` field as malformed structured output. +- Use synthetic D&D transcript fixture text only. Do not include private + campaign transcript content. -## Stage 1: Seriatim Package Skeleton And External Model +## Stage 1: Domain Model, Module Skeleton, And Registry Metadata ### Goal -Create the Seriatim input module package, define the module-local JSON model, -and add registry-facing module metadata without changing framework contracts. +Create the D&D spells extractor package, define the artifact and response +models, and make the extractor discoverable through the existing extractor +registry without calling an LLM yet. -### Files To Add +### Files To Add Or Update -- `internal/modules/input/seriatim/adapter.go` -- `internal/modules/input/seriatim/model.go` -- `internal/modules/input/seriatim/metadata.go` -- `internal/modules/input/seriatim/registry_test.go` +- `internal/modules/extract/dnd/spells/extractor.go` +- `internal/modules/extract/dnd/spells/model.go` +- `internal/modules/extract/dnd/spells/registry_test.go` ### Required API Add: ```go -package seriatim +package spells -const Key = "seriatim" +const Key = "dnd/spells" +const ArtifactType = "dnd.spell_cast" +const SchemaVersion = "v1" -const ( - DocumentKind = "transcript" - UnitKind = "transcript_segment" - Format = "application/vnd.seriatim.minimal+json" -) +type SpellCast struct { + Caster string `json:"caster"` + Spell string `json:"spell"` + Effect string `json:"effect"` + NarrativeDescription string `json:"narrative_description"` +} -const ( - MetadataSpeaker = "speaker" - MetadataStart = "start" - MetadataEnd = "end" -) +type Extractor struct{} -type Adapter struct{} - -func New() *Adapter -func (a *Adapter) Key() string -func (a *Adapter) Parse(ctx context.Context, req contracts.ParseRequest) (*source.SourceDocument, error) +func New() *Extractor +func (e *Extractor) Key() string +func (e *Extractor) ArtifactType() string +func (e *Extractor) SchemaVersion() string +func (e *Extractor) Validators() []contracts.Validator +func (e *Extractor) Extract(ctx context.Context, req contracts.ExtractionRequest) (contracts.ExtractionResult, error) func ModuleSpec() pipeline.ModuleSpec -func Register(registry *pipeline.InputAdapterRegistry) error +func Register(registry *pipeline.ExtractorRegistry) error ``` -Add typed metadata helpers: +Add unexported response structs in the same package: ```go -func Speaker(unit source.SourceUnit) (string, bool) -func Start(unit source.SourceUnit) (json.Number, bool) -func End(unit source.SourceUnit) (json.Number, bool) -``` - -### Seriatim JSON Shape - -Define module-local structs for the minimal external shape: - -```go -type transcript struct { - Metadata map[string]any `json:"metadata"` - Segments []segment `json:"segments"` +type extractionResponse struct { + SpellCasts []spellCastResponse `json:"spell_casts"` } -type segment struct { - ID string `json:"id"` - Start json.Number `json:"start"` - End json.Number `json:"end"` - Speaker string `json:"speaker"` - Text string `json:"text"` +type spellCastResponse struct { + Caster string `json:"caster"` + Spell string `json:"spell"` + Effect string `json:"effect"` + NarrativeDescription string `json:"narrative_description"` + SourceRefs []source.SourceRef `json:"source_refs"` } ``` -Use an internal decode helper based on `json.NewDecoder(bytes.NewReader(raw))` -and `UseNumber`. - ### Required Behavior -- `New()` returns a non-nil adapter. -- `Adapter.Key()` returns `Key`. -- `ModuleSpec()` returns defensive slices and the capability set listed in the - global decisions. -- `Register()` calls `InputAdapterRegistry.RegisterWithSpec(ModuleSpec(), ...)`. +- `New()` returns a non-nil extractor. +- `ModuleSpec()` returns defensive slices with stage `pipeline.StageExtract`, + key `dnd/spells`, requires `chunks` and `source.transcript`, and provides + `dnd.spell_casts`. +- `Register()` calls `ExtractorRegistry.RegisterWithSpec(ModuleSpec(), ...)`. - `Register(nil)` returns an error from the registry path rather than panicking. -- Keep external JSON structs unexported. +- `Extract()` may return a clear not-yet-implemented error in this stage only. +- `Validators()` returns nil in this stage only; later stages must replace it + with the real validator chain. ### Required Tests -- `New()` returns an adapter whose key is `seriatim`. -- `ModuleSpec()` uses input stage and declares the required provided - capabilities. -- `Register()` makes the adapter buildable from an `InputAdapterRegistry`. -- Registry lookup returns the Seriatim module spec. +- `New()` returns an extractor whose key, artifact type, and schema version + match the constants. +- `ModuleSpec()` uses extract stage and declares the required capabilities. +- Caller mutation of `ModuleSpec().Provides` or `ModuleSpec().Requires` does not + affect later calls. +- `Register()` makes the extractor buildable from an `ExtractorRegistry`. +- Registry lookup returns the spells module spec. +- `Register(nil)` returns an error containing extractor registry context. ### Validation Run: ```sh -gofmt -w internal/modules/input/seriatim -go test ./internal/modules/input/seriatim +gofmt -w internal/modules/extract/dnd/spells +go test ./internal/modules/extract/dnd/spells go test ./... ``` -## Stage 2: Parse, Validate, And Map To SourceDocument +## Stage 2: Structured Response Schema Asset ### Goal -Implement the Seriatim parser and mapper from minimal Seriatim JSON into the -generic source model. +Add the structured response schema used by the D&D spells extractor and register +it through the existing LLM schema registry. ### Files To Add Or Update -- `internal/modules/input/seriatim/adapter.go` -- `internal/modules/input/seriatim/model.go` -- `internal/modules/input/seriatim/adapter_test.go` -- `internal/modules/input/seriatim/testdata/valid_minimal.json` -- `internal/modules/input/seriatim/testdata/duplicate_segment_id.json` +- `internal/framework/llm/assets/schemas/dnd_spells.v1.json` +- `internal/framework/llm/schema_registry.go` +- `internal/framework/llm/schema_registry_test.go` +- `internal/modules/extract/dnd/spells/schema_test.go` -### Required Validation +### Schema Decisions -Reject: +Register: -- nil or canceled context before parsing; -- empty raw input; -- malformed JSON; -- valid JSON with trailing non-whitespace data; -- missing, null, or non-object top-level `metadata`; -- missing, null, empty, or non-array top-level `segments`; -- segment IDs that are empty after trimming; -- segment IDs with leading or trailing whitespace; -- duplicate segment IDs; -- missing or empty `speaker`; -- missing, empty, non-numeric, negative, or non-finite `start`; -- missing, empty, non-numeric, negative, or non-finite `end`; -- segments where `end < start`; -- missing or empty `text`. +- response schema key: `dnd_spells` +- schema ID: `notarius.dnd.spells` +- schema version: `v1` +- response schema name: `notarius_dnd_spells_v1` +- asset path: `assets/schemas/dnd_spells.v1.json` -The parser may preserve leading and trailing whitespace in segment text as long -as the text is not empty after trimming. +The JSON schema must require a top-level object: -### Mapping Rules +```json +{ + "spell_casts": [ + { + "caster": "Aria", + "spell": "Cure Wounds", + "effect": "Heals an injured ally.", + "narrative_description": "Aria casts Cure Wounds after the fight.", + "source_refs": [ + { + "source_id": "session-alpha", + "start_unit_id": "seg-001", + "end_unit_id": "seg-002" + } + ] + } + ] +} +``` -- `segment.id` becomes `SourceUnit.ID`. -- `segment.text` becomes `SourceUnit.Text`. -- `speaker`, `start`, and `end` become unit metadata under the exact keys - defined in stage 1. -- The document metadata is a shallow copy of top-level Seriatim metadata. -- The document digest is based on raw input bytes, not normalized JSON. -- Call `source.ValidateDocument` before returning the document and wrap any - validation failure with Seriatim context. +Required schema constraints: + +- `additionalProperties: false` at every object level. +- top-level `spell_casts` is required and must be an array. +- `spell_casts` may be empty. +- each spell cast requires non-empty `caster`, `spell`, `effect`, and + `narrative_description`. +- each spell cast requires `source_refs` with `minItems: 1`. +- each source ref requires non-empty `source_id`, `start_unit_id`, and + `end_unit_id`. ### Required Tests -Add tests for: - -- valid minimal transcript parses to a source document with expected ID, kind, - format, digest, units, and metadata; -- `ParseRequest.SourceID` overrides metadata-derived IDs; -- fallback document ID is deterministic and has prefix `seriatim:`; -- malformed JSON returns an actionable Seriatim parse error; -- missing metadata is rejected; -- missing or empty segments is rejected; -- duplicate segment IDs are rejected; -- empty segment text is rejected; -- missing speaker is rejected; -- invalid timestamp values are rejected; -- `end < start` is rejected; -- typed metadata helpers return the expected speaker and timestamp values; -- a `source.SourceRef` using the first and last generated unit IDs validates - with `source.ValidateRef`. +- `llm.LookupResponseSchema(llm.DNDSpellsSchemaKey)` succeeds. +- Registered schema list remains sorted and now includes the D&D spells schema. +- The schema content is valid JSON and mutation-safe through registry lookups. +- The spells package can look up the schema key it will use during extraction. +- The schema diagnostics map omits raw schema content. ### Validation Run: ```sh -gofmt -w internal/modules/input/seriatim -go test ./internal/modules/input/seriatim +gofmt -w internal/framework/llm internal/modules/extract/dnd/spells +go test ./internal/framework/llm +go test ./internal/modules/extract/dnd/spells go test ./... ``` -## Stage 3: Pipeline Resolution And Config Compatibility +## Stage 3: Prompt Assets And Prompt Rendering ### Goal -Prove the Seriatim input module participates in pipeline-profile resolution and -capability validation through existing registries and config loading. +Add embedded prompt assets for D&D spell extraction and render deterministic +system/user messages from generic source chunks. ### Files To Add Or Update -- `internal/modules/input/seriatim/config_test.go` -- `internal/modules/input/seriatim/testdata/pipeline.yml` +- `internal/framework/prompt/assets/dnd/spells/system.md` +- `internal/framework/prompt/assets/dnd/spells/user.md` +- `internal/framework/prompt/registry.go` +- `internal/framework/prompt/registry_test.go` +- `internal/framework/prompt/render_test.go` +- `internal/modules/extract/dnd/spells/prompt.go` +- `internal/modules/extract/dnd/spells/prompt_test.go` + +### Prompt Decisions + +Register: + +- prompt ID: `dnd.spells` +- prompt constant: `DNDSpellsPromptID` +- prompt version: `v1` +- embedded directory: `assets/dnd/spells` + +The system prompt must: + +- include the shared prompt hardening text via `{{ hardening }}`; +- identify the task as extracting D&D spell casts only; +- state that the model must not infer spells not supported by the provided + source text; +- state that source references must use source-unit IDs exactly as provided. + +The user prompt must render: + +- source document ID; +- chunk ID and chunk index when a chunk is present; +- source units in their existing order; +- each source unit's ID and text; +- selected metadata only as optional context, using generic labels. Include + `speaker`, `start`, and `end` when present without importing Seriatim helper + APIs. + +Add a module-local prompt data builder that accepts +`contracts.ExtractionRequest` and returns template data. It must not mutate the +request, source document, chunk, units, or metadata maps. + +### Required Tests + +- Prompt metadata lookup succeeds for `dnd.spells`. +- Registered prompt metadata remains sorted. +- Rendering includes hardening text. +- Rendering includes source ID, chunk ID, unit IDs, unit text, and optional + speaker/timestamp metadata when present. +- Rendering fails clearly if required template data is missing. +- Module-local prompt data construction works with a generic source chunk and + does not depend on concrete Seriatim package helpers. + +### Validation + +Run: + +```sh +gofmt -w internal/framework/prompt internal/modules/extract/dnd/spells +go test ./internal/framework/prompt +go test ./internal/modules/extract/dnd/spells +go test ./... +``` + +## Stage 4: Extractor LLM Call And Candidate Mapping + +### Goal + +Implement `Extractor.Extract` so a source chunk can flow through prompt +rendering, structured LLM completion, and candidate creation. + +### Files To Add Or Update + +- `internal/modules/extract/dnd/spells/extractor.go` +- `internal/modules/extract/dnd/spells/model.go` +- `internal/modules/extract/dnd/spells/extractor_test.go` + +### Required Behavior + +`Extract()` must: + +- reject nil extractor, nil context, canceled context, nil source, nil chunk, + empty chunk units, and nil LLM client with clear `dnd spells` error context; +- render the `dnd.spells` prompt using the stage 3 prompt data builder; +- look up the stage 2 response schema; +- call `req.LLMClient.CompleteStructured` with: + - `StageName: Key`; + - two messages, system then user; + - `ResponseSchemaName: schema.Name`; + - `ResponseSchema: schema.JSONSchema`; +- decode into `extractionResponse`; +- reject a nil `SpellCasts` slice as malformed structured output; +- return no candidates for an empty `SpellCasts` slice; +- preserve response order when creating candidates; +- trim `caster`, `spell`, `effect`, and `narrative_description` before + marshaling the `SpellCast` payload; +- copy response source refs into `ArtifactCandidate.SourceRefs`; +- leave `ExtractorKey`, `ArtifactType`, `SchemaVersion`, and `Index` empty or + zero so the runner's existing candidate normalization remains authoritative; +- wrap LLM, prompt, schema lookup, response validation, and JSON marshal errors + with useful `dnd spells` context. + +Do not call validators from `Extract()`. Validation belongs to the runner's +validator phase. + +### Required Tests + +Use a fake `contracts.StructuredLLMClient`. + +- Successful extraction returns one candidate with expected payload fields, + source refs, response schema name, schema JSON, prompt messages, and stage + name. +- Empty `spell_casts: []` returns no candidates and no error. +- Missing or null `spell_casts` is rejected as malformed structured output. +- LLM client errors are wrapped with D&D spells context. +- Nil source, nil chunk, empty chunk units, nil LLM client, nil context, and + canceled context are rejected. +- Multiple response spell casts produce candidates in response order. +- Candidate source refs are copied so later mutation of the fake response does + not mutate returned candidates. + +### Validation + +Run: + +```sh +gofmt -w internal/modules/extract/dnd/spells +go test ./internal/modules/extract/dnd/spells +go test ./... +``` + +## Stage 5: Deterministic Validator Chain + +### Goal + +Add module-owned deterministic validators for spell payload shape, required +fields, and source-reference grounding. + +### Files To Add Or Update + +- `internal/modules/extract/dnd/spells/validator.go` +- `internal/modules/extract/dnd/spells/validator_test.go` +- `internal/modules/extract/dnd/spells/extractor.go` + +### Validator Decisions + +Add two validators: + +1. `ShapeValidator` + - `Name()` returns `dnd/spells/shape`. + - Rejects malformed JSON payloads with reason code `invalid_payload`. + - Rejects blank `caster`, `spell`, `effect`, or `narrative_description` + with reason code `missing_required_field`. + - Approves candidates with valid payload shape and required fields. + +2. `SourceRefValidator` + - `Name()` returns `dnd/spells/source_refs`. + - Rejects candidates with no source refs using reason code + `missing_source_ref`. + - Rejects any invalid source ref using reason code `invalid_source_ref` and + the `source.ValidateRef` error message. + - Approves candidates whose source refs all validate against the request + source document. + +`Extractor.Validators()` must return `ShapeValidator{}` followed by +`SourceRefValidator{}`. Return a fresh slice each time. + +Both validators must: + +- satisfy `contracts.Validator`; +- return one decision for every candidate; +- preserve candidate indexes in decisions; +- return an error, not rejection decisions, when called with nil source only if + source-reference validation cannot run. Shape validation does not require a + source document. + +### Required Tests + +- `Extractor.Validators()` returns the two validators in the required order and + is mutation-safe. +- Each validator approves a valid candidate. +- Shape validator rejects malformed payload JSON. +- Shape validator rejects each blank required field. +- Source ref validator rejects missing refs. +- Source ref validator rejects unknown source IDs, unknown unit IDs, and + reversed unit ranges. +- Both validators return one decision per candidate and preserve indexes. +- Approved decisions use `validate.Approved`; rejected decisions use the reason + codes listed above. + +### Validation + +Run: + +```sh +gofmt -w internal/modules/extract/dnd/spells +go test ./internal/modules/extract/dnd/spells +go test ./... +``` + +## Stage 6: Pipeline Resolution And Runner Integration + +### Goal + +Prove Seriatim input can flow through the existing runner into the real D&D +spells extractor, fake downstream infrastructure, and the extractor-owned +validators. + +### Files To Add Or Update + +- `internal/modules/extract/dnd/spells/config_test.go` +- `internal/modules/extract/dnd/spells/runner_test.go` +- `internal/modules/extract/dnd/spells/testdata/seriatim_spell_session.json` +- `internal/modules/extract/dnd/spells/testdata/pipeline.yml` ### Required Test Catalog -Build a test-only module catalog with: +Build test-only catalogs and registries with: - Seriatim input registered through `seriatim.Register`; -- a fake chunker requiring `source.transcript` and providing `chunks`; -- a fake extractor requiring `chunks`, `transcript.speaker`, and - `transcript.timestamps`, and providing `fake.artifacts`; +- a fake chunker registered as `fake/chunk`, requiring `source.transcript` and + providing `chunks`; +- D&D spells extractor registered through `spells.Register`; - `pipeline.AppendOrderMerger` registered as `appendorder`, requiring - `fake.artifacts`; + `dnd.spell_casts`; - `pipeline.NoopNormalizer` registered as `noop`; - a fake `json` output encoder registered as output stage. -Do not add real extract, chunk, normalize, or output modules for this checkpoint. +Do not add real chunk or output modules for this checkpoint. ### YAML Fixture -Use a synthetic pipeline fixture shaped like: +Use a synthetic pipeline fixture: ```yaml version: 1 pipelines: - seriatim-fixture: + dnd-spells-fixture: input: seriatim chunk: fake/chunk artifacts: - events: - extract: fake/extract + spells: + extract: dnd/spells merge: appendorder normalize: noop output: json ``` -The default LLM profile supplied by `config.Default()` is sufficient. Do not -add real provider settings to this fixture. - ### Required Tests - `config.ParseFileConfigYAML` and `Config.ApplyFileConfig` load the fixture. -- `Config.Resolve` succeeds with pipeline ID `seriatim-fixture` and the +- `Config.Resolve` succeeds with pipeline ID `dnd-spells-fixture` and the test-only catalog. -- The resolved pipeline input module is `seriatim`. +- The resolved artifact lane ID is `spells` and extractor module is + `dnd/spells`. - The resolved pipeline digest is non-empty and stable across repeated resolution. -- Removing `transcript.timestamps` from the Seriatim module spec in the - test-only catalog causes resolution to fail with a missing capability error. +- Removing `source.transcript` from the Seriatim module spec causes resolution + to fail with a missing capability error for `dnd/spells`. +- Removing `dnd.spell_casts` from the extractor spec causes resolution to fail + with a missing capability error for `appendorder`. - Selecting an unknown `--only` lane still fails through existing resolution behavior. +- Runner success path: + - parse the Seriatim fixture; + - use a fake LLM that returns at least two valid spell casts; + - assert approved artifacts preserve response order; + - assert approved payloads contain spell data; + - assert approved artifact source refs validate with `source.ValidateRef`; + - assert the manifest records input module `seriatim`, extractor + `dnd/spells`, lane `spells`, and validation status `approved`. +- Runner rejection path: + - fake LLM returns a spell cast with an invalid source ref; + - runner completes with rejected artifacts and validation status `rejected`; + - rejection contains validator `dnd/spells/source_refs` and reason code + `invalid_source_ref`. +- Runner malformed-output path: + - fake LLM returns or reports malformed structured output; + - runner returns an extraction error with D&D spells context and failed + validation status. ### Validation Run: ```sh -gofmt -w internal/modules/input/seriatim -go test ./internal/modules/input/seriatim -go test ./internal/core/config -go test ./... -``` - -## Stage 4: Runner Integration With Fake Downstream Stages - -### Goal - -Prove real Seriatim input can flow through the existing runner into fake -downstream stages while preserving source-unit IDs and metadata. - -### Files To Add Or Update - -- `internal/modules/input/seriatim/runner_test.go` - -### Required Behavior - -Use the same Seriatim fixture from stage 2 and a resolved pipeline from stage 3. -Register fake downstream stages only inside the test. - -The fake extractor should: - -- inspect the received `SourceDocument` and `SourceChunk`; -- assert that unit IDs match Seriatim segment IDs; -- assert that speaker and timestamp metadata are present; -- return one generic artifact candidate with a source reference pointing at - existing Seriatim-derived unit IDs. - -The test should then assert: - -- `Runner.Run` succeeds; -- the manifest records input module `seriatim`; -- the manifest source digest equals the parsed document digest; -- approved artifacts preserve valid source references; -- no transcript-specific type has been added outside the module. - -### Required Tests - -- successful runner execution from Seriatim JSON through fake chunk, extract, - merge, normalize, and output stages; -- runner failure when the Seriatim adapter returns an invalid source document, - using a malformed fixture or test input; -- validation of the fake candidate's source reference with - `source.ValidateRef`. - -### Validation - -Run: - -```sh -gofmt -w internal/modules/input/seriatim +gofmt -w internal/modules/extract/dnd/spells +go test ./internal/modules/extract/dnd/spells go test ./internal/modules/input/seriatim go test ./internal/framework/pipeline go test ./... ``` -## Stage 5: Documentation And Final Verification +## Stage 7: Documentation And Final Verification ### Goal -Document the implemented Seriatim integration contract once the module exists, -without describing unimplemented D&D extraction or run-command behavior. +Document the implemented D&D spells extraction contract without describing +unimplemented extractors or a `notarius run` command. ### Files To Add Or Update -- `docs/integrations/seriatim.md` -- `docs/roadmap/5-seriatim-input-module.md` +- `docs/integrations/dnd-spells.md` +- `docs/roadmap/6-dnd-spells-extractor.md` ### Required Documentation -Create `docs/integrations/seriatim.md` as implemented-behavior documentation +Create `docs/integrations/dnd-spells.md` as implemented-behavior documentation with: -- accepted minimal JSON shape; -- required fields and validation rules; -- mapping from Seriatim fields to `SourceDocument` and `SourceUnit`; -- metadata key conventions for `speaker`, `start`, and `end`; -- capability strings declared by the module; -- note that broader Seriatim schema variants are not yet supported. +- module key, artifact type, schema version, prompt ID, and response schema key; +- accepted source expectations: generic source document/chunk with transcript + capability supplied by pipeline resolution; +- spell payload fields; +- source-reference behavior: LLM response includes refs, durable artifact + output carries refs in the generic artifact envelope; +- default deterministic validators and rejection reason codes; +- declared required/provided capabilities; +- note that item, NPC, combat, encounter, broad D&D rules, and CLI `run` + workflows are not implemented by this checkpoint. -Update `docs/roadmap/5-seriatim-input-module.md` only if implementation -reveals a real scope or policy correction. Keep future D&D extraction behavior -out of the integration doc. +Update `docs/roadmap/6-dnd-spells-extractor.md` only if implementation reveals +a real scope or policy correction. Keep implementation staging in this file, +not in the feature roadmap. ### Final Validation Run: ```sh -gofmt -w internal/modules/input/seriatim +gofmt -w internal/modules/extract/dnd/spells internal/framework/llm internal/framework/prompt go test ./... go build ./cmd/notarius rm -f ./notarius @@ -427,17 +586,19 @@ rm -f ./notarius - `go test ./...` passes. - `go build ./cmd/notarius` passes. -- Seriatim minimal transcript JSON maps into `SourceDocument`. -- Unit IDs are stable and validate in source references. -- Transcript fields do not appear in core runner contracts. -- The input module is selectable through the input registry and pipeline-profile - resolution. -- The input module declares transcript-oriented flat capabilities for pipeline - validation. -- Tests prove transcript-specific assumptions are isolated to - `internal/modules/input/seriatim`. +- The D&D spells extractor is registered through the extractor registry. +- Pipeline-profile resolution can select a `spells` artifact lane using + extractor module `dnd/spells`. +- Seriatim minimal transcript JSON can flow through the runner into the D&D + spells extractor in tests. +- Spell artifacts include valid generic source references in the artifact + envelope. +- Invalid source references are rejected by the extractor-owned validator chain. +- D&D concepts do not appear in core runner, source, pipeline, config, prompt, + LLM, or validator contracts. +- No CLI `run` behavior is documented or implemented in this checkpoint. ## Open Questions -None. This plan chooses the checkpoint-5 behavior needed to implement the +None. This plan chooses the checkpoint-6 behavior needed to implement the feature without requiring additional product decisions.