diff --git a/docs/roadmap/dnd-item-events.md b/docs/roadmap/dnd-item-events.md new file mode 100644 index 0000000..8b1adc0 --- /dev/null +++ b/docs/roadmap/dnd-item-events.md @@ -0,0 +1,212 @@ +# D&D Item Events + +## Status + +Ready for implementation. This document defines the desired artifact and +pipeline contract; the concrete work is specified in +[the implementation plan](implementation.md). + +## Purpose + +Add a source-grounded D&D item-event lane that records meaningful changes in +the party's knowledge or possession of items. The lane should answer what item +was discovered, acquired, lost, consumed, or transferred, who held it when that +is known, how many were involved when the transcript states a quantity, and +where the event appears in the source. + +The feature should follow the application's minimal-extraction policy. It +records structured occurrences supported by transcript ranges and does not ask +the model to maintain an inventory, infer unstated ownership, resolve semantic +item identity, summarize an item's significance, or analyze how it was used. + +## One Event Extractor + +The initial production lane should use one LLM extractor and one deterministic +normalizer under the selectable key `dnd/item-events`. Its durable artifact +should be an ordered `dnd/item-event-list`. + +Unlike NPC identification, item identification is not initially a separate +artifact. An item matters to this feature because a tracked event happened to +it, so a preliminary item-list extractor would duplicate evidence and add an +ordered dependency without establishing a distinct useful contract. + +An item registry may be reconsidered after real transcripts demonstrate a need +for canonical aliases, stable cross-event identity, or downstream consumers +that need items independently of events. If introduced later, it should be an +explicit earlier artifact consumed through the existing ordered generated- +reference mechanism. It should not be anticipated through placeholder IDs or +implicit coupling in the v1 event schema. + +## Durable Event Contract + +The durable payload should contain one required `events` array, which may be +empty. Each event should contain: + +- `name`: a concise item name supported by the cited transcript; +- `kind`: one of the five event categories below; +- `quantity`: an optional positive integer, present only when the transcript + establishes a count; +- `from`: an optional holder losing possession; +- `to`: an optional holder gaining possession; and +- `source_refs`: one or more supporting current-source ranges. + +`from` and `to` should use a transcript-supported party-member name when the +holder is known. The reserved display value `party` should represent collective +party possession when the event is clear but no individual holder is +established. The extractor must not guess an individual holder. + +Every extraction-time source range must be wholly contained in the current +accepted chunk. Campaign references may disambiguate item or holder names but +must never become item-event evidence. + +The initial contract should not assign item IDs. Name-derived IDs would conflate +distinct generic objects, while model-assigned instance IDs would require +unreliable entity resolution. Separately evidenced events involving identically +named items must remain representable. + +## Event Categories + +| Kind | Meaning | +| --- | --- | +| `discovered` | The party learns of or directly encounters an item without establishing possession. | +| `acquired` | The party or a party member gains possession or control of an item. | +| `lost` | The party or a party member ceases to possess or control an item without consuming it. | +| `consumed` | Use depletes, expends, or destroys the tracked item. | +| `transferred` | Possession moves from one party member to another without changing party-level ownership. | + +Category and holder rules should be: + +- `discovered` has neither `from` nor `to`. +- `acquired` requires `to` and has no party-holder `from`. The item's external + source may be described by the transcript but is not modeled as a holder. +- `lost` requires `from` and has no party-holder `to`. +- `consumed` requires `from` and has no `to`. +- `transferred` requires distinct `from` and `to` party members. +- Giving an item to an NPC, spending currency, selling an item, or otherwise + moving it outside party possession is `lost`, not `transferred`. +- An item destroyed through its intended or immediate use is `consumed`; an + item destroyed, abandoned, stolen, or otherwise removed from control is + `lost`. +- Ordinary non-depleting use, such as swinging a sword, wearing armor, reading + a reusable book, or activating a reusable item, is not an event. + +When discovery and acquisition are separately established, each is a separate +ordered event. A statement that establishes only acquisition must not cause the +extractor to invent an additional discovery event. + +## Quantities And Currency + +Currency is an item for this contract. For example, recovering 20 silver pieces +should produce an `acquired` event with the transcript-supported denomination, +quantity `20`, the gaining holder or `party`, and source evidence. + +Each denomination should remain a separate item name and event. The feature +must not convert values between denominations, infer omitted quantities, or +calculate balances. An absent quantity means the source did not establish a +reliable count; it does not mean one. + +Distinct explicit quantities remain distinct events. The normalizer must not +sum nearby acquisitions, subtract losses, or reconcile contradictory counts. + +## Event Boundaries And Normalization + +One event represents one item, one category, one holder transition, and one +locally coherent passage within one accepted chunk. Events never span chunks. +A category change, holder change, distinct possession transition, intervening +scene, or meaningful absence begins a new event. + +The deterministic normalizer should: + +- trim item and holder display values without semantic rewriting; +- canonicalize and validate source-reference ordering; +- order events by earliest source-document position, followed by stable + deterministic tie-breakers over item name, kind, holders, quantity, and the + complete evidence sequence; and +- remove only exact duplicates after deterministic formatting. + +It must not merge nearby events, reconcile aliases, singularize or rename +items, infer missing holders or quantities, calculate party inventory, or use +an LLM. Case-folded comparison may support deterministic ordering, but it must +not silently make differently displayed item names the same durable identity. + +## Prompt And Reference Policy + +The extractor should follow the established shared D&D prompt organization and +prompt-caching conventions. Stable shared instructions and stable campaign +references should precede the variable transcript input. + +It may declare the existing optional `players`, `party`, and `glossary` +campaign-reference slots for name disambiguation. It should not require the NPC +registry, scene-description artifact, or a new item registry. Item events are +independent of those lanes and should not add an implicit cross-lane +dependency. + +The prompt should emphasize literal extraction, category and holder rules, +current-session evidence, and omission when the event or quantity is uncertain. +It should not request descriptions, item properties, rarity, value, narrative +importance, inferred ownership history, or inventory summaries. + +## Validation, Provenance, And Quality + +The lane should follow the established typed D&D module pattern: + +- a strict durable codec and a separate private structured-response schema; +- deterministic shape, source-reference, and normalized-invariant validators; +- warning-only source relatedness where it provides useful signal; +- exact source identity and chunk containment during extraction; +- deterministic candidate mapping that adds the current source ID rather than + asking the model to emit it; +- bounded, content-safe diagnostics and metadata; and +- prompt, schema, mapping, and normalization policy fingerprints for checkpoint + correctness. + +Validation should reject unsupported kinds, invalid category/holder +combinations, non-positive quantities, malformed or out-of-chunk references, +and normalized ordering or duplicate invariants. Relatedness checks should +remain advisory because pronouns, currency notation, and contextual item names +may make exact lexical matching incomplete. + +Tests should protect the durable schema, category and holder rules, explicit +currency quantities, source containment, deterministic ordering, exact +duplicate removal, prompt/reference boundaries, registration, and one +representative assembled pipeline. They should remain deterministic and +offline, use a fake only at the LLM boundary, and avoid assertions over private +helper structure or probabilistic model quality. + +## Configuration And Documentation + +The feature should become a selectable D&D artifact lane in production module +registration and configuration documentation. The maintained complete D&D +example should include the item-event lane in the pipeline step appropriate for +independent extraction; it must not add a generated-reference dependency merely +to group item events with other event artifacts. The minimal example should +remain unchanged. + +When implemented, the durable schema and consumer semantics should have a +canonical integration contract. Internal documentation should describe package +ownership and module mechanics without duplicating that external contract. + +## Non-Goals + +This scope does not: + +- produce a complete current inventory or starting inventory; +- calculate running balances, net currency, encumbrance, or item value; +- identify every mentioned item when no tracked event occurs; +- record ordinary non-consuming item use; +- model containers, equipment slots, attunement, charges, or item properties; +- assign stable instance IDs or semantically reconcile aliases; +- derive item ownership from NPC, combat-turn, or scene artifacts; +- introduce a second LLM extractor, an LLM-backed normalizer, or an item + registry; or +- introduce new pipeline orchestration or reference syntax. + +## Completion Criteria + +The feature is complete when a configured D&D lane can produce and normalize a +strict ordered item-event artifact for discoveries, acquisitions, losses, +consumption, intra-party transfers, and currency; every event is grounded in +current transcript ranges; deterministic validation and normalization enforce +the category, holder, quantity, ordering, and exact-duplicate policies; the +complete maintained example selects the lane; and canonical current-behavior +documentation replaces this roadmap as the source of implemented contracts. diff --git a/docs/roadmap/future.md b/docs/roadmap/future.md index 22395d1..ec79e7c 100644 --- a/docs/roadmap/future.md +++ b/docs/roadmap/future.md @@ -7,6 +7,14 @@ not as committed release dates. ## Near-Term D&D Pipeline +### Add D&D Item Events + +- Implement the selected + [D&D item-event contract](dnd-item-events.md) as the next near-term feature. + Track source-grounded discoveries, acquisitions, losses, consumption, + intra-party transfers, and explicit currency quantities without inferring an + inventory ledger or introducing a preliminary item registry. + ### Evaluate Spell Extraction And Normalization - Evaluate ordinary extraction retries and the completed normalization path diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 803e7f7..176351d 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,412 +1,661 @@ -# Scene-Aware Combat Extraction Implementation Plan +# D&D Item Events Implementation Plan -## Status +## Purpose -Ready for implementation. Execute each stage in order, with one implementation -prompt per stage. +This document is the decision-complete implementation plan for +[D&D Item Events](dnd-item-events.md). Implement the stages below in order, with +one stage per coding-agent prompt. The feature roadmap is authoritative for +product intent, artifact semantics, and non-goals; this document defines how to +reach that target state in the current architecture. -## Feature Contract +The work adds one LLM-backed `dnd/item-events` extractor. It records a +source-grounded, ordered history of item and currency events without attempting +to maintain inventory state. A deterministic normalizer prepares the extractor +output for downstream use. -The target behavior and policy choices are defined in -[Scene-Aware Combat Extraction](scene-aware-combat-extraction.md). That document -is authoritative when this plan and the feature contract appear to overlap. +## Cross-Stage Rules -The central rule is strict opt-in: `dnd/combat-turns` may call the LLM only for -an accepted chunk with one exact scene-description record whose `kind` is -`combat`. Exact non-combat matches and missing or mismatched coverage produce an -accepted empty combat-turn list without an LLM call. +Apply these rules in every stage: -## Required Reading +- Follow [Architecture Policy](../policy/architecture.md), + [Documentation Policy](../policy/documentation.md), and + [Testing Policy](../policy/testing.md). +- Preserve the durable boundary: private provider response types belong to the + extractor package; public item-event artifacts belong to `internal/modules/dnd`; + codecs own strict wire validation. +- Keep LLM extraction and deterministic processing separate. The extractor may + map provider fields and attach the current source ID, but it must not repair, + infer, merge, or semantically reinterpret events. +- Reuse the existing shared D&D prompt assets and prompt assembly helpers. Do not + copy their text into item-specific assets. Stable shared references must + precede item-specific instructions and the changing transcript message must + remain last. +- Use strict option decoding and reject unknown module options. +- Use policy identities with a `v1` suffix because this is the first item-event + contract. Do not introduce a `v2` schema or backward-compatibility layer; the + application is pre-release. +- Keep validation diagnostics bounded and content-safe, and keep capability and + identity metadata free of transcript, prompt, reference, and credential + content. +- Keep all tests offline and deterministic. Do not add live-provider tests, + wall-clock-dependent assertions, exact prompt-prefix-length tests, or tests + coupled only to private helper structure. +- Do not make unrelated cleanup changes. Preserve user changes already present + in the worktree. +- At the end of every stage, run the narrowest relevant package tests and + `go test ./...`. Run the repository's documented formatting and static-analysis + commands before completing the final stage. -Before Stage 1, read: +## Fixed Contract Decisions -- `docs/development.md`; -- every document under `docs/policy/`; -- `docs/roadmap/scene-aware-combat-extraction.md`; -- `docs/internal/pipeline.md`; -- `docs/internal/modules.md`; -- `docs/integrations/dnd-scene-description-artifacts.md`; -- `docs/integrations/dnd-combat-turn-artifacts.md`; and -- the focused code and tests named in the applicable stage. +The implementation must use the following names and shapes consistently. -Before changing a subsystem in a later stage, reread its focused current -documentation and tests if they have changed since the earlier stage. +### Module and artifact identities -## Global Implementation Constraints +- Extractor key: `dnd/item-events` +- Extractor capability: `dnd.item_events` +- Durable artifact kind: `dnd/item-event-list` +- Durable schema ID: `notarius.dnd.item_events` +- Durable schema name: `notarius_dnd_item_events_v1` +- Durable schema version: `v1` +- Private LLM schema ID: `notarius.dnd.item_events.llm` +- Private LLM schema name: `notarius_dnd_item_events_llm_v1` +- Private response schema key: `dnd_item_events_llm` +- Prompt ID: `dnd.item_events` +- Extraction mapping policy: `dnd.item_events.extract_mapping.v1` +- Normalization policy: `dnd.item_events.normalize.v1` -- Keep scene interpretation under `internal/modules/dnd`; generic framework and - source packages must not import D&D types or interpret scene kinds. -- Use the existing ordered generated-reference mechanism. Do not add implicit - lane discovery, a new dependency syntax, a general DAG, or direct output-file - reads. -- Add `scene_descriptions` only to the combat extractor. The combat normalizer - continues to accept only the optional `npcs` registry. -- Keep both durable v1 schemas unchanged. -- Do not add the scene artifact or a derived classification to the combat - prompt. Do not change combat prompt assets, prompt ordering, prompt inputs, - response schema, or prompt fingerprint. -- Preserve existing combat extraction behavior for eligible combat chunks, - including NPC grounding, retries, validators, candidate mapping, and - normalization. -- Treat the scene artifact as control context, never as combat evidence. Never - copy its references, title, or summary into combat turns. -- Use deterministic, offline tests and existing LLM fakes. Do not call a live - provider or turn probabilistic model output into a correctness gate. -- Test stable behavior at the narrowest owning boundary. Do not add tests for - private helper shape, cache implementation details, or exact diagnostic prose. -- Do not add a framework abstraction unless implementation proves the existing - reference contracts cannot express this feature. No such framework gap is - currently expected. +Use the same identity formats, MIME type, metadata keys, and fingerprint +construction conventions as the other current D&D extractors. -## Stage 1 — Add the Domain Scene-Eligibility Reference Boundary +### Durable Go shape + +Add public D&D domain types equivalent to: + +```go +type ItemEventKind string + +const ( + ItemEventKindDiscovered ItemEventKind = "discovered" + ItemEventKindAcquired ItemEventKind = "acquired" + ItemEventKindLost ItemEventKind = "lost" + ItemEventKindConsumed ItemEventKind = "consumed" + ItemEventKindTransferred ItemEventKind = "transferred" +) + +type ItemEventList struct { + Events []ItemEvent `json:"events"` +} + +type ItemEvent struct { + Name string `json:"name"` + Kind ItemEventKind `json:"kind"` + Quantity *int `json:"quantity,omitempty"` + From string `json:"from,omitempty"` + To string `json:"to,omitempty"` + SourceRefs []source.SourceRef `json:"source_refs"` +} +``` + +The exact source import follows the existing D&D types file. `Quantity` is a +pointer so omission is distinguishable from an explicitly invalid zero. Empty +`From` and `To` values mean the optional property is absent. + +### Event semantics + +The allowed combinations are: + +| Kind | `from` | `to` | Meaning | +| --- | --- | --- | --- | +| `discovered` | absent | absent | The party learned that the item exists or encountered it without establishing possession. | +| `acquired` | absent | required | A party member or the collective party gained possession. | +| `lost` | required | absent | A party member or the collective party ceased to possess the item for a reason other than intended consumption. | +| `consumed` | required | absent | Intended use depleted the item or currency. | +| `transferred` | required | required | Possession moved between two distinct named party members. | + +The reserved holder value `party` represents collective or unresolved party +possession. It is allowed for `acquired`, `lost`, and `consumed`, but not for +either side of `transferred`. A transfer must name two distinct party members. +Compare holders case-insensitively and Unicode-normalized when determining +whether they are distinct, but preserve their display spelling. + +`Quantity`, when present, must be a positive integer grounded explicitly in the +source. Currency is represented as an item whose `name` preserves the stated +denomination; do not convert denominations or calculate balances. + +Every event must contain at least one source reference. Ordinary non-consuming +use is not an event. Gifts, sales, or payments to someone outside the party are +`lost`; destruction, abandonment, and theft are also `lost`. Emit both +`discovered` and `acquired` only when the source independently establishes both +events. + +### Deterministic order and equality + +Create one canonical item-event helper package and make both normalization and +invariant validation use it. The canonical order is: + +1. earliest valid source position, using the pipeline source-document order; +2. normalized comparison form of `name`, then exact trimmed `name`; +3. `kind`; +4. `from` presence, normalized comparison form, then exact trimmed value; +5. `to` presence, normalized comparison form, then exact trimmed value; +6. `quantity` presence, then numeric value; +7. the complete canonical source-reference sequence. + +Absent optional fields sort before present fields. Valid source positions sort +before malformed positions; comparison must remain total and panic-free for +invalid candidates so validators can report them. + +An exact duplicate has the same trimmed name, kind, quantity presence and value, +trimmed holders, and complete canonical source-reference sequence. Do not merge +records that differ in any of those fields. Use collision-safe equality or key +construction rather than delimiter concatenation. + +## Stage 1: Domain Contract, Canonical Rules, and Durable Codec ### Goal -Create one domain-owned, immutable boundary that decodes an approved -scene-description reference, retains only gating data, and supports both -construction-time external references and operation-time generated references. -This stage must not change combat extraction behavior. +Establish the public artifact contract and strict durable boundary without +registering an incomplete production module. -### Implementation +### Changes -1. Add `internal/modules/dnd/scenedescriptions/registry`, following the useful - external/generated resolution pattern in `internal/modules/dnd/npcs/registry` - without copying NPC-specific projection or identity policy. -2. Define these package contracts: - - reference slot: `scene_descriptions`; - - maximum item size: 1 MiB (`1048576` bytes); - - accepted representation: exactly one `application/json` item decoded by - `codec/scenedescriptions`; - - eligibility projection: scene ID, exact `source_ref`, and `kind` only. -3. Decode through the approved scene-description codec. Reject: - - more than one item; - - invalid or non-JSON media types; - - oversized content; - - invalid approved scene JSON; and - - duplicate scene IDs, even if duplicate records are otherwise identical. -4. Do not retain titles, summaries, original bytes, paths, or prompt material in - the resolved eligibility view. Framework reference provenance remains the - owner of raw reference identity. -5. Build an immutable lookup keyed by exact scene ID. Expose a chunk-match result - with three stable states: - - exact: ID, source ID, start unit ID, and end unit ID all match; - - missing: no record has the current chunk ID; - - mismatched: the ID exists but any source-range field differs. - Only the exact state exposes the scene kind. -6. Compute a semantic eligibility digest from a canonical projection sorted by - scene ID. The digest must: - - ignore title, summary, and input array order; and - - change when an ID, source-range field, or kind changes. - Represent an unbound view with the canonical empty projection - `{"scenes":[]}` and its `sha256:` digest so generated-reference construction - has a stable sentinel without pretending the required reference was bound. -7. Provide a resolver that: - - seeds and validates an external materialized item during construction; - - permits an unbound seed because a generated required reference is not - populated until the operation-time handoff; - - resolves an operation-time generated item when present; - - otherwise returns the seeded view; and - - safely reuses resolved immutable views across concurrent extract jobs. -8. Expose only the accessors required by combat extraction: bound state, record - count, eligibility digest, and exact chunk matching. Return defensive values - rather than mutable maps or scene records. +1. Extend `internal/modules/dnd/types.go` with: + - `ItemEventListKind`; + - `ItemEventKind` and the five constants above; + - `ItemEventList` and `ItemEvent` using the fixed durable shape. +2. Add `internal/modules/dnd/itemevents` as the canonical domain helper package. + It must own: + - supported-kind checks; + - validation of the kind-specific holder combination; + - holder comparison sufficient to reject `party` transfers and + self-transfers; + - canonical ordering; + - exact-duplicate comparison; + - source-reference equality and validity helpers needed by normalization and + invariant validation. +3. Reuse the existing normalized comparison-key implementation used by D&D NPC + identity code for Unicode- and case-insensitive comparison. Use + `strings.TrimSpace` for item and holder display normalization. Do not broaden + this stage into a generic identity-package refactor. +4. Add `internal/modules/dnd/codec/itemevents` following the current D&D typed + codec pattern: + - embed a strict JSON Schema; + - distinguish candidate decoding from approved encode/decode validation; + - reject unknown fields; + - expose the fixed artifact and schema identities; + - report only bounded structural metadata such as `event_count`; + - deep-clone source-reference slices and quantity pointers where ownership + crosses a boundary. +5. Define the durable JSON Schema with: + - a required top-level `events` array, which may be empty; + - required event properties `name`, `kind`, and non-empty `source_refs`; + - the five-value `kind` enum; + - non-empty strings for `name` and for holders when present; + - an integer `quantity` with minimum `1`; + - strict item, source-reference, and top-level objects; + - conditional requirements/prohibitions matching the event-semantics table. +6. Enforce transfer holders being distinct and not equal to `party` in approved + codec validation, because those comparisons are not cleanly expressed by the + JSON Schema. Candidate decoding must still preserve semantically invalid + values for the validation/retry pipeline. ### Tests -Add focused package tests that own: +Add package tests covering: -- approved decoding and exact match classification; -- missing and mismatched match states; -- strict item count, media type, size, codec, and duplicate-ID failures; -- semantic digest stability across prose and ordering changes; -- semantic digest changes for each gating field; -- construction-time seed versus operation-time generated override; and -- immutability and concurrent-safe reuse at the public package boundary where - practical. +- all valid event kinds and holder combinations; +- collective-party acquisition, loss, and consumption; +- rejection of `party` transfers and case/Unicode-equivalent self-transfers; +- omitted versus present quantity and rejection of zero or negative quantity; +- strict unknown-field rejection; +- empty event lists; +- missing or empty source references; +- candidate decoding preserving values that approved decoding rejects; +- canonical order, including all tie-breakers and malformed references; +- exact equality distinguishing optional-field presence/value and complete + evidence; +- deep-copy behavior for source references and quantity pointers; +- stable schema identity and metadata without payload leakage. -Do not duplicate the scene codec's exhaustive JSON-shape tests. +### Completion Criteria -### Validation +- Public item-event values round-trip through the approved codec only when they + satisfy the durable contract. +- Candidate decoding can carry invalid semantic values to validators. +- Canonical comparison and equality have a single tested implementation. +- No item-event module, validator chain, or prompt is globally registered yet. -Run: - -```sh -go test ./internal/modules/dnd/scenedescriptions/registry -go test ./internal/modules/dnd/codec/scenedescriptions -git diff --check -``` - -Stage 1 is complete when the new package is independently usable and tested but -no production module selects it yet. - -## Stage 2 — Enforce the Required Combat Scene Gate +## Stage 2: Extractor, Private Response Schema, and Prompt Assets ### Goal -Make `dnd/combat-turns` require the scene-description artifact and invoke its -existing LLM path only for exact combat scenes. Update directly affected -configuration fixtures and contract tests in the same stage so the repository -does not retain knowingly invalid combat configurations. +Implement source-grounded item-event extraction while preserving the boundary +between provider output and the durable artifact. -### Module Contract +### Changes -1. In `internal/modules/dnd/extract/combatturns`, add aliases for the - scene-registry slot and size limit and declare a reference slot with: - - name `scene_descriptions`; - - `Required: true`; - - media type `application/json`; - - accepted artifact kind `dnd/scene-description-list`; - - maximum size 1 MiB; and - - a description stating that it gates combat eligibility and is not combat - evidence. -2. Preserve sorted reference-slot ordering and the existing agreement between - `ReferenceSlots()` and `ModuleSpec().ReferenceSlots`. -3. Add a scene resolver to the immutable extractor. Constructor behavior must: - - validate a materialized external scene artifact before execution; - - allow the construction reference set to lack an item for a configured - generated binding; and - - retain no scene prose. -4. Define a stable gating policy identity - `dnd.combat_turns.scene_gate.v1`. -5. Extend manifest metadata with `scene_gate_policy`. For a seeded external - reference only, also report `scene_eligibility_digest` and - `scene_description_count`. Do not publish those operation-time values for a - generated reference in singleton component metadata; framework provenance - owns that handoff. -6. Extend prepared-component checkpoint fingerprints with: - - `scene_gate_policy`; and - - `scene_eligibility`, using the seeded external eligibility digest or the - registry's stable unbound sentinel for a generated binding. - Keep the existing prompt, response-schema, mapping-policy, and NPC-registry - fingerprints unchanged. +1. Add `internal/modules/dnd/extract/itemevents` following the common D&D + extractor structure: + - key `dnd/item-events`; + - requires `chunks` and `source.transcript`; + - provides `dnd.item_events`; + - strict empty options; + - nil-client guard; + - shared chunk preflight and source-reference ordering; + - the current D&D default model profile and repair-attempt convention. +2. Define private provider response structs inside the extractor package. The + response contains a required `events` array whose entries have: + - required `name`, `kind`, and `source_refs`; + - optional pointer `quantity`; + - optional `from` and `to`; + - private source ranges containing only `start_segment` and `end_segment`. +3. Add the embedded private structured-output schema using the fixed private + identities. Keep it structurally strict, but do not encode the durable + semantic enum, positive-quantity, holder-combination, or transcript-range + rules there. Those rules belong to deterministic application validators so + invalid model output participates in the normal retry/diagnostic flow. +4. Map the private response to `dnd.ItemEventList` by: + - copying all provider fields without semantic rewriting; + - attaching the current source ID to every private source range; + - canonicalizing and deduplicating source references using the shared source + order; + - deterministically ordering candidates by earliest evidence as the existing + D&D extractors do; + - preserving quantity presence with a fresh pointer. +5. Add item-specific prompt assets for the task and instructions only. The + instructions must state all event semantics from the feature roadmap, + including currency, holder rules, non-consuming use, and the distinction + between discovery and acquisition. +6. Assemble the prompt from existing shared assets in this order: + - shared D&D system message; + - shared extraction-evidence user message; + - shared identity/reference context messages; + - item-event task message; + - rendered item-event instructions; + - shared transcript message last. +7. Accept the same prompt inputs as the NPC extractor: + - required transcript; + - optional players, party, and glossary references. + Do not accept or inject NPC registries, scene descriptions, item registries, + or output from another generated lane. +8. Use the shared prompt manifest and caching metadata conventions. Reuse shared + asset descriptors rather than creating item-specific copies. Publish the + prompt, response-schema, and extraction-mapping policy identities and their + fingerprints in module metadata. -### Extraction Behavior +### Tests -In `Extractor.Extract`, retain common request validation through -`shared.PrepareChunkExtraction`, then resolve and apply the scene gate before -building prompt inputs or resolving NPC prompt grounding: +Add extractor and prompt tests covering: -1. If the operation-time scene reference is absent, return a contextual module - error stating that the required reference is missing. Normal configured runs - should have failed earlier during resolution or required-producer handoff; - this check protects direct module use. -2. For an exact `combat` match, continue through the existing NPC resolution, - prompt construction, LLM request, response mapping, and validation path - without semantic changes. -3. For an exact `narrative`, `recap`, or `meta` match, return - `dnd.CombatTurnList{CombatTurns: []dnd.CombatTurn{}}`. Make no LLM call and - emit no warning. -4. For a missing or mismatched match, return the same non-nil empty list without - an LLM call and attach exactly one content-safe warning: - - scope: `scene_descriptions`; - - reason code: `scene_classification_unavailable`; - - message: explain that no exact classification was available and combat - extraction was skipped, without scene prose or transcript content. -5. The deterministic result is an accepted extract result, not an error or - rejection. It must pass the existing combat shape, source-reference, schema, - merge, and normalization boundaries and must not consume retry attempts. -6. Do not pass `scene_descriptions` into `shared.PromptInputs` or add it to the - structured completion request. The current shared helper already selects - only campaign and transcript inputs; preserve that boundary explicitly. +- key, capabilities, options, nil-client behavior, and preflight errors; +- private-schema identity and strict structural decoding; +- all five kinds, currency quantity, collective `party`, and omitted optional + fields mapping into durable candidates; +- source-ID attachment, source-reference canonicalization, deterministic + ordering, and quantity-pointer independence; +- preservation of invalid semantic candidates for validators; +- empty provider results producing a valid empty candidate list; +- exact prompt asset sequence and roles, with transcript last; +- optional reference rendering and absence behavior; +- reuse of shared asset descriptors and no module-specific duplicate of shared + prompt text; +- stable fingerprint metadata that changes for item-specific prompt/schema/ + mapping changes without exposing rendered content. -### Required Test And Fixture Migration +Do not add a test that asserts the numeric length of a shared prompt prefix. -1. Update combat extractor test helpers so tests of the existing LLM behavior - supply an exact combat scene artifact for their current chunk. Keep - NPC-specific test intent unchanged. -2. Add a compact table-driven extractor test covering: - - exact combat; - - exact narrative, recap, and meta; - - missing scene ID; - - matching ID with each mismatched range identity; and - - an operation-time missing required reference. - Assert typed output, warning reason where applicable, and whether the fake LLM - was called. Do not assert private helper calls. -3. Update module specification, registration, metadata, and fingerprint tests - for the required slot and new policy identities. Assert that changing only - scene prose does not change the eligibility fingerprint, while changing - kind or range does. -4. Update every in-repository pipeline or programmatic test configuration that - selects `dnd/combat-turns`: - - the maintained complete example must bind the earlier - `scene-descriptions` lane to the later `scene_descriptions` step reference; - - CLI combat contract configurations must bind a compatible external scene - artifact when testing a standalone one-step profile; - - the ordered D&D integration fixture must produce scene descriptions in its - first step and bind them in its consumer step; and - - fake clients used by those integration tests must return an exact combat - scene where the test expects the combat LLM path. -5. Add or update a configuration-resolution assertion proving that a selected - combat extractor without `scene_descriptions` is rejected as an unbound - required slot. -6. Update existing expected lane counts, schemas, request sets, fingerprints, - and provenance counts only where the new scene producer or consumer - materially changes the contract. Preserve the original NPC-grounding - assertions. +### Completion Criteria -### Validation +- A fake structured-completion client can drive the extractor from a transcript + chunk to a typed candidate artifact. +- Prompt ordering supports backend prompt caching and reuses shared assets + exactly. +- The extractor is package-complete but is not yet added to production + registration. -Run: - -```sh -go run ./cmd/notarius config validate \ - --config examples/dnd-complete.config.yml \ - --pipeline dnd-session -go test ./internal/modules/dnd/... -go test ./internal/modules/integration -go test ./internal/cli -git diff --check -``` - -Stage 2 is complete when required-slot resolution is enforced, every combat -extract path obeys the eligibility rule, all affected configurations are valid, -and focused suites pass. - -## Stage 3 — Prove Ordered Handoff, Provenance, And Retry Behavior +## Stage 3: Shape and Source-Reference Validators ### Goal -Protect the cross-component behavior that cannot be established by the -scene-registry and extractor package tests alone. Keep this stage lean: reuse -the real codecs, resolver, pipeline, and validators, with a deterministic fake -only at the LLM boundary. +Add deterministic blocking validation for event semantics and evidence. -### Integration Coverage +### Changes -1. Extend or refactor the focused ordered D&D integration test so it proves, in - one representative assembled workflow: - - scene descriptions and NPCs normalize in the first step; - - their accepted artifacts are supplied to the intended second-step targets; - - an exact combat scene causes exactly one combat completion request; - - the combat request contains transcript, campaign, and NPC inputs as - configured but no `scene_descriptions` input; - - the combat output and existing NPC canonicalization remain correct; - - the manifest records one generated scene-description reference for the - combat extract target with the expected kind, schema, media type, digest, - and producer identity; and - - manifest and component metadata contain no scene title, summary, transcript - text, or generated artifact payload. -2. Add one focused non-combat ordered-run case using an exact `narrative` - classification. Configure combat retries to a value greater than zero, then - prove: - - no combat completion request occurs; - - one accepted empty combat lane reaches normalized output; - - no retry or rejection is recorded for the deterministic skip; and - - no unavailable-classification warning is emitted for an exact non-combat - match. -3. Add one focused missing-or-mismatched case at the narrowest stable boundary - not already covered by Stage 2. Prove the accepted empty result and - `scene_classification_unavailable` warning reach the runner's durable warning - collection without a combat LLM call. Do not repeat every mismatch variant - at integration scope. +1. Add `internal/modules/dnd/validate/itemevents/shape`: + - key `extract/dnd/item-events/shape`; + - policy identity `dnd.item_events.shape.v1`; + - blocking reason code `invalid_item_event_shape`. +2. The shape validator must reject: + - absent or incorrectly typed item-event artifacts; + - blank item names after trimming; + - unsupported kinds; + - invalid kind-specific `from`/`to` combinations; + - `party` on either side of a transfer; + - transfers whose holders have the same normalized comparison key; + - present quantities less than one; + - missing source-reference lists. +3. Permit leading/trailing whitespace in nonblank display fields at the + extraction boundary so the normalizer can perform deterministic trimming. + Treat an empty optional holder string as absence. +4. Add `internal/modules/dnd/validate/itemevents/source_refs`: + - key `extract/dnd/item-events/source_refs`; + - policy identity `dnd.item_events.source_refs.v1`; + - blocking reason code `invalid_item_event_source_references`. +5. Mirror the current D&D source-reference validator behavior: + - validate source IDs and inclusive segment ranges against the available + source document; + - when validating extraction for a specific chunk, require each range to be + wholly contained in that chunk; + - when validating a merged or normalized artifact without a chunk, validate + against the source document without imposing a single-chunk condition; + - emit bounded, indexed diagnostics. +6. Share the canonical domain helpers from Stage 1 rather than independently + encoding holder semantics or source-reference validity. -### Checkpoint And Identity Coverage +### Tests -1. Confirm the existing framework-generated-reference dependency fingerprint - already includes the canonical generated scene artifact. Reuse that - mechanism; do not add a second framework fingerprint path. -2. Add focused D&D coverage proving: - - a seeded external reference's component fingerprint changes when eligibility - kind or exact range changes; - - title-only or summary-only changes leave that semantic component - fingerprint unchanged; and - - the generated reference appears in the combat extract checkpoint - dependencies, so changing the generated artifact prevents reuse. -3. Prefer an existing checkpoint identity or prepared-pipeline test boundary. - Do not add a full CLI resume test if the same dependency invalidation is - already credibly protected by the framework checkpoint tests plus the D&D - prepared-pipeline assertion. +Cover every accepted and rejected row of the event-semantics table, plus: -### Validation +- whitespace that is normalizable versus whitespace-only required fields; +- quantities `nil`, `1`, `0`, and negative; +- transfer holder comparison across case and Unicode normalization; +- `party` in each applicable and inapplicable position; +- empty lists and multiple independently diagnosed invalid records; +- unknown source IDs, reversed ranges, out-of-bounds ranges, and extraction + ranges crossing the current chunk; +- valid multi-range and multi-chunk post-merge evidence; +- diagnostic caps and stable policy metadata. -Run: +### Completion Criteria -```sh -go test ./internal/modules/integration -go test ./internal/framework/pipeline -go test ./internal/cli -go test ./internal/modules/dnd/extract/combatturns -go test ./internal/modules/dnd/scenedescriptions/registry -git diff --check -``` +- Invalid event semantics and invalid evidence fail with distinct owning + validators and reason codes. +- Valid candidates, including empty lists and currency events, pass. +- The validators remain package-complete but are not yet in production chains. -Stage 3 is complete when the ordered generated handoff, no-call behavior, -warning propagation, provenance privacy, and checkpoint invalidation are -protected at their stable owning boundaries without redundant end-to-end cases. - -## Stage 4 — Update Canonical Documentation And Finish Validation +## Stage 4: Deterministic Normalizer and Normalized Invariants ### Goal -Move the implemented behavior out of future-only documentation and into each -canonical current-behavior owner, then perform repository-wide verification. +Normalize presentation and ordering without inventing inventory semantics, then +verify the normalized contract independently. -### Documentation +### Changes -1. Update `docs/config.md` to own: - - the combat extractor's required `scene_descriptions` slot; - - accepted artifact kind, JSON media type, single-item and 1 MiB limits; - - configuration-time rejection when the slot is unbound; and - - the explicit generated binding used by the complete example. - Update illustrative combat configuration fragments so none show an invalid - unbound combat extractor. -2. Update `docs/operations.md` to explain that the first step produces NPC and - scene-description artifacts and the second step runs combat extraction only - for exact combat scenes. -3. Update `docs/integrations/dnd-combat-turn-artifacts.md` to own the externally - observable eligibility, empty-result, warning, retry, prompt-exclusion, and - provenance behavior. -4. Add a concise downstream-consumer link or summary to - `docs/integrations/dnd-scene-description-artifacts.md` without duplicating - the combat contract. -5. Update `docs/internal/modules.md` with the scene eligibility registry, - construction-time versus operation-time resolution, exact matching, - fingerprint, and content-retention boundaries. Keep generic ordered-handoff - mechanics canonical in `docs/internal/pipeline.md`; update that document only - if implementation changed or clarified a generic mechanism. -6. Update `docs/internal/overview.md` only if its component inventory requires - the new domain package. -7. Remove the completed combat-gating bullet from - `docs/roadmap/future.md`. Preserve the remaining scene-chunking evaluation - and ordered-pipeline guidance. -8. Review `docs/roadmap/scene-aware-combat-extraction.md` for consistency with - the final implementation. Keep it as feature history until a later explicit - retirement request; do not duplicate its implementation sequence there. -9. Verify that README and CLI documentation need no changes. Update them only - if an existing command or linked example became inaccurate. +1. Add `internal/modules/dnd/normalize/itemevents`: + - key `dnd/item-events`; + - requires `merged`; + - provides `normalized`; + - strict empty options; + - policy identity `dnd.item_events.normalize.v1`; + - no generated-reference dependency. +2. For each event, the normalizer must: + - trim leading/trailing whitespace from `name`, `from`, and `to`; + - preserve case and internal spelling; + - clone the optional quantity; + - canonicalize and deduplicate source references using the source-document + order; + - avoid changing kind, quantity, holders, or event meaning. +3. Sort the list with the Stage 1 canonical comparator. +4. Collapse exact duplicates only after display trimming and source-reference + canonicalization. Preserve invalid records when safe to do so; do not allow + malformed evidence to panic or cause unrelated records to disappear. +5. Emit bounded warning diagnostics, following existing D&D normalizer + conventions, for: + - normalized display fields; + - canonicalized source references; + - reordered events; + - collapsed exact duplicates; + - omitted warnings after the cap. +6. Publish normalization policy metadata and fingerprint using the common + normalizer conventions. +7. Add `internal/modules/dnd/validate/itemevents/invariants`: + - key `normalize/dnd/item-events/invariants`; + - policy identity `dnd.item_events.normalize_invariants.v1`; + - blocking reason code `invalid_normalized_item_event_invariants`. +8. The invariant validator must require: + - trimmed display fields; + - canonical source-reference sequences; + - canonical list order; + - no exact duplicates. + It must use the same Stage 1 helpers as the normalizer. It should defer + malformed shape and range reporting to their owning validators instead of + emitting competing diagnoses. + +### Tests + +Cover: + +- each individual trim and source-reference normalization; +- canonical ordering through every tie-breaker; +- exact-duplicate collapse and preservation of near-duplicates; +- quantity-pointer cloning; +- empty and singleton lists; +- deterministic behavior under repeated normalization; +- malformed candidates remaining diagnosable without panics; +- every invariant failure independently; +- agreement between normalizer output and invariant validation; +- warning caps and non-sensitive metadata/fingerprints. + +Include at least one test demonstrating that no aliasing, singularization, +denomination conversion, discovery inference, acquisition inference, transfer +inference, or ledger calculation occurs. + +### Completion Criteria + +- Normalizing the same merged artifact repeatedly is idempotent. +- Every valid normalizer output passes the invariant validator. +- Semantically distinct events remain distinct. +- The normalizer and invariant validator are not yet globally registered. + +## Stage 5: Relatedness Validator and Production Registration + +### Goal + +Add advisory grounding checks, then register the complete module stack and its +default validation chains atomically. + +### Changes + +1. Add `internal/modules/dnd/validate/itemevents/source_relatedness`: + - key `extract/dnd/item-events/source_relatedness`; + - policy identity `dnd.item_events.source_relatedness.v1`; + - warning reason code `item_event_source_unrelated`. +2. For every event with otherwise readable evidence, aggregate the cited + transcript text and look for a normalized token sequence derived from the + item `name`, using the existing shared D&D token helpers. Do not require + holder names or quantities to appear in every cited range. Relatedness is + advisory only, so aliases, currency abbreviations, and transcript variation + may warn but must not block. +3. Bound warnings consistently with the other D&D relatedness validators and + avoid duplicating range-validity diagnostics owned by the source-reference + validator. +4. Update D&D production registration to add: + - the typed item-event codec; + - the standard always-accept and always-reject typed validators; + - an append-order merger that deep-clones item events, quantity pointers, and + source references while preserving nil-versus-empty list behavior; + - the item-event extractor and its prompt/schema assets; + - the custom item-event normalizer and standard no-op normalizer; + - shape, source-reference, source-relatedness, and invariant validators. +5. Add default chains with these exact members and ordering: + + Extraction: + + 1. generic valid JSON; + 2. item-event shape; + 3. item-event source references; + 4. generic valid JSON Schema; + 5. item-event source relatedness. + + Normalization: + + 1. generic valid JSON; + 2. item-event shape; + 3. normalized item-event invariants; + 4. item-event source references; + 5. generic valid JSON Schema; + 6. item-event source relatedness. + +6. Do not add a registry validator or a generated-reference capability. Item + events intentionally operate independently of NPC, scene, and other + extraction outputs. +7. Extend registration and chain contract tests so omissions, duplicate + registration, incorrect order, policy-identity collisions, and capability + drift fail clearly. + +### Tests + +Add tests for: + +- related and unrelated item names, punctuation/case variation, multiple source + ranges, invalid ranges, and capped warnings; +- registration of every item-event component and asset; +- exact default-chain order and blocking/advisory behavior; +- append-order merge semantics, deep cloning, and nil/empty preservation; +- duplicate-key and identity uniqueness across the full production registry; +- module metadata and capabilities containing identities only, not rendered + content. + +### Completion Criteria + +- The fully assembled registry exposes the item-event extractor, codec, merger, + normalizers, validators, prompt assets, and both default chains. +- The default extraction and normalization chains accept valid item events, + block invalid shape/evidence, and only warn on relatedness. +- Existing registered modules and chains remain unchanged. + +## Stage 6: End-to-End Integration and Maintained Configuration + +### Goal + +Prove the production path and make the complete supported configuration exercise +the new independent lane. + +### Changes + +1. Add a focused production-registry integration test that runs an item-events + lane from a transcript chunk through: + - extraction; + - candidate validation; + - merge; + - deterministic normalization; + - normalized validation; + - durable encode/decode. +2. Use a deterministic fake structured-completion client. Exercise at least: + - one currency event with explicit quantity; + - one event using collective `party`; + - one transfer between named party members; + - multiple events returned out of source order; + - an exact duplicate that normalization removes; + - a relatedness warning that does not fail the run. +3. Add focused negative integration coverage proving a blocking shape or + source-reference failure follows the configured retry/failure path rather + than bypassing validation. Do not use a live LLM. +4. Add `dnd/item-events` to the complete D&D configuration example. Place it in + the first pipeline step alongside other independent extraction lanes; do not + give it a dependency on generated NPC or scene artifacts. Leave the minimal + example unchanged. +5. Update maintained example contract tests and deterministic CLI/provider test + doubles so they recognize `dnd.item_events`, return a structurally valid + private response, and account for the additional output artifact. Avoid + brittle whole-file snapshots where focused semantic assertions suffice. +6. Verify the example still demonstrates the architecture already selected for + other lanes: independent work in the first step and only genuinely dependent + lanes in later steps. + +### Tests and Verification + +Run: + +- the focused new integration tests; +- example/configuration contract tests; +- CLI tests that execute or inspect the complete example; +- `go test ./...`. + +Also perform a local configuration validation or dry run using the repository's +documented offline-safe mechanism. Do not require credentials or a provider +network call. + +### Completion Criteria + +- A production-registry pipeline produces a durable, normalized + `dnd/item-event-list`. +- The complete example configures the lane in the correct independent step and + remains accepted by configuration validation. +- The minimal example and all existing module behavior remain unchanged. + +## Stage 7: Canonical Documentation and Final Verification + +### Goal + +Document the implemented contract in its owning locations, retire the future +work entry, and perform repository-wide verification. + +### Changes + +1. Add a canonical item-event artifact guide under `docs/integrations/`, + following the current D&D artifact documentation pattern. Document: + - module, capability, artifact, and schema identities; + - every field and event-kind rule; + - the reserved `party` holder; + - currency representation; + - source-reference requirements; + - deterministic order and exact-duplicate behavior; + - representative JSON for every kind; + - the distinction between event extraction and inventory/ledger state; + - the absence of dependencies on generated NPC, scene, or item registries. +2. Update the canonical configuration/module documentation to list: + - the extractor; + - supported prompt inputs; + - the merger and normalizers; + - validator keys and default chains; + - the artifact kind and codec. +3. Update internal architecture/module documentation and operational workflow + documentation wherever their exhaustive lists or complete-example walkthrough + would otherwise become inaccurate. +4. Update `docs/roadmap/future.md` to remove the implemented item-tracking entry + rather than leaving duplicate current and future documentation. Preserve + unrelated future ideas. +5. Mark `docs/roadmap/dnd-item-events.md` as implemented. Keep it as the feature + intent record for now; roadmap retirement is a separate cleanup decision. +6. Check links, terminology, and identity strings across code, schemas, examples, + and docs. There must be no accidental use of `items`, `item-tracking`, + `inventory-events`, or a `v2` identity where the fixed names require + `item-events` and `v1`. ### Final Verification -Run all repository checks: +Run the repository-documented: -```sh -go run ./cmd/notarius config validate \ - --config examples/dnd-minimal.config.yml \ - --pipeline dnd-session -go run ./cmd/notarius config validate \ - --config examples/dnd-complete.config.yml \ - --pipeline dnd-session -go test ./... -go vet ./... -go build ./cmd/notarius -git diff --check -``` +- formatter; +- full test suite; +- static analysis; +- documentation/link checks, if available; +- configuration/example validation. -Also verify: +Inspect `git diff --check` and the final diff. Confirm that: -- only the two maintained D&D configuration examples remain; -- every current documentation link points to an existing file; -- no current-behavior document describes combat extraction without the required - scene gate; -- no prompt asset or combat response schema changed; and -- no generated build artifact is left in the working tree. +- no live-provider or credential-bearing test was introduced; +- no shared prompt content was copied into item-specific assets; +- no exact shared-prefix-length change-detector test was added; +- no raw transcript, prompt, reference, or credential data appears in metadata, + diagnostics, or fingerprints; +- no item ledger, balance, item identity registry, alias resolution, ordinary-use + tracking, or other roadmap non-goal slipped into scope; +- unrelated roadmap and user changes are preserved. -Stage 4 is complete when documentation ownership is correct, future work no -longer lists the implemented item, both examples validate, and all repository -checks pass. +### Completion Criteria + +- Code, examples, and canonical documentation describe the same item-event + contract and identities. +- The future roadmap no longer presents this implemented feature as future work. +- All required repository checks pass. ## Open Questions -None. The feature contract and this plan resolve the required behavior, -reference ownership, matching semantics, failure policy, warning policy, -checkpoint identity, documentation ownership, and test boundaries. +None. The feature roadmap and the fixed decisions above define the artifact, +event semantics, pipeline placement, validation ownership, normalization rules, +and documentation boundary needed to implement each stage without further +product decisions.