Files
notarius/docs/integrations/dnd-combat-turn-artifacts.md

8.5 KiB

D&D Combat-Turn Artifact Contract

This document defines the durable artifact, serialization, extraction, candidate-validation, normalization, and production lane boundaries for D&D combat turns.

Artifact identity

Property Value
Artifact kind dnd/combat-turn-list
Schema ID notarius.dnd.combat_turns
Schema name notarius_dnd_combat_turns_v1
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.

JSON shape

Each combat turn contains these required fields:

Field Shape
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.

Source references use the shared source-reference shape:

{
  "source_id": "session-alpha",
  "start_unit_id": 1,
  "end_unit_id": 2
}

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.

Codec behavior

The codec exposes two representations of the same typed artifact:

  • 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/<lane-id>.json by the JSON output module, and warnings and rejection summaries remain in their shared companion files.