From d3e171aa82294d218c95ddc60e4868c3452e76ef Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Mon, 20 Jul 2026 23:22:03 -0500 Subject: [PATCH] Add feature roadmap and implementation plan for a D&D combat turn extraction module --- docs/roadmap/dnd-combat-turn-extraction.md | 201 ++++++++ docs/roadmap/future.md | 6 +- docs/roadmap/implementation.md | 515 +++++++++++++++++++++ 3 files changed, 719 insertions(+), 3 deletions(-) create mode 100644 docs/roadmap/dnd-combat-turn-extraction.md create mode 100644 docs/roadmap/implementation.md diff --git a/docs/roadmap/dnd-combat-turn-extraction.md b/docs/roadmap/dnd-combat-turn-extraction.md new file mode 100644 index 0000000..7e98a6f --- /dev/null +++ b/docs/roadmap/dnd-combat-turn-extraction.md @@ -0,0 +1,201 @@ +# D&D Combat-Turn Extraction + +Status: Accepted. + +## Purpose + +Add a production D&D combat-turn pipeline that converts session transcripts +into an ordered, evidence-backed account of combat activity. This is the next +recommended increment because spell and NPC extraction now provide the two +most important grounding vocabularies, while combat turns are the next useful +artifact explicitly identified by the sequential-pipeline strategy. + +The feature uses the existing fixed pipeline and scene chunks for a more +structurally demanding artifact without requiring automatic workflow +composition, richer scene routing, or generic semantic deduplication. + +## Target Outcome + +An operator can: + +1. run the NPC pipeline against a transcript; +2. run a combat-turn pipeline over the same transcript, explicitly binding the + normalized NPC artifact as a reference; +3. receive an ordered JSON list of validated combat turns and interrupting + combat events; and +4. trace every reported declaration and immediate resolution back to the + transcript units that support it. + +The runs remain independent CLI invocations. Notarius does not discover prior +outputs, schedule dependent pipelines, or reconcile spell and combat artifacts +automatically. + +## Combat-Turn Artifact + +The new typed artifact represents an ordered list of combat-turn records. Each +record contains: + +- the canonical in-world actor; +- a turn kind distinguishing an ordinary turn from a reaction, legendary + action, lair action, or other interrupting combat event; +- the combat round as a positive integer when it is explicit or unambiguous, + and `null` otherwise; +- one or more ordered actions; +- a concise turn-level summary; and +- one or more transcript source references that collectively support every + reported field. + +Each action contains a conservative category, a concise declaration, zero or +more targets, and its immediate observed resolution. Resolution is `null` when +the cited passage establishes the declaration but no immediate resolution. +Categories cover attacks, spells, movement, items, ability checks, saving +throws, condition or state changes, and an `other` fallback without requiring +the transcript to use formal rules terminology. + +Reactions and similar out-of-turn events appear at the point where they occur +in transcript chronology rather than being moved to the reacting creature's +later turn. Output order is derived from cited source position; numeric source +unit IDs are identifiers, not chronology. + +## Inclusion And Evidence Policy + +Include a record when the transcript establishes that an in-world participant +takes a combat turn or performs a discrete interrupting combat event. Report +only declarations and their immediate resolutions, including directly +associated rolls, damage, healing, movement, conditions, or target outcomes. +For every detail reported, cite all supporting transcript units. + +Exclude: + +- initiative setup that contains no turn or combat event; +- tactical planning, table talk, rules lookup, and hypothetical actions; +- corrected or abandoned declarations that never become an attempted action, + except where the correction is necessary to describe the final declaration; +- recap of combat that occurred outside the current source passage; and +- downstream consequences that occur on later turns or elsewhere in the + scene. + +Do not infer a round number, action-economy classification, target, roll, +amount, condition, or outcome merely from D&D rules knowledge. Preserve the +session as played, and attribute nonstandard rulings to the GM or table when +that detail is relevant to the immediate resolution. + +## Identity And Reference Grounding + +The extractor uses the existing D&D transcript, player, party, and glossary +prompt inputs. It also accepts the normalized NPC artifact through an optional +structured `npcs` reference slot with the same validation, size, provenance, +content-safety, and semantic-checkpoint rules used by spell extraction. + +The NPC registry helps select canonical actors and targets and recognize +aliases. It does not establish that combat occurred and never becomes source +evidence. Unmatched actors and targets remain permitted because a session may +introduce combatants that were omitted from an earlier NPC run. + +The deterministic normalizer also accepts the registry. Exact canonical-name +or alias matches are rewritten to the registry's canonical display name for +actors and targets; ambiguous aliases and unmatched values remain unchanged +for validation and human review. Opaque player and party references continue +to guide the LLM but are not parsed into a new roster contract in this scope. + +## Extraction, Validation, And Normalization + +The extractor uses one structured LLM call per supplied chunk and returns typed +combat-turn candidates. It must preserve malformed candidates for the normal +validation and retry boundary rather than silently repairing unsupported +content in mapping code. + +The production default validator chain is deterministic and covers: + +- required fields, arrays, nullable-round shape, and supported enum values; +- a required non-empty evidence collection, source identity, unit existence, + and range order; +- actor and declared-action relatedness to cited transcript text, expressed as + bounded warnings where deterministic substring checks are only advisory; and +- normalized identity and duplicate invariants. + +Normalization is deterministic and conservative. It normalizes display +whitespace, canonicalizes exact NPC identity matches, orders and deduplicates +exact source references, and collapses only exact duplicate records with the +same normalized actor, turn kind, round value, and complete valid evidence set. +The first record is retained without synthesizing or merging prose. Every +mutation or collapse emits a scoped warning. + +An LLM-backed validator and semantic reconciliation normalizer are outside the +production chain. Human evaluation owns judgments such as whether the +model grouped a long turn correctly or omitted a subtle reaction. + +## Scene Strategy + +The combat extractor processes every chunk delivered by the configured +chunker. Existing D&D scene annotations remain useful context, but a +`primary_mode` value does not suppress an LLM call. Avoiding a +call based on an imperfect non-combat classification could silently lose the +very turns this artifact is intended to recover. + +Scene-classification and routing improvements remain separate future work. The +combat pipeline stays compatible with generic chunks that carry no D&D +annotation. + +## Provenance And Checkpoints + +The extractor, normalizer, and validators report stable semantic identities +through the existing manifest and prepared-component fingerprint contracts. +Prompt, private response schema, artifact policy, normalization policy, and a +bound NPC registry's semantic digest must invalidate incompatible checkpoints. + +Metadata and fingerprints contain identities, counts, and digests only. They +must not contain transcript text, combat records, NPC names, reference paths, +or raw reference content. Preparation failures remain bounded and content-safe +and occur before checkpoint handlers or pipeline execution are constructed. + +## Evaluation + +Use human-reviewed development runs rather than exact model-output goldens. +Evaluate at least the existing transcripts used for spell and NPC development, +with separate attention to: + +- combat-turn detection precision and recall; +- actor and target identity; +- turn boundaries and chronological order; +- reactions and other interrupting events; +- declaration and immediate-resolution fidelity; +- round-number restraint; +- completeness and precision of evidence; and +- duplicate behavior at chunk or scene boundaries. + +Frontier and inexpensive development models may differ substantially in +semantic quality. Deterministic tests should protect structure, provenance, +identity, ordering, normalization, and orchestration rather than require exact +combat prose. + +## Out Of Scope + +- Initiative trackers, current hit points, complete encounter-state replay, or + rules-engine validation. +- Automatic comparison or reconciliation with spell artifacts. +- NPC discovery, campaign-wide entity persistence, or deterministic PC-roster + parsing. +- LLM-backed validation or generic LLM-assisted deduplication. +- Automatic pipeline scheduling, prior-output discovery, or DAG execution. +- Scene-classification changes or skipping provider calls for non-combat + chunks. +- Narrative summaries outside the immediate combat-turn scope. + +## Acceptance Criteria + +- A selectable D&D combat lane produces a typed, durable JSON artifact with the + turn, action, chronology, and evidence semantics above. +- The default deterministic validator chain rejects malformed or invalidly + sourced records and emits bounded advisory relatedness warnings. +- Deterministic normalization canonicalizes exact NPC identities and collapses + only safely identical combat records while preserving chronology and + evidence. +- A normalized NPC artifact can be bound explicitly to both extraction and + normalization without becoming combat evidence. +- Semantic contracts and structured references participate in checkpoint + identity without leaking application content. +- Maintained configuration and operational examples demonstrate independent + NPC and combat invocations over the same transcript. +- Human review on representative sessions demonstrates useful turn extraction + without requiring scene-based call suppression or semantic reconciliation. diff --git a/docs/roadmap/future.md b/docs/roadmap/future.md index 64a8be4..a5b5c15 100644 --- a/docs/roadmap/future.md +++ b/docs/roadmap/future.md @@ -18,9 +18,9 @@ not as committed release dates. ### Add Sequential D&D Artifacts -- Add combat-turn extraction with explicit event and source-reference - semantics. Use earlier NPC output as a reference to improve participant - identity and consistency. +- The next proposed increment is + [D&D combat-turn extraction](dnd-combat-turn-extraction.md), using earlier NPC + output as an explicit identity reference while preserving independent runs. - Add narrative extraction for scene summaries, party actions, and NPCs encountered when that output proves useful beyond the dedicated NPC artifact. - Define the preferred operational sequence for independent pipelines on the diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..231084d --- /dev/null +++ b/docs/roadmap/implementation.md @@ -0,0 +1,515 @@ +# D&D Combat-Turn Extraction Implementation Plan + +Status: Ready for implementation. + +Implement this plan in order. The feature policy and target state are defined +in [D&D Combat-Turn Extraction](dnd-combat-turn-extraction.md); this document +owns implementation sequencing and concrete technical decisions. + +Do not introduce a DAG, prior-run discovery, encounter state engine, scene-based +provider-call suppression, LLM-backed validator, or semantic reconciliation. +Preserve the fixed `input -> chunk -> extract -> merge -> normalize -> output` +architecture and the existing framework retry, warning, rejection, checkpoint, +debug, and output contracts. + +## Cross-Stage Decisions + +### Production Identities + +Use these exact identities: + +- artifact kind: `dnd/combat-turn-list`; +- extractor key and normalizer key: `dnd/combat-turns`; +- extractor capability: `dnd.combat_turns`; +- prompt ID: `dnd.combat_turns`, version `v1`; +- private response-schema key: `dnd_combat_turns_llm`; +- private schema ID: `notarius.dnd.combat_turns.llm`; +- private schema name: `notarius_dnd_combat_turns_llm_v1`; +- durable schema ID: `notarius.dnd.combat_turns`; +- durable schema name: `notarius_dnd_combat_turns_v1`; +- durable schema version: `v1`; and +- durable media type: `application/json`. + +The private response schema omits `source_id`; mapping code assigns the current +source identity. No combat-turn ID is added in v1. + +### Canonical Go And JSON Shape + +Add these types to the canonical D&D model: + +```go +type CombatTurnList struct { + CombatTurns []CombatTurn `json:"combat_turns"` +} + +type CombatTurn struct { + Actor string `json:"actor"` + TurnKind CombatTurnKind `json:"turn_kind"` + Round *int `json:"round"` + Actions []CombatAction `json:"actions"` + Summary string `json:"summary"` + SourceRefs []source.SourceRef `json:"source_refs"` +} + +type CombatAction struct { + Category CombatActionCategory `json:"category"` + Declaration string `json:"declaration"` + Targets []string `json:"targets"` + Resolution *string `json:"resolution"` +} +``` + +Define string types and only these lowercase durable values: + +- `CombatTurnKind`: `turn`, `reaction`, `legendary_action`, `lair_action`, + `other`; +- `CombatActionCategory`: `attack`, `spell`, `movement`, `item`, + `ability_check`, `saving_throw`, `condition`, `other`. + +All object fields and the top-level `combat_turns` array are required. +`combat_turns` may be empty. Every turn requires a non-empty actor, supported +turn kind, at least one action, non-empty summary, and at least one source +reference. `round` is either `null` or a positive integer. Every action requires +a supported category, non-empty declaration, and present `targets` array; +targets may be empty but may not contain empty strings. `resolution` is either +`null` or non-empty after trimming. Unknown fields are rejected at every object +level. + +Strict raw decoding must distinguish a missing required `round` or `resolution` +key from an explicitly present JSON `null`, even though the accepted Go value +represents `null` with a nil pointer. Use presence-aware wire decoding or raw +key validation at codec and private-response boundaries; do not silently turn a +missing nullable field into an accepted null. + +One turn-level source-reference collection collectively supports the actor, +kind, round, actions, summary, targets, and resolutions. Do not add per-action +references in v1. + +### Ordering And Duplicate Identity + +Source-document slice position is chronology. Numeric unit IDs are identifiers +only. An item's earliest evidence position is the minimum valid start-unit +index across all its ranges. Stable ordering puts records with valid evidence +in ascending earliest-position order, preserves encounter order for ties, and +puts records without valid evidence after valid records without reordering +them. + +The normalizer may collapse two records only when all of these match: + +- actor under `npcs/identity.ComparisonKey`; +- exact `turn_kind`; +- both round values, including `nil` versus a value; and +- the complete, non-empty, canonically ordered set of exact source references. + +Every reference in the duplicate key must pass `source.ValidateRef` against the +current document. Invalid or empty evidence never participates in duplicate +collapse. Actions and prose do not participate in identity because two model +representations of the same cited turn should collapse; retain the complete +first record without merging prose or action lists. + +### NPC Registry Contract And Prompt Reuse + +Promote NPC registry preparation from the spell extractor into +`internal/modules/dnd/npcs/registry`. It owns: + +- slot name `npcs` and maximum size 1,048,576 bytes; +- exact-one-item and `application/json` validation when bound; +- approved NPC codec decoding, whole-registry identity validation, canonical + re-encoding, semantic SHA-256 digest, and content-safe bounded failures; +- the exact unbound prompt value `{"npcs":[]}`; +- immutable accessors for bound state, a defensive NPC list, canonical bytes, + digest, count, and prompt input; and +- exact canonical-name/alias lookup using the NPC identity comparison policy. + +Registry source references may identify another session and are not checked +against the current source. The resolver never returns content, names, aliases, +paths, or raw decoder errors in failures. Identity failures use issue codes and +record/alias indexes under the established 20-issue, 128-rune, and 4,096-byte +limits. + +Move the generic bounded diagnostic helper from `dnd/npcs/diagnostics` to +`dnd/shared/diagnostics`, updating existing NPC consumers. This is an internal +package move with no diagnostic-policy change. + +Add `common-dnd-npcs.md` to the shared D&D prompt assets. Its generic policy +allows exact canonical participant names and alias recognition while stating +that registry content is context, not event evidence. Both spell and combat +prompts use this exact fragment immediately after the existing shared campaign +reference message. Remove the spell-owned NPC fragment and include the shared +fragment in both prompt hashes. This intentionally changes the spell prompt +fingerprint once but must not change its request inputs, metadata shape, +registry semantics, or output contract. + +Both combat extraction and normalization declare the same optional registry +slot. A bound registry contributes manifest metadata `npc_registry_digest` and +`npc_count` and a local checkpoint fingerprint `npc_registry`. An absent slot +contributes neither metadata fields nor that fingerprint. + +### Validation And Diagnostics + +Add deterministic validators with these exact identities: + +| Validator key | Result reason | Policy fingerprint | +| --- | --- | --- | +| `extract/dnd/combat-turns/shape` | rejection `invalid_combat_turn_shape` | `dnd.combat_turns.validator.shape.v1` | +| `extract/dnd/combat-turns/source_refs` | rejection `invalid_combat_turn_source_refs` | `dnd.combat_turns.validator.source_refs.v1` | +| `extract/dnd/combat-turns/source_relatedness` | warning `combat_turn_not_near_source` | `dnd.combat_turns.validator.source_relatedness.v1` | +| `normalize/dnd/combat-turns/invariants` | rejection `invalid_combat_turn_normalization` | `dnd.combat_turns.validator.normalized.v1` | + +Shape owns required arrays, enum membership, nullable fields, positive rounds, +and non-empty strings. Source-reference validation owns source identity, unit +existence, and range order through `source.ValidateRef`. Later validators defer +when shape is invalid; relatedness also defers when any source range is invalid. + +Relatedness combines the turn's cited units once in document order, removing +overlap. The actor is related when its NPC comparison key is a substring of the +comparison-normalized cited text. For each action declaration, split its +comparison-normalized form on runes that are not Unicode letters or digits, +retain tokens of at least four Unicode code points, and require at least one +retained token to occur as a complete cited-text token. No retained token means +the declaration is unrelated. Emit at most one approved warning per turn, +listing whether the actor and which action indexes were not related. Do not +check targets deterministically. + +The normalize invariant validator requires display-normalized strings, +comparison-unique targets within each action, canonical exact source-reference +order without duplicates, chronological record order for valid evidence, and +absence of the duplicate identity defined above. It does not require every +actor or target to exist in the optional NPC registry. + +All rejection and warning messages use the shared diagnostic helper: at most 20 +displayed issues, at most 128 Unicode code points per displayed value, valid +UTF-8, at most 4,096 bytes, Go quoting for control characters, and the exact +total omitted count. Validators use strict empty-options decoders and provide +one local `policy` checkpoint fingerprint with the value in the table. + +### Normalization Policy + +Use normalization policy `dnd.combat_turns.normalize.v1` and these warning +reason codes: + +- `combat_turn_fields_normalized`; +- `combat_actor_canonicalized`; +- `combat_target_canonicalized`; +- `source_references_normalized`; +- `combat_turns_reordered`; and +- `duplicate_combat_turn_collapsed`. + +Deep-clone all nested slices and pointers. Normalize actor, summary, +declarations, targets, and non-null resolutions by collapsing Unicode +whitespace through `npcs/identity.NormalizeDisplay`. Preserve `nil` resolution. +Remove comparison-duplicate targets while retaining the first display value and +target order. Do not deduplicate or reorder actions. + +When a registry is bound, rewrite an actor or target only if its comparison key +matches exactly one validated canonical name or alias. Preserve unmatched +values. Registry validation makes ambiguous lookup impossible at preparation; +do not guess or perform fuzzy matching. + +Sort every source-reference list by exact `source_id`, `start_unit_id`, and +`end_unit_id`, then remove exact duplicates. Stable-sort records by the +chronology rule before duplicate detection. Collapse duplicates in that order +and retain the first record unchanged after its per-record normalization. + +Warning scopes use merged input indexes such as `combat_turns[3]`, even after +sorting or collapse. Emit one bounded warning for each affected record or +collapsed group; group warnings identify the retained and removed input indexes. +Only warnings from a validator-approved attempt become durable, under existing +framework policy. + +The normalizer exposes manifest metadata `normalization_policy`, +`identity_policy`, and optional registry digest/count. Its checkpoint +fingerprints are `normalization_policy`, `identity_policy`, and optional +`npc_registry`; bump the normalization policy when any transformation, +ordering, or duplicate rule changes. + +### Testing Rules + +Follow `docs/policy/testing.md`. Tests are offline and deterministic. Use a fake +structured LLM only at the completion boundary. Protect durable shapes, domain +invariants, preparation failures, reference sensitivity, registration, and one +representative assembled workflow. + +Do not add prompt-prose change detectors or tests requiring particular words or +phrases. Prompt tests may verify registration, message/input wiring, prompt and +schema identities, stable shared-fragment use, and absence of raw content from +metadata or diagnostics. Human evaluation owns semantic output quality. + +## Stage 1: Shared NPC Registry And Prompt Grounding + +### Goal + +Create one reusable, content-safe NPC registry boundary before adding a second +consumer, while preserving spell behavior. + +### Changes + +- Add the domain registry resolver and immutable lookup described above; migrate + spell extraction from its private resolver without changing the spell + extractor's public module contract. +- Relocate bounded D&D diagnostics to the shared domain package and update all + NPC imports and tests. +- Add the shared NPC prompt fragment, move the spell prompt to it, place it + immediately after shared campaign references, and remove the spell-owned + fragment. +- Keep raw reference provenance in framework identity independently from the + semantic registry fingerprint. Preserve the exact empty prompt input and all + existing content-safe error behavior. +- Update the internal overview, module, and LLM documentation in this stage so + the implemented registry owner and shared prompt-fragment ownership remain + accurate; do not claim combat extraction exists yet. + +### Tests + +- Move resolver contract tests to the domain package: absent, valid, formatted- + equivalent, malformed, unknown-field, invalid-ID, collision, media type, + item count, byte limit, defensive copies, lookup, digest, and bounded + content-free diagnostics. +- Retain spell-level tests only for spell request wiring, metadata/fingerprint + behavior, and no-regression output; remove duplicated resolver cases. +- Verify the shared prompt asset registers for spells and hashes the shared + fragment without asserting its prose. + +### Completion Check + +Run `go test ./internal/modules/dnd/npcs/... ./internal/modules/dnd/extract/spells ./internal/modules/dnd/validate/npcs/...` +and `git diff --check`. + +## Stage 2: Combat Domain Contract And Codec + +### Goal + +Establish the typed artifact and durable serialization boundary without making +the combat module selectable. + +### Changes + +- Add the canonical combat types, enums, constants, and artifact kind using the + exact shape and values above. +- Add `internal/modules/dnd/codec/combatturns`, following the existing candidate + versus approved codec boundary: + - strict single-value JSON with unknown-field rejection; + - candidate encode/decode preserving validator-visible invalid values; + - approved encode/decode enforcing structural validity only; + - defensive schema and metadata values; and + - metadata containing `combat_turn_count` only. +- Add the durable v1 JSON Schema with required nullable fields, enum values, + source-reference shape, array rules, and `additionalProperties: false`. +- Add `docs/integrations/dnd-combat-turn-artifacts.md`, describing only the + implemented artifact and codec at this stage. + +### Tests + +- Test approved round trips and candidate preservation for every nullable, + enum, required-array, required-string, target, and source-reference boundary. +- Test malformed, trailing, unknown-field, and invalid structural input without + snapshotting complete errors. +- Test defensive schema/metadata copies, nil versus present-empty arrays, and + exact codec identity. +- Keep one compact durable v1 fixture for intentional compatibility coverage. + +### Completion Check + +Run `go test ./internal/modules/dnd/codec/combatturns` and `git diff --check`. + +## Stage 3: Combat Extractor And Extraction Validators + +### Goal + +Implement package-complete LLM extraction and deterministic candidate +validation without production composition. + +### Changes + +- Add `internal/modules/dnd/extract/combatturns` with the exact module, prompt, + schema, capability, artifact, and reference identities above. It accepts no + options and requires `chunks` and `source.transcript`. The prompt manifest is + `dnd.combat_turns.yaml`, uses default profile `gemini-2-flash`, JSON Schema + validation, and zero provider-side repair attempts so pipeline retries remain + the only extraction retry policy. +- Declare existing optional `players`, `party`, `glossary`, and deprecated + `roster` slots plus the optional structured NPC registry slot. Reuse shared + D&D prompt inputs and the shared NPC prompt fragment. +- Embed a private response schema matching the durable shape except that source + ranges omit `source_id`. Require present arrays and nullable round/resolution + values exactly as in the durable contract. +- Prompt for the inclusion, exclusion, chronology, identity, round restraint, + and immediate-resolution policy in the feature roadmap. Instruct the model + that campaign and NPC references disambiguate identities but are never combat + evidence. +- Preserve malformed response values for validators. Canonicalize and exactly + deduplicate source ranges, assign the current source ID, and stable-sort + records by earliest valid source-document position. Do not merge records, + canonicalize NPC names deterministically, or skip any chunk in the extractor. +- Expose prompt and private-schema manifest metadata plus optional registry + digest/count. Fingerprint local names `prompt`, `response_schema`, + `mapping_policy` with value `dnd.combat_turns.extract_mapping.v1`, and optional + `npc_registry`. +- Implement shape, source-reference, and source-relatedness validators with the + exact contracts and policies above. + +### Tests + +- Through a fake LLM, test request identity, profile/session propagation, + chunk-scoped source input, campaign and NPC inputs, response mapping, source + assignment, non-monotonic-unit chronology, invalid-candidate preservation, + cancellation, and contextual provider failures. +- Test prompt/schema registration, required input wiring, metadata and + fingerprint sensitivity, defensive copies, and content redaction without + asserting prompt prose. +- Test each validator's meaningful approval, rejection, deferral, and warning + categories, including Unicode comparison, overlapping evidence, declaration + tokenization, bounded diagnostics, strict options, and typed registration. +- Verify that a bound NPC registry affects grounding metadata and checkpoint + identity but never supplies combat source references. + +### Completion Check + +Run `go test ./internal/modules/dnd/extract/combatturns ./internal/modules/dnd/validate/combatturns/...` +and `git diff --check`. + +## Stage 4: Combat Normalization And Final Invariants + +### Goal + +Implement conservative identity canonicalization, chronological ordering, and +exact-evidence duplicate collapse. + +### Changes + +- Add `internal/modules/dnd/normalize/combatturns` with key + `dnd/combat-turns`, requirements `merged`, capability `normalized`, no + options, and the optional NPC registry slot. +- Resolve and retain the immutable registry during preparation. Runtime uses + the prepared view; it does not parse references repeatedly. +- Apply the exact per-record, registry, source-reference, chronology, duplicate, + warning, metadata, and fingerprint policies above. Never mutate or retain + caller-owned slices, pointers, registry data, or source data. +- Add the normalized-invariants validator. It remains deterministic, accepts no + references of its own, and defers malformed shape or invalid source evidence + to their owning validators. + +### Tests + +- Use table-driven cases for whitespace, nullable resolution, target + deduplication, actor/target registry matches, unmatched names, reference + normalization, non-monotonic source IDs, stable ties, and warning scopes. +- Cover duplicate collapse and non-collapse for every identity dimension, + especially invalid evidence, distinct ranges, different turn kinds, and + `nil` versus numbered rounds. +- Prove actions/prose come only from the first retained record, merged input is + immutable, and all nested output storage is independent. +- Test absent and bound registries, preparation failures, metadata, + fingerprints, policy sensitivity, cancellation, strict options, and module + registration. +- Test normalized-invariant rejection for each owned invariant without + duplicating shape and source-validator case matrices. + +### Completion Check + +Run `go test ./internal/modules/dnd/normalize/combatturns ./internal/modules/dnd/validate/combatturns/...` +and `git diff --check`. + +## Stage 5: Production Composition, Sequential Workflow, And Documentation + +### Goal + +Make the complete combat lane selectable, verify the assembled workflow, and +document current behavior in canonical locations. + +### Changes + +- Extend the D&D registrar with the combat codec, extractor and prompt assets, + typed append-order merger, combat normalizer, typed no-op normalizer, all four + validators, and typed always-accept/always-reject variants. +- Add append behavior that preserves present-empty versus nil output and chunk + record order before normalization. +- Register the extract default chain in this order: + `generic/valid_json`, `generic/valid_json_schema`, combat shape, combat source + references, combat source relatedness. +- Register the normalize default chain in this order: + `generic/valid_json`, `generic/valid_json_schema`, combat shape, normalized + invariants, combat source references, combat source relatedness. +- Do not add a merge validator chain or change framework defaults. +- Add `examples/dnd-combat-turns.config.yml` and + `examples/dnd-npc-combat-sequential.config.yml`. Use version 3, safe relative + state paths, explicit checkpoint `enabled: false`, a generic chunker, combat + extraction with `retries: 2`, and combat normalization. Keep the dynamic NPC + output unbound in the static sequential example. +- Demonstrate runtime binding to both stage-local slots with selectors + `combat.extract.npcs=/lanes/npcs.json` and + `combat.normalize.npcs=/lanes/npcs.json`. +- Update canonical current-behavior documentation: + - Configuration owns module/validator catalogs, reference slots, limits, + default chains, and maintained examples; + - CLI owns the explicit two-selector invocation syntax; + - Operations owns the independent NPC-then-combat workflow and state + sensitivity; + - the combat integration contract owns durable fields, enum values, + evidence, normalization, warnings, and manifest metadata; + - the NPC integration contract notes combat as a consumer without redefining + combat fields; + - JSON output links the new lane payload; and + - internal overview, module, and LLM docs describe concrete packages, + preparation, prompt reuse, and fingerprints. +- At completion, mark this plan and the feature roadmap complete. Remove the + implemented combat proposal from `future.md` while retaining scene-routing, + narrative, generic deduplication, and other unimplemented work. + +### Tests + +- Extend registrar tests for production keys, typed variants, prompt assets, + default chain order, nil dependencies, and duplicate registration behavior. +- Add config/example resolution coverage for stage-local NPC bindings, + capabilities, codec compatibility, strict options, and invalid validator + placement. +- Add one assembled pipeline integration covering extract, retry-capable + validation, append merge, registry-backed normalization, final validation, + JSON output, warnings, manifest metadata, and checkpoint fingerprints using a + fake LLM. +- Add one sequential integration that materializes normalized NPC output as + both combat references and verifies canonical actor/target output, reference + provenance, and that NPC source ranges never become combat evidence. +- Add preparation-boundary coverage showing malformed or oversized NPC input + fails before checkpoint construction or pipeline execution. Do not duplicate + the shared resolver's complete malformed-input matrix. +- Validate maintained examples and documentation links through existing test + mechanisms. + +### Completion Check + +Run the final verification suite. + +## Final Verification + +Run: + +```sh +git diff --check +go test ./... +go vet ./... +go build ./cmd/notarius +go test -race ./internal/modules/dnd/... ./internal/framework/pipeline \ + ./internal/cli ./internal/modules/integration +``` + +Review the final diff for: + +- D&D or provider behavior leaking into generic framework packages; +- accidental scene-based call suppression or automatic pipeline composition; +- mutation or aliasing of typed artifacts, schemas, references, metadata, or + fingerprints; +- transcript, registry, prompt, schema, path, or decoder content leaking into + errors, manifests, fingerprints, or redacted summaries; +- prompt-prose change-detector tests, redundant cross-layer cases, or exact LLM + output goldens; +- current-behavior documentation claiming features before the implementing + stage lands; and +- unrelated worktree changes. + +## Open Questions + +None. Artifact fields and enums, nullable behavior, chronology, duplicate +identity, reference reuse, prompt composition, validator placement, +normalization, checkpoint semantics, documentation ownership, and stage +boundaries are fixed by this plan.