diff --git a/docs/integrations/dnd-combat-turn-artifacts.md b/docs/integrations/dnd-combat-turn-artifacts.md index 6ffdf22..c7c0b93 100644 --- a/docs/integrations/dnd-combat-turn-artifacts.md +++ b/docs/integrations/dnd-combat-turn-artifacts.md @@ -1,10 +1,10 @@ -# D&D Combat-Turn Artifact Contract +# D&D Combat-Turn Artifact -This document defines the durable artifact, serialization, extraction, -candidate-validation, normalization, and production lane boundaries for D&D -combat turns. +This contract defines the durable combat-action occurrence list produced by +`dnd/combat-turns`. It records source-grounded turns and actions; it is not a +complete initiative tracker, combat summary, or state model. -## Artifact identity +## Identity and compatibility | Property | Value | | --- | --- | @@ -14,167 +14,56 @@ combat turns. | Schema version | `v1` | | Media type | `application/json` | -The top-level JSON object contains the required `combat_turns` array, which -may be empty. Every object rejects unknown fields. +`v1` is a strict JSON object with required `combat_turns`; the array may be +empty. Turn and source-reference objects reject unknown fields. A future +incompatible shape requires a new schema version. -## JSON shape +## Wire shape -Each combat turn contains these required fields: +Each combat turn has these required fields: -| Field | Shape | +| Field | Contract | | --- | --- | -| `actor` | Non-empty string. | -| `turn_kind` | One of `turn`, `reaction`, `legendary_action`, `lair_action`, or `other`. | -| `source_refs` | Required array with at least one source reference. | +| `actor` | Non-empty acting character or creature name. | +| `turn_kind` | `turn`, `reaction`, `legendary_action`, `lair_action`, or `other`. | +| `source_refs` | One or more transcript evidence ranges. | -Source references use the shared source-reference shape: +Each source reference has exactly `source_id`, `start_unit_id`, and +`end_unit_id`. It identifies an inclusive current-transcript range; unit IDs +are positive and the start may not follow the end. ```json { - "source_id": "session-alpha", - "start_unit_id": 1, - "end_unit_id": 2 + "combat_turns": [ + { + "actor": "Mira Thorn", + "turn_kind": "turn", + "source_refs": [ + {"source_id": "session-7", "start_unit_id": 31, "end_unit_id": 32} + ] + } + ] } ``` -`source_id` must be non-empty and both unit IDs must be positive integers. The -codec does not resolve references against a source document or enforce source -range ordering; those checks belong to the later source-reference validation -boundary. +## Eligibility, evidence, and normalized form -## Codec behavior +The extractor requires an approved [scene-description artifact](dnd-scene-description-artifacts.md). +It emits combat turns only for a chunk with an exact matching scene classified +`combat`; an exact non-combat scene produces an accepted empty list. The scene +record controls eligibility only: its title, summary, and reference do not +become turn evidence. No exact matching scene also produces an empty list and +the `scene_classification_unavailable` warning. -The codec exposes two representations of the same typed artifact: +An optional normalized [NPC artifact](dnd-npc-artifacts.md) can ground an +actor name. Its registry references are provenance, never combat evidence. +Normalization trims and, where possible, canonicalizes actor names; orders and +deduplicates exact source references; orders valid-evidence turns by source +chronology; and collapses only duplicates with the same actor identity, turn +kind, and complete valid evidence. It does not infer turns, initiative, or +actions from registry or scene data. -- Candidate encode/decode preserves invalid actor and turn-kind values, - collection presence, and source references so later validators can report - them. Candidate decoding still requires valid JSON, one JSON value, known - fields, and compatible JSON types. -- Approved encode/decode enforces the structural rules in this contract. - -The codec owns the durable JSON Schema, whose object layers all set -`additionalProperties` to `false`. Codec metadata contains only -`combat_turn_count`. - -The maintained compact fixture is -`internal/modules/dnd/codec/combatturns/testdata/dnd_combat_turns.v1.json`. - -## Extraction boundary - -The standalone extractor uses these identities: - -| Property | Value | -| --- | --- | -| Extractor key | `dnd/combat-turns` | -| Capability | `dnd.combat_turns` | -| Prompt ID | `dnd.combat_turns` | -| Prompt version | `v1` | -| Private response-schema key | `dnd_combat_turns_llm` | -| Private response-schema ID | `notarius.dnd.combat_turns.llm` | -| Default profile | `gemini-2-flash` | - -It requires `chunks` and `source.transcript`, accepts no options, and requires -one `scene_descriptions` reference. That reference must be exactly one approved -`dnd/scene-description-list` artifact with media type `application/json` and a -maximum size of 1 MiB (1048576 bytes). It may be an external file validated -during preparation or a canonical generated artifact supplied at an ordered -step handoff. An unbound slot is a configuration error. - -The scene artifact controls eligibility, not evidence. The extractor calls the -LLM only when exactly one record has the current chunk's ID, source ID, start -unit ID, and end unit ID, and that record has `kind: combat`. An exact -`narrative`, `recap`, or `meta` record returns an accepted empty -`combat_turns` array without an LLM call, warning, or retry attempt. A missing -or mismatched exact record returns the same accepted empty result without an -LLM call or retry and emits one content-safe -`scene_classification_unavailable` warning. The scene artifact, its title and -summary, and its source references are never copied into combat turns. - -For an eligible combat chunk, the prompt receives the chunk-scoped transcript -plus the existing `players`, `party`, and `glossary` inputs, and optionally the -deprecated `roster` reference through the shared party mapping. The optional -`npcs` reference is an approved normalized NPC artifact used only for identity -grounding; it never supplies combat evidence. `scene_descriptions` is never a -combat prompt input. - -The private response envelope has the same turn fields and JSON types as the -durable shape except that source references contain only `start_unit_id` -and `end_unit_id`. It enforces required field presence, types, and -unknown-field rejection, while deterministic validators own enum membership, -non-empty values and collections, and positive-number requirements. The -extractor assigns the current source ID, removes exact duplicate ranges, and -stable-sorts turns by the earliest valid source-document position. Numeric unit -IDs are identifiers; source-document slice position determines chronology. -Semantically malformed candidate fields remain in the typed result for the -configured validation and retry boundary. - -## Deterministic candidate validation - -The standalone validator keys are: - -| Validator | Responsibility | -| --- | --- | -| `extract/dnd/combat-turns/shape` | Required list, actor, turn kind, and source references, plus supported turn-kind values. | -| `extract/dnd/combat-turns/source_refs` | Source identity, source-unit existence, and range order through the source document. | -| `extract/dnd/combat-turns/source_relatedness` | At most one advisory warning per turn when the actor is not related to cited transcript text. | - -Source-reference and relatedness validators defer malformed shape to the shape -validator. Relatedness also defers when any cited source range is invalid. It -combines overlapping cited ranges once in document order and compares actors -with the shared Unicode-aware NPC identity policy. - -The production D&D registrar exposes the extractor and these validators. Its -default extraction chain preserves this order: JSON syntax, combat shape, -source references, private response schema, then source relatedness. - -## Normalization boundary - -The standalone normalizer uses key `dnd/combat-turns`, requires `merged`, -provides `normalized`, accepts no options, and accepts only the optional -structured `npcs` reference. Campaign references are LLM extraction context and -are not normalizer inputs. For an external file, the NPC registry is resolved -during preparation; for a generated binding, it is resolved at the operation- -time handoff. Runtime normalization uses that immutable prepared or handed-off -view. - -Normalization policy is `dnd.combat_turns.normalize.v1`. It display-normalizes -the actor, canonicalizes exact registry actor matches, orders and deduplicates -exact source references, stable-sorts records by earliest valid source-document -position, and collapses only records with the same actor identity, turn kind, -and complete valid evidence set. The first normalized record is retained. -Invalid evidence is never eligible for duplicate collapse. Every mutation and -collapse emits a bounded warning using the merged input index in its scope. - -The normalizer reports `normalization_policy` and `identity_policy` metadata -and fingerprints. An external registry may additionally contribute -`npc_registry_digest` and `npc_count`; generated registry identity is retained -in framework handoff provenance and dependency fingerprints. The -normalized-invariants validator is -`normalize/dnd/combat-turns/invariants`; it defers shape and source-reference -failures, then checks actor display normalization, canonical evidence ordering, -chronology, and duplicate identity. It rejects -with `invalid_combat_turn_normalization` under policy -`dnd.combat_turns.validator.normalized.v1`. - -The production D&D registrar exposes the normalizer and normalized-invariants -validator. Its default normalization chain is JSON syntax, combat shape, -normalized invariants, source references, durable schema, then source -relatedness. The lane uses the framework's typed append-order merger and has no -merge validator chain. - -## Production manifest and references - -The selectable lane uses extractor and normalizer key `dnd/combat-turns`, -`appendorder` for the typed merger, and the durable codec above. Bound external -references contribute raw-file provenance to the run manifest. Generated -bindings contribute artifact kind, schema identity, media type, canonical -digest, size, and bounded producer provenance. This includes the generated -scene-description artifact for the combat extractor. Consumer metadata and -checkpoint fingerprints contain no reference names, content, paths, source -ranges, scene titles, or scene summaries. For an external scene artifact, -component metadata records the gate policy plus a semantic eligibility digest -and record count; generated scene identity remains in framework handoff -provenance and dependencies. The eligibility digest changes with scene ID, -exact source range, or kind, but not with title or summary. The normalized lane -is emitted as `lanes/.json` by the JSON output module, and warnings -and rejection summaries remain in their shared companion files. +The [NPC-interaction artifact](dnd-npc-interaction-artifacts.md) records +broader NPC occurrences. The [JSON output contract](json-output.md) defines +publication, and [D&D module internals](../internal/dnd.md) describes routing +and validation mechanics. diff --git a/docs/integrations/dnd-item-event-artifacts.md b/docs/integrations/dnd-item-event-artifacts.md index 13ee451..f9a2059 100644 --- a/docs/integrations/dnd-item-event-artifacts.md +++ b/docs/integrations/dnd-item-event-artifacts.md @@ -1,133 +1,78 @@ -# D&D Item-Event Artifact Contract +# D&D Item-Event Artifact -This document defines the durable D&D item-event artifact and its production -boundaries. It records source-grounded discoveries and possession changes; it -does not maintain an inventory or ledger. +This contract defines the durable item and currency occurrence list produced by +`dnd/item-events`. It records source-grounded discoveries and possession +changes; it does not maintain an inventory, balance, or ledger. -## Artifact Identity +## Identity and compatibility | Property | Value | | --- | --- | -| Extractor key | `dnd/item-events` | -| Extract capability | `dnd.item_events` | | Artifact kind | `dnd/item-event-list` | | Schema ID | `notarius.dnd.item_events` | | Schema name | `notarius_dnd_item_events_v1` | | Schema version | `v1` | | Media type | `application/json` | -| Normalizer key | `dnd/item-events` | -The payload is one strict JSON object containing a required `events` array, -which may be empty. Objects reject unknown fields. +`v1` is a strict JSON object with required `events`; the array may be empty. +Event and source-reference objects reject unknown fields. A future incompatible +shape requires a new schema version. -## Event Shape And Categories +## Wire shape -Every event has `name`, `kind`, and a non-empty `source_refs` array. `quantity` -is optional and, when present, is a positive integer. `from` and `to` are -optional display values whose presence depends on `kind`. +Every event has required `name`, `kind`, and `source_refs`. `quantity`, `from`, +and `to` are optional where the event kind permits them. -| Field | Rule | +| Field | Contract | | --- | --- | -| `name` | Non-empty, transcript-supported display value. | -| `kind` | One of the categories below. | -| `quantity` | Optional positive integer; omitted when the source does not establish a count. | -| `from` | Holder that loses possession when the category permits it. | -| `to` | Holder that gains possession when the category permits it. | -| `source_refs` | One or more current-source references. | +| `name` | Non-empty item or currency display name. | +| `kind` | `discovered`, `acquired`, `lost`, `consumed`, or `transferred`. | +| `quantity` | Optional positive integer; omit it when no count is established. | +| `from` | Optional non-empty losing holder, when allowed by `kind`. | +| `to` | Optional non-empty gaining holder, when allowed by `kind`. | +| `source_refs` | One or more transcript evidence ranges. | -| Kind | Meaning and holder rule | -| --- | --- | -| `discovered` | The party learns of or encounters an item without established possession; neither holder is present. | -| `acquired` | A party member or `party` gains possession; `to` is required and `from` is absent. | -| `lost` | A party member or `party` ceases to possess an item without consuming it; `from` is required and `to` is absent. | -| `consumed` | Use depletes, expends, or destroys an item; `from` is required and `to` is absent. | -| `transferred` | Possession moves between distinct party members; both holders are required and neither may be `party`. | - -`party` is the reserved display holder for collective party possession when an -individual holder is not established. Its comparison is case- and -Unicode-insensitive. Transfers require distinct normalized holder values. -Giving an item to an NPC, spending currency, selling an item, or another move -outside party possession is `lost`, not `transferred` or `consumed`. Monetary -spending, purchases, and payments are always `lost`. Currency is `consumed` only -when the source explicitly describes its physical destruction or expenditure as -a non-payment component. Ordinary non-depleting use is not an event. - -Currency is represented as an ordinary event name plus an explicit quantity -when the transcript supplies one. Each denomination remains separate. The -artifact never converts denominations, infers a missing count, calculates a -balance, or sums nearby events. - -## Source Evidence And Normalization - -Each source reference contains `source_id`, `start_unit_id`, and `end_unit_id`. -It must identify an ordered range in the current source document. During -extraction, every cited range must also be wholly contained in the current -accepted chunk. Campaign references may disambiguate names, but never provide -event evidence. - -The deterministic normalizer trims only display-edge whitespace in names and -holders, canonicalizes source-reference order and exact duplicate references, -then orders events by earliest valid source position and stable tie-breakers -over name, kind, holders, quantity, and complete evidence. It removes only -events with the same normalized fields and complete valid evidence sequence. -Invalid evidence is never collapsed. It does not rename, singularize, resolve -aliases, infer holders or quantities, or merge nearby events. - -The default extraction validation chain is JSON syntax, item-event shape, -source references, durable JSON Schema, then advisory source relatedness. The -normalization chain adds normalized invariants after shape validation and before -source references and schema validation. Relatedness warnings are advisory so -contextual names and currency notation do not reject otherwise valid evidence. - -## Representative JSON +Each source reference has exactly `source_id`, `start_unit_id`, and +`end_unit_id`. It identifies an inclusive current-transcript range; unit IDs +are positive and the start may not follow the end. ```json { "events": [ - { - "name": "Hidden Cache", - "kind": "discovered", - "source_refs": [{"source_id": "session-7", "start_unit_id": 1, "end_unit_id": 1}] - }, { "name": "Silver Pieces", "kind": "acquired", "quantity": 20, "to": "party", - "source_refs": [{"source_id": "session-7", "start_unit_id": 2, "end_unit_id": 2}] - }, - { - "name": "Torch", - "kind": "lost", - "from": "party", - "source_refs": [{"source_id": "session-7", "start_unit_id": 3, "end_unit_id": 3}] - }, - { - "name": "Healing Potion", - "kind": "consumed", - "from": "Aria", - "source_refs": [{"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 4}] - }, - { - "name": "Moonblade", - "kind": "transferred", - "from": "Aria", - "to": "Borin", - "source_refs": [{"source_id": "session-7", "start_unit_id": 5, "end_unit_id": 5}] + "source_refs": [ + {"source_id": "session-7", "start_unit_id": 2, "end_unit_id": 2} + ] } ] } ``` -## Production Boundary +## Holder rules and minimal extraction -The extractor requires `chunks` and `source.transcript`, and accepts optional -`glossary`, `party`, `players`, and deprecated `roster` campaign references for -disambiguation only. It has no generated NPC, scene-description, or item-registry -dependency. The append-order merger preserves chunk order, and the normalizer -has no reference slots. +`discovered` has neither holder; `acquired` requires `to` and forbids `from`; +`lost` and `consumed` require `from` and forbid `to`; `transferred` requires +both holders. `party` denotes collective possession. A transfer cannot use +`party` for either holder and its two normalized holders must differ. -The normalized lane is emitted as `lanes/.json` by the JSON output -module. See [Configuration](../config.md#implemented-production-modules) for -the selectable keys and default chains, and the -[JSON output contract](json-output.md) for bundle paths. +Only an evidenced discovery or possession change belongs in this artifact. +It does not infer quantities or holders, convert currency denominations, +calculate balances, or merge nearby events. Campaign references may +disambiguate names but are never event evidence. Currency uses the ordinary +`name` field and an explicit `quantity` only when the transcript establishes +one; each denomination remains a separate event. + +Normalization trims display whitespace, orders and removes exact duplicate +source references, then orders events by valid source chronology, name identity +and display value, kind, holders, quantity, and reference sequence. It +collapses only entries with the same normalized durable fields and complete +valid evidence. + +The [JSON output contract](json-output.md) defines publication. See +[D&D module internals](../internal/dnd.md) for implementation details and the +[NPC-interaction artifact](dnd-npc-interaction-artifacts.md) for a distinct +kind of occurrence. diff --git a/docs/integrations/dnd-npc-artifacts.md b/docs/integrations/dnd-npc-artifacts.md index 79478c3..66fca2e 100644 --- a/docs/integrations/dnd-npc-artifacts.md +++ b/docs/integrations/dnd-npc-artifacts.md @@ -1,160 +1,69 @@ # D&D NPC Artifact -This document defines the durable D&D NPC-list artifact, its JSON codec, and -the selectable production NPC pipeline. The normalized JSON payload can be -passed explicitly to the spell extractor as an optional caster-name registry -or to the combat extractor and normalizer as an actor registry. It -remains a reference, not spell or combat evidence. -The NPC interaction extractor and normalizer also consume this registry for -canonical identity; registry source references remain provenance and never -become interaction evidence. Their occurrence contract is defined in the -[D&D NPC interaction artifact](dnd-npc-interaction-artifacts.md). +This contract defines the durable NPC registry produced by `dnd/npcs`. It is a +minimal, source-grounded identity registry for other D&D artifacts, not a +character sheet or a relationship summary. -## Identity +## Identity and compatibility -- Artifact kind: `dnd/npc-list` -- Durable schema ID: `notarius.dnd.npcs` -- Durable schema name: `notarius_dnd_npcs_v1` -- Durable schema version: `v1` -- Media type: `application/json` -- Identity policy: `dnd.npcs.identity.v1` +| Property | Value | +| --- | --- | +| Artifact kind | `dnd/npc-list` | +| Schema ID | `notarius.dnd.npcs` | +| Schema name | `notarius_dnd_npcs_v1` | +| Schema version | `v1` | +| Media type | `application/json` | +| Identity policy | `dnd.npcs.identity.v1` | -The durable JSON Schema is owned by the D&D NPC codec. NPC IDs are derived from -the Unicode-normalized, case-folded canonical name using the identity policy. -The durable codec enforces the artifact shape and ID syntax; registry identity -validation remains a separate deterministic concern. +`v1` accepts one strict JSON object with required `npcs`; the array may be +empty. NPC and source-reference objects reject unknown fields. A future +incompatible artifact shape or identity policy uses a new version or policy. -The extractor's private LLM response schema is a separate structural transport -contract. It omits framework-assigned NPC and source IDs and admits semantic -candidates for the deterministic shape and source-reference validators; it is -not part of this durable contract. +## Wire shape and identity -## Output Shape +Each NPC has these required fields: -The payload is one object with a required top-level `npcs` array: +| Field | Contract | +| --- | --- | +| `id` | `npc:sha256:` followed by 64 lowercase hexadecimal characters. | +| `name` | Non-empty canonical display name. | +| `source_refs` | One or more transcript evidence ranges for the identity. | + +A source reference has exactly `source_id`, `start_unit_id`, and `end_unit_id`. +The source ID identifies the transcript, unit IDs are positive inclusive unit +identifiers, and the start may not follow the end. ```json -{"npcs": []} +{ + "npcs": [ + { + "id": "npc:sha256:99a16589618a04f535a7d21fdcc71a0b1c05d22f752cd492065b1086d97bc3d7", + "name": "Mira Thorn", + "source_refs": [ + {"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 5} + ] + } + ] +} ``` -The array may be empty. Every object and nested object rejects unknown fields. +The ID is deterministic: normalize the name to Unicode NFKC, normalize the +supported apostrophe forms, collapse whitespace, case-fold it, SHA-256 the +result, then prefix the lowercase hexadecimal digest with `npc:sha256:`. Each +canonical identity and ID appears at most once. Normalization collapses records +with the same canonical identity, retains their earliest position, and merges +their canonicalized evidence; it does not add aliases, roles, descriptions, or +relationship fields. -## NPC Fields +## Scope and consumers -Each NPC contains exactly these required fields: +Only individually identifiable NPC names with transcript evidence belong in +this artifact. Groups, generic roles, invented labels, and descriptive +enrichment are excluded. Its source references prove registry provenance; they +do not become evidence for a spell, interaction, or combat occurrence. -- `id`: `npc:sha256:` followed by 64 lowercase hexadecimal characters; -- `name`: the canonical display name; -- `source_refs`: at least one source reference supporting the NPC record. - -Each source reference contains required `source_id`, `start_unit_id`, and -`end_unit_id`; unit IDs are positive integers. Source document identity, unit -existence, and range ordering are validated by the source-reference validator -when the artifact is used by a pipeline. - -## Codec Boundary - -`EncodeCandidate` and `DecodeCandidate` provide strict single-value JSON -serialization while preserving typed values that still need semantic -validation. `Encode` and `Decode` are the approved-artifact boundary and -require all durable structural fields, non-empty required strings, valid source -reference shapes, and the NPC ID pattern. - -Codec metadata contains only `npc_count`. Schema bytes and returned metadata -are independent values so callers cannot mutate codec-owned state. - -## Production Pipeline - -The production identities are: - -- extractor: `dnd/npcs`; -- artifact kind: `dnd/npc-list`; -- normalizer: `dnd/npcs`; and -- durable schema: `notarius.dnd.npcs`, version `v1`, media type - `application/json`. - -The extractor maps private model records to the current source identity and -assigns deterministic IDs. Extraction validation checks shape, source -references, and source relatedness. The normalizer first consolidates equal -canonical-name matches, then may make one document-level LLM-assisted identity -decision per configured normalize attempt for eligible distinctly named -records. Consolidation is name-based: it retains a supplied canonical display -name, keeps the earliest affected output position, derives its ID again, and -unions exact evidence in canonical order. Ambiguous, invalid, or conflicting -proposals are not applied; independently safe matches may still be retained. -After the retry budget is exhausted, the safe result is accepted with bounded -normalization warnings and the usual validation. The durable v1 artifact shape -does not add aliases, proposal fields, or any other semantic-normalization -representation. - -The extraction prompt asks only for individually identifiable NPC names backed -by source evidence. Groups, generic roles, invented labels, and descriptive or -relationship enrichment are outside the contract. - -The default extraction chain is `generic/valid_json`, -`extract/dnd/npcs/shape`, `extract/dnd/npcs/source_refs`, -`generic/valid_json_schema`, and `extract/dnd/npcs/source_relatedness`. The -default normalize chain is `generic/valid_json`, `extract/dnd/npcs/shape`, -`normalize/dnd/npcs/identity`, `extract/dnd/npcs/source_refs`, -`generic/valid_json_schema`, and `extract/dnd/npcs/source_relatedness`. -Relatedness emits bounded warnings when an NPC canonical name is not present -near its cited transcript text; opaque campaign -references may explain such a warning but do not become evidence. - -## Manifest And Artifact Handoff - -The NPC extractor records prompt and response-schema identities. The durable -codec records only `npc_count`; raw names, source references, and payload bytes -stay in the lane file rather than manifest -metadata. The normalized lane can be consumed by a later ordered step through -the registered canonical codec: - -```yaml -steps: - - id: identify-npcs - artifacts: - npcs: - extract: dnd/npcs - normalize: dnd/npcs - scene-descriptions: - extract: dnd/scene-descriptions - normalize: dnd/scene-descriptions - - id: grounded-events - references: - npcs: - artifact: - step: identify-npcs - lane: npcs - scene_descriptions: - artifact: - step: identify-npcs - lane: scene-descriptions - artifacts: - spells: - extract: dnd/spells - normalize: dnd/spells - combat: - extract: dnd/combat-turns - normalize: dnd/combat-turns -``` - -The framework hands only accepted normalized artifacts across the barrier. It -validates the canonical bytes against each consumer slot and clones the NPC -operation-time reference for spell and combat consumers. The accompanying -scene-description reference is required by the combat extractor for eligibility -only; its consumer contract is defined in the -[D&D combat-turn artifact contract](dnd-combat-turn-artifacts.md). Generated -provenance records the artifact kind, schema identity, media type, canonical -digest, size, and producer step/lane/module, but not names, source ranges, or -payload bytes. External normalized files remain supported as explicit references -and retain their file provenance. - -NPC source references are registry provenance and are never accepted as spell -or combat evidence. Current transcript units remain the only event evidence. - -Consumers receive a separate names-only projection in normalized registry -order, for example `{"npcs":[{"name":"Mira Thorn"}]}`. The projection omits -IDs and evidence. Its digest covers the exact projected bytes and is used for -consumer-local checkpoint identity, while the full durable artifact digest -remains the manifest and generated-reference provenance identity. The unbound -projection is exactly `{"npcs":[]}` and also has a projection digest. +This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md) +and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to +resolve the canonical `name` in an [NPC interaction](dnd-npc-interaction-artifacts.md). +The [JSON output contract](json-output.md) defines publication, and +[D&D module internals](../internal/dnd.md) owns pipeline mechanics. diff --git a/docs/integrations/dnd-npc-interaction-artifacts.md b/docs/integrations/dnd-npc-interaction-artifacts.md index f8bd248..71583cc 100644 --- a/docs/integrations/dnd-npc-interaction-artifacts.md +++ b/docs/integrations/dnd-npc-interaction-artifacts.md @@ -1,20 +1,38 @@ # D&D NPC Interaction Artifact -This document defines the durable D&D NPC-interaction-list artifact and its -two-step production pipeline. It records discrete, source-grounded occurrences -for NPCs already accepted into a normalized NPC registry; it does not expand -the registry or summarize events. +This contract defines the durable occurrence list produced by +`dnd/npc-interactions`. It records discrete, source-grounded interactions with +NPCs already present in a normalized registry; it does not extend that registry +or summarize the session. -## Identity And JSON +## Identity and compatibility -- Artifact kind: `dnd/npc-interaction-list` -- Durable schema ID: `notarius.dnd.npc_interactions` -- Durable schema name: `notarius_dnd_npc_interactions_v1` -- Durable schema version: `v1` -- Media type: `application/json` +| Property | Value | +| --- | --- | +| Artifact kind | `dnd/npc-interaction-list` | +| Schema ID | `notarius.dnd.npc_interactions` | +| Schema name | `notarius_dnd_npc_interactions_v1` | +| Schema version | `v1` | +| Media type | `application/json` | -The payload is one strict JSON object with only an `interactions` array. The -array may be empty. Each item has exactly `name`, `kind`, and `source_refs`: +`v1` is a strict JSON object with required `interactions`; the array may be +empty. Interaction and source-reference objects reject unknown fields. A future +incompatible shape requires a new schema version. + +## Wire shape + +Each interaction has these required fields: + +| Field | Contract | +| --- | --- | +| `name` | Non-empty canonical display name from the required NPC registry. | +| `kind` | One of the interaction categories below. | +| `source_refs` | One or more transcript evidence ranges. | + +Each source reference has exactly `source_id`, `start_unit_id`, and +`end_unit_id`. It identifies an inclusive range in the current transcript; +unit IDs are positive and the start may not follow the end. Extraction evidence +for an interaction is confined to its accepted chunk. ```json { @@ -30,119 +48,31 @@ array may be empty. Each item has exactly `name`, `kind`, and `source_refs`: } ``` -`name` is the canonical display name from the required NPC registry. -`source_refs` contains one or more current-source ranges with required -`source_id`, `start_unit_id`, and `end_unit_id`; unit IDs are positive integers. -During extraction, every range must be wholly contained in the current accepted -chunk. This prevents a candidate from citing valid units that were not presented -to that extraction call. -Unknown fields are rejected. - -## Interaction Categories - -`kind` is exactly one of: +## Interaction categories | Kind | Meaning | | --- | --- | -| `mentioned` | The NPC is referred to, but is not established as present or communicating in the evidenced passage. | -| `noncombat_presence` | The NPC is present and relevant to the passage but does not meaningfully participate in dialogue or combat. | -| `dialogue` | The NPC speaks, responds, or is directly engaged in a meaningful non-combat exchange. | +| `mentioned` | The NPC is referred to but is not established as present or communicating. | +| `noncombat_presence` | The NPC is present and relevant without meaningful dialogue or combat participation. | +| `dialogue` | The NPC speaks, responds, or meaningfully participates in a non-combat exchange. | | `combat_ally` | The NPC actively participates in combat on the party's side. | | `combat_opponent` | The NPC actively participates in combat against the party. | -| `other` | The transcript clearly establishes a direct NPC occurrence that fits none of the preceding kinds. | +| `other` | A clearly evidenced direct occurrence not covered by another category. | -`other` is a residual category for positively evidenced activity, not a fallback -for uncertain classification. When activities overlap, active combat -participation outranks dialogue, presence, and mention; dialogue outranks -non-combat presence and mention; and non-combat presence outranks mention. -Combat alignment is not resolved by precedence: a meaningful change between -ally and opponent creates separate occurrences. +The categories do not represent motives, relationships, state, or events that +the cited transcript does not establish. An `other` entry is not a substitute +for uncertain classification. -These categories do not encode summaries, relationships, state, motives, or -unobserved events. +## Identity, evidence, and order -## Occurrence Boundaries And Ordering +The required normalized [NPC artifact](dnd-npc-artifacts.md) resolves `name`. +Registry references are provenance only and never replace an interaction's own +evidence. Normalization canonicalizes recognized registry names, orders and +deduplicates exact source references, then orders interactions by valid source +chronology, NPC comparison identity, display name, kind, and reference sequence. +Only entries with the same canonical name, kind, and complete valid evidence +sequence are collapsed; distinct categories or evidence remain separate. -One occurrence represents one NPC, one kind, and one locally coherent passage -within one accepted chunk. Repeated evidence belongs to the same occurrence -only while it supports the same uninterrupted activity. A kind change, combat -alignment change, intervening scene or meaningful absence, or transition from -mention to presence starts a new occurrence. Occurrences never span chunks, and -merge or normalization never semantically combines nearby, overlapping, or -cross-chunk records. - -Normalization orders records by: - -1. earliest valid source-document position; -2. the NPC identity comparison key; -3. the exact canonical NPC display name; -4. interaction kind in lexical order; and -5. the complete canonical source-reference sequence, ordered by source ID and - the source-document positions of each range's start and end. - -Only records with identical canonical names, kinds, and complete valid evidence -sequences are duplicates. Different categories, ranges, or separately grounded -occurrences remain separate. - -## Evidence, Registry, And Normalization - -The registry proves only the canonical NPC identity. Its source references are -registry provenance and are never interaction evidence. Every durable -interaction must cite current transcript units supporting both the name and its -classification. - -The extractor receives a names-only registry projection such as -`{"npcs":[{"name":"Mira Thorn"}]}`. The normalizer uses the full immutable -registry for exact canonical-name lookup. It canonicalizes source references -and applies the ordering and exact-duplicate rules above. - -## Production Pipeline - -The extractor and normalizer key is `dnd/npc-interactions`. Both require the -structured `npcs` slot, so an accepted normalized registry must come from an -earlier step: - -```yaml -pipelines: - dnd-npc-interactions: - input: seriatim - steps: - - id: identify-npcs - artifacts: - npcs: - extract: dnd/npcs - normalize: dnd/npcs - - id: extract-interactions - references: - npcs: - artifact: - step: identify-npcs - lane: npcs - artifacts: - interactions: - extract: dnd/npc-interactions - normalize: dnd/npc-interactions -``` - -The framework passes only the accepted normalized producer. A missing, rejected, -or incompatible NPC artifact prevents the consumer step from executing. It -records generated artifact identity and bounded producer provenance without -copying registry names, source ranges, or payload content into the manifest. - -## Validation And Metadata - -The default extract chain is `generic/valid_json`, interaction shape, registry, -and source-reference validation, `generic/valid_json_schema`, then warning-only -source relatedness. The normalize chain runs normalized invariants after -registry validation and before source-reference and schema validation, followed -by relatedness. Normalizer and relatedness warnings are bounded and end with an -omission summary when necessary. The codec metadata contains only -`interaction_count`. Extractor metadata identifies its prompt and private -response schema; component-local checkpoint identities include the names-only -registry projection where relevant. Generated registry identity stays in -framework provenance and dependency fingerprints. - -See [Configuration](../config.md#implemented-production-modules) for selectable -keys and chains, [the NPC artifact contract](dnd-npc-artifacts.md) for the -registry boundary, and the copyable -[complete D&D example](../../examples/dnd-complete.config.yml). +See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action +occurrences and the [JSON output contract](json-output.md) for publication. +Pipeline mechanics are described in [D&D module internals](../internal/dnd.md). diff --git a/docs/integrations/dnd-scene-description-artifacts.md b/docs/integrations/dnd-scene-description-artifacts.md index 35c6725..b435365 100644 --- a/docs/integrations/dnd-scene-description-artifacts.md +++ b/docs/integrations/dnd-scene-description-artifacts.md @@ -1,9 +1,10 @@ -# D&D Scene Description Artifact Contract +# D&D Scene-Description Artifact -This document defines the durable `dnd/scene-description-list` artifact -emitted by the D&D scene-description lane. +This contract defines the durable output of `dnd/scene-descriptions`. Each +record classifies one accepted transcript chunk and gives it a minimal +source-grounded title and summary. -## Artifact identity +## Identity and compatibility | Property | Value | | --- | --- | @@ -13,38 +14,32 @@ emitted by the D&D scene-description lane. | Schema version | `v1` | | Media type | `application/json` | -The normalized payload is written by the JSON output module to -`lanes/.json`. See [JSON output](json-output.md) for the surrounding -output bundle. +`v1` is a strict JSON object with required non-empty `scenes`. Scene and +source-reference objects reject unknown fields. A future incompatible shape +requires a new schema version. -## JSON shape +## Wire shape -The payload is a JSON object containing exactly one required field, `scenes`. -Each scene object contains exactly these required fields: +Each scene has exactly these required fields: -| Field | Shape and ownership | +| Field | Contract | | --- | --- | | `id` | Non-empty accepted chunk ID, assigned by Notarius. | -| `source_ref` | Exact inclusive accepted chunk range, assigned by Notarius. | -| `kind` | One of `combat`, `narrative`, `recap`, or `meta`. | +| `source_ref` | The assigned inclusive source range for that chunk. | +| `kind` | `combat`, `narrative`, `recap`, or `meta`. | | `title` | Non-empty, trimmed, source-grounded title. | | `summary` | Non-empty, trimmed, source-grounded summary. | -All object layers reject unknown fields. The `scenes` array must be present and -non-empty. `source_ref` has exactly `source_id`, `start_unit_id`, and -`end_unit_id`; its source ID is non-empty and its unit IDs are positive -integers. +`source_ref` has exactly `source_id`, `start_unit_id`, and `end_unit_id`. +Its source ID identifies the input transcript; its positive unit IDs identify +the chunk's inclusive range, with the start no later than the end. ```json { "scenes": [ { "id": "chunk-000001", - "source_ref": { - "source_id": "session-alpha", - "start_unit_id": 1, - "end_unit_id": 3 - }, + "source_ref": {"source_id": "session-7", "start_unit_id": 1, "end_unit_id": 3}, "kind": "narrative", "title": "Arrival at the watchtower", "summary": "The party reaches the ruined watchtower and begins to investigate it." @@ -53,78 +48,22 @@ integers. } ``` -`id` and `source_ref` are application-owned identity and evidence. The model -provides only `kind`, `title`, and `summary`; it is not asked for chunk IDs, -source IDs, unit IDs, ranges, participants, or confidence. +## Meaning and normalized form -## Scene kinds +`combat` identifies a chunk where active combat is the central activity. +`narrative` is current in-world play that is not principally combat, recap, or +meta discussion. `recap` is primarily a recounting of an earlier session, and +`meta` is primarily out-of-character discussion. The artifact does not add +participants, confidence, events, or information absent from the chunk. -| Kind | Meaning | -| --- | --- | -| `combat` | Active combat is a substantive central activity. | -| `narrative` | Current-session in-world play that is not principally combat, recap, or meta discussion. | -| `recap` | The table is primarily recounting a previous session. | -| `meta` | Sustained out-of-character discussion is the scene's primary purpose. | +Normalization trims title and summary, orders scenes by source position and +then ID, and removes exact duplicate records. A reused ID with different +durable fields, or the same source range with different kind, title, or +summary, is invalid. It does not merge adjacent ranges, alter prose, or infer +missing scenes. -For a mixed accepted chunk, classification prefers `combat`, then `recap`, -then `meta`, then `narrative`. Brief table talk, rules clarification, or a -short recollection does not replace the main current-session activity. - -## Extraction and evidence - -The extractor runs once for each accepted chunk and maps one successful model -response to one scene record. It copies the current chunk ID and exact chunk -range, preserves the model kind without repair, and trims only surrounding -whitespace from title and summary. A model response cannot represent an empty -result; extraction failure follows the configured retry and rejection policy. - -Optional `players`, `party`, and `glossary` campaign references can help -disambiguate names or setting terms. They never supply scene evidence or add -events absent from the accepted chunk. The lane requires no NPC registry or -other generated artifact reference. - -## Merge and normalization - -Accepted per-chunk lists are appended in chunk order. Normalization then: - -1. validates the current-source range, non-empty ID and prose, and closed kind; -2. trims only title and summary whitespace; -3. sorts records by source-document start position, then ID; -4. removes records only when all five durable fields are identical; -5. rejects a reused ID when any remaining durable field differs; and -6. rejects the same exact range when `kind`, `title`, or `summary` differs. - -Two different IDs with the same range and identical model-owned content remain -separate records. Normalization does not join adjacent ranges, rewrite prose, -repair kinds, infer missing scenes, or use chunk annotations. - -## Validation and warnings - -Extraction validation requires exactly one record with an ID and range exactly -equal to its current chunk. Later validation checks source membership without a -current chunk. Shape, source-range, kind, ID, and normalized-invariant failures -reject the artifact. - -Relatedness checks are advisory. They separately warn when a scene title or -summary has no significant lexical token in its cited transcript range. The -check ignores short tokens and common function words, uses transcript text only, -and does not treat campaign references as evidence. Warning diagnostics are -bounded and do not copy transcript or campaign-reference content. - -## Configuration - -Use `dnd/scenes` to form accepted scene chunks, then select -`dnd/scene-descriptions` for extraction and normalization. The maintained -[complete D&D configuration](../../examples/dnd-complete.config.yml) includes -this lane alongside the other D&D artifacts. Selectable keys and default -validator chains are defined in [Configuration](../config.md). - -## Downstream combat use - -The combat-turn extractor consumes this approved artifact as required -eligibility control context through an explicit ordered reference binding. It -uses only an exact chunk ID and source-range match with `kind: combat` to permit -combat extraction; titles, summaries, and scene references do not become combat -prompt material or evidence. The complete downstream behavior, including empty -results and warnings for unavailable coverage, is defined in the -[D&D combat-turn artifact contract](dnd-combat-turn-artifacts.md). +The [combat-turn artifact](dnd-combat-turn-artifacts.md) uses an exact matching +`combat` scene only as eligibility control; scene title, summary, and source +reference never become combat evidence. Publication is defined by the +[JSON output contract](json-output.md); implementation details live in +[D&D module internals](../internal/dnd.md). diff --git a/docs/integrations/dnd-spell-artifacts.md b/docs/integrations/dnd-spell-artifacts.md index 11114ad..7919c03 100644 --- a/docs/integrations/dnd-spell-artifacts.md +++ b/docs/integrations/dnd-spell-artifacts.md @@ -1,204 +1,73 @@ # D&D Spell Artifact -This document is the durable serialized artifact contract for the production -D&D spell extractor. Selectable extractor keys are cataloged in -[Configuration](../config.md#implemented-production-modules). +This contract defines the durable output of the `dnd/spells` extractor and +normalizer. It records source-grounded spell-casting occurrences; it is not a +spellbook, a rules lookup result, or a record of hypothetical casts. -## Identity +## Identity and compatibility -- Artifact kind: `dnd/spell-list` -- Prompt ID: `dnd.spells` -- Response schema key: `dnd_spells` -- Response schema ID: `notarius.dnd.spells` -- Response schema name: `notarius_dnd_spells_v1` -- Response schema version: `v1` -- Media type: `application/json` - -The durable JSON Schema is owned by the D&D spell artifact codec. The -extractor's private LLM response schema is a separate transport contract: its -source-reference objects omit `source_id`, which the extractor assigns while -mapping the response to the canonical artifact. The LLM DTO and transport -schema are not part of this durable contract. The private schema owns required -fields, JSON types, object and array shapes, and unknown-field rejection; -deterministic validators own the durable artifact's semantic constraints. - -The output contains canonical spell casts derived from transcript evidence. -Source IDs are assigned from the input identity; source-unit ranges identify -the evidence location. - -## Output Shape - -The extractor payload is a JSON object with one required top-level array. Its -structure is: - -```text -{"spell_casts": [, ...]} -``` - -`spell_casts` must be present. It may be empty when no spell casts are found. -When multiple chunk results are combined, spell casts remain in chunk order. -When the payload is written as durable output, its logical path is derived from -the configured artifact lane ID as defined by the -[JSON output contract](json-output.md#output-payload-files). - -## Spell-Cast Fields - -Each spell cast contains exactly these required fields: - -- `caster`: in-world character or creature casting the spell; -- `spell`: spell name; -- `source_refs`: transcript source references with extractor-assigned source - IDs and evidence unit ranges. It must contain at least one entry. - -Both string fields must be non-empty. `caster` is the canonical in-world -caster, not the human player, transcript speaker, or GM when the associated -character or creature can be identified. Player and party references may -disambiguate that identity, but do not independently establish that a cast -occurred. The `spell` value must resolve through the effective SRD-plus-overlay -catalog as either a canonical name or alias. Catalog validation accepts aliases -but does not rewrite them; unknown fields are rejected. - -The artifact includes an actual casting event or an unambiguous declared -casting attempt. Spell mentions, hypothetical plans, rules discussion, and -catalog matches without a casting event are excluded. The spell catalog is a -name-recognition policy and never evidence that a cast occurred. - -## Source References - -Each source reference contains exactly three required fields: `source_id`, -`start_unit_id`, and `end_unit_id`. The source ID must match the input identity. -The unit IDs must be positive integers present in the input, and the start unit -must not appear after the end unit. Unknown fields are rejected. - -For each cast, the complete `source_refs` collection identifies the transcript -evidence for the caster, spell name, and occurrence of the cast or declared -attempt. The deterministic validators establish that ranges are structurally -valid and that the spell name is related to cited text. Semantic evidence -sufficiency is an extraction policy and remains subject to evaluation rather -than deterministic proof. - -Reference slot keys and accepted file types are defined in -[Configuration](../config.md#implemented-production-modules). References are -supporting disambiguation material, not source evidence, and are not -addressable through `source_refs`. - -## Optional NPC Grounding - -The `dnd/spells` extractor accepts an optional `npcs` reference containing one -normalized NPC artifact as `application/json`, up to 1 MiB. An external file is -validated during preparation; an ordered generated binding is validated at the -step handoff. Both paths use the approved NPC codec and identity policy, -re-encode canonical durable JSON for registry provenance, and supply only the -registry's ordered names as the operation-time spell prompt input. It helps the -model prefer canonical caster names; it does not establish that a spell was -cast. - -NPC source references may identify the run that produced the registry or any -other session. They remain registry provenance and are never copied into a -spell cast's `source_refs`; every spell evidence range must still identify the -current transcript. Generated provenance records producer and canonical -artifact identity without payload content or a path. When the slot is absent, -the prompt receives exactly `{"npcs":[]}` with its projection digest, and the -run has no NPC reference provenance. - -## Normalization Behavior - -When the `dnd/spells` normalizer is selected, each recognized spell name is -rewritten to the effective catalog's canonical display name. Lookup uses the -catalog's case-insensitive, whitespace-normalizing, apostrophe-normalizing, and -alias rules. Unknown names are preserved exactly for the normalize validators; -the normalizer does not guess or apply fuzzy matching. - -Each cast's `source_refs` is copied, sorted by exact `source_id`, -`start_unit_id`, and `end_unit_id`, and stripped of exact structural -duplicates. Adjacent or overlapping ranges are not merged, and the normalizer -does not synthesize references or change their boundaries. - -After those per-cast changes, duplicate identity requires the same canonical -spell name, the same caster after case folding and whitespace normalization, -and the same complete, non-empty set of source references valid for the source -document. Only the first occurrence is retained, in stable order. Its caster -and canonical references are preserved. Unknown names, empty or invalid -evidence, and casts with different evidence remain separate. - -Mutation and duplicate decisions are returned through the normal warnings -surface. Warning scopes use the merged input index, such as `spell_casts[0]`, -so they remain meaningful even when a later duplicate is removed. The -normalizer uses these reason codes: - -| Reason code | Meaning | +| Property | Value | | --- | --- | -| `spell_name_canonicalized` | A catalog lookup replaced an input name with its canonical display name. | -| `spell_name_unresolved` | A name was not found in the effective catalog and was retained unchanged. | -| `source_references_normalized` | Reference order changed or exact duplicate references were removed. | -| `duplicate_spell_cast_collapsed` | A later cast matched the retained cast's complete duplicate identity. | +| Artifact kind | `dnd/spell-list` | +| Schema ID | `notarius.dnd.spells` | +| Schema name | `notarius_dnd_spells_v1` | +| Schema version | `v1` | +| Media type | `application/json` | -Only warnings from an accepted normalize attempt are promoted to -`warnings.json`. If an unresolved name reaches the default normalize validator -chain, the catalog validator rejects the candidate with `unknown_spell`; the -`spell_name_unresolved` warning remains in the attempt's debug artifact. An -explicit validator override that accepts the candidate promotes the unresolved -warning normally. +`v1` is a single strict JSON object. It requires `spell_casts`; the array may +be empty. Each spell-cast object and source-reference object rejects unknown +fields. A future incompatible shape requires a new schema version. -The default extraction and normalization chains both preserve this registered -order: JSON syntax, spell shape, catalog membership, source references, JSON -Schema, then source relatedness. Extraction validates the private response -schema; normalization validates the durable artifact schema. +## Wire shape -## Manifest Metadata +Each `spell_casts` entry has these required fields: -The extractor adds prompt and response-schema provenance under the artifact lane -manifest metadata: +| Field | Contract | +| --- | --- | +| `caster` | Non-empty in-world character or creature name. | +| `spell` | Non-empty spell name. | +| `source_refs` | One or more transcript evidence ranges. | + +Every source reference has exactly `source_id`, `start_unit_id`, and +`end_unit_id`. The source ID identifies the input transcript; the unit IDs are +positive inclusive unit identifiers, and the start may not follow the end in +that source. References are evidence for the cast, not campaign-reference or +NPC-registry provenance. ```json { - "metadata": { - "extractor": { - "prompt_id": "dnd.spells", - "prompt_version": "v1", - "prompt_sha256": "sha256:...", - "response_schema_key": "dnd_spells", - "response_schema_id": "notarius.dnd.spells", - "response_schema_name": "notarius_dnd_spells_v1", - "response_schema_version": "v1", - "response_schema_sha256": "sha256:...", - "catalog_base_id": "dnd-5e-2014-srd-spells", - "catalog_digest": "sha256:...", - "catalog_overlay_ids": ["campaign.example"], - "npc_registry_digest": "sha256:...", - "npc_count": 3 - }, - "normalizer": { - "catalog_base_id": "dnd-5e-2014-srd-spells", - "catalog_digest": "sha256:...", - "catalog_overlay_ids": ["campaign.example"] + "spell_casts": [ + { + "caster": "Mira Thorn", + "spell": "Fireball", + "source_refs": [ + {"source_id": "session-7", "start_unit_id": 12, "end_unit_id": 13} + ] } - } + ] } ``` -`catalog_digest` identifies the effective semantic catalog, while -`catalog_overlay_ids` is sorted and empty for a base-only configuration. Raw -prompt, schema, catalog, alias, and local overlay-file content are not -included in manifest metadata. The `normalizer` metadata uses the same catalog -identity fields when that module is selected. Overlay origin, media type, byte -size, and raw digest are recorded separately in the manifest's reference -provenance; see the [JSON output contract](json-output.md#manifestjson). +## Evidence and normalized form -The `npc_registry_digest` and `npc_count` fields in the example are present for -an external NPC registry when the extractor publishes its prepared module -metadata. They contain no NPC names, source references, paths, or raw -bytes. A generated registry's identity is instead represented by the framework -handoff provenance and dependency fingerprint, so the consumer module metadata -does not duplicate it. +An entry represents an actual cast or an unambiguous declared attempt. A spell +mention, rules discussion, plan, or catalog match alone is not an occurrence. +The configured catalog checks the name; it does not establish evidence. -The extractor's prompt hash, private response-schema hash, and effective catalog -digest also contribute independently scoped semantic checkpoint fingerprints. -Changing any of those prepared contracts intentionally produces a cold -checkpoint miss. Fingerprints contain only digests, never prompt, schema, -catalog, or reference content. When an NPC registry is bound, its semantic -digest contributes an additional local `npc_registry` fingerprint for an -external binding; the manifest metadata contains only that digest and -`npc_count`. Raw NPC file provenance remains independently recorded in the -manifest's `references` list. Generated bindings contribute the canonical -artifact dependency fingerprint and bounded producer provenance instead. +When normalization is selected, recognized spell names use the effective +catalog's canonical display name. Source references are put in canonical source +order and exact duplicate references are removed. A later entry is collapsed +only when it has the same canonical spell, the same case- and +whitespace-insensitive caster identity, and the same complete valid reference +sequence. Remaining entries retain their merged order. + +The optional normalized [NPC artifact](dnd-npc-artifacts.md) can ground a +caster name. Its own references remain registry provenance and are never copied +into `source_refs`. + +## Related contracts + +The [spell-catalog overlay contract](dnd-spell-catalog-overlays.md) defines +the configured catalog additions. The [JSON output contract](json-output.md) +defines where this logical artifact is published; [D&D module internals](../internal/dnd.md) +describes extraction and validation mechanics.