Compare commits
87 Commits
v0.2.0
...
d9b87347b8
| Author | SHA1 | Date | |
|---|---|---|---|
| d9b87347b8 | |||
| 20397ef710 | |||
| fc449863f2 | |||
| 51d62de1f3 | |||
| fc76805075 | |||
| 8e680cf96e | |||
| ece1bca460 | |||
| 516af12916 | |||
| 1015d61b2d | |||
| 4192aa8584 | |||
| 8cf03a2a44 | |||
| 2ee6b495e1 | |||
| f1b120b590 | |||
| 2ec17f5b4f | |||
| 6de470d541 | |||
| a6e176e160 | |||
| 3dfefd0e14 | |||
| f91237e9c0 | |||
| ad9ee076b5 | |||
| ce04387dbc | |||
| e8965ebbbb | |||
| c006b163d5 | |||
| 2f61118e78 | |||
| 5e2ccffc0f | |||
| 3f4a1f2647 | |||
| 9653e06297 | |||
| 916a32195b | |||
| 299c110267 | |||
| 4093ff2e8b | |||
| 39563f3ea0 | |||
| 4b52cace76 | |||
| 16d1b29b14 | |||
| 856b26b718 | |||
| ab70347c5d | |||
| 8ed99ceaee | |||
| 257bca0dc2 | |||
| bad5db3ef3 | |||
| 406ad1d362 | |||
| 5f207b2ab1 | |||
| c613b306ae | |||
| 1c7291e17d | |||
| 2345da106a | |||
| 03761c97dd | |||
| 9cb9ee48a7 | |||
| 08954f17e2 | |||
| a57f83e30d | |||
| f4c05c34ef | |||
| 29fcad6e9b | |||
| f5fd115046 | |||
| 7f28899730 | |||
| 84c0758455 | |||
| 5002864e88 | |||
| 55b188fd84 | |||
| d1f43df88e | |||
| d52387c1f7 | |||
| 9c5e3cff14 | |||
| 811d5b8bd9 | |||
| a168c13b85 | |||
| dd61a4efda | |||
| 228cc6ee83 | |||
| 06170e1f65 | |||
| 7715baa1f6 | |||
| 98506db1a9 | |||
| bb4855f0c6 | |||
| c51934d5c6 | |||
| c3513da880 | |||
| c7d853ea52 | |||
| da7fcdaffd | |||
| b6aad4fa98 | |||
| 9c6af28d02 | |||
| 04eabdfcb9 | |||
| a8a99c1037 | |||
| e15007fffb | |||
| e6b7c61f45 | |||
| 42973215fa | |||
| ba1d112d1f | |||
| b722131d57 | |||
| a92d2c0885 | |||
| 02ec10d66b | |||
| 9dd57dbfa4 | |||
| c164a3fc69 | |||
| 8834df617f | |||
| db2adb52da | |||
| fc3c128171 | |||
| 8a15b083a0 | |||
| d9dae2b639 | |||
| 7a4fd7be7a |
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
Notarius is a Go CLI for turning source material into structured artifacts with
|
Notarius is a Go CLI for turning source material into structured artifacts with
|
||||||
configured extraction pipelines. The implemented D&D workflow reads Seriatim
|
configured extraction pipelines. The implemented D&D workflow reads Seriatim
|
||||||
transcript JSON and can produce scene descriptions, item and currency events,
|
transcript JSON and can produce NPC, location, and item registries; their
|
||||||
NPC identities, combat turns, NPC interactions, and spell casts.
|
source-grounded occurrences; scene descriptions, combat turns, enemy events,
|
||||||
|
and spell casts.
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
|||||||
18
assets/dnd/combat-turns/prompts/instructions.md
Normal file
18
assets/dnd/combat-turns/prompts/instructions.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Extract Dungeons & Dragons combat-turn artifacts from the supplied transcript.
|
||||||
|
Include a record only when the transcript establishes that an in-world
|
||||||
|
participant takes a combat turn or performs a discrete interrupting combat
|
||||||
|
event. Keep events in transcript chronology; place an interrupting event where
|
||||||
|
it occurs.
|
||||||
|
|
||||||
|
Exclude initiative setup without a turn or combat event, tactical planning,
|
||||||
|
table talk, rules lookup, hypothetical events, abandoned intentions, recaps
|
||||||
|
outside the current passage, and downstream consequences. Do not infer combat
|
||||||
|
events from Dungeons & Dragons rules knowledge. Preserve the session as played
|
||||||
|
and attribute relevant nonstandard rulings to the GM or table. Unmatched actors
|
||||||
|
remain permitted.
|
||||||
|
|
||||||
|
Treat each record as one turn-level event and keep its supporting transcript
|
||||||
|
evidence together. Use `turn` for a regular combat turn, `reaction` for an
|
||||||
|
off-turn reaction, `legendary_action` for a legendary action,
|
||||||
|
`lair_action` for a lair action, and `other` for another discrete combat
|
||||||
|
event that does not fit those categories.
|
||||||
@@ -14,7 +14,7 @@ inputs:
|
|||||||
- name: glossary
|
- name: glossary
|
||||||
required: false
|
required: false
|
||||||
content_type: text/plain
|
content_type: text/plain
|
||||||
- name: npcs
|
- name: npc_registry
|
||||||
required: false
|
required: false
|
||||||
content_type: application/json
|
content_type: application/json
|
||||||
messages:
|
messages:
|
||||||
@@ -27,15 +27,13 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-npcs.md
|
content_file: ./sharedassets/common-dnd-npc-registry.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
12
assets/dnd/enemy-events/prompts/combat-grounding.md
Normal file
12
assets/dnd/enemy-events/prompts/combat-grounding.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Compact combat grounding is supplied below. It can guide attention and
|
||||||
|
disambiguation, but it is not evidence. Do not derive an event, subject,
|
||||||
|
outcome, or source range from either list. The current transcript alone must
|
||||||
|
directly establish every returned event.
|
||||||
|
|
||||||
|
Combat-turn grounding:
|
||||||
|
|
||||||
|
{{ input "combat_turns" }}
|
||||||
|
|
||||||
|
Named combat-opponent grounding:
|
||||||
|
|
||||||
|
{{ input "npc_occurrences" }}
|
||||||
25
assets/dnd/enemy-events/prompts/instructions.md
Normal file
25
assets/dnd/enemy-events/prompts/instructions.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Extract Dungeons & Dragons enemy events from the supplied combat transcript.
|
||||||
|
An `engaged` event requires direct establishment that a subject is actively
|
||||||
|
opposing the party in combat. A `killed`, `fled`, `captured`, or
|
||||||
|
`incapacitated` event requires explicit establishment of that outcome. An
|
||||||
|
outcome may share evidence with an engagement, and a later engagement or
|
||||||
|
outcome for the same subject remains a separate observation. Emit at most one
|
||||||
|
`engaged` observation for the same subject in this combat scene.
|
||||||
|
|
||||||
|
For `killed`, direct death or killing is required. For `fled`, the subject
|
||||||
|
must explicitly escape, retreat, or leave combat to avoid continued engagement.
|
||||||
|
For `captured`, the subject must be explicitly taken prisoner or secured
|
||||||
|
under the party's control. For `incapacitated`, the subject must be explicitly
|
||||||
|
unable to continue acting without being established as killed or captured.
|
||||||
|
|
||||||
|
When the transcript identifies a named NPC, use its normalized registry
|
||||||
|
spelling. A hostile creature without a registry entry is allowed. For unnamed
|
||||||
|
individuals or groups, use only the narrowest transcript-grounded label, such
|
||||||
|
as `Orcs`, `One orc`, or `Remaining orcs`; never invent member names, IDs,
|
||||||
|
or quantities.
|
||||||
|
|
||||||
|
Exclude party members, allies, neutral observers, mentioned-but-absent enemies,
|
||||||
|
hazards, traps, environmental effects, uncertain allegiance, table talk,
|
||||||
|
planning, hypotheses, recaps outside this passage, and downstream inference.
|
||||||
|
Do not infer an engagement or outcome from initiative, turn absence, damage,
|
||||||
|
defeat, movement, or a scene ending.
|
||||||
53
assets/dnd/enemy-events/prompts/prompt.yaml
Normal file
53
assets/dnd/enemy-events/prompts/prompt.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
id: dnd.enemy_events
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: players
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: party
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: glossary
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: npc_registry
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: combat_turns
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: npc_occurrences
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-identity.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-references.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-npc-registry.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./combat-grounding.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_enemy_events_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
33
assets/dnd/enemy-events/schemas/dnd_enemy_events_llm.v1.json
Normal file
33
assets/dnd/enemy-events/schemas/dnd_enemy_events_llm.v1.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "notarius.dnd.enemy_events.llm",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["events"],
|
||||||
|
"properties": {
|
||||||
|
"events": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "kind", "source_refs"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"kind": {"type": "string"},
|
||||||
|
"source_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer"},
|
||||||
|
"end_unit_id": {"type": "integer"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "notarius.dnd.entity_reconcile.llm",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["duplicate_groups"],
|
||||||
|
"properties": {
|
||||||
|
"duplicate_groups": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["members", "canonical"],
|
||||||
|
"properties": {
|
||||||
|
"members": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"$ref": "#/$defs/selector"}
|
||||||
|
},
|
||||||
|
"canonical": {"$ref": "#/$defs/selector"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$defs": {
|
||||||
|
"selector": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "source_refs"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string", "minLength": 1},
|
||||||
|
"source_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer", "minimum": 1},
|
||||||
|
"end_unit_id": {"type": "integer", "minimum": 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
assets/dnd/item-occurrences/prompts/instructions.md
Normal file
30
assets/dnd/item-occurrences/prompts/instructions.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Extract meaningful Dungeons & Dragons item and currency occurrences: discoveries and changes
|
||||||
|
in party possession established by the transcript. This is an occurrence history,
|
||||||
|
not an inventory or ledger: do not calculate balances, resolve item identity
|
||||||
|
across records, or infer ownership that the transcript does not establish.
|
||||||
|
|
||||||
|
For every occurrence, use the supplied canonical item `name`. Record a stated
|
||||||
|
quantity as an integer and leave it null when the transcript does not state
|
||||||
|
one. Preserve the stated currency denomination through the selected canonical
|
||||||
|
registry name.
|
||||||
|
|
||||||
|
Use `discovered` when the party learns of or encounters an item without
|
||||||
|
establishing possession. Use `acquired` when the party or a party member gains
|
||||||
|
possession. Use `lost` when party possession ends through a gift, sale, payment,
|
||||||
|
theft, abandonment, or destruction not caused by intended use. Use `consumed`
|
||||||
|
when intended use depletes an expendable item. Monetary spending, purchases, and
|
||||||
|
payments are always `lost`, not `consumed`. Classify currency as `consumed` only
|
||||||
|
when the transcript explicitly describes it being physically destroyed or
|
||||||
|
expended as a non-payment component. Use `transferred` only when possession
|
||||||
|
moves between two distinct named party members.
|
||||||
|
|
||||||
|
Return both `from` and `to` for every occurrence, using `null` when a holder does not
|
||||||
|
apply. For `discovered`, set both holders to `null`. For `acquired`, set `from`
|
||||||
|
to `null` and provide `to`; for `lost` and `consumed`, provide `from` and set
|
||||||
|
`to` to `null`; and for `transferred`, provide both holders. Use `party` only
|
||||||
|
for collective or unresolved party possession, never for either side of a
|
||||||
|
transfer. Do not emit a transfer for a gift, sale, or payment outside the party.
|
||||||
|
|
||||||
|
Ordinary non-depleting use is not an occurrence. Do not infer acquisition from a
|
||||||
|
discovery, or discovery from an acquisition: emit both only when each is
|
||||||
|
independently established.
|
||||||
6
assets/dnd/item-occurrences/prompts/item-registry.md
Normal file
6
assets/dnd/item-occurrences/prompts/item-registry.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Use the supplied item registry only to ground each occurrence. Every record
|
||||||
|
must use one registry item's canonical `name`; do not invent, rename, merge,
|
||||||
|
or infer registry items. The registry is not transcript evidence: cite only the
|
||||||
|
current transcript chunk in `source_refs`.
|
||||||
|
|
||||||
|
{{ input "item_registry" }}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
id: dnd.npc_interactions
|
id: dnd.item_occurrences
|
||||||
version: "v1"
|
version: "v1"
|
||||||
default_profile: dnd-extraction
|
default_profile: dnd-extraction
|
||||||
inputs:
|
inputs:
|
||||||
@@ -14,7 +14,7 @@ inputs:
|
|||||||
- name: glossary
|
- name: glossary
|
||||||
required: false
|
required: false
|
||||||
content_type: text/plain
|
content_type: text/plain
|
||||||
- name: npcs
|
- name: item_registry
|
||||||
required: true
|
required: true
|
||||||
content_type: application/json
|
content_type: application/json
|
||||||
messages:
|
messages:
|
||||||
@@ -27,15 +27,13 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-npcs.md
|
content_file: ./item-registry.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
@@ -43,5 +41,5 @@ messages:
|
|||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
schema_path: dnd_npc_interactions_llm.v1.json
|
schema_path: dnd_item_occurrences_llm.v1.json
|
||||||
repair_attempts: 0
|
repair_attempts: 0
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "notarius.dnd.item_events.llm",
|
"$id": "notarius.dnd.item_occurrences.llm",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["events"],
|
"required": ["occurrences"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"events": {
|
"occurrences": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["name", "kind", "source_refs"],
|
"required": ["name", "kind", "quantity", "from", "to", "source_refs"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {"type": "string"},
|
"name": {"type": "string"},
|
||||||
"kind": {"type": "string"},
|
"kind": {"type": "string"},
|
||||||
"quantity": {"type": "integer"},
|
"quantity": {"type": ["integer", "null"]},
|
||||||
"from": {"type": "string"},
|
"from": {"type": ["string", "null"]},
|
||||||
"to": {"type": "string"},
|
"to": {"type": ["string", "null"]},
|
||||||
"source_refs": {
|
"source_refs": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
12
assets/dnd/item-registry/extract/prompts/instructions.md
Normal file
12
assets/dnd/item-registry/extract/prompts/instructions.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Extract only items established by the provided Dungeons & Dragons transcript.
|
||||||
|
|
||||||
|
Include named unique items, concrete reusable item types, and stable unique
|
||||||
|
designations. Record each currency denomination separately when it is
|
||||||
|
established, such as copper pieces, silver pieces, gold pieces, or platinum
|
||||||
|
pieces. Do not use capitalization as an eligibility test. Keep distinct names
|
||||||
|
and designations as separate candidates; do not merge aliases or invent
|
||||||
|
qualifiers.
|
||||||
|
|
||||||
|
Do not record vague categories such as "loot", "treasure", or "some gear";
|
||||||
|
generic weapons; inferred properties; quantities; or inferred uniqueness. Omit
|
||||||
|
uncertain or unsupported items.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
id: dnd.npcs
|
id: dnd.item_registry
|
||||||
version: "v1"
|
version: "v1"
|
||||||
default_profile: dnd-extraction
|
default_profile: dnd-extraction
|
||||||
inputs:
|
inputs:
|
||||||
@@ -24,13 +24,11 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
@@ -38,5 +36,5 @@ messages:
|
|||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
schema_path: dnd_npcs_llm.v1.json
|
schema_path: dnd_item_registry_llm.v1.json
|
||||||
repair_attempts: 0
|
repair_attempts: 0
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "notarius.dnd.item_registry.llm",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["items"],
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "source_refs"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"source_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer"},
|
||||||
|
"end_unit_id": {"type": "integer"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
assets/dnd/item-registry/normalize/prompts/candidates.md
Normal file
2
assets/dnd/item-registry/normalize/prompts/candidates.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Item candidates:
|
||||||
|
{{ input "candidates" }}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
Use candidate names and cited transcript windows only to determine whether
|
||||||
|
candidates identify the same item type or unique designation. Do not treat
|
||||||
|
nearby evidence, similar objects, or a shared owner as sufficient. Keep
|
||||||
|
currency denominations, materially different item types, and uncertain aliases
|
||||||
|
separate. Do not infer an item property or uniqueness.
|
||||||
|
|
||||||
|
When selecting a canonical display name, choose one supplied candidate name
|
||||||
|
that is the clearest established designation.
|
||||||
30
assets/dnd/item-registry/normalize/prompts/prompt.yaml
Normal file
30
assets/dnd/item-registry/normalize/prompts/prompt.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
id: dnd.item_registry.normalize
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: candidates
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-entity-reconciliation.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./candidates.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-windows.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_entity_reconcile_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
34
assets/dnd/location-occurrences/prompts/instructions.md
Normal file
34
assets/dnd/location-occurrences/prompts/instructions.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
Extract Dungeons & Dragons location occurrences from the supplied transcript.
|
||||||
|
Include an occurrence only when the transcript establishes one supplied
|
||||||
|
location, one occurrence kind, and a coherent passage supporting both.
|
||||||
|
|
||||||
|
Use exactly one kind per occurrence:
|
||||||
|
|
||||||
|
- visited: party members are physically present, arrive, remain, or depart;
|
||||||
|
- planned: the party explicitly proposes, intends, or agrees to future travel;
|
||||||
|
- recalled: the transcript explicitly recounts an earlier party visit; or
|
||||||
|
- mentioned: the location is explicitly referenced without stronger support,
|
||||||
|
including non-actionable speculation or a mere hypothetical reference.
|
||||||
|
|
||||||
|
A mere hypothetical or speculative reference is not planned unless the
|
||||||
|
transcript also establishes an actual proposal, intention, or agreement to
|
||||||
|
travel. When the hypothetical explicitly names a supplied location, it may be
|
||||||
|
mentioned.
|
||||||
|
|
||||||
|
A generic phrase in the current chunk may refer to a supplied named registry
|
||||||
|
location only when the chunk's context supports that coreference. It must not
|
||||||
|
create a registry location, and registry content or provenance must never
|
||||||
|
replace current-chunk evidence.
|
||||||
|
|
||||||
|
For every occurrence, return the exact selector from the location registry:
|
||||||
|
the canonical `name`, plus an empty `registry_refs` array for a unique name or
|
||||||
|
the complete ordered `registry_refs` array for a repeated name. Registry ranges
|
||||||
|
and context identify the location only; they are not occurrence evidence.
|
||||||
|
|
||||||
|
For overlapping support, visited outranks planned, recalled, and mentioned;
|
||||||
|
planned outranks recalled and mentioned; recalled outranks mentioned. A passage
|
||||||
|
may produce multiple records when it independently establishes separate facts,
|
||||||
|
such as recalling an earlier visit while planning a return. Omit inferred,
|
||||||
|
unstated, uncertain, or unsupported places and occurrences. Do not infer a
|
||||||
|
location or occurrence from surrounding events when the transcript does not
|
||||||
|
state it. Do not summarize location descriptions.
|
||||||
11
assets/dnd/location-occurrences/prompts/location-registry.md
Normal file
11
assets/dnd/location-occurrences/prompts/location-registry.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
A contextual location registry is provided below for identity grounding. It may
|
||||||
|
be empty. Every record supplies a canonical display name. A name that appears
|
||||||
|
once is selected with that name and an empty `registry_refs` array. A repeated
|
||||||
|
name is selected only by copying both its name and its complete, ordered
|
||||||
|
`registry_refs` array exactly as supplied.
|
||||||
|
|
||||||
|
Registry content is context, not occurrence evidence. Do not derive an
|
||||||
|
occurrence or `source_refs` range from the registry. Do not invent a location
|
||||||
|
or selector that is absent from it.
|
||||||
|
|
||||||
|
{{ input "location_registry" }}
|
||||||
45
assets/dnd/location-occurrences/prompts/prompt.yaml
Normal file
45
assets/dnd/location-occurrences/prompts/prompt.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
id: dnd.location_occurrences
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: players
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: party
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: glossary
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: location_registry
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-identity.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-references.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./location-registry.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_location_occurrences_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "notarius.dnd.location_occurrences.llm",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["occurrences"],
|
||||||
|
"properties": {
|
||||||
|
"occurrences": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "registry_refs", "kind", "source_refs"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"registry_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer", "minimum": 1},
|
||||||
|
"end_unit_id": {"type": "integer", "minimum": 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"kind": {"enum": ["visited", "planned", "recalled", "mentioned"]},
|
||||||
|
"source_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer"},
|
||||||
|
"end_unit_id": {"type": "integer"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
assets/dnd/location-registry/extract/prompts/instructions.md
Normal file
13
assets/dnd/location-registry/extract/prompts/instructions.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Extract only physical places established by the provided Dungeons & Dragons
|
||||||
|
transcript that have a stable proper name or unique in-world designation. This
|
||||||
|
includes named planes, regions, settlements, districts, buildings, rooms,
|
||||||
|
landmarks, routes, and geographic features.
|
||||||
|
|
||||||
|
Do not create a registry location for generic, temporary, relative, or merely
|
||||||
|
descriptive phrases, including "the room", "the bar", "the hallway",
|
||||||
|
"outside", and "upstairs". Do not use capitalization as an eligibility test.
|
||||||
|
Keep aliases and nested places when the transcript identifies them; do not merge
|
||||||
|
or invent qualifiers for similarly named places.
|
||||||
|
|
||||||
|
Exclude people, creatures, objects, organizations, abstract concepts, and
|
||||||
|
places merely inferred from an event. Omit uncertain or unsupported places.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
id: dnd.item_events
|
id: dnd.location_registry
|
||||||
version: "v1"
|
version: "v1"
|
||||||
default_profile: dnd-extraction
|
default_profile: dnd-extraction
|
||||||
inputs:
|
inputs:
|
||||||
@@ -24,13 +24,11 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
@@ -38,5 +36,5 @@ messages:
|
|||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
schema_path: dnd_item_events_llm.v1.json
|
schema_path: dnd_location_registry_llm.v1.json
|
||||||
repair_attempts: 0
|
repair_attempts: 0
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "notarius.dnd.location_registry.llm",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["locations"],
|
||||||
|
"properties": {
|
||||||
|
"locations": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "source_refs"],
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"source_refs": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["start_unit_id", "end_unit_id"],
|
||||||
|
"properties": {
|
||||||
|
"start_unit_id": {"type": "integer"},
|
||||||
|
"end_unit_id": {"type": "integer"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Location candidates:
|
||||||
|
{{ input "candidates" }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Use candidate names and their cited transcript windows to determine whether
|
||||||
|
candidates identify the same physical place. Do not treat matching names,
|
||||||
|
nearby evidence, nested places, or generic labels as sufficient. Keep parent
|
||||||
|
and child places, similarly named places, and uncertain aliases separate.
|
||||||
|
|
||||||
|
When selecting a canonical display name, prefer the clearest established name.
|
||||||
30
assets/dnd/location-registry/normalize/prompts/prompt.yaml
Normal file
30
assets/dnd/location-registry/normalize/prompts/prompt.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
id: dnd.location_registry.normalize
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: candidates
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-entity-reconciliation.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./candidates.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-windows.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_entity_reconcile_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
Return the interactions array even when no interaction is established. Every
|
Extract Dungeons & Dragons NPC occurrences from the supplied
|
||||||
record must contain name, kind, and source_refs. Cite transcript ranges that
|
transcript. Include an occurrence only when the transcript establishes one
|
||||||
support both the NPC identity and the interaction kind.
|
supplied NPC, one occurrence kind, and a coherent passage supporting both.
|
||||||
|
Use the supplied canonical NPC `name`; never invent or substitute a similar
|
||||||
|
name. Cite current-transcript evidence for every occurrence.
|
||||||
|
|
||||||
|
Do not summarize, infer relationships, sentiment, factions, motives, aliases,
|
||||||
|
or persistent state. Do not identify player characters, anonymous groups, or
|
||||||
|
invented NPCs. Split records when an NPC's occurrence kind changes, when
|
||||||
|
combat alignment changes, or when an NPC is first mentioned and later becomes
|
||||||
|
present.
|
||||||
|
|
||||||
Use exactly one kind per occurrence:
|
Use exactly one kind per occurrence:
|
||||||
|
|
||||||
45
assets/dnd/npc-occurrences/prompts/prompt.yaml
Normal file
45
assets/dnd/npc-occurrences/prompts/prompt.yaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
id: dnd.npc_occurrences
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: players
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: party
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: glossary
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: npc_registry
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-identity.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-references.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-npc-registry.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_npc_occurrences_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "notarius.dnd.npc_interactions.llm",
|
"$id": "notarius.dnd.npc_occurrences.llm",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["interactions"],
|
"required": ["occurrences"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"interactions": {
|
"occurrences": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
19
assets/dnd/npc-registry/extract/prompts/instructions.md
Normal file
19
assets/dnd/npc-registry/extract/prompts/instructions.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Extract the individually identifiable Dungeons & Dragons non-player characters
|
||||||
|
established by the provided transcript.
|
||||||
|
|
||||||
|
Include an in-world non-PC only when the transcript factually establishes a
|
||||||
|
proper name or a stable, individually distinguishing title or alias. A factual
|
||||||
|
third-party mention establishes that identity even when the NPC is not
|
||||||
|
physically present, does not speak, and takes no direct action in this chunk.
|
||||||
|
Record only the NPC identity and the transcript evidence that establishes it;
|
||||||
|
do not infer or classify a separate occurrence.
|
||||||
|
|
||||||
|
Exclude human players, transcript speakers, and the GM as out-of-world people;
|
||||||
|
player characters identified by the player or party references; names used only
|
||||||
|
in hypothetical, speculative, or imagined examples; corrected transcription
|
||||||
|
mistakes; anonymous or generic roles; indistinguishable crowds or groups;
|
||||||
|
invented descriptive labels; and temporary summoned creatures or spell effects
|
||||||
|
without a persistent individual identity.
|
||||||
|
|
||||||
|
Preserve observed display spelling. Do not invent a label for an anonymous
|
||||||
|
creature, crowd, or generic role.
|
||||||
40
assets/dnd/npc-registry/extract/prompts/prompt.yaml
Normal file
40
assets/dnd/npc-registry/extract/prompts/prompt.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
id: dnd.npc_registry
|
||||||
|
version: "v1"
|
||||||
|
default_profile: dnd-extraction
|
||||||
|
inputs:
|
||||||
|
- name: transcript
|
||||||
|
required: true
|
||||||
|
content_type: application/json
|
||||||
|
- name: players
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: party
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
- name: glossary
|
||||||
|
required: false
|
||||||
|
content_type: text/plain
|
||||||
|
messages:
|
||||||
|
- role: system
|
||||||
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-identity.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-references.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./instructions.md
|
||||||
|
cache_control:
|
||||||
|
type: ephemeral
|
||||||
|
output:
|
||||||
|
format: json
|
||||||
|
validation_mode: json_schema
|
||||||
|
schema_path: dnd_npc_registry_llm.v1.json
|
||||||
|
repair_attempts: 0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "notarius.dnd.npcs.llm",
|
"$id": "notarius.dnd.npc_registry.llm",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["npcs"],
|
"required": ["npcs"],
|
||||||
3
assets/dnd/npc-registry/normalize/prompts/candidates.md
Normal file
3
assets/dnd/npc-registry/normalize/prompts/candidates.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
NPC candidates for identity comparison:
|
||||||
|
|
||||||
|
{{ input "candidates" }}
|
||||||
10
assets/dnd/npc-registry/normalize/prompts/instructions.md
Normal file
10
assets/dnd/npc-registry/normalize/prompts/instructions.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Use candidate aliases and their cited transcript windows to determine whether
|
||||||
|
candidates refer to the same individual. Preserve distinct individuals even
|
||||||
|
when their names are similar.
|
||||||
|
|
||||||
|
When selecting a canonical display name, prefer a complete, stable proper name
|
||||||
|
over an abbreviation. Prefer an unadorned proper name over that name plus a
|
||||||
|
contextual class, role, title, or relationship descriptor unless the transcript
|
||||||
|
establishes the descriptor as part of the person's name. A longer display name
|
||||||
|
is not inherently more canonical; for example, do not prefer `Captain Aria`
|
||||||
|
over `Aria` solely because it includes the contextual title `Captain`.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
id: dnd.npcs.normalize
|
id: dnd.npc_registry.normalize
|
||||||
version: "v1"
|
version: "v1"
|
||||||
default_profile: dnd-extraction
|
default_profile: dnd-extraction
|
||||||
inputs:
|
inputs:
|
||||||
@@ -11,20 +11,20 @@ inputs:
|
|||||||
messages:
|
messages:
|
||||||
- role: system
|
- role: system
|
||||||
content_file: ./sharedassets/common-dnd-system.md
|
content_file: ./sharedassets/common-dnd-system.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-entity-reconciliation.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./candidates.md
|
content_file: ./candidates.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-windows.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
schema_path: dnd_npcs_normalize_llm.v1.json
|
schema_path: dnd_entity_reconcile_llm.v1.json
|
||||||
repair_attempts: 0
|
repair_attempts: 0
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
Choose exactly one kind:
|
Describe exactly one accepted Dungeons & Dragons scene from the supplied
|
||||||
|
transcript chunk. The complete chunk is the evidence boundary: do not split it
|
||||||
|
into multiple scenes or use facts that are not supported by it.
|
||||||
|
|
||||||
|
Return one kind, one concise title, and one concise summary. Choose exactly one
|
||||||
|
kind:
|
||||||
|
|
||||||
- combat: active combat materially organizes the scene, including
|
- combat: active combat materially organizes the scene, including
|
||||||
initiative-like exchanges or sustained hostile action. Planning a fight or
|
initiative-like exchanges or sustained hostile action. Planning a fight or
|
||||||
@@ -37,8 +42,4 @@ must not invent a proper noun.
|
|||||||
The summary must briefly state the main activity and material transition or
|
The summary must briefly state the main activity and material transition or
|
||||||
outcome established within the accepted chunk. Do not add analysis, inferred
|
outcome established within the accepted chunk. Do not add analysis, inferred
|
||||||
motives, hidden state, future consequences, relationship claims, or facts from
|
motives, hidden state, future consequences, relationship claims, or facts from
|
||||||
outside the chunk. Campaign references may disambiguate names but never add
|
outside the chunk.
|
||||||
events or lore.
|
|
||||||
|
|
||||||
Do not return identifiers, source identifiers, source ranges, unit identifiers,
|
|
||||||
participants, confidence, or any fields besides kind, title, and summary.
|
|
||||||
@@ -24,11 +24,9 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Divide the provided transcript into coherent Dungeons & Dragons scenes for the
|
Divide the complete provided transcript into coherent Dungeons & Dragons scenes
|
||||||
`dnd/scenes` chunk module.
|
for the `dnd/scenes` chunk module.
|
||||||
|
|
||||||
A scene is a coherent unit of play. Start a new scene when the transcript
|
A scene is a coherent unit of play. Start a new scene when the transcript
|
||||||
establishes a meaningful change in location, objective, threat, activity,
|
establishes a meaningful change in location, objective, threat, activity,
|
||||||
@@ -13,6 +13,7 @@ Do not split a scene merely because a speaker or combat round changes, a
|
|||||||
routine turn occurs, or the table briefly digresses. Prefer fewer coherent
|
routine turn occurs, or the table briefly digresses. Prefer fewer coherent
|
||||||
scenes over speculative or fine-grained boundaries.
|
scenes over speculative or fine-grained boundaries.
|
||||||
|
|
||||||
Return only inclusive `start_unit_id` and `end_unit_id` endpoints for each
|
Cover the complete transcript from its first source unit to its last. Return
|
||||||
scene. Do not return titles, modes, participants, summaries, boundary notes,
|
scenes in source-unit order with no gaps or overlaps. Use only positive integer
|
||||||
confidence, caveats, final chunk IDs, or chunk indexes.
|
source-unit IDs from the transcript, and give every scene one inclusive
|
||||||
|
`start_unit_id` and one inclusive `end_unit_id`.
|
||||||
@@ -21,16 +21,14 @@ messages:
|
|||||||
content_file: ./sharedassets/common-dnd-references.md
|
content_file: ./sharedassets/common-dnd-references.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-full.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
output:
|
output:
|
||||||
format: json
|
format: json
|
||||||
validation_mode: json_schema
|
validation_mode: json_schema
|
||||||
schema_path: dnd_scenes.v1.json
|
schema_path: dnd_scenes_llm.v1.json
|
||||||
repair_attempts: 0
|
repair_attempts: 0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "notarius.dnd.scenes",
|
"$id": "notarius.dnd.scenes.llm",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["scenes"],
|
"required": ["scenes"],
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Identify only well-supported duplicate groups among the supplied candidates.
|
||||||
|
|
||||||
|
Return each selected candidate's supplied contextual descriptor exactly: its
|
||||||
|
`name` and complete ordered `source_refs`. A group must contain at least two
|
||||||
|
supplied descriptors, and its `canonical` descriptor must be one of its
|
||||||
|
members. Do not invent names, ranges, records, evidence, or replacement values.
|
||||||
|
Omit any uncertain or unsafe group.
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
Transcript units are the only evidence for extracted events and factual claims.
|
Transcript units are the only evidence for extracted events and factual claims.
|
||||||
Campaign and registry references may disambiguate names, aliases, speakers, or
|
|
||||||
other identities, but they do not establish events, participation, effects, or
|
|
||||||
source evidence.
|
|
||||||
|
|
||||||
Every reported factual claim must be supported by cited transcript units. Use
|
Every reported factual claim must be supported by cited transcript units. Use
|
||||||
integer `start_unit_id` and `end_unit_id` values from the transcript. Omit
|
integer `start_unit_id` and `end_unit_id` values from the transcript. Omit
|
||||||
`source_id`; Notarius assigns the current source identity.
|
`source_id`; Notarius assigns the current source identity.
|
||||||
|
|
||||||
When supporting evidence is non-contiguous, use multiple narrow ranges rather
|
When supporting evidence is non-contiguous, use multiple narrow ranges rather
|
||||||
than a broad range that bridges unrelated conversation.
|
than a broad range that bridges unrelated conversation.
|
||||||
|
|
||||||
Return exactly one JSON object and no explanatory text. Output only the
|
|
||||||
configured JSON object and fields defined by its response schema.
|
|
||||||
@@ -7,4 +7,4 @@ participants, effects, or source references from the registry. Registry source
|
|||||||
references describe registry provenance and may belong to another session; they
|
references describe registry provenance and may belong to another session; they
|
||||||
are never evidence for the current transcript.
|
are never evidence for the current transcript.
|
||||||
|
|
||||||
{{ input "npcs" }}
|
{{ input "npc_registry" }}
|
||||||
7
assets/dnd/shared/prompts/common-dnd-system.md
Normal file
7
assets/dnd/shared/prompts/common-dnd-system.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
You process Dungeons & Dragons gameplay transcripts.
|
||||||
|
|
||||||
|
Rely only on the supplied inputs. They may contain transcription errors,
|
||||||
|
repeated lines, incomplete sentences, and misheard proper nouns.
|
||||||
|
|
||||||
|
Return exactly one JSON object that conforms to the configured response schema,
|
||||||
|
with no explanatory prose.
|
||||||
5
assets/dnd/shared/prompts/common-dnd-transcript-chunk.md
Normal file
5
assets/dnd/shared/prompts/common-dnd-transcript-chunk.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
One extraction chunk from a Dungeons & Dragons gameplay transcript is provided
|
||||||
|
below. Report and infer only what is within this chunk. Its unit IDs retain
|
||||||
|
their source-wide meaning.
|
||||||
|
|
||||||
|
{{ input "transcript" }}
|
||||||
4
assets/dnd/shared/prompts/common-dnd-transcript-full.md
Normal file
4
assets/dnd/shared/prompts/common-dnd-transcript-full.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
The complete ordered transcript of this Dungeons & Dragons gameplay session is
|
||||||
|
provided below. It may contain multiple scenes.
|
||||||
|
|
||||||
|
{{ input "transcript" }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Selected Dungeons & Dragons gameplay transcript evidence windows are provided
|
||||||
|
below. They may be incomplete, non-contiguous, or overlapping. Use them to
|
||||||
|
evaluate candidate identity, but do not treat absence outside these windows as
|
||||||
|
evidence.
|
||||||
|
|
||||||
|
{{ input "transcript" }}
|
||||||
14
assets/dnd/spells/prompts/instructions.md
Normal file
14
assets/dnd/spells/prompts/instructions.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
Extract Dungeons & Dragons spell-cast artifacts from the provided transcript.
|
||||||
|
Include an actual casting event or an unambiguous declared casting attempt.
|
||||||
|
Exclude spell mentions, hypothetical plans, rules discussion, and catalog
|
||||||
|
matches that do not establish a casting event in the transcript.
|
||||||
|
|
||||||
|
For every extracted cast, the transcript evidence must collectively support the
|
||||||
|
in-world caster, the spell, and the fact that the cast or declared attempt
|
||||||
|
occurred.
|
||||||
|
|
||||||
|
Attribute every cast to its in-world caster. Map first-person player speech to
|
||||||
|
the associated player character, and attribute a spell narrated by the GM to
|
||||||
|
the in-world creature that casts it. If the caster cannot be resolved, use only
|
||||||
|
the most specific in-world identity supported by the transcript; do not invent
|
||||||
|
a name.
|
||||||
@@ -17,7 +17,7 @@ inputs:
|
|||||||
- name: glossary
|
- name: glossary
|
||||||
required: false
|
required: false
|
||||||
content_type: text/plain
|
content_type: text/plain
|
||||||
- name: npcs
|
- name: npc_registry
|
||||||
required: false
|
required: false
|
||||||
content_type: application/json
|
content_type: application/json
|
||||||
messages:
|
messages:
|
||||||
@@ -30,17 +30,15 @@ messages:
|
|||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-transcript.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
content_file: ./sharedassets/common-dnd-extraction-evidence.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./sharedassets/common-dnd-npcs.md
|
content_file: ./sharedassets/common-dnd-npc-registry.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./catalog.md
|
content_file: ./spell-catalog.md
|
||||||
- role: user
|
|
||||||
content_file: ./task.md
|
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "notarius.dnd.spells",
|
"$id": "notarius.dnd.spells.llm",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["spell_casts"],
|
"required": ["spell_casts"],
|
||||||
15
assets/package.go
Normal file
15
assets/package.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Package assets exposes embedded LLM-facing content.
|
||||||
|
package assets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dnd generic
|
||||||
|
var embedded embed.FS
|
||||||
|
|
||||||
|
// FS returns the embedded read-only asset filesystem.
|
||||||
|
func FS() fs.FS {
|
||||||
|
return embedded
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# ADR-0004: Package modules by domain, not by stage
|
# ADR-0004: Package modules by domain, not by stage
|
||||||
|
|
||||||
**Status:** Accepted
|
**Status:** Accepted — its asset-co-location rule is superseded by [ADR-0011](0011-centralize-llm-assets.md); its domain-first module packaging decision remains accepted.
|
||||||
**Date:** 2026-07-13
|
**Date:** 2026-07-13
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|||||||
69
docs/adr/0011-centralize-llm-assets.md
Normal file
69
docs/adr/0011-centralize-llm-assets.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# ADR-0011: Centralize LLM-facing assets in a content-only package
|
||||||
|
|
||||||
|
**Status:** Accepted
|
||||||
|
**Date:** 2026-08-05
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
LLM prompts, private response schemas, generic schemas, and fallback profiles
|
||||||
|
are authored and reviewed as content, but package-local embedding scattered that
|
||||||
|
content across implementation trees. Finding all of the assets that contribute
|
||||||
|
to a prompt family required navigating code ownership boundaries rather than a
|
||||||
|
single discoverable content boundary.
|
||||||
|
|
||||||
|
The repository must retain module ownership of prompt semantics, schema
|
||||||
|
identities, registration, and prompt-cache behavior. Durable artifact schemas
|
||||||
|
and non-LLM domain data have different compatibility and ownership rules, so
|
||||||
|
they must not move merely because they are embedded files.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
LLM-facing content is embedded by the root `assets` package. It is a data-only
|
||||||
|
dependency leaf: its single `FS() fs.FS` API returns the read-only embedded
|
||||||
|
filesystem, and the package contains no business logic or internal or PromptKit
|
||||||
|
dependencies. The accepted import path is
|
||||||
|
`gitea.maximumdirect.net/eric/notarius/assets`; it makes repository-owned
|
||||||
|
content available to its consumers, not a public extension contract.
|
||||||
|
|
||||||
|
Consumers scope that filesystem to the subtree they own before reading or
|
||||||
|
registering content. Modules continue to own their manifests, prompt ordering,
|
||||||
|
private response-schema identity, and registration. Centralizing physical files
|
||||||
|
does not centralize domain semantics or transfer those responsibilities to the
|
||||||
|
root package.
|
||||||
|
|
||||||
|
The root package contains prompt content, private LLM response schemas, generic
|
||||||
|
LLM schemas, shared fragments, and fallback profiles. Durable artifact schemas
|
||||||
|
and non-LLM domain data remain with their current owners. A module fingerprint
|
||||||
|
is derived from its manifest-selected module and shared files, rather than from
|
||||||
|
an entire asset tree. The relocation is accepted to cause a one-time checkpoint
|
||||||
|
invalidation.
|
||||||
|
|
||||||
|
This decision supersedes only the physical asset-co-location portion of
|
||||||
|
ADR-0004's decision that places domain-specific prompt fragments and schemas
|
||||||
|
within the domain tree. ADR-0004's domain-first packaging and registrar
|
||||||
|
ownership decisions remain accepted.
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
- Keep package-local assets. This preserves physical co-location with code but
|
||||||
|
makes prompt-author discovery and cross-family review unnecessarily costly.
|
||||||
|
- Use `internal/llmassets`. This would hide content from legitimate owners
|
||||||
|
outside the `internal` subtree and would make the root asset boundary depend
|
||||||
|
on implementation-layer placement.
|
||||||
|
- Build a behavioral central registry. This would mix content discovery with
|
||||||
|
prompt selection and registration behavior, moving module semantics into a
|
||||||
|
shared registry.
|
||||||
|
- Use runtime filesystem overlays. This would add runtime configuration and
|
||||||
|
failure modes where compile-time embedded content is sufficient.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Prompt authors can find in-scope LLM content in one top-level tree while module
|
||||||
|
packages continue to define its meaning and registration. Consumers have an
|
||||||
|
explicit, narrow dependency on only the content they need. The root package is
|
||||||
|
intentionally importable but must remain a stable, content-only leaf rather
|
||||||
|
than becoming a general extension API.
|
||||||
|
|
||||||
|
The initial relocation invalidates existing checkpoints once. Later checkpoint
|
||||||
|
identity changes remain limited to the manifest-selected prompt and shared
|
||||||
|
content, so unrelated files do not trigger recomputation.
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# ADR-0012: Resolve opaque entity identifiers deterministically
|
||||||
|
|
||||||
|
**Status:** Accepted
|
||||||
|
**Date:** 2026-08-08
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Entity IDs in durable Notarius artifacts are application-owned, deterministic
|
||||||
|
identifiers. They are useful to artifact consumers, but their hash-based form
|
||||||
|
does not help a model distinguish entities and would make the model reproduce
|
||||||
|
an opaque implementation detail. A plain name is likewise insufficient where
|
||||||
|
multiple supplied records share that name.
|
||||||
|
|
||||||
|
The LLM boundary must preserve the typed artifact and durable-schema ownership
|
||||||
|
of [ADR-0003](0003-strongly-typed-stage-interfaces.md) and the distinction
|
||||||
|
between disambiguating references and source evidence in
|
||||||
|
[ADR-0009](0009-prefer-minimal-evidence-grounded-extraction-artifacts.md).
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Callers present a model with semantic selections: a canonical name when it is
|
||||||
|
unique in the request, or a contextual descriptor containing the name and
|
||||||
|
source coordinates when that context is needed to distinguish supplied
|
||||||
|
records. The model returns only those supplied selections. The caller resolves
|
||||||
|
each accepted selection against the request-local supplied records and attaches
|
||||||
|
the opaque application ID deterministically.
|
||||||
|
|
||||||
|
Source coordinates are permitted in a selection solely as identity context.
|
||||||
|
They neither establish an occurrence fact nor replace that occurrence's
|
||||||
|
current-transcript evidence. A selector must resolve exactly; unknown,
|
||||||
|
ambiguous, partial, reordered, or otherwise unsafe selections are not mapped.
|
||||||
|
Where an operation requires a complete grounded artifact, that failure rejects
|
||||||
|
the complete artifact rather than accepting a partially mapped result.
|
||||||
|
|
||||||
|
An explicitly scoped request-local short label is permitted only when a
|
||||||
|
contextual descriptor would be impractical and the caller can deterministically
|
||||||
|
map the label within that one request. Such a label is not a durable ID, must
|
||||||
|
not escape the request boundary, and requires a concrete justification in its
|
||||||
|
own module contract.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- Ask the model to return durable IDs. This exposes opaque implementation
|
||||||
|
state, does not improve semantic disambiguation, and makes model output
|
||||||
|
depend on hash formatting.
|
||||||
|
- Select by name alone. This cannot safely distinguish same-name records.
|
||||||
|
- Make request-local labels durable identifiers. This would turn prompt
|
||||||
|
presentation into a public identity contract and create avoidable migration
|
||||||
|
pressure.
|
||||||
|
- Let the model invent identifiers or resolve ambiguity. This makes identity
|
||||||
|
assignment non-deterministic and weakens validation.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Durable integration contracts retain their exact ID/name pairs while models
|
||||||
|
operate on readable contextual selections. Calling modules must own selector
|
||||||
|
construction, exact resolution, ambiguity handling, and conversion into their
|
||||||
|
durable artifact type; PromptKit and its adapter remain transport-only.
|
||||||
|
|
||||||
|
Some ambiguous or invalid proposals are deliberately omitted, retried, or
|
||||||
|
rejected according to the caller's existing failure policy. Internal candidate
|
||||||
|
keys may support deterministic request-local mapping, but they are not
|
||||||
|
model-visible selectors or durable data. This adds local validation work while
|
||||||
|
keeping identity assignment auditable and stable.
|
||||||
12
docs/cli.md
12
docs/cli.md
@@ -40,7 +40,7 @@ pipeline ID and **--input** are required.
|
|||||||
| **--debug-dir path** | Override the debug-bundle root. Requires **--debug**. |
|
| **--debug-dir path** | Override the debug-bundle root. Requires **--debug**. |
|
||||||
| **--only lane-a,lane-b** | Run only the selected comma-separated artifact lanes when that selection is valid for the configured pipeline. |
|
| **--only lane-a,lane-b** | Run only the selected comma-separated artifact lanes when that selection is valid for the configured pipeline. |
|
||||||
| **--llm-profile id** | Highest-precedence configured profile for selected LLM-backed bindings and validators; it replaces binding and [pipeline](config.md#pipelines) defaults. |
|
| **--llm-profile id** | Highest-precedence configured profile for selected LLM-backed bindings and validators; it replaces binding and [pipeline](config.md#pipelines) defaults. |
|
||||||
| **--session-id id** | Supply a non-empty prompt session identifier to LLM-backed module calls. |
|
| **--session-id id** | Override the generated prompt session identifier with a non-empty value for LLM-backed module calls. |
|
||||||
| **--reasoning-effort value** | Replace the selected PromptKit profile's reasoning effort for every LLM-backed call in this run. The value must be non-empty and the flag may be specified only once. |
|
| **--reasoning-effort value** | Replace the selected PromptKit profile's reasoning effort for every LLM-backed call in this run. The value must be non-empty and the flag may be specified only once. |
|
||||||
| **--clear-reasoning-effort** | Clear reasoning effort inherited from the selected PromptKit profile for every LLM-backed call in this run. |
|
| **--clear-reasoning-effort** | Clear reasoning effort inherited from the selected PromptKit profile for every LLM-backed call in this run. |
|
||||||
| **--reference selector=path** | Add or replace a file reference binding. Repeatable. |
|
| **--reference selector=path** | Add or replace a file reference binding. Repeatable. |
|
||||||
@@ -57,6 +57,16 @@ Persistent reasoning settings remain a PromptKit profile concern.
|
|||||||
**--recompute-step** requires **--resume**; checkpoint requirements and reuse
|
**--recompute-step** requires **--resume**; checkpoint requirements and reuse
|
||||||
behavior are documented in [Operations](operations.md).
|
behavior are documented in [Operations](operations.md).
|
||||||
|
|
||||||
|
Every run uses one effective prompt session. Without **--session-id**, Notarius
|
||||||
|
generates a stable `notarius:v1:` identifier from the trimmed resolved input
|
||||||
|
module key and the input file's exact raw bytes. The same module and bytes
|
||||||
|
therefore produce the same identifier, regardless of pipeline, references,
|
||||||
|
profile, retries, or run settings. An explicit non-empty value replaces that
|
||||||
|
default. Session identifiers are visible to providers; they are non-secret
|
||||||
|
correlation identifiers, not credential storage. See
|
||||||
|
[Operations](operations.md#operational-limits) for privacy and workflow
|
||||||
|
guidance.
|
||||||
|
|
||||||
### Reference selectors
|
### Reference selectors
|
||||||
|
|
||||||
Use **--reference** only for a reference slot declared by the selected
|
Use **--reference** only for a reference slot declared by the selected
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Built-in defaults are:
|
|||||||
|
|
||||||
| Field | Default |
|
| Field | Default |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **concurrency.total_llm** | 1 |
|
| **concurrency.total_llm** | 16 |
|
||||||
| **concurrency.stage_workers.extract** | Effective **total_llm** |
|
| **concurrency.stage_workers.extract** | Effective **total_llm** |
|
||||||
| **output.directory** | **./notarius-output** |
|
| **output.directory** | **./notarius-output** |
|
||||||
| **cache.chunk_plans.mode** | **auto** |
|
| **cache.chunk_plans.mode** | **auto** |
|
||||||
@@ -298,7 +298,7 @@ output:
|
|||||||
enabled: true
|
enabled: true
|
||||||
window_units: 3
|
window_units: 3
|
||||||
lanes:
|
lanes:
|
||||||
- npcs
|
- npc-registry
|
||||||
- spells
|
- spells
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
@@ -332,15 +332,15 @@ step:
|
|||||||
steps:
|
steps:
|
||||||
- id: describe-session
|
- id: describe-session
|
||||||
artifacts:
|
artifacts:
|
||||||
npcs:
|
npc-registry:
|
||||||
extract: dnd/npcs
|
extract: dnd/npc-registry
|
||||||
normalize: dnd/npcs
|
normalize: dnd/npc-registry
|
||||||
- id: extract-events
|
- id: extract-events
|
||||||
references:
|
references:
|
||||||
npcs:
|
npc_registry:
|
||||||
artifact:
|
artifact:
|
||||||
step: describe-session
|
step: describe-session
|
||||||
lane: npcs
|
lane: npc-registry
|
||||||
artifacts:
|
artifacts:
|
||||||
spells:
|
spells:
|
||||||
extract: dnd/spells
|
extract: dnd/spells
|
||||||
@@ -370,14 +370,38 @@ selected target declares them:
|
|||||||
| **players** | Optional text player context. |
|
| **players** | Optional text player context. |
|
||||||
| **glossary** | Optional text campaign glossary. |
|
| **glossary** | Optional text campaign glossary. |
|
||||||
| **spell_catalog** | Optional JSON spell-catalog overlay for spell extraction and normalization. See [spell-catalog overlays](integrations/dnd-spell-catalog-overlays.md). |
|
| **spell_catalog** | Optional JSON spell-catalog overlay for spell extraction and normalization. See [spell-catalog overlays](integrations/dnd-spell-catalog-overlays.md). |
|
||||||
| **npcs** | Normalized NPC registry. Optional for spells and combat turns; required for NPC interactions. |
|
| **location_registry** | Required normalized location registry for location-occurrence extraction and normalization. |
|
||||||
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn extraction. |
|
| **item_registry** | Required normalized item registry for item-occurrence extraction and normalization. |
|
||||||
|
| **npc_registry** | Normalized NPC registry. Optional for spells and combat turns; required for NPC occurrences and enemy-event extraction and normalization. |
|
||||||
|
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn and enemy-event extraction. |
|
||||||
|
| **combat_turns** | Required normalized combat-turn artifact for enemy-event extraction. |
|
||||||
|
| **npc_occurrences** | Required normalized NPC-occurrence artifact for enemy-event extraction. |
|
||||||
|
|
||||||
|
Registry-backed occurrence and enemy-event artifact slots have the following
|
||||||
|
exact binding contracts. Durable semantics and wire shapes remain in their
|
||||||
|
[NPC occurrence](integrations/dnd-npc-occurrence-artifacts.md),
|
||||||
|
[location occurrence](integrations/dnd-location-occurrence-artifacts.md),
|
||||||
|
[item occurrence](integrations/dnd-item-occurrence-artifacts.md), and
|
||||||
|
[enemy-event](integrations/dnd-enemy-event-artifacts.md) contracts.
|
||||||
|
|
||||||
|
| Slot | Accepted artifact kind | Media type | Maximum size | Required stage |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `npc_registry` | `dnd/npc-registry` | `application/json` | 1,048,576 bytes | extract and normalize |
|
||||||
|
| `scene_descriptions` | `dnd/scene-description-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
|
| `combat_turns` | `dnd/combat-turn-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
|
| `npc_occurrences` | `dnd/npc-occurrence-list` | `application/json` | 1,048,576 bytes | extract only |
|
||||||
|
| `location_registry` | `dnd/location-registry` | `application/json` | 1,048,576 bytes | location-occurrence extract and normalize |
|
||||||
|
| `item_registry` | `dnd/item-registry` | `application/json` | 1,048,576 bytes | item-occurrence extract and normalize |
|
||||||
|
|
||||||
Scene descriptions accept **party**, **players**, and **glossary**, but not
|
Scene descriptions accept **party**, **players**, and **glossary**, but not
|
||||||
**roster**. NPC interactions require **npcs** for both extraction and
|
**roster**. NPC occurrences require **npc_registry** for both extraction and
|
||||||
normalization. Combat turns require **scene_descriptions** for extraction; the
|
normalization. Combat turns require **scene_descriptions** for extraction; the
|
||||||
normalized combat-turn module may use optional **npcs**. The complete example
|
normalized combat-turn module may use optional **npc_registry**. Location occurrences
|
||||||
shows generated **npcs** and **scene_descriptions** bindings.
|
require **location_registry** for extraction and normalization. Item occurrences require
|
||||||
|
**item_registry** for extraction and normalization. Enemy-event extraction requires all
|
||||||
|
four of its JSON artifact slots; its normalizer requires **npc_registry**.
|
||||||
|
The [complete example](../examples/dnd-complete.config.yml) shows the ordered
|
||||||
|
generated bindings.
|
||||||
|
|
||||||
## Production Module Keys
|
## Production Module Keys
|
||||||
|
|
||||||
@@ -385,18 +409,29 @@ shows generated **npcs** and **scene_descriptions** bindings.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Input | **seriatim** |
|
| Input | **seriatim** |
|
||||||
| Chunk | **generic**, **dnd/scenes** |
|
| Chunk | **generic**, **dnd/scenes** |
|
||||||
| Extract | **dnd/spells**, **dnd/npcs**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions** |
|
| Extract | **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-occurrences**, **dnd/item-registry**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/location-registry**, **dnd/location-occurrences** |
|
||||||
| Merge | **appendorder** |
|
| Merge | **appendorder** |
|
||||||
| Normalize | **noop**, **dnd/spells**, **dnd/npcs**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions** |
|
| Normalize | **noop**, **dnd/spells**, **dnd/npc-registry**, **dnd/combat-turns**, **dnd/item-occurrences**, **dnd/item-registry**, **dnd/npc-occurrences**, **dnd/scene-descriptions**, **dnd/enemy-events**, **dnd/location-registry**, **dnd/location-occurrences** |
|
||||||
| Output | **json** |
|
| Output | **json** |
|
||||||
|
|
||||||
|
`dnd/scenes` and every D&D extractor are `llm_backed`. The
|
||||||
|
`dnd/npc-registry`, `dnd/location-registry`, and `dnd/item-registry`
|
||||||
|
normalizers are also `llm_backed` for bounded duplicate proposals; every other
|
||||||
|
D&D normalizer is `deterministic`. LLM-backed bindings use the effective
|
||||||
|
[PromptKit profile](#promptkit-profiles). The complete example binds each
|
||||||
|
registry in an earlier step before its occurrence consumer.
|
||||||
|
|
||||||
The D&D artifact contracts define each emitted schema:
|
The D&D artifact contracts define each emitted schema:
|
||||||
[spells](integrations/dnd-spell-artifacts.md),
|
[spells](integrations/dnd-spell-artifacts.md),
|
||||||
[NPCs](integrations/dnd-npc-artifacts.md),
|
[NPC registry](integrations/dnd-npc-registry-artifacts.md),
|
||||||
[NPC interactions](integrations/dnd-npc-interaction-artifacts.md),
|
[NPC occurrences](integrations/dnd-npc-occurrence-artifacts.md),
|
||||||
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
||||||
[item events](integrations/dnd-item-event-artifacts.md), and
|
[item registry](integrations/dnd-item-registry-artifacts.md),
|
||||||
[scene descriptions](integrations/dnd-scene-description-artifacts.md).
|
[item occurrences](integrations/dnd-item-occurrence-artifacts.md),
|
||||||
|
[scene descriptions](integrations/dnd-scene-description-artifacts.md),
|
||||||
|
[enemy events](integrations/dnd-enemy-event-artifacts.md),
|
||||||
|
[location registry](integrations/dnd-location-registry-artifacts.md), and
|
||||||
|
[location occurrences](integrations/dnd-location-occurrence-artifacts.md).
|
||||||
|
|
||||||
## Production Validator Keys And Default Chains
|
## Production Validator Keys And Default Chains
|
||||||
|
|
||||||
@@ -406,11 +441,15 @@ Available validator keys are:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Generic | **generic/always_accept**, **generic/always_reject**, **generic/valid_json**, **generic/valid_json_schema** |
|
| Generic | **generic/always_accept**, **generic/always_reject**, **generic/valid_json**, **generic/valid_json_schema** |
|
||||||
| Spells | **extract/dnd/spells/shape**, **extract/dnd/spells/catalog**, **extract/dnd/spells/source_refs**, **extract/dnd/spells/source_relatedness** |
|
| Spells | **extract/dnd/spells/shape**, **extract/dnd/spells/catalog**, **extract/dnd/spells/source_refs**, **extract/dnd/spells/source_relatedness** |
|
||||||
| NPCs | **extract/dnd/npcs/shape**, **extract/dnd/npcs/source_refs**, **extract/dnd/npcs/source_relatedness**, **normalize/dnd/npcs/identity** |
|
| NPC registry | **extract/dnd/npc-registry/shape**, **extract/dnd/npc-registry/source_refs**, **extract/dnd/npc-registry/source_relatedness**, **normalize/dnd/npc-registry/identity** |
|
||||||
| Combat turns | **extract/dnd/combat-turns/shape**, **extract/dnd/combat-turns/source_refs**, **extract/dnd/combat-turns/source_relatedness**, **normalize/dnd/combat-turns/invariants** |
|
| Combat turns | **extract/dnd/combat-turns/shape**, **extract/dnd/combat-turns/source_refs**, **extract/dnd/combat-turns/source_relatedness**, **normalize/dnd/combat-turns/invariants** |
|
||||||
| Item events | **extract/dnd/item-events/shape**, **extract/dnd/item-events/source_refs**, **extract/dnd/item-events/source_relatedness**, **normalize/dnd/item-events/invariants** |
|
| Item occurrences | **extract/dnd/item-occurrences/shape**, **extract/dnd/item-occurrences/registry**, **extract/dnd/item-occurrences/source_refs**, **extract/dnd/item-occurrences/source_relatedness**, **normalize/dnd/item-occurrences/invariants** |
|
||||||
| NPC interactions | **extract/dnd/npc-interactions/shape**, **extract/dnd/npc-interactions/registry**, **extract/dnd/npc-interactions/source_refs**, **extract/dnd/npc-interactions/source_relatedness**, **normalize/dnd/npc-interactions/invariants** |
|
| Item registry | **extract/dnd/item-registry/shape**, **extract/dnd/item-registry/source_refs**, **extract/dnd/item-registry/source_relatedness**, **normalize/dnd/item-registry/identity** |
|
||||||
|
| NPC occurrences | **extract/dnd/npc-occurrences/shape**, **extract/dnd/npc-occurrences/registry**, **extract/dnd/npc-occurrences/source_refs**, **extract/dnd/npc-occurrences/source_relatedness**, **normalize/dnd/npc-occurrences/invariants** |
|
||||||
| Scene descriptions | **extract/dnd/scene-descriptions/shape**, **extract/dnd/scene-descriptions/source_refs**, **extract/dnd/scene-descriptions/source_relatedness**, **normalize/dnd/scene-descriptions/invariants** |
|
| Scene descriptions | **extract/dnd/scene-descriptions/shape**, **extract/dnd/scene-descriptions/source_refs**, **extract/dnd/scene-descriptions/source_relatedness**, **normalize/dnd/scene-descriptions/invariants** |
|
||||||
|
| Enemy events | **extract/dnd/enemy-events/shape**, **extract/dnd/enemy-events/engagements**, **extract/dnd/enemy-events/source_refs**, **extract/dnd/enemy-events/source_relatedness**, **normalize/dnd/enemy-events/invariants** |
|
||||||
|
| Location registry | **extract/dnd/location-registry/shape**, **extract/dnd/location-registry/source_refs**, **extract/dnd/location-registry/source_relatedness**, **normalize/dnd/location-registry/identity** |
|
||||||
|
| Location occurrences | **extract/dnd/location-occurrences/shape**, **extract/dnd/location-occurrences/registry**, **extract/dnd/location-occurrences/source_refs**, **extract/dnd/location-occurrences/source_relatedness**, **normalize/dnd/location-occurrences/invariants** |
|
||||||
|
|
||||||
When no override is configured, production D&D bindings use the following
|
When no override is configured, production D&D bindings use the following
|
||||||
ordered chains. Each row lists extract then normalize; spell chains are the
|
ordered chains. Each row lists extract then normalize; spell chains are the
|
||||||
@@ -419,11 +458,15 @@ same at both stages.
|
|||||||
| Lane | Extract | Normalize |
|
| Lane | Extract | Normalize |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Spells | generic/valid_json, extract/dnd/spells/shape, extract/dnd/spells/catalog, extract/dnd/spells/source_refs, generic/valid_json_schema, extract/dnd/spells/source_relatedness | Same as extract |
|
| Spells | generic/valid_json, extract/dnd/spells/shape, extract/dnd/spells/catalog, extract/dnd/spells/source_refs, generic/valid_json_schema, extract/dnd/spells/source_relatedness | Same as extract |
|
||||||
| NPCs | generic/valid_json, extract/dnd/npcs/shape, extract/dnd/npcs/source_refs, generic/valid_json_schema, extract/dnd/npcs/source_relatedness | generic/valid_json, extract/dnd/npcs/shape, normalize/dnd/npcs/identity, extract/dnd/npcs/source_refs, generic/valid_json_schema, extract/dnd/npcs/source_relatedness |
|
| NPC registry | generic/valid_json, extract/dnd/npc-registry/shape, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness | generic/valid_json, extract/dnd/npc-registry/shape, normalize/dnd/npc-registry/identity, extract/dnd/npc-registry/source_refs, generic/valid_json_schema, extract/dnd/npc-registry/source_relatedness |
|
||||||
| Combat turns | generic/valid_json, extract/dnd/combat-turns/shape, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness | generic/valid_json, extract/dnd/combat-turns/shape, normalize/dnd/combat-turns/invariants, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness |
|
| Combat turns | generic/valid_json, extract/dnd/combat-turns/shape, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness | generic/valid_json, extract/dnd/combat-turns/shape, normalize/dnd/combat-turns/invariants, extract/dnd/combat-turns/source_refs, generic/valid_json_schema, extract/dnd/combat-turns/source_relatedness |
|
||||||
| Item events | generic/valid_json, extract/dnd/item-events/shape, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness | generic/valid_json, extract/dnd/item-events/shape, normalize/dnd/item-events/invariants, extract/dnd/item-events/source_refs, generic/valid_json_schema, extract/dnd/item-events/source_relatedness |
|
| Item occurrences | generic/valid_json, extract/dnd/item-occurrences/shape, extract/dnd/item-occurrences/registry, extract/dnd/item-occurrences/source_refs, generic/valid_json_schema, extract/dnd/item-occurrences/source_relatedness | generic/valid_json, extract/dnd/item-occurrences/shape, extract/dnd/item-occurrences/registry, normalize/dnd/item-occurrences/invariants, extract/dnd/item-occurrences/source_refs, generic/valid_json_schema, extract/dnd/item-occurrences/source_relatedness |
|
||||||
| NPC interactions | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness | generic/valid_json, extract/dnd/npc-interactions/shape, extract/dnd/npc-interactions/registry, normalize/dnd/npc-interactions/invariants, extract/dnd/npc-interactions/source_refs, generic/valid_json_schema, extract/dnd/npc-interactions/source_relatedness |
|
| Item registry | generic/valid_json, extract/dnd/item-registry/shape, extract/dnd/item-registry/source_refs, generic/valid_json_schema, extract/dnd/item-registry/source_relatedness | generic/valid_json, extract/dnd/item-registry/shape, normalize/dnd/item-registry/identity, extract/dnd/item-registry/source_refs, generic/valid_json_schema, extract/dnd/item-registry/source_relatedness |
|
||||||
|
| NPC occurrences | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness | generic/valid_json, extract/dnd/npc-occurrences/shape, extract/dnd/npc-occurrences/registry, normalize/dnd/npc-occurrences/invariants, extract/dnd/npc-occurrences/source_refs, generic/valid_json_schema, extract/dnd/npc-occurrences/source_relatedness |
|
||||||
| Scene descriptions | generic/valid_json, extract/dnd/scene-descriptions/shape, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness | generic/valid_json, extract/dnd/scene-descriptions/shape, normalize/dnd/scene-descriptions/invariants, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness |
|
| Scene descriptions | generic/valid_json, extract/dnd/scene-descriptions/shape, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness | generic/valid_json, extract/dnd/scene-descriptions/shape, normalize/dnd/scene-descriptions/invariants, extract/dnd/scene-descriptions/source_refs, generic/valid_json_schema, extract/dnd/scene-descriptions/source_relatedness |
|
||||||
|
| Enemy events | generic/valid_json, extract/dnd/enemy-events/shape, extract/dnd/enemy-events/engagements, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness | generic/valid_json, extract/dnd/enemy-events/shape, normalize/dnd/enemy-events/invariants, extract/dnd/enemy-events/source_refs, generic/valid_json_schema, extract/dnd/enemy-events/source_relatedness |
|
||||||
|
| Location registry | generic/valid_json, extract/dnd/location-registry/shape, extract/dnd/location-registry/source_refs, generic/valid_json_schema, extract/dnd/location-registry/source_relatedness | generic/valid_json, extract/dnd/location-registry/shape, normalize/dnd/location-registry/identity, extract/dnd/location-registry/source_refs, generic/valid_json_schema, extract/dnd/location-registry/source_relatedness |
|
||||||
|
| Location occurrences | generic/valid_json, extract/dnd/location-occurrences/shape, extract/dnd/location-occurrences/registry, extract/dnd/location-occurrences/source_refs, generic/valid_json_schema, extract/dnd/location-occurrences/source_relatedness | generic/valid_json, extract/dnd/location-occurrences/shape, extract/dnd/location-occurrences/registry, normalize/dnd/location-occurrences/invariants, extract/dnd/location-occurrences/source_refs, generic/valid_json_schema, extract/dnd/location-occurrences/source_relatedness |
|
||||||
|
|
||||||
Chains are only registered for the D&D extract and normalize modules shown
|
Chains are only registered for the D&D extract and normalize modules shown
|
||||||
above; select an explicit override when a different compatible chain is
|
above; select an explicit override when a different compatible chain is
|
||||||
|
|||||||
@@ -27,10 +27,13 @@ notarius run pipeline-id \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use absolute paths for supplied input, configuration, output-root, and
|
Use absolute paths for supplied input, configuration, output-root, and
|
||||||
reference files. When a stable prompt session identifier or references are
|
reference files. Notarius generates a stable prompt session for the resolved
|
||||||
needed, pass the supported CLI flags. Supply credentials through Notarius's
|
input module and exact input bytes. Pass **--session-id** only when intentionally
|
||||||
documented configuration and environment mechanisms, never as command-line
|
grouping different invocations under a different session. Supply credentials
|
||||||
arguments or generated secret-bearing configuration.
|
through Notarius's documented configuration and environment mechanisms, never
|
||||||
|
as command-line arguments or generated secret-bearing configuration. In
|
||||||
|
particular, a session identifier is provider-visible and is not a credential
|
||||||
|
mechanism.
|
||||||
|
|
||||||
Wait for the process before interpreting standard output. Only an exit status
|
Wait for the process before interpreting standard output. Only an exit status
|
||||||
of 0 permits decoding the receipt. On a nonzero exit, retain standard error for
|
of 0 permits decoding the receipt. On a nonzero exit, retain standard error for
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ record controls eligibility only: its title, summary, and reference do not
|
|||||||
become turn evidence. No exact matching scene also produces an empty list and
|
become turn evidence. No exact matching scene also produces an empty list and
|
||||||
the `scene_classification_unavailable` warning.
|
the `scene_classification_unavailable` warning.
|
||||||
|
|
||||||
An optional normalized [NPC artifact](dnd-npc-artifacts.md) can ground an
|
An optional normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) can ground an
|
||||||
actor name. Its registry references are provenance, never combat evidence.
|
actor name. Its registry references are provenance, never combat evidence.
|
||||||
Normalization trims and, where possible, canonicalizes actor names; orders and
|
Normalization trims and, where possible, canonicalizes actor names; orders and
|
||||||
deduplicates exact source references; orders valid-evidence turns by source
|
deduplicates exact source references; orders valid-evidence turns by source
|
||||||
@@ -63,7 +63,9 @@ chronology; and collapses only duplicates with the same actor identity, turn
|
|||||||
kind, and complete valid evidence. It does not infer turns, initiative, or
|
kind, and complete valid evidence. It does not infer turns, initiative, or
|
||||||
actions from registry or scene data.
|
actions from registry or scene data.
|
||||||
|
|
||||||
The [NPC-interaction artifact](dnd-npc-interaction-artifacts.md) records
|
The [NPC-occurrence artifact](dnd-npc-occurrence-artifacts.md) records
|
||||||
broader NPC occurrences. The [JSON output contract](json-output.md) defines
|
broader NPC occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
||||||
publication, and [D&D module internals](../internal/dnd.md) describes routing
|
uses combat turns as grounding only; turns do not establish an enemy event or
|
||||||
and validation mechanics.
|
its outcome. The [JSON output contract](json-output.md) defines publication,
|
||||||
|
and [D&D module internals](../internal/dnd.md) describes routing and validation
|
||||||
|
mechanics.
|
||||||
|
|||||||
114
docs/integrations/dnd-enemy-event-artifacts.md
Normal file
114
docs/integrations/dnd-enemy-event-artifacts.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# D&D Enemy-Event Artifact
|
||||||
|
|
||||||
|
This contract defines the durable, source-grounded enemy-event occurrence list.
|
||||||
|
It records enemies directly established as opposing the party and explicitly
|
||||||
|
observed combat outcomes. It is an ordered observation artifact from which a
|
||||||
|
consumer may derive a ledger; it is not a ledger, encounter roster, or terminal
|
||||||
|
state model.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/enemy-event-list` |
|
||||||
|
| Schema ID | `notarius.dnd.enemy_events` |
|
||||||
|
| Schema name | `notarius_dnd_enemy_events_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
|
||||||
|
`v1` is a strict JSON object with required `events`; the array may be empty.
|
||||||
|
Event and source-reference objects reject unknown fields. An incompatible shape
|
||||||
|
change requires a new schema version.
|
||||||
|
|
||||||
|
## Wire shape
|
||||||
|
|
||||||
|
Every event has these required fields:
|
||||||
|
|
||||||
|
| Field | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `name` | Non-empty display name or directly grounded collective subject label. |
|
||||||
|
| `kind` | `engaged`, `killed`, `fled`, `captured`, or `incapacitated`. |
|
||||||
|
| `source_refs` | One or more current-transcript evidence ranges. |
|
||||||
|
|
||||||
|
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": "Ashfang",
|
||||||
|
"kind": "engaged",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 41, "end_unit_id": 42}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ashfang",
|
||||||
|
"kind": "fled",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 57, "end_unit_id": 58}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event semantics and evidence
|
||||||
|
|
||||||
|
| Kind | Required evidence |
|
||||||
|
| --- | --- |
|
||||||
|
| `engaged` | The subject is directly established as actively opposing the party in combat. At most one engagement is emitted for one subject in one combat scene. |
|
||||||
|
| `killed` | The transcript explicitly establishes that the subject died or was killed. Damage, defeat, disappearance, or combat ending is insufficient. |
|
||||||
|
| `fled` | The subject explicitly escapes, retreats, or otherwise leaves combat to avoid continued engagement. Movement or absence from later turns is insufficient. |
|
||||||
|
| `captured` | The subject is explicitly taken prisoner or secured under the party's control. A grapple or temporary restraint alone is insufficient. |
|
||||||
|
| `incapacitated` | The subject is explicitly rendered unable to continue acting without being established as killed or captured. A missed turn is insufficient. |
|
||||||
|
|
||||||
|
The current transcript is the only event evidence. Campaign context and
|
||||||
|
normalized NPC, scene-description, combat-turn, and NPC-occurrence artifacts
|
||||||
|
can ground names or control combat eligibility, but none may supply event
|
||||||
|
evidence. An outcome may share evidence with an engagement, in which case both
|
||||||
|
events are retained.
|
||||||
|
|
||||||
|
Extraction is limited to chunks with an exact combat-scene classification. An
|
||||||
|
exact non-combat classification produces an accepted empty list. Missing or
|
||||||
|
mismatched classification also produces an accepted empty list and a
|
||||||
|
`scene_classification_unavailable` warning.
|
||||||
|
|
||||||
|
## Subjects, normalization, and order
|
||||||
|
|
||||||
|
A subject matching the normalized NPC registry uses that registry's canonical
|
||||||
|
display name. Unmatched hostile creatures, summoned entities, and directly
|
||||||
|
grounded groups remain valid subjects. An unnamed homogeneous group uses the
|
||||||
|
narrowest transcript-grounded label, such as `Orcs`, `One orc`, or `Remaining
|
||||||
|
orcs`; the artifact never invents synthetic member identities or quantities.
|
||||||
|
Party members, allies, neutral observers, mentioned-but-absent enemies, hazards,
|
||||||
|
traps, and environmental effects are excluded.
|
||||||
|
|
||||||
|
Normalization collapses surrounding and repeated internal whitespace in subject
|
||||||
|
display values, canonicalizes recognized registry names, canonicalizes and
|
||||||
|
deduplicates exact source ranges, then orders events by valid evidence
|
||||||
|
chronology, normalized subject identity, display name, kind, and reference
|
||||||
|
sequence. The deterministic kind tie order is `engaged`,
|
||||||
|
`incapacitated`, `captured`, `fled`, then `killed`. Only entries with the same
|
||||||
|
normalized name, kind, and complete canonical evidence sequence are collapsed.
|
||||||
|
Different kinds, evidence, repeated engagement in separate scenes, and later
|
||||||
|
outcomes remain separate. A later engagement for the same named subject is
|
||||||
|
preserved after an earlier outcome because the artifact does not assert an
|
||||||
|
irreversible state transition.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
The artifact has no NPC or scene ID, quantity, confidence, description,
|
||||||
|
rationale, summary, current state, or inferred terminal outcome. It does not
|
||||||
|
emit `active` or `unresolved`; consumers may derive an unresolved ledger view
|
||||||
|
only when an engagement has no later explicit outcome. It never infers an
|
||||||
|
outcome from turn absence, scene termination, initiative order, hit-point
|
||||||
|
guesses, or other artifacts.
|
||||||
|
|
||||||
|
The [JSON output contract](json-output.md) defines publication. Configuration
|
||||||
|
keys, required generated-reference slots, and validator-chain selection are
|
||||||
|
defined in the [configuration reference](../config.md). Implementation and
|
||||||
|
prompt-grounding mechanics are described in the
|
||||||
|
[D&D module internals](../internal/dnd.md).
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
# D&D Item-Event Artifact
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Identity and compatibility
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
| --- | --- |
|
|
||||||
| 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` |
|
|
||||||
|
|
||||||
`v1` is a strict JSON object with required `events`; the array may be empty.
|
|
||||||
Event and source-reference objects reject unknown fields. An incompatible
|
|
||||||
shape change requires a new schema version.
|
|
||||||
|
|
||||||
## Wire shape
|
|
||||||
|
|
||||||
Every event has required `name`, `kind`, and `source_refs`. `quantity`, `from`,
|
|
||||||
and `to` are optional where the event kind permits them.
|
|
||||||
|
|
||||||
| Field | Contract |
|
|
||||||
| --- | --- |
|
|
||||||
| `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. |
|
|
||||||
|
|
||||||
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": "Silver Pieces",
|
|
||||||
"kind": "acquired",
|
|
||||||
"quantity": 20,
|
|
||||||
"to": "party",
|
|
||||||
"source_refs": [
|
|
||||||
{"source_id": "session-7", "start_unit_id": 2, "end_unit_id": 2}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Holder rules and minimal extraction
|
|
||||||
|
|
||||||
`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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
72
docs/integrations/dnd-item-occurrence-artifacts.md
Normal file
72
docs/integrations/dnd-item-occurrence-artifacts.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# D&D Item-Occurrence Artifact
|
||||||
|
|
||||||
|
`dnd/item-occurrences` currently produces this source-grounded item and currency
|
||||||
|
occurrence list. It records discoveries and possession changes, not an
|
||||||
|
inventory, balance, or ledger.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/item-occurrence-list` |
|
||||||
|
| Schema ID | `notarius.dnd.item_occurrences` |
|
||||||
|
| Schema name | `notarius_dnd_item_occurrences_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
|
||||||
|
`v1` accepts one strict JSON object with required `occurrences`; the array may
|
||||||
|
be empty. Each occurrence has required `item_id`, `name`, `kind`, and
|
||||||
|
`source_refs`, and occurrence and source-reference objects reject unknown
|
||||||
|
fields. `quantity`, `from`, and `to` appear only when their kind permits them.
|
||||||
|
An incompatible shape change requires a new schema version.
|
||||||
|
|
||||||
|
## Registry grounding
|
||||||
|
|
||||||
|
Both extraction and normalization require an `item_registry` reference bound to
|
||||||
|
an earlier normalized `dnd/item-registry` artifact. The registry is immutable
|
||||||
|
for an operation and contributes names-only grounding after the shared evidence
|
||||||
|
message. Notarius resolves the model's selected name into the unchanged exact
|
||||||
|
durable ID/name pair. It is never occurrence evidence.
|
||||||
|
|
||||||
|
Each occurrence must use one exact registry ID/name pair. An extraction response
|
||||||
|
with an unknown or ambiguous selected name is rejected as invalid model output;
|
||||||
|
the configured pipeline may retry it and never accepts a partial artifact.
|
||||||
|
Normalization and validation remain defense in depth for artifacts entering
|
||||||
|
through other boundaries: normalization canonicalizes a recognized name by ID,
|
||||||
|
preserves unknown values for the registry validator, and the registry validator
|
||||||
|
rejects unknown or mismatched pairs.
|
||||||
|
|
||||||
|
## Wire shape
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"occurrences": [
|
||||||
|
{
|
||||||
|
"item_id": "item:sha256:…",
|
||||||
|
"name": "Silver Pieces",
|
||||||
|
"kind": "acquired",
|
||||||
|
"quantity": 20,
|
||||||
|
"to": "party",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 2, "end_unit_id": 2}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The five kinds remain `discovered`, `acquired`, `lost`, `consumed`, and
|
||||||
|
`transferred`. Holder, quantity, currency, ordering, and exact-duplicate rules
|
||||||
|
are unchanged: discovered has no holder; acquired requires `to`; lost and
|
||||||
|
consumed require `from`; transferred requires distinct non-`party` holders.
|
||||||
|
The only current downstream compatibility requirement is its registry handoff;
|
||||||
|
the normalized occurrence list is otherwise published for callers. See
|
||||||
|
[Configuration](../config.md#d-d-reference-slots) for the binding and
|
||||||
|
[JSON output](json-output.md) for publication.
|
||||||
|
|
||||||
|
See [item registry](dnd-item-registry-artifacts.md) for the grounding artifact
|
||||||
|
and [D&D module internals](../internal/dnd.md) for implementation details.
|
||||||
95
docs/integrations/dnd-item-registry-artifacts.md
Normal file
95
docs/integrations/dnd-item-registry-artifacts.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# D&D Item Registry Artifact
|
||||||
|
|
||||||
|
This contract defines the durable, source-grounded item registry produced by
|
||||||
|
`dnd/item-registry`. It records transcript-established item types and unique
|
||||||
|
designations for one source document; it is not an inventory, holder record,
|
||||||
|
quantity ledger, or item-occurrence artifact.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/item-registry` |
|
||||||
|
| Schema ID | `notarius.dnd.item_registry` |
|
||||||
|
| Schema name | `notarius_dnd_item_registry_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
| Identity policy | `dnd.item_registry.identity.v1` |
|
||||||
|
|
||||||
|
`v1` accepts one strict JSON object with required `items`; the array may be
|
||||||
|
empty. Item and source-reference objects reject unknown fields. An incompatible
|
||||||
|
artifact shape or identity-policy change uses a new version or policy.
|
||||||
|
|
||||||
|
## Wire shape and identity
|
||||||
|
|
||||||
|
Each item has these required fields:
|
||||||
|
|
||||||
|
| Field | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `id` | `item:sha256:` followed by 64 lowercase hexadecimal characters. |
|
||||||
|
| `name` | Non-empty transcript-established item type or unique designation. |
|
||||||
|
| `source_refs` | One or more transcript evidence ranges that establish the item. |
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "item:sha256:31e73b6280ef98e4d8070e07fd4de9b2c3e842cc03af1a09ca631cb95b73e3b3",
|
||||||
|
"name": "Star Compass",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 5}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ID is deterministic for an item name or type, rather than for one physical
|
||||||
|
instance. Notarius normalizes the display name for comparison with Unicode
|
||||||
|
NFKC, supported apostrophe normalization, collapsed whitespace, and case
|
||||||
|
folding. It hashes compact JSON for this array:
|
||||||
|
|
||||||
|
```text
|
||||||
|
["dnd.item_registry.identity.v1", comparison_name]
|
||||||
|
```
|
||||||
|
|
||||||
|
The canonical ID is the lowercase SHA-256 digest of those bytes with the
|
||||||
|
`item:sha256:` prefix. Equal comparison names represent one item identity;
|
||||||
|
normalization unions their transcript evidence when it safely consolidates a
|
||||||
|
candidate group.
|
||||||
|
|
||||||
|
## Scope, reconciliation, and evidence
|
||||||
|
|
||||||
|
The registry includes named unique items, concrete reusable item types, stable
|
||||||
|
unique designations, and separately established currency denominations. It
|
||||||
|
excludes vague loot or treasure, generic weapons, quantities, inferred
|
||||||
|
properties, and inferred uniqueness. Capitalization alone does not establish
|
||||||
|
eligibility.
|
||||||
|
|
||||||
|
Normalization first applies deterministic display, evidence, and ID rules. It
|
||||||
|
then may use a bounded LLM-assisted proposal to reconcile semantically duplicate
|
||||||
|
records. The proposal may choose only a supplied candidate display name;
|
||||||
|
invalid, uncertain, overlapping, or unsafe proposals retain the deterministic
|
||||||
|
result with retry or fallback diagnostics. A proposal that mixes a recognized
|
||||||
|
currency denomination with a non-currency item, or combines recognized
|
||||||
|
denominations, is unsafe and retains every deterministic record. Currency
|
||||||
|
denominations, materially different item types, and merely nearby objects
|
||||||
|
remain distinct. Source references establish registry provenance, not evidence
|
||||||
|
for later artifacts.
|
||||||
|
|
||||||
|
## Consumers and publication
|
||||||
|
|
||||||
|
`dnd/item-occurrences` requires one approved item registry through its
|
||||||
|
`item_registry` reference slot for both extraction and normalization. Its
|
||||||
|
consumer receives names-only grounding; Notarius resolves the selected name
|
||||||
|
into the unchanged exact durable ID/name pair. The registry’s source references
|
||||||
|
are never occurrence evidence. Unknown or ambiguous selections are rejected by
|
||||||
|
the occurrence contract. See the
|
||||||
|
[item-occurrence artifact](dnd-item-occurrence-artifacts.md) for that strict
|
||||||
|
wire contract, [Configuration](../config.md#d-d-reference-slots) for binding
|
||||||
|
rules and validator selection, and the [JSON output contract](json-output.md)
|
||||||
|
for publication.
|
||||||
86
docs/integrations/dnd-location-occurrence-artifacts.md
Normal file
86
docs/integrations/dnd-location-occurrence-artifacts.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# D&D Location-Occurrence Artifact
|
||||||
|
|
||||||
|
This contract defines the durable occurrence list produced by
|
||||||
|
`dnd/location-occurrences`. It records source-grounded ways the party relates
|
||||||
|
to locations in a required normalized location registry; it does not extend
|
||||||
|
that registry or infer a place absent from it.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/location-occurrence-list` |
|
||||||
|
| Schema ID | `notarius.dnd.location_occurrences` |
|
||||||
|
| Schema name | `notarius_dnd_location_occurrences_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
|
||||||
|
`v1` accepts one strict JSON object with required `occurrences`; the array may
|
||||||
|
be empty. Occurrence and source-reference objects reject unknown fields. An
|
||||||
|
incompatible shape change requires a new schema version.
|
||||||
|
|
||||||
|
## Wire shape
|
||||||
|
|
||||||
|
Each occurrence has these required fields:
|
||||||
|
|
||||||
|
| Field | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `location_id` | Exact ID from the required normalized [location registry](dnd-location-registry-artifacts.md). |
|
||||||
|
| `name` | Exact canonical display name for `location_id` in that registry. |
|
||||||
|
| `kind` | One of `visited`, `planned`, `recalled`, or `mentioned`. |
|
||||||
|
| `source_refs` | One or more current-transcript evidence ranges for this occurrence. |
|
||||||
|
|
||||||
|
A 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.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"occurrences": [
|
||||||
|
{
|
||||||
|
"location_id": "location:sha256:fb05475da0fc7debf994b517e1906ffe7209887a6a1ec306356d84de820b1a24",
|
||||||
|
"name": "Moon Gate",
|
||||||
|
"kind": "visited",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 12, "end_unit_id": 13}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Occurrence categories
|
||||||
|
|
||||||
|
| Kind | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| `visited` | The transcript establishes physical party presence, including arrival, continuing presence, or departure. |
|
||||||
|
| `planned` | The party explicitly proposes, intends, or agrees to future travel; speculation alone is not enough. |
|
||||||
|
| `recalled` | The transcript explicitly recounts prior party presence before the current live events. |
|
||||||
|
| `mentioned` | The location is explicit but no stronger category applies, including lore, directions, third-party activity, non-actionable speculation, a mere hypothetical reference, or out-of-character discussion. |
|
||||||
|
|
||||||
|
For overlapping evidence, precedence is `visited`, then `planned`, then
|
||||||
|
`recalled`, then `mentioned`. For example, “What if we went to Moon Gate?” is
|
||||||
|
eligible as `mentioned` when its narrow evidence explicitly references that
|
||||||
|
registry location, but it is not `planned` without an actual proposal,
|
||||||
|
intention, or agreement to travel. Inferred, unstated, uncertain, and
|
||||||
|
unsupported places or occurrences are omitted. Normalization
|
||||||
|
canonicalizes the registry name, orders and deduplicates source references, and
|
||||||
|
orders occurrences by source chronology, location ID, name, kind, and reference
|
||||||
|
sequence. It collapses only exact duplicates with the same ID, kind, and
|
||||||
|
complete canonical evidence sequence.
|
||||||
|
|
||||||
|
## Required grounding and evidence
|
||||||
|
|
||||||
|
Both extraction and normalization require exactly one `location_registry` reference of
|
||||||
|
kind `dnd/location-registry`, media type `application/json`, and at most 1 MiB. The
|
||||||
|
registry provides identity grounding only. The model selects a supplied
|
||||||
|
contextual name-and-registry-reference descriptor, and Notarius resolves it
|
||||||
|
into the exact durable ID/name pair. Unknown, partial, or ambiguous selections
|
||||||
|
are rejected rather than guessed or reassigned. The current transcript is the
|
||||||
|
only evidence source for an occurrence; registry evidence and provenance never
|
||||||
|
become occurrence evidence.
|
||||||
|
|
||||||
|
See [Configuration](../config.md#d-d-reference-slots) for the selectable slot
|
||||||
|
and generated-handoff compatibility, [D&D module internals](../internal/dnd.md)
|
||||||
|
for implementation behavior, and the [JSON output contract](json-output.md)
|
||||||
|
for publication.
|
||||||
93
docs/integrations/dnd-location-registry-artifacts.md
Normal file
93
docs/integrations/dnd-location-registry-artifacts.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# D&D Location Registry Artifact
|
||||||
|
|
||||||
|
This contract defines the durable, source-grounded location registry produced
|
||||||
|
by `dnd/location-registry`. It records transcript-established physical places for one
|
||||||
|
source document; it is not a map, location hierarchy, campaign-wide world
|
||||||
|
registry, or location description.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/location-registry` |
|
||||||
|
| Schema ID | `notarius.dnd.location_registry` |
|
||||||
|
| Schema name | `notarius_dnd_location_registry_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
| Identity policy | `dnd.location_registry.identity.v1` |
|
||||||
|
|
||||||
|
`v1` accepts one strict JSON object with required `locations`; the array may be
|
||||||
|
empty. Location and source-reference objects reject unknown fields. An
|
||||||
|
incompatible artifact shape or identity-policy change uses a new version or
|
||||||
|
policy.
|
||||||
|
|
||||||
|
## Wire shape and identity
|
||||||
|
|
||||||
|
Each location has these required fields:
|
||||||
|
|
||||||
|
| Field | Contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `id` | `location:sha256:` followed by 64 lowercase hexadecimal characters. |
|
||||||
|
| `name` | Non-empty transcript-established display name. |
|
||||||
|
| `source_refs` | One or more transcript evidence ranges that identify the place. |
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"id": "location:sha256:fb05475da0fc7debf994b517e1906ffe7209887a6a1ec306356d84de820b1a24",
|
||||||
|
"name": "Moon Gate",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 5}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ID is deterministic and scoped to the source document. Notarius normalizes
|
||||||
|
the display name for comparison with Unicode NFKC, supported apostrophe
|
||||||
|
normalization, collapsed whitespace, and case folding. It hashes compact JSON
|
||||||
|
for this array, using the earliest canonical source reference as the anchor:
|
||||||
|
|
||||||
|
```text
|
||||||
|
["dnd.location_registry.identity.v1", comparison_name, source_id, start_unit_id, end_unit_id]
|
||||||
|
```
|
||||||
|
|
||||||
|
The canonical ID is the lowercase SHA-256 digest of those bytes with the
|
||||||
|
`location:sha256:` prefix. Equal display names are allowed when their evidence
|
||||||
|
anchors differ, so a generic name does not force distinct places to collapse.
|
||||||
|
|
||||||
|
## Scope, reconciliation, and evidence
|
||||||
|
|
||||||
|
Locations are physical or spatial places established by the transcript with a
|
||||||
|
stable proper name or unique in-world designation, such as named planes,
|
||||||
|
regions, settlements, districts, buildings, rooms, landmarks, routes, and
|
||||||
|
geographic features. Generic, temporary, relative, and descriptive phrases
|
||||||
|
such as “the room,” “the bar,” “the hallway,” “outside,” and “upstairs” are not
|
||||||
|
registry locations. Capitalization alone does not establish eligibility.
|
||||||
|
Notarius does not infer an unstated place or add hierarchy, coordinates,
|
||||||
|
descriptions, participants, or ownership.
|
||||||
|
|
||||||
|
Normalization first applies deterministic display, evidence, and ID rules. It
|
||||||
|
then may use a bounded LLM-assisted proposal to reconcile semantically duplicate
|
||||||
|
records. The proposal is validated and applied conservatively; invalid or
|
||||||
|
unusable proposals retain the deterministic result with retry or fallback
|
||||||
|
diagnostics. The registry's source references establish registry provenance,
|
||||||
|
not evidence for later artifacts.
|
||||||
|
|
||||||
|
## Consumers and publication
|
||||||
|
|
||||||
|
`dnd/location-occurrences` requires one approved location registry through its
|
||||||
|
`location_registry` reference slot. Its prompt receives contextual selectors
|
||||||
|
containing a canonical name and registry references; Notarius resolves a
|
||||||
|
selection into the unchanged exact durable ID/name pair. Registry references
|
||||||
|
must not be treated as occurrence evidence. See the
|
||||||
|
[location-occurrence artifact](dnd-location-occurrence-artifacts.md)
|
||||||
|
for that contract, [Configuration](../config.md#references-and-ordered-handoffs)
|
||||||
|
for binding rules, and the [JSON output contract](json-output.md) for
|
||||||
|
publication.
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# D&D NPC Artifact
|
|
||||||
|
|
||||||
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 and compatibility
|
|
||||||
|
|
||||||
| 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` |
|
|
||||||
|
|
||||||
`v1` accepts one strict JSON object with required `npcs`; the array may be
|
|
||||||
empty. NPC and source-reference objects reject unknown fields. An incompatible
|
|
||||||
artifact shape or identity-policy change uses a new version or policy.
|
|
||||||
|
|
||||||
## Wire shape and identity
|
|
||||||
|
|
||||||
Each NPC has these required fields:
|
|
||||||
|
|
||||||
| 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": [
|
|
||||||
{
|
|
||||||
"id": "npc:sha256:99a16589618a04f535a7d21fdcc71a0b1c05d22f752cd492065b1086d97bc3d7",
|
|
||||||
"name": "Mira Thorn",
|
|
||||||
"source_refs": [
|
|
||||||
{"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 5}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Scope and consumers
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# D&D NPC Interaction Artifact
|
# D&D NPC Occurrence Artifact
|
||||||
|
|
||||||
This contract defines the durable occurrence list produced by
|
This contract defines the durable occurrence list produced by
|
||||||
`dnd/npc-interactions`. It records discrete, source-grounded interactions with
|
`dnd/npc-occurrences`. It records discrete, source-grounded occurrences with
|
||||||
NPCs already present in a normalized registry; it does not extend that registry
|
NPCs already present in a normalized registry; it does not extend that registry
|
||||||
or summarize the session.
|
or summarize the session.
|
||||||
|
|
||||||
@@ -9,35 +9,37 @@ or summarize the session.
|
|||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Artifact kind | `dnd/npc-interaction-list` |
|
| Artifact kind | `dnd/npc-occurrence-list` |
|
||||||
| Schema ID | `notarius.dnd.npc_interactions` |
|
| Schema ID | `notarius.dnd.npc_occurrences` |
|
||||||
| Schema name | `notarius_dnd_npc_interactions_v1` |
|
| Schema name | `notarius_dnd_npc_occurrences_v1` |
|
||||||
| Schema version | `v1` |
|
| Schema version | `v1` |
|
||||||
| Media type | `application/json` |
|
| Media type | `application/json` |
|
||||||
|
|
||||||
`v1` is a strict JSON object with required `interactions`; the array may be
|
`v1` is a strict JSON object with required `occurrences`; the array may be
|
||||||
empty. Interaction and source-reference objects reject unknown fields. An
|
empty. Occurrence and source-reference objects reject unknown fields. An
|
||||||
incompatible shape change requires a new schema version.
|
incompatible shape change requires a new schema version.
|
||||||
|
|
||||||
## Wire shape
|
## Wire shape
|
||||||
|
|
||||||
Each interaction has these required fields:
|
Each occurrence has these required fields:
|
||||||
|
|
||||||
| Field | Contract |
|
| Field | Contract |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `npc_id` | Exact durable ID from the required NPC registry. |
|
||||||
| `name` | Non-empty canonical display name from the required NPC registry. |
|
| `name` | Non-empty canonical display name from the required NPC registry. |
|
||||||
| `kind` | One of the interaction categories below. |
|
| `kind` | One of the occurrence categories below. |
|
||||||
| `source_refs` | One or more transcript evidence ranges. |
|
| `source_refs` | One or more transcript evidence ranges. |
|
||||||
|
|
||||||
Each source reference has exactly `source_id`, `start_unit_id`, and
|
Each source reference has exactly `source_id`, `start_unit_id`, and
|
||||||
`end_unit_id`. It identifies an inclusive range in the current transcript;
|
`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
|
unit IDs are positive and the start may not follow the end. Extraction evidence
|
||||||
for an interaction is confined to its accepted chunk.
|
for an occurrence is confined to its accepted chunk.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"interactions": [
|
"occurrences": [
|
||||||
{
|
{
|
||||||
|
"npc_id": "npc:sha256:example",
|
||||||
"name": "Mira Thorn",
|
"name": "Mira Thorn",
|
||||||
"kind": "dialogue",
|
"kind": "dialogue",
|
||||||
"source_refs": [
|
"source_refs": [
|
||||||
@@ -48,7 +50,7 @@ for an interaction is confined to its accepted chunk.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Interaction categories
|
## Occurrence categories
|
||||||
|
|
||||||
| Kind | Meaning |
|
| Kind | Meaning |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -65,14 +67,24 @@ for uncertain classification.
|
|||||||
|
|
||||||
## Identity, evidence, and order
|
## Identity, evidence, and order
|
||||||
|
|
||||||
The required normalized [NPC artifact](dnd-npc-artifacts.md) resolves `name`.
|
The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md)
|
||||||
Registry references are provenance only and never replace an interaction's own
|
supplies names-only contextual grounding to the model. Notarius resolves the
|
||||||
evidence. Normalization canonicalizes recognized registry names, orders and
|
selected name and writes the exact `{npc_id, name}` pair. An unknown or
|
||||||
deduplicates exact source references, then orders interactions by valid source
|
ambiguous selection rejects the complete model result; normalization does not
|
||||||
|
repair names by similarity. Registry references are provenance only and never
|
||||||
|
replace an occurrence's own evidence.
|
||||||
|
The registry may include an identity established by a factual third-party
|
||||||
|
mention; that provenance alone does not create a `mentioned` occurrence. Each
|
||||||
|
occurrence remains a separately cited fact in the current transcript.
|
||||||
|
Normalization validates the exact pair, orders and
|
||||||
|
deduplicates exact source references, then orders occurrences by valid source
|
||||||
chronology, NPC comparison identity, display name, kind, and reference sequence.
|
chronology, NPC comparison identity, display name, kind, and reference sequence.
|
||||||
Only entries with the same canonical name, kind, and complete valid evidence
|
Only entries with the same NPC ID, canonical name, kind, and complete valid evidence
|
||||||
sequence are collapsed; distinct categories or evidence remain separate.
|
sequence are collapsed; distinct categories or evidence remain separate.
|
||||||
|
|
||||||
See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action
|
See the [combat-turn artifact](dnd-combat-turn-artifacts.md) for combat-action
|
||||||
occurrences and the [JSON output contract](json-output.md) for publication.
|
occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md) consumes
|
||||||
Pipeline mechanics are described in [D&D module internals](../internal/dnd.md).
|
only `combat_opponent` occurrences as grounding; they never establish an enemy
|
||||||
|
event or outcome. The [JSON output contract](json-output.md) defines
|
||||||
|
publication. Pipeline mechanics are described in
|
||||||
|
[D&D module internals](../internal/dnd.md).
|
||||||
92
docs/integrations/dnd-npc-registry-artifacts.md
Normal file
92
docs/integrations/dnd-npc-registry-artifacts.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# D&D NPC Registry Artifact
|
||||||
|
|
||||||
|
This contract defines the durable NPC registry produced by `dnd/npc-registry`. It is a
|
||||||
|
minimal, source-grounded identity registry for other D&D artifacts, not a
|
||||||
|
character sheet or a relationship summary.
|
||||||
|
|
||||||
|
## Identity and compatibility
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Artifact kind | `dnd/npc-registry` |
|
||||||
|
| Schema ID | `notarius.dnd.npc_registry` |
|
||||||
|
| Schema name | `notarius_dnd_npc_registry_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
| Identity policy | `dnd.npc_registry.identity.v1` |
|
||||||
|
|
||||||
|
`v1` accepts one strict JSON object with required `npcs`; the array may be
|
||||||
|
empty. NPC and source-reference objects reject unknown fields. An incompatible
|
||||||
|
artifact shape or identity-policy change uses a new version or policy.
|
||||||
|
|
||||||
|
## Wire shape and identity
|
||||||
|
|
||||||
|
Each NPC has these required fields:
|
||||||
|
|
||||||
|
| 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": [
|
||||||
|
{
|
||||||
|
"id": "npc:sha256:35ba5f679aee69e07ae3bd65c44278f29539d5dc9bb5225db1c0060555b23221",
|
||||||
|
"name": "Mira Thorn",
|
||||||
|
"source_refs": [
|
||||||
|
{"source_id": "session-7", "start_unit_id": 4, "end_unit_id": 5}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ID is deterministic: normalize the name to Unicode NFKC, normalize the
|
||||||
|
supported apostrophe forms, collapse whitespace, case-fold it, then serialize
|
||||||
|
`["dnd.npc_registry.identity.v1", comparison_name]` as compact JSON. SHA-256
|
||||||
|
those UTF-8 bytes and 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.
|
||||||
|
|
||||||
|
When evidence supports a semantically duplicate group, the canonical display
|
||||||
|
name is one of that group's supplied candidates. A complete, stable proper name
|
||||||
|
is preferred over an abbreviation. An unadorned proper name is preferred over
|
||||||
|
the same name plus a contextual class, role, title, or relationship descriptor
|
||||||
|
unless the transcript establishes that descriptor as part of the person's
|
||||||
|
name. A longer candidate is not preferred solely because it includes such a
|
||||||
|
descriptor.
|
||||||
|
|
||||||
|
## Scope and consumers
|
||||||
|
|
||||||
|
Only individually identifiable NPC names with transcript evidence belong in
|
||||||
|
this artifact. A factual third-party mention can establish an identity even if
|
||||||
|
the NPC is not present, speaking, or acting in the cited passage. Names used
|
||||||
|
only in hypothetical, speculative, or imagined examples are excluded, as are
|
||||||
|
groups, generic roles, invented labels, and descriptive enrichment. Its source
|
||||||
|
references prove registry provenance; they do not become evidence for a spell,
|
||||||
|
occurrence, combat, or enemy-event occurrence.
|
||||||
|
|
||||||
|
Registry evidence establishes an identity, not an [NPC occurrence](dnd-npc-occurrence-artifacts.md).
|
||||||
|
That later artifact independently records any current-transcript occurrence
|
||||||
|
with its own cited evidence and category.
|
||||||
|
|
||||||
|
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 occurrence](dnd-npc-occurrence-artifacts.md).
|
||||||
|
Occurrence consumers receive names-only grounding; Notarius resolves the
|
||||||
|
selected canonical name and writes the unchanged exact durable ID/name pair.
|
||||||
|
Spells, combat turns, and the [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
||||||
|
also receive names-only grounding for actor or subject display. None of these
|
||||||
|
projections supply later-artifact evidence. [Configuration](../config.md#d-d-reference-slots)
|
||||||
|
owns the `npc_registry` binding rules.
|
||||||
|
The [JSON output contract](json-output.md) defines publication, and
|
||||||
|
[D&D module internals](../internal/dnd.md) owns pipeline mechanics.
|
||||||
@@ -62,8 +62,9 @@ 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
|
summary, is invalid. It does not merge adjacent ranges, alter prose, or infer
|
||||||
missing scenes.
|
missing scenes.
|
||||||
|
|
||||||
The [combat-turn artifact](dnd-combat-turn-artifacts.md) uses an exact matching
|
The [combat-turn artifact](dnd-combat-turn-artifacts.md) and
|
||||||
|
[enemy-event artifact](dnd-enemy-event-artifacts.md) use an exact matching
|
||||||
`combat` scene only as eligibility control; scene title, summary, and source
|
`combat` scene only as eligibility control; scene title, summary, and source
|
||||||
reference never become combat evidence. Publication is defined by the
|
reference never become their evidence. Publication is defined by the
|
||||||
[JSON output contract](json-output.md); implementation details live in
|
[JSON output contract](json-output.md); implementation details live in
|
||||||
[D&D module internals](../internal/dnd.md).
|
[D&D module internals](../internal/dnd.md).
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ only when it has the same canonical spell, the same case- and
|
|||||||
whitespace-insensitive caster identity, and the same complete valid reference
|
whitespace-insensitive caster identity, and the same complete valid reference
|
||||||
sequence. Remaining entries retain their merged order.
|
sequence. Remaining entries retain their merged order.
|
||||||
|
|
||||||
The optional normalized [NPC artifact](dnd-npc-artifacts.md) can ground a
|
The optional normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) can ground a
|
||||||
caster name. Its own references remain registry provenance and are never copied
|
caster name. Its own references remain registry provenance and are never copied
|
||||||
into `source_refs`.
|
into `source_refs`.
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ evidence publishes `contexts: []`.
|
|||||||
"source_id": "session-alpha",
|
"source_id": "session-alpha",
|
||||||
"source_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
"source_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
"window_units": 1,
|
"window_units": 1,
|
||||||
"selected_lanes": ["npcs", "spells"],
|
"selected_lanes": ["npc_registry", "spells"],
|
||||||
"contexts": [
|
"contexts": [
|
||||||
{
|
{
|
||||||
"context_ref": {
|
"context_ref": {
|
||||||
|
|||||||
@@ -71,11 +71,15 @@ output encoding fail.
|
|||||||
Each `lanes/<safe-lane-id>.json` file is the codec-owned normalized JSON for
|
Each `lanes/<safe-lane-id>.json` file is the codec-owned normalized JSON for
|
||||||
that lane. Consumers should use the index descriptor’s schema identity rather
|
that lane. Consumers should use the index descriptor’s schema identity rather
|
||||||
than infer a lane schema from its name. The current D&D payload contracts are
|
than infer a lane schema from its name. The current D&D payload contracts are
|
||||||
[spells](dnd-spell-artifacts.md), [NPCs](dnd-npc-artifacts.md),
|
[spells](dnd-spell-artifacts.md), [NPC registry](dnd-npc-registry-artifacts.md),
|
||||||
[NPC interactions](dnd-npc-interaction-artifacts.md),
|
[NPC occurrences](dnd-npc-occurrence-artifacts.md),
|
||||||
[combat turns](dnd-combat-turn-artifacts.md),
|
[combat turns](dnd-combat-turn-artifacts.md),
|
||||||
[item events](dnd-item-event-artifacts.md), and
|
[item registry](dnd-item-registry-artifacts.md),
|
||||||
[scene descriptions](dnd-scene-description-artifacts.md).
|
[item occurrences](dnd-item-occurrence-artifacts.md),
|
||||||
|
[scene descriptions](dnd-scene-description-artifacts.md),
|
||||||
|
[enemy events](dnd-enemy-event-artifacts.md),
|
||||||
|
[location registry](dnd-location-registry-artifacts.md), and
|
||||||
|
[location occurrences](dnd-location-occurrence-artifacts.md).
|
||||||
|
|
||||||
## `manifest.json`
|
## `manifest.json`
|
||||||
|
|
||||||
@@ -98,6 +102,11 @@ summarize results without embedding lane payload bytes. A chunk-plan summary is
|
|||||||
provenance for the plan used by this run; cache records, debug artifacts, and
|
provenance for the plan used by this run; cache records, debug artifacts, and
|
||||||
other operational state are not published as bundle files.
|
other operational state are not published as bundle files.
|
||||||
|
|
||||||
|
When present, `metadata.session_id` is the effective non-secret routing
|
||||||
|
correlation identifier used for the run. It can be visible to providers and is
|
||||||
|
not a substitute for a cache or checkpoint identity. Its generation and
|
||||||
|
override behavior are defined by the [CLI reference](../cli.md#run).
|
||||||
|
|
||||||
Each `llm_profiles` entry identifies effective, non-secret LLM execution
|
Each `llm_profiles` entry identifies effective, non-secret LLM execution
|
||||||
provenance:
|
provenance:
|
||||||
|
|
||||||
|
|||||||
@@ -48,11 +48,13 @@ adapter boundary. It also retains responsibility for pipeline retries,
|
|||||||
scheduling, debug persistence, redaction, profile provenance, and conversion
|
scheduling, debug persistence, redaction, profile provenance, and conversion
|
||||||
from private model responses into durable domain artifacts.
|
from private model responses into durable domain artifacts.
|
||||||
|
|
||||||
Notarius sends its trimmed run session through PromptKit's direct session
|
Notarius sends one stable effective session through PromptKit's direct session
|
||||||
field, which is authoritative for provider session behavior. It also retains
|
field, which is authoritative for provider session behavior. It also retains
|
||||||
the same value as the `session_id` prompt variable for maintained prompt
|
the same value as the `session_id` prompt variable for maintained prompt
|
||||||
compatibility. Session IDs are stable, non-secret correlation identifiers and
|
compatibility. The generated identifier is 76 ASCII characters, within
|
||||||
may be exposed to providers and provider observability.
|
PromptKit v0.5.0's 256-code-point session limit. Session IDs are non-secret
|
||||||
|
correlation identifiers and may be exposed to providers and provider
|
||||||
|
observability. The CLI contract owns generation and override behavior.
|
||||||
|
|
||||||
Notarius records PromptKit's selected backend ID and effective reasoning
|
Notarius records PromptKit's selected backend ID and effective reasoning
|
||||||
setting as optional run-manifest provenance. Endpoint-only profiles have no
|
setting as optional run-manifest provenance. Endpoint-only profiles have no
|
||||||
|
|||||||
@@ -89,9 +89,11 @@ handoff:
|
|||||||
profiles;
|
profiles;
|
||||||
4. materialize external or generated references and record redacted invocation
|
4. materialize external or generated references and record redacted invocation
|
||||||
and resolution provenance when debug capture is enabled;
|
and resolution provenance when debug capture is enabled;
|
||||||
5. construct registries, the scheduled LLM client, prepared modules, and the
|
5. construct registries, the scheduled LLM client, and prepared modules;
|
||||||
requested cache/checkpoint collaborators;
|
6. read the source input once, resolve its effective session from the explicit
|
||||||
6. read the source input and invoke the framework runner; and
|
override or resolved input module and raw bytes, then construct requested
|
||||||
|
checkpoint collaborators and invoke the framework runner with that same
|
||||||
|
value; and
|
||||||
7. write the runner's logical output files only after a successful run, then
|
7. write the runner's logical output files only after a successful run, then
|
||||||
complete the command report and user-facing result.
|
complete the command report and user-facing result.
|
||||||
|
|
||||||
@@ -102,6 +104,13 @@ final command result. Detailed state lifecycle, resume handling, and physical
|
|||||||
path confinement are maintained in [Run State Internals](state.md) and
|
path confinement are maintained in [Run State Internals](state.md) and
|
||||||
[Operations](../operations.md).
|
[Operations](../operations.md).
|
||||||
|
|
||||||
|
The CLI owns the versioned generated-session policy and resolves the sole
|
||||||
|
effective value before checkpoint construction. It records that value in the
|
||||||
|
final debug invocation summary when capture is enabled and passes it unchanged
|
||||||
|
to checkpoint identity and `pipeline.RunInput`. The public flag and stability
|
||||||
|
contract are defined by the [CLI reference](../cli.md#run); framework and LLM
|
||||||
|
packages only transport the supplied value.
|
||||||
|
|
||||||
For `run --json`, the CLI constructs and encodes its private run-result receipt
|
For `run --json`, the CLI constructs and encodes its private run-result receipt
|
||||||
after a successful runner result is available, before it publishes logical
|
after a successful runner result is available, before it publishes logical
|
||||||
output files. It writes the prepared receipt to standard output only after
|
output files. It writes the prepared receipt to standard output only after
|
||||||
|
|||||||
@@ -7,17 +7,21 @@ selectable keys, bindings, reference syntax, and default validator chains.
|
|||||||
|
|
||||||
## Durable Artifact Contracts
|
## Durable Artifact Contracts
|
||||||
|
|
||||||
The six lanes have separate durable wire contracts. This guide deliberately
|
The ten lanes have separate durable wire contracts. This guide deliberately
|
||||||
does not repeat their JSON shapes or schemas.
|
does not repeat their JSON shapes or schemas.
|
||||||
|
|
||||||
| Lane | Durable contract |
|
| Lane | Durable contract |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Spells | [spell artifacts](../integrations/dnd-spell-artifacts.md) |
|
| Spells | [spell artifacts](../integrations/dnd-spell-artifacts.md) |
|
||||||
| NPCs | [NPC artifacts](../integrations/dnd-npc-artifacts.md) |
|
| NPC registry | [NPC registry artifacts](../integrations/dnd-npc-registry-artifacts.md) |
|
||||||
| Combat turns | [combat-turn artifacts](../integrations/dnd-combat-turn-artifacts.md) |
|
| Combat turns | [combat-turn artifacts](../integrations/dnd-combat-turn-artifacts.md) |
|
||||||
| Item events | [item-event artifacts](../integrations/dnd-item-event-artifacts.md) |
|
| Item occurrences | [item-occurrence artifacts](../integrations/dnd-item-occurrence-artifacts.md) |
|
||||||
| NPC interactions | [NPC-interaction artifacts](../integrations/dnd-npc-interaction-artifacts.md) |
|
| Item registry | [item-registry artifacts](../integrations/dnd-item-registry-artifacts.md) |
|
||||||
|
| NPC occurrences | [NPC-occurrence artifacts](../integrations/dnd-npc-occurrence-artifacts.md) |
|
||||||
| Scene descriptions | [scene-description artifacts](../integrations/dnd-scene-description-artifacts.md) |
|
| Scene descriptions | [scene-description artifacts](../integrations/dnd-scene-description-artifacts.md) |
|
||||||
|
| Enemy events | [enemy-event artifacts](../integrations/dnd-enemy-event-artifacts.md) |
|
||||||
|
| Location registry | [location-registry artifacts](../integrations/dnd-location-registry-artifacts.md) |
|
||||||
|
| Location occurrences | [location-occurrence artifacts](../integrations/dnd-location-occurrence-artifacts.md) |
|
||||||
|
|
||||||
## Family Composition
|
## Family Composition
|
||||||
|
|
||||||
@@ -25,9 +29,9 @@ The D&D registrar registers the family’s artifact codecs, extractors, typed
|
|||||||
append-order mergers, normalizers, validators, prompt assets, fallback LLM
|
append-order mergers, normalizers, validators, prompt assets, fallback LLM
|
||||||
profile asset, and default validator chains. Each extractor and normalizer has
|
profile asset, and default validator chains. Each extractor and normalizer has
|
||||||
a stable module spec, explicit execution class, strict option decoding, and a
|
a stable module spec, explicit execution class, strict option decoding, and a
|
||||||
typed builder. Scene chunking, every extractor, and NPC normalization are
|
typed builder. Scene chunking, every extractor, and NPC, location, and item-registry
|
||||||
registered as `llm_backed`; the remaining current D&D mergers and normalizers
|
normalization are registered as `llm_backed`; the remaining current D&D mergers
|
||||||
are `deterministic`. The metadata is available to catalog inspection and
|
and normalizers are `deterministic`. The metadata is available to catalog inspection and
|
||||||
resolved-pipeline debug data and determines which selected bindings inherit the
|
resolved-pipeline debug data and determines which selected bindings inherit the
|
||||||
pipeline profile. Configuration remains the canonical owner of the exact keys,
|
pipeline profile. Configuration remains the canonical owner of the exact keys,
|
||||||
profile precedence, and validator order.
|
profile precedence, and validator order.
|
||||||
@@ -40,50 +44,66 @@ the contracts above define durable data.
|
|||||||
|
|
||||||
## Prompt Construction
|
## Prompt Construction
|
||||||
|
|
||||||
D&D extractors assemble prompts from an ordered manifest of shared and
|
D&D LLM-facing content lives beneath `assets/dnd/`. Each module contributes a
|
||||||
module-owned assets. Reuse the shared D&D system, evidence, identity,
|
local `prompt.yaml` declaration and `instructions.md`; input-specific files
|
||||||
reference, and transcript assets instead of copying their text into individual
|
such as a catalog, registry, grounding projection, or candidate collection are
|
||||||
modules. A manifest’s declared sequence, including cache-control placement, is
|
local only when that module needs them. New extractor content uses its feature
|
||||||
part of the prompt behavior.
|
subtree, while families with both extraction and normalization content use their
|
||||||
|
`extract` and `normalize` subtrees. Shared visual-provenance fragments use
|
||||||
|
the `common-dnd-` prefix. Production lane code belongs with its D&D codec,
|
||||||
|
extractor, normalizer, and validator packages; registry projections and
|
||||||
|
identity helpers remain in their owning entity packages rather than in a
|
||||||
|
consumer lane.
|
||||||
|
|
||||||
|
The owning module’s manifest is the source of truth for which local and shared
|
||||||
|
assets are selected, their mount paths, their message order, cache controls,
|
||||||
|
and the files included in its prompt fingerprint. Shared fragments belong to
|
||||||
|
the D&D shared implementation and are selected by name rather than copied into
|
||||||
|
module directories. The root `assets` package is a content-only boundary; its
|
||||||
|
physical ownership and rationale are defined by
|
||||||
|
[ADR-0011](../adr/0011-centralize-llm-assets.md).
|
||||||
|
|
||||||
|
Put each rule at its narrowest owner:
|
||||||
|
|
||||||
|
- universal behavior belongs in the shared system asset;
|
||||||
|
- D&D-family behavior belongs in a selected `common-dnd-` asset;
|
||||||
|
- rules for an input projection belong with that input asset;
|
||||||
|
- lane-specific policy belongs in the module’s `instructions.md`; and
|
||||||
|
- transport-envelope shape belongs in the private response schema.
|
||||||
|
|
||||||
|
A rule is eligible for the system prompt only when every D&D LLM prompt needs
|
||||||
|
it regardless of lane, inputs, or response shape. Module instructions must not
|
||||||
|
repeat rules selected from shared assets or schemas. Reintroduce such repetition
|
||||||
|
only after observational evaluation with representative transcripts shows that
|
||||||
|
it improves results at the intended target models and cost; structural prompt
|
||||||
|
tests alone are not that evidence.
|
||||||
|
|
||||||
Every maintained D&D LLM prompt selects `dnd-extraction` as its default
|
Every maintained D&D LLM prompt selects `dnd-extraction` as its default
|
||||||
profile. The D&D registrar embeds that fallback profile with the maintained
|
profile. The D&D registrar registers the fallback, while an operator can
|
||||||
OpenRouter model, timeout, and service-tier policy. An operator may provide a
|
replace it with a complete profile of the same ID from the configured PromptKit
|
||||||
complete profile with the same ID through the configured PromptKit source; that
|
source. Deployment profile selection is documented in
|
||||||
definition replaces the fallback rather than merging with it. The fallback
|
|
||||||
leaves reasoning and optional sampling controls unspecified. Deployment profile
|
|
||||||
selection and the maintained operator example are documented in
|
|
||||||
[Configuration](../config.md#promptkit-profiles).
|
[Configuration](../config.md#promptkit-profiles).
|
||||||
|
|
||||||
All extraction prompts share this four-message rendered prefix: the system
|
The transcript assets have distinct consumers. Scene chunking consumes the
|
||||||
message without cache control, the identity message without cache control, the
|
complete-session `common-dnd-transcript-full.md`; extraction prompts consume
|
||||||
campaign-reference message with ephemeral cache control, and the chunk
|
the current-chunk `common-dnd-transcript-chunk.md`; and NPC, location, and item
|
||||||
transcript message with ephemeral cache control. This gives equivalent
|
normalization consume `common-dnd-transcript-windows.md` alongside their
|
||||||
extraction requests the same reusable prefix through their source material.
|
candidate collections. Player, party, glossary, and compatible campaign
|
||||||
|
references provide disambiguating context, not evidence. Reference material is
|
||||||
|
canonically ordered before rendering so equivalent inputs remain stable.
|
||||||
|
|
||||||
Extraction-evidence policy, generated NPC registries, spell catalogs, module
|
Extraction prompts render the common system and identity messages first, then
|
||||||
tasks, and instructions follow the transcript because they are not universal
|
cached campaign references and the cached chunk transcript. Evidence policy and
|
||||||
across all extraction lanes. The final instructions message carries ephemeral
|
any lane-specific registry, catalog, or grounding projection follow that
|
||||||
cache control; evidence, registry, catalog, and task messages do not. Preserve
|
prefix. The final module instructions message is ephemeral. This keeps the
|
||||||
this division when changing an extractor or its assets so prompt-cache behavior
|
reusable extraction prefix identical while preserving the lane-specific suffix.
|
||||||
remains stable.
|
|
||||||
|
|
||||||
The other D&D LLM prompts intentionally follow different patterns. Scene
|
Scene chunking intentionally uses a different order: system, cached campaign
|
||||||
chunking has no sibling extraction lane with which to share its full transcript,
|
references, uncached module instructions, then the final ephemeral full
|
||||||
so it renders campaign references before its task and instructions, then places
|
transcript. Entity normalization also has its own order: system, uncached
|
||||||
the cacheable full transcript last. NPC normalization keeps its task and
|
module instructions, ephemeral reconciliation policy, uncached candidates, and
|
||||||
cacheable instructions before the candidate collection, followed by the
|
final ephemeral transcript windows. These orders and cache controls are prompt
|
||||||
cacheable transcript windows: candidates must be available before their
|
behavior; change them only through the owning manifest and prompt declaration.
|
||||||
supporting evidence is evaluated, and those windows are not a cross-lane
|
|
||||||
prefix. Mounted assets and their declared message order determine the prompt
|
|
||||||
fingerprint, so intentional prompt edits continue to invalidate stale
|
|
||||||
checkpoints.
|
|
||||||
|
|
||||||
All extractors use the shared prompt-input preparation rules. The current chunk
|
|
||||||
is copied into transcript material; player, party, glossary, and compatible
|
|
||||||
campaign references are context for disambiguation, not source evidence.
|
|
||||||
Reference prompt material is canonically ordered before it is rendered, which
|
|
||||||
keeps equivalent inputs stable across runs.
|
|
||||||
|
|
||||||
## Evidence, Candidates, And Normalization
|
## Evidence, Candidates, And Normalization
|
||||||
|
|
||||||
@@ -101,13 +121,22 @@ relatedness validators report advisory evidence concerns. The configured order
|
|||||||
is documented in
|
is documented in
|
||||||
[Configuration](../config.md#production-validator-keys-and-default-chains).
|
[Configuration](../config.md#production-validator-keys-and-default-chains).
|
||||||
|
|
||||||
Normalizers are deterministic for spells, combat turns, item events, NPC
|
Enemy-event extraction additionally rejects a second `engaged` observation for
|
||||||
interactions, and scene descriptions. They canonicalize display values and
|
the same comparison identity within one scene-scoped result. Normalization may
|
||||||
evidence, use source-document order for stable output, and issue bounded
|
combine results from distinct scenes, so it intentionally does not apply that
|
||||||
warnings for changes or collapsed duplicates. The NPC normalizer is the
|
rule. Configuration owns the exact validator key and chain position.
|
||||||
intentional exception: it first produces a deterministic candidate set, then
|
|
||||||
uses a bounded structured-LLM proposal to reconcile identity groups. Invalid
|
Normalizers are deterministic for spells, combat turns, item occurrences, NPC
|
||||||
or unusable proposals retain the deterministic result and surface retry or
|
occurrences, scene descriptions, enemy events, and location occurrences. They
|
||||||
|
canonicalize display values and evidence, use source-document order for stable
|
||||||
|
output, and issue bounded warnings for changes or collapsed duplicates. NPC,
|
||||||
|
item, and location registry normalizers are intentional exceptions: each first
|
||||||
|
produces a deterministic candidate set, then may use a bounded structured-LLM
|
||||||
|
proposal to reconcile identity groups. The proposal selects supplied
|
||||||
|
descriptors—names with their candidate source references—not durable IDs.
|
||||||
|
Request-local candidate keys may support resolution internally, but are never
|
||||||
|
included in model input or output. Colliding descriptors are ineligible, and
|
||||||
|
invalid or unusable proposals retain the deterministic result with retry or
|
||||||
fallback diagnostics; the model does not directly replace durable records.
|
fallback diagnostics; the model does not directly replace durable records.
|
||||||
|
|
||||||
## Generated References And Grounding
|
## Generated References And Grounding
|
||||||
@@ -118,30 +147,55 @@ producer provenance; consumers resolve the handed-off artifact into an
|
|||||||
immutable, validated projection for each operation. External files are checked
|
immutable, validated projection for each operation. External files are checked
|
||||||
during preparation, while generated artifacts are resolved at the handoff.
|
during preparation, while generated artifacts are resolved at the handoff.
|
||||||
|
|
||||||
NPC registries are names-only grounding projections: they may canonicalize
|
NPC and item registry consumers receive names-only grounding. Location
|
||||||
actors for spells and combat turns and are required for NPC interactions, but
|
consumers receive a contextual selector containing the canonical name and the
|
||||||
they do not supply evidence. Scene-description registries are eligibility-only
|
registry references needed to distinguish same-name places. The calling module
|
||||||
projections: they retain the current chunk’s classification data, not scene
|
resolves those supplied selections locally and maps them into the unchanged
|
||||||
prose or evidence, and exist to route combat extraction.
|
durable ID/name pair; an unknown or ambiguous selection rejects the complete
|
||||||
|
occurrence result rather than accepting a partial mapping. The NPC registry
|
||||||
|
additionally supplies names-only actor grounding to spells, combat turns, and
|
||||||
|
enemy events.
|
||||||
|
|
||||||
|
Registry references establish a registry identity and may disambiguate a
|
||||||
|
selection, but never become occurrence evidence. Each occurrence keeps its own
|
||||||
|
current-transcript source references, even when it was grounded through the
|
||||||
|
same registry record.
|
||||||
|
Scene descriptions are eligibility-only projections: they retain current-chunk
|
||||||
|
classification data, not scene prose or evidence, and exist to route combat
|
||||||
|
extraction. Enemy-event extraction also projects combat turns to `actor` and
|
||||||
|
`turn_kind` and filters NPC occurrences to `combat_opponent` names and kinds.
|
||||||
|
These projections are guidance only and never event evidence.
|
||||||
|
|
||||||
## Lane-Specific Rules
|
## Lane-Specific Rules
|
||||||
|
|
||||||
The following differences are intentional and should remain explicit when a
|
The following differences are intentional and should remain explicit when a
|
||||||
shared helper changes.
|
shared helper changes.
|
||||||
|
|
||||||
|
Shared D&D text comparison is identified by `dnd.text_comparison.v1`. Any
|
||||||
|
semantic change requires an explicit policy-version review for every affected
|
||||||
|
identity, mapping, normalization, and validator policy; helper source is not a
|
||||||
|
checkpoint fingerprint.
|
||||||
|
|
||||||
| Lane | Intentional behavior |
|
| Lane | Intentional behavior |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Spells | May use a spell-catalog overlay and optional NPC grounding; the catalog validator supplies domain-specific semantic checks. |
|
| Spells | May use a spell-catalog overlay and optional NPC grounding; the catalog validator supplies domain-specific semantic checks. |
|
||||||
| NPCs | Does not consume an NPC registry. Its normalizer is the LLM-assisted reconciliation exception described above. |
|
| NPC registry | Establishes transcript-grounded NPC identities, including factual third-party mentions, without assigning occurrence categories. It does not consume an NPC registry, and its normalizer is the LLM-assisted reconciliation exception described above. |
|
||||||
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
|
| Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. |
|
||||||
| Item events | Uses campaign context for disambiguation but has no NPC-registry or scene-description dependency. |
|
| Item occurrences | Requires the normalized item registry for exact deterministic grounding at extraction and normalization. Campaign context may disambiguate, but the registry never becomes occurrence evidence. |
|
||||||
| NPC interactions | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
| Item registry | Produces source-grounded item types and unique designations. Its LLM-assisted reconciliation is proposal-only, preserves distinct currency denominations and item types, and does not create per-instance identities. |
|
||||||
|
| NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. It separately emits cited current-transcript occurrence facts, including `mentioned`, rather than deriving them from registry provenance. |
|
||||||
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
|
| Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. |
|
||||||
|
| Enemy events | Requires NPC, scene-description, combat-turn, and NPC-occurrence artifacts. It calls the LLM only for an exact `combat` classification, records ordered observations rather than terminal state, and normalizes recognized names through the NPC registry while preserving grounded collective labels. |
|
||||||
|
| Location registry | Produces a source-anchored, session-scoped registry from stable proper names or unique in-world designations. Its LLM-assisted reconciliation is proposal-only and never collapses same-name places without validated identity and evidence rules. |
|
||||||
|
| Location occurrences | Requires the normalized location registry for both extraction and normalization. Its [durable occurrence categories](../integrations/dnd-location-occurrence-artifacts.md#occurrence-categories) distinguish explicit speculation from unsupported inference; the deterministic normalizer enforces exact registry grounding and never turns registry provenance into occurrence evidence. |
|
||||||
|
|
||||||
The combat and scene-description contracts describe their exact handoff and
|
The combat and scene-description contracts describe their exact handoff and
|
||||||
empty-result behavior in more detail:
|
empty-result behavior in more detail:
|
||||||
[combat turns](../integrations/dnd-combat-turn-artifacts.md) and
|
[combat turns](../integrations/dnd-combat-turn-artifacts.md) and
|
||||||
[scene descriptions](../integrations/dnd-scene-description-artifacts.md).
|
[scene descriptions](../integrations/dnd-scene-description-artifacts.md).
|
||||||
|
The [enemy-event contract](../integrations/dnd-enemy-event-artifacts.md)
|
||||||
|
defines its durable semantics; [Configuration](../config.md) owns its
|
||||||
|
selectable bindings and validation chains.
|
||||||
|
|
||||||
## Focused Verification
|
## Focused Verification
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ adapter does not own source evidence, artifact conversion, normalization, or
|
|||||||
durable schemas. Those responsibilities remain with the module and its
|
durable schemas. Those responsibilities remain with the module and its
|
||||||
[integration contract](../integrations/).
|
[integration contract](../integrations/).
|
||||||
|
|
||||||
|
The calling module also resolves contextual entity selections and attaches any
|
||||||
|
application identity; PromptKit and this adapter do not own entity identity.
|
||||||
|
|
||||||
`PromptKitClient` validates the request target and prompt identity, maps each
|
`PromptKitClient` validates the request target and prompt identity, maps each
|
||||||
named material to a PromptKit inline artifact while preserving its origin URI,
|
named material to a PromptKit inline artifact while preserving its origin URI,
|
||||||
maps the trimmed request session to PromptKit's direct per-run session field,
|
passes the supplied request session through to PromptKit's direct per-run
|
||||||
retains the same value as the `session_id` prompt variable for maintained
|
session field, retains the same value as the `session_id` prompt variable for
|
||||||
prompt compatibility, and forwards profile selection. It then creates one
|
maintained prompt compatibility, and forwards profile selection. It does not
|
||||||
|
derive or replace session values; the CLI owns that policy. It then creates one
|
||||||
frozen prepared execution, captures its caller-owned credential-redacted
|
frozen prepared execution, captures its caller-owned credential-redacted
|
||||||
details for debug material, and executes that exact snapshot through
|
details for debug material, and executes that exact snapshot through
|
||||||
PromptKit's prepared-execution boundary. The direct field
|
PromptKit's prepared-execution boundary. The direct field
|
||||||
@@ -124,16 +128,24 @@ the corresponding PromptKit filesystems and rejects invalid roots, unreadable
|
|||||||
assets, duplicate paths, and missing prompt or schema files during preparation.
|
assets, duplicate paths, and missing prompt or schema files during preparation.
|
||||||
Fallback assets receive a safe content digest for checkpoint identity; raw
|
Fallback assets receive a safe content digest for checkpoint identity; raw
|
||||||
paths and bytes are never included. The framework’s `promptfs` helper combines
|
paths and bytes are never included. The framework’s `promptfs` helper combines
|
||||||
module-owned prompt files with reusable domain fragments without making the
|
module-selected prompt files with reusable domain fragments without making the
|
||||||
framework depend on D&D content.
|
framework depend on D&D content.
|
||||||
|
|
||||||
Each LLM-backed module owns its prompt declaration, package-specific assets,
|
LLM-facing content is embedded once by the root `assets` package. Each consumer
|
||||||
and private response schema. Shared D&D wording is owned by the D&D shared
|
uses only its scoped subtree, while the module retains ownership of its prompt
|
||||||
asset package; the detailed D&D conventions are in
|
declaration, ordered manifest, private response-schema identity, and
|
||||||
[D&D Module Internals](dnd.md). The mounted prompt assets used by a module also
|
registration. Shared D&D fragments are selected by D&D's shared implementation;
|
||||||
determine its prompt fingerprint. Schema loaders validate JSON, attach identity
|
the detailed convention is in [D&D Module Internals](dnd.md). This physical
|
||||||
and digest metadata, make defensive copies, and expose diagnostics without raw
|
arrangement and its data-only boundary are defined by
|
||||||
schema bytes.
|
[Architecture](../policy/architecture.md) and
|
||||||
|
[ADR-0011](../adr/0011-centralize-llm-assets.md), rather than by this runtime
|
||||||
|
guide.
|
||||||
|
|
||||||
|
Mounted prompt assets determine a module's fingerprint. The fingerprint hashes
|
||||||
|
only the module and shared files explicitly selected by its manifest, so an
|
||||||
|
unrelated asset does not invalidate a checkpoint. Schema loaders validate JSON,
|
||||||
|
attach identity and digest metadata, make defensive copies, and expose
|
||||||
|
diagnostics without raw schema bytes.
|
||||||
|
|
||||||
Private response schemas validate a model transport envelope. They are not the
|
Private response schemas validate a model transport envelope. They are not the
|
||||||
durable artifact schema and should not be documented as an external wire
|
durable artifact schema and should not be documented as an external wire
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ physical state roots.
|
|||||||
| Generic models | **internal/core/source**, **internal/core/artifacts**, **internal/framework/contracts** | Source documents and chunks, manifests and provenance, plus typed artifact, reference, validation, output, and structured-completion contracts. |
|
| Generic models | **internal/core/source**, **internal/core/artifacts**, **internal/framework/contracts** | Source documents and chunks, manifests and provenance, plus typed artifact, reference, validation, output, and structured-completion contracts. |
|
||||||
| Pipeline framework | **internal/framework/pipeline** | Registries, profile and reference resolution, typed preparation, validation, retry coordination, ordered execution, handoff, and result assembly. |
|
| Pipeline framework | **internal/framework/pipeline** | Registries, profile and reference resolution, typed preparation, validation, retry coordination, ordered execution, handoff, and result assembly. |
|
||||||
| LLM and prompt runtime | **internal/framework/llm**, **internal/framework/promptfs** | Provider-neutral structured completions, scheduling, profile recording, prompt assets, schema registration, and credential-shaped-value redaction. |
|
| LLM and prompt runtime | **internal/framework/llm**, **internal/framework/promptfs** | Provider-neutral structured completions, scheduling, profile recording, prompt assets, schema registration, and credential-shaped-value redaction. |
|
||||||
|
| Embedded LLM content | **assets** | Read-only centralized LLM-facing content, scoped by its consuming package; see [LLM Runtime](llm.md#prompt-and-schema-assets) and [D&D Module Internals](dnd.md#prompt-construction). |
|
||||||
| Runtime state | **internal/core/fileio**, **internal/core/debugbundle**, **internal/framework/checkpoint**, **internal/framework/chunkplan**, **internal/framework/chunkmap**, **internal/framework/debug** | Confined atomic files, debug bundles, checkpoint and chunk-plan state, accepted chunk maps, and pipeline-facing debug recording. |
|
| Runtime state | **internal/core/fileio**, **internal/core/debugbundle**, **internal/framework/checkpoint**, **internal/framework/chunkplan**, **internal/framework/chunkmap**, **internal/framework/debug** | Confined atomic files, debug bundles, checkpoint and chunk-plan state, accepted chunk maps, and pipeline-facing debug recording. |
|
||||||
| Production extensions | **internal/modules/generic**, **internal/modules/seriatim**, **internal/modules/dnd** | Domain-neutral extensions, Seriatim input support, and D&D extraction families registered into the production catalog. |
|
| Production extensions | **internal/modules/generic**, **internal/modules/seriatim**, **internal/modules/dnd** | Domain-neutral extensions, Seriatim input support, and D&D extraction families registered into the production catalog. |
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ own durable output shapes. Concrete production extensions are covered by
|
|||||||
## Boundary
|
## Boundary
|
||||||
|
|
||||||
The pipeline framework accepts a resolved composition, registries, shared
|
The pipeline framework accepts a resolved composition, registries, shared
|
||||||
dependencies, input bytes, and state/debug collaborators. It returns logical
|
dependencies, input bytes, a supplied prompt session, and state/debug
|
||||||
output files, normalized artifacts, recorded rejections and warnings, manifest
|
collaborators. It returns logical output files, normalized artifacts, recorded
|
||||||
provenance, and checkpoint decisions. The CLI owns process arguments,
|
rejections and warnings, manifest provenance, and checkpoint decisions. The
|
||||||
configuration discovery, physical roots, and placement of returned output
|
CLI owns process arguments, configuration discovery, session resolution,
|
||||||
files.
|
physical roots, and placement of returned output files.
|
||||||
|
|
||||||
The framework has one fixed shape:
|
The framework has one fixed shape:
|
||||||
|
|
||||||
@@ -84,6 +84,10 @@ incompatible producer prevents the consumer step from starting.
|
|||||||
|
|
||||||
The runner validates its input, installs no-op state collaborators when none
|
The runner validates its input, installs no-op state collaborators when none
|
||||||
were supplied, and serially performs source parsing and chunk-plan selection.
|
were supplied, and serially performs source parsing and chunk-plan selection.
|
||||||
|
It transports the supplied session unchanged to prompt-facing operations and
|
||||||
|
run-manifest metadata; it neither derives a session nor substitutes a parsed
|
||||||
|
source document identifier. The public session contract is owned by the
|
||||||
|
[CLI reference](../cli.md#run).
|
||||||
An accepted plan is materialized into source-addressed chunks and passes the
|
An accepted plan is materialized into source-addressed chunks and passes the
|
||||||
configured chunk validators before any lane runs. A chunk rejection is a
|
configured chunk validators before any lane runs. A chunk rejection is a
|
||||||
recorded pipeline outcome: lanes do not start, but the output stage can encode
|
recorded pipeline outcome: lanes do not start, but the output stage can encode
|
||||||
|
|||||||
@@ -266,16 +266,18 @@ transport-wide cap. Notarius does not add another timeout around PromptKit.
|
|||||||
The pinned upstream boundary and profile-format links are in
|
The pinned upstream boundary and profile-format links are in
|
||||||
[PromptKit Integration](integrations/pkg-promptkit.md).
|
[PromptKit Integration](integrations/pkg-promptkit.md).
|
||||||
|
|
||||||
Concurrency has two independent layers. Notarius **total_llm** is the
|
Concurrency has two independent layers. Notarius **total_llm** defaults to 16
|
||||||
application-wide provider-call limit shared by all backends, modules, retries,
|
and is the application-wide provider-call limit shared by all backends,
|
||||||
and validators. PromptKit may impose a narrower admission limit for the
|
modules, retries, and validators. PromptKit may impose a narrower admission
|
||||||
selected backend. The effective active-generation bound is the intersection of
|
limit for the selected backend. The effective active-generation bound is the
|
||||||
both limits and can therefore be lower than **total_llm**. Built-in OpenRouter
|
intersection of the Notarius limit, any PromptKit backend limit, and work made
|
||||||
profiles use PromptKit's upstream backend limit; endpoint-only profiles have no
|
available by the pipeline. Built-in OpenRouter profiles use PromptKit's
|
||||||
PromptKit backend limit and remain bounded by Notarius. For the configured
|
upstream backend limit; endpoint-only profiles have no PromptKit backend limit
|
||||||
local backend, a zero **concurrency_limit** leaves only the Notarius scheduler
|
and remain bounded by Notarius. For the configured local backend, a zero
|
||||||
as a call limit. A positive value makes the effective active local-generation
|
**concurrency_limit** leaves only the Notarius scheduler as a call limit. A
|
||||||
bound the smaller of **total_llm** and that local limit.
|
positive value makes the effective active local-generation bound the smaller
|
||||||
|
of **total_llm** and that local limit, so a local limit of four permits no more
|
||||||
|
than four active local generations.
|
||||||
|
|
||||||
For a positive local limit, PromptKit owns its default waiting capacity and
|
For a positive local limit, PromptKit owns its default waiting capacity and
|
||||||
admission behavior. When a PromptKit backend has admitted all active and queued
|
admission behavior. When a PromptKit backend has admitted all active and queued
|
||||||
@@ -289,3 +291,11 @@ under [PromptKit profiles](config.md#promptkit-profiles) and
|
|||||||
limits and actual provider-call limits are independent. Notarius writes local
|
limits and actual provider-call limits are independent. Notarius writes local
|
||||||
filesystem state only; remote storage, archival, and retention automation are
|
filesystem state only; remote storage, archival, and retention automation are
|
||||||
outside the implemented CLI.
|
outside the implemented CLI.
|
||||||
|
|
||||||
|
Every run has an effective prompt session used for provider routing and run
|
||||||
|
provenance. The generated default is stable for the same input module and raw
|
||||||
|
input bytes; use [**--session-id**](cli.md#run) only when intentionally grouping
|
||||||
|
different invocations. Both generated and explicit values can be visible to
|
||||||
|
providers, manifests, checkpoints, and requested debug bundles. Do not put
|
||||||
|
credentials or other secrets in an explicit session identifier; command-line
|
||||||
|
values are not a credential mechanism.
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ must not compose the application or take ownership of process behavior. The
|
|||||||
current packages implementing these layers are inventoried in
|
current packages implementing these layers are inventoried in
|
||||||
[Internal Overview](../internal/overview.md).
|
[Internal Overview](../internal/overview.md).
|
||||||
|
|
||||||
|
The root `assets` package is a content-only dependency leaf. It may expose a
|
||||||
|
read-only embedded filesystem, but it must contain no business logic and must
|
||||||
|
not depend on `internal` packages or PromptKit. Consumers scope that filesystem
|
||||||
|
to the content they own; the root package is not a behavioral registry or a
|
||||||
|
public extension contract. The rationale and compatibility consequence are
|
||||||
|
recorded in [ADR-0011](../adr/0011-centralize-llm-assets.md).
|
||||||
|
|
||||||
The following dependency boundaries are mandatory:
|
The following dependency boundaries are mandatory:
|
||||||
|
|
||||||
- extractors and validators do not depend on concrete input adapters;
|
- extractors and validators do not depend on concrete input adapters;
|
||||||
@@ -73,6 +80,12 @@ Extract modules own artifact semantics, prompt use, response schemas, and
|
|||||||
domain interpretation. Domain-specific concepts remain in the relevant module,
|
domain interpretation. Domain-specific concepts remain in the relevant module,
|
||||||
validator, shared domain helper, and artifact contract.
|
validator, shared domain helper, and artifact contract.
|
||||||
|
|
||||||
|
Physical centralization of LLM-facing content does not transfer semantic
|
||||||
|
ownership from those modules. Modules retain their manifests, response-schema
|
||||||
|
identities, prompt ordering, and registration, while reading only their scoped
|
||||||
|
content subtree. Generic framework code remains domain-neutral when it reads
|
||||||
|
its own scoped generic assets from the shared content container.
|
||||||
|
|
||||||
Typed artifact registrations declare one stable artifact kind and exact Go
|
Typed artifact registrations declare one stable artifact kind and exact Go
|
||||||
type from extraction through merge, normalization, and semantic validation.
|
type from extraction through merge, normalization, and semantic validation.
|
||||||
Pipeline resolution requires a compatible codec and matching kind-specific
|
Pipeline resolution requires a compatible codec and matching kind-specific
|
||||||
@@ -170,6 +183,12 @@ The caller of the LLM owns prompt selection, prompt inputs, response schema,
|
|||||||
and interpretation of structured output. Provider adapters do not own source-
|
and interpretation of structured output. Provider adapters do not own source-
|
||||||
or domain-specific prompt logic.
|
or domain-specific prompt logic.
|
||||||
|
|
||||||
|
When a model selects an application entity, callers must supply a contextual
|
||||||
|
selection and deterministically attach the opaque application identity whenever
|
||||||
|
the selection resolves exactly. Models do not receive or reproduce opaque
|
||||||
|
application identifiers; [ADR-0012](../adr/0012-resolve-opaque-entity-identifiers-deterministically.md)
|
||||||
|
records the rationale and limited request-local-label exception.
|
||||||
|
|
||||||
LLM calls and other external operations accept cancellation and respect
|
LLM calls and other external operations accept cancellation and respect
|
||||||
timeouts. Concurrency control belongs in shared runtime plumbing rather than in
|
timeouts. Concurrency control belongs in shared runtime plumbing rather than in
|
||||||
individual modules.
|
individual modules.
|
||||||
|
|||||||
350
docs/roadmap/contextual-entity-grounding.md
Normal file
350
docs/roadmap/contextual-entity-grounding.md
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
# Contextual Entity Grounding
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Notarius should use an LLM for semantic interpretation of source evidence, not
|
||||||
|
for referential-integrity work that deterministic code can perform more
|
||||||
|
reliably. D&D prompts must therefore stop requiring models to reproduce opaque
|
||||||
|
machine identifiers such as hash-derived entity IDs. Models should identify
|
||||||
|
entities through human-readable, evidence-grounded context, after which
|
||||||
|
Notarius resolves the selection and attaches the canonical durable identity.
|
||||||
|
|
||||||
|
This roadmap defines the policy, affected D&D prompt families, and intended
|
||||||
|
end state. The ordered work needed to reach that state is maintained in
|
||||||
|
[Implementation Plan](implementation.md).
|
||||||
|
|
||||||
|
## User Intent
|
||||||
|
|
||||||
|
The change has two goals:
|
||||||
|
|
||||||
|
- prevent otherwise useful model responses from failing because a long,
|
||||||
|
non-semantic string was copied incorrectly; and
|
||||||
|
- avoid spending prompt space and model effort on exact-copy work that provides
|
||||||
|
no semantic value.
|
||||||
|
|
||||||
|
The policy is not a ban on identifiers. Durable artifacts may continue to use
|
||||||
|
application-owned IDs, and prompts may continue to request source-unit ranges
|
||||||
|
that locate evidence. The policy governs which identity work is assigned to
|
||||||
|
the model.
|
||||||
|
|
||||||
|
## Policy
|
||||||
|
|
||||||
|
An LLM-facing prompt input or private response schema must not require a model
|
||||||
|
to reproduce an opaque machine identifier when Notarius can establish the same
|
||||||
|
association deterministically.
|
||||||
|
|
||||||
|
Opaque machine identifiers include cryptographic hashes, UUIDs, digests,
|
||||||
|
database keys, durable entity IDs, and other tokens whose characters do not
|
||||||
|
carry source-grounded meaning for the model. These values may remain in
|
||||||
|
application state, provenance, diagnostics, checkpoints, and durable artifact
|
||||||
|
contracts, but should be omitted from model-visible material when they do not
|
||||||
|
help the model make a semantic decision.
|
||||||
|
|
||||||
|
The intended responsibility boundary is:
|
||||||
|
|
||||||
|
- the model decides which contextual entity is supported by the supplied
|
||||||
|
evidence and returns the bounded semantic facts requested by the module;
|
||||||
|
- the calling module validates that the contextual selection resolves to
|
||||||
|
exactly one supplied candidate;
|
||||||
|
- deterministic code supplies the canonical display value and durable entity
|
||||||
|
ID; and
|
||||||
|
- existing validators continue to enforce referential integrity at later
|
||||||
|
artifact boundaries.
|
||||||
|
|
||||||
|
Transcript `start_unit_id` and `end_unit_id` values are permitted. They are
|
||||||
|
contextual source coordinates and form part of the evidence contract rather
|
||||||
|
than arbitrary identity tokens. Prompt IDs, schema IDs, fingerprints, session
|
||||||
|
IDs, and digests may also remain in runtime metadata that the model is not
|
||||||
|
asked to reproduce.
|
||||||
|
|
||||||
|
Short request-local labels are a narrowly permitted fallback only when a
|
||||||
|
contextual selector cannot uniquely represent the available choices without
|
||||||
|
unreasonable prompt cost. Such a label must be compact, scoped to one request,
|
||||||
|
validated against the supplied candidate set, and never reused as a durable
|
||||||
|
identity. Current D&D occurrence and reconciliation prompts should be designed
|
||||||
|
without this exception; adopting it later requires a concrete demonstrated
|
||||||
|
need and documented rationale.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
The initial NPC, item, and location registry extractors already follow the
|
||||||
|
desired pattern: the model returns contextual names and evidence, and Notarius
|
||||||
|
derives durable IDs afterward. Spells, combat turns, and enemy events use
|
||||||
|
contextual actor names rather than requiring hash-derived NPC IDs.
|
||||||
|
|
||||||
|
Two current prompt families diverge from that pattern:
|
||||||
|
|
||||||
|
1. `dnd/npc-occurrences`, `dnd/item-occurrences`, and
|
||||||
|
`dnd/location-occurrences` place durable registry IDs in model-visible
|
||||||
|
projections and require the private LLM response to repeat those IDs.
|
||||||
|
2. NPC-, item-, and location-registry normalization use the shared entity
|
||||||
|
reconciliation prompt, which labels candidates with opaque
|
||||||
|
`candidate-000001`-style keys and requires the model to copy those keys into
|
||||||
|
duplicate-group proposals.
|
||||||
|
|
||||||
|
The durable occurrence artifacts correctly retain canonical entity IDs. The
|
||||||
|
problem is the private model transport contract, not the published artifact
|
||||||
|
contract.
|
||||||
|
|
||||||
|
## Target Architecture
|
||||||
|
|
||||||
|
### Model proposals and durable artifacts
|
||||||
|
|
||||||
|
Private LLM response types must express contextual semantic proposals rather
|
||||||
|
than reuse the durable artifact type when that type contains an opaque entity
|
||||||
|
ID. The extractor maps a validated private response into the existing durable
|
||||||
|
artifact only after identity resolution succeeds.
|
||||||
|
|
||||||
|
No affected durable artifact kind, media type, schema ID, schema version, or
|
||||||
|
JSON field changes as part of this work. NPC, item, and location occurrence
|
||||||
|
artifacts continue to publish their exact canonical ID/name pair. Registry
|
||||||
|
artifacts likewise retain their IDs and evidence.
|
||||||
|
|
||||||
|
The private schemas and prompt declarations may remain at their current `v1`
|
||||||
|
identities because Notarius is pre-release and these are not external
|
||||||
|
contracts. Their content hashes, mapping-policy fingerprints, and affected
|
||||||
|
prompt fingerprints must change so incompatible checkpoints are not reused.
|
||||||
|
|
||||||
|
### NPC occurrence grounding
|
||||||
|
|
||||||
|
The NPC occurrence prompt receives an ordered names-only projection of the
|
||||||
|
normalized NPC registry. Its private response contains the canonical NPC name,
|
||||||
|
occurrence kind, and current-transcript source ranges, but no `npc_id`.
|
||||||
|
|
||||||
|
The extractor resolves the returned name under the existing NPC comparison
|
||||||
|
policy. Resolution must produce exactly one registry entry. It then writes that
|
||||||
|
entry's canonical display name and durable ID into the `dnd.NPCOccurrence`.
|
||||||
|
An unknown or ambiguous selection invalidates the extraction operation; the
|
||||||
|
extractor must not guess, use fuzzy matching, silently omit the record, or
|
||||||
|
accept a partial response.
|
||||||
|
|
||||||
|
### Item occurrence grounding
|
||||||
|
|
||||||
|
The item occurrence prompt receives an ordered names-only projection of the
|
||||||
|
normalized item registry. Its private response contains the canonical item
|
||||||
|
name, occurrence kind, kind-specific fields, and current-transcript source
|
||||||
|
ranges, but no `item_id`.
|
||||||
|
|
||||||
|
The extractor resolves the returned name under the existing item comparison
|
||||||
|
and identity policies. Resolution must produce exactly one registry entry,
|
||||||
|
whose canonical name and durable ID are attached deterministically. Unknown or
|
||||||
|
ambiguous selections invalidate the complete extraction operation rather than
|
||||||
|
being guessed, repaired by similarity, or dropped.
|
||||||
|
|
||||||
|
### Location occurrence grounding
|
||||||
|
|
||||||
|
Location identity cannot always be resolved from a display name alone: the
|
||||||
|
current registry intentionally permits same-name locations with distinct
|
||||||
|
source anchors. The location occurrence prompt must therefore receive a
|
||||||
|
contextual registry descriptor that contains the canonical display name plus
|
||||||
|
the minimum source-grounded registry evidence needed to distinguish same-name
|
||||||
|
records. It must not contain the durable `location:sha256:...` value.
|
||||||
|
|
||||||
|
The private response uses two required selector fields: `name` and
|
||||||
|
`registry_refs`. For a comparison-unique canonical name, `registry_refs` is an
|
||||||
|
empty array and Notarius resolves the name under the location comparison
|
||||||
|
policy. For a name shared by multiple registry records, `registry_refs`
|
||||||
|
contains that record's complete canonically ordered registry ranges as
|
||||||
|
`start_unit_id` and `end_unit_id` pairs, without `source_id`.
|
||||||
|
|
||||||
|
Every model-facing registry entry uses one fixed shape with required `name`,
|
||||||
|
`registry_refs`, and `context` fields. `context` is an array of strict objects
|
||||||
|
containing only `unit_id` and `text`. Comparison-unique entries use empty
|
||||||
|
`registry_refs` and `context` arrays. Same-name entries use the complete
|
||||||
|
registry-range selector and the bounded context described below. The model
|
||||||
|
returns only `name` and `registry_refs`; it does not reproduce `context`.
|
||||||
|
|
||||||
|
For same-name groups, the projection also supplies bounded transcript units
|
||||||
|
covered by each record's registry ranges so the model receives meaningful
|
||||||
|
identity context rather than coordinates alone. Those ranges must resolve
|
||||||
|
against the current source document, and the resulting contextual selectors
|
||||||
|
must be unique. An invalid range or selector collision prevents the LLM call
|
||||||
|
and fails the operation. Unique-name entries do not repeat registry ranges or
|
||||||
|
context in the selector, preserving compatibility with a valid registry from
|
||||||
|
another source when the name alone is unambiguous.
|
||||||
|
|
||||||
|
The private response separately supplies current-transcript `source_refs` that
|
||||||
|
prove the occurrence. Registry identity evidence and occurrence evidence must
|
||||||
|
remain different fields and must never be merged. The model should omit an
|
||||||
|
occurrence when the transcript does not support choosing among same-name
|
||||||
|
locations. If a returned selector does not resolve to exactly one supplied
|
||||||
|
registry record, the extractor invalidates the complete operation rather than
|
||||||
|
guessing.
|
||||||
|
|
||||||
|
### Registry reconciliation
|
||||||
|
|
||||||
|
The shared entity-reconciliation input replaces opaque candidate keys with
|
||||||
|
contextual candidate descriptors. At minimum, a descriptor contains the
|
||||||
|
candidate's display name and its canonically ordered source-reference ranges;
|
||||||
|
the existing transcript windows remain available for semantic judgment.
|
||||||
|
|
||||||
|
Duplicate-group members and the canonical member in the private response use
|
||||||
|
the same contextual descriptor shape. The shared reconciliation helper maps
|
||||||
|
each descriptor back to exactly one internal candidate before assessing the
|
||||||
|
proposal. Exact deterministic duplicates should already be removed before the
|
||||||
|
LLM call; any remaining descriptor collision makes the affected candidate
|
||||||
|
ineligible for model-assisted reconciliation rather than authorizing an
|
||||||
|
arbitrary choice.
|
||||||
|
|
||||||
|
Existing safety behavior remains in force: groups must contain at least two
|
||||||
|
supplied candidates, the canonical candidate must be a member, groups must not
|
||||||
|
overlap, and domain-specific eligibility rules remain authoritative. Invalid,
|
||||||
|
ambiguous, or unsafe groups are discarded through the existing bounded
|
||||||
|
fallback and diagnostic behavior. The model never directly mutates the
|
||||||
|
durable registry.
|
||||||
|
|
||||||
|
The shared private reconciliation schema and helper must remain domain-neutral
|
||||||
|
within the D&D family. NPC-, item-, and location-specific duplicate policy
|
||||||
|
continues to live in the owning normalizer.
|
||||||
|
|
||||||
|
## Prompt And Asset Changes
|
||||||
|
|
||||||
|
The following LLM-facing assets are in scope:
|
||||||
|
|
||||||
|
- the prompt instructions, registry input fragments, and private response
|
||||||
|
schemas for NPC, item, and location occurrences;
|
||||||
|
- the prompt manifests where input shape or selected fragments change;
|
||||||
|
- the shared D&D entity-reconciliation fragment and private response schema;
|
||||||
|
and
|
||||||
|
- the NPC-, item-, and location-registry normalization prompt inputs that use
|
||||||
|
the shared reconciliation contract.
|
||||||
|
|
||||||
|
Affected projections must exclude durable entity IDs rather than merely stop
|
||||||
|
mentioning them in prose. Prompt instructions should describe the contextual
|
||||||
|
selection rule once at the narrowest owning asset and must preserve the current
|
||||||
|
distinction between registry grounding and transcript evidence.
|
||||||
|
|
||||||
|
Prompt ordering and cache controls should remain unchanged unless the new
|
||||||
|
contextual input requires an intentional manifest change. Unrelated shared
|
||||||
|
prompt bytes should not be edited. Prompt and schema fingerprints should
|
||||||
|
invalidate only the operations whose selected assets or mapping semantics
|
||||||
|
changed.
|
||||||
|
|
||||||
|
## Code And Validation Changes
|
||||||
|
|
||||||
|
The occurrence extractors need private response types and deterministic
|
||||||
|
registry-resolution paths appropriate to their domain. Shared code is
|
||||||
|
appropriate only for demonstrated mechanics that have identical semantics;
|
||||||
|
NPC, item, and location ambiguity policies must not be forced behind a generic
|
||||||
|
resolver merely to reduce line count.
|
||||||
|
|
||||||
|
Registry projections should expose explicit model-facing methods whose names
|
||||||
|
describe whether they are names-only or contextual identity projections. The
|
||||||
|
existing ID/name projections may remain only for deterministic consumers that
|
||||||
|
genuinely require them; they must no longer be wired to an LLM input.
|
||||||
|
|
||||||
|
Mapping-policy and normalization-policy identifiers must be reviewed and
|
||||||
|
advanced wherever their semantics change. Checkpoint fingerprints must cover
|
||||||
|
the new projection content, private schema, prompt assets, and mapping policy,
|
||||||
|
while continuing to exclude irrelevant internal implementation details.
|
||||||
|
|
||||||
|
Durable occurrence normalizers and registry validators remain defense in
|
||||||
|
depth. They continue to validate exact ID/name pairs on artifacts entering
|
||||||
|
through checkpoints, codecs, or other boundaries even though the LLM no longer
|
||||||
|
produces the ID directly.
|
||||||
|
|
||||||
|
## Testing And Evaluation
|
||||||
|
|
||||||
|
Tests should protect the behavioral boundary rather than prompt prose or
|
||||||
|
private helper structure. The completed work should demonstrate that:
|
||||||
|
|
||||||
|
- affected model-facing registry projections do not contain durable entity
|
||||||
|
IDs;
|
||||||
|
- private occurrence schemas reject opaque ID fields and accept the intended
|
||||||
|
contextual shape;
|
||||||
|
- valid contextual selections map to the exact canonical durable ID/name pair;
|
||||||
|
- unknown, mismatched, and ambiguous selections fail without fuzzy matching,
|
||||||
|
partial acceptance, or arbitrary reassignment;
|
||||||
|
- same-name locations remain distinguishable through contextual evidence;
|
||||||
|
- reconciliation preserves equal-name candidates, resolves valid contextual
|
||||||
|
groups, and discards ambiguous or unsafe proposals;
|
||||||
|
- registry evidence never becomes occurrence evidence;
|
||||||
|
- durable codec, normalization, and validator behavior remains compatible; and
|
||||||
|
- representative assembled D&D pipelines still prepare and execute with fake
|
||||||
|
structured-LLM responses.
|
||||||
|
|
||||||
|
Do not add repository-wide prompt-prose snapshots, exact-message-count tests,
|
||||||
|
or a change-detector test that merely scans for today's field names. Focused
|
||||||
|
projection, schema, mapping, fallback, and integration tests are the stable
|
||||||
|
owners of these risks. Model-quality evaluation with representative
|
||||||
|
transcripts remains a manual development aid rather than an offline test gate.
|
||||||
|
|
||||||
|
## Documentation And Architectural Record
|
||||||
|
|
||||||
|
This policy is durable and applies to future modules, so it warrants
|
||||||
|
`docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md`, which
|
||||||
|
records:
|
||||||
|
|
||||||
|
- the semantic-proposal versus referential-integrity boundary;
|
||||||
|
- why durable opaque IDs are excluded from model response contracts;
|
||||||
|
- why contextual evidence coordinates remain permitted;
|
||||||
|
- the narrowly scoped request-local-label exception;
|
||||||
|
- alternatives including durable IDs, names-only matching, and short opaque
|
||||||
|
handles; and
|
||||||
|
- the consequences for private schemas, deterministic resolution, debugging,
|
||||||
|
and ambiguous identities.
|
||||||
|
|
||||||
|
`docs/policy/architecture.md` states the general LLM boundary invariant and
|
||||||
|
links to the ADR. `docs/internal/dnd.md` describes the concrete occurrence
|
||||||
|
projections, contextual reconciliation selectors, resolution and failure
|
||||||
|
behavior, and the continued separation of registry grounding from occurrence
|
||||||
|
evidence. `docs/internal/llm.md` contains only a short clarification that
|
||||||
|
caller-owned modules, not PromptKit or the transport adapter, resolve
|
||||||
|
contextual model selections into application identities.
|
||||||
|
|
||||||
|
The NPC, item, and location occurrence and registry integration documents must
|
||||||
|
continue to own their durable wire contracts, while removing current claims
|
||||||
|
that the model-facing consumer projection contains `{id,name}` or that the raw
|
||||||
|
LLM response supplies the durable ID. They should instead explain that
|
||||||
|
Notarius resolves contextual model output and publishes the same exact durable
|
||||||
|
ID/name pair. No public schema examples need to remove those IDs.
|
||||||
|
|
||||||
|
The generic LLM-assisted deduplication entry in `docs/roadmap/future.md` must be
|
||||||
|
reconciled with this policy: stable IDs may exist inside deterministic state,
|
||||||
|
but a future model-facing proposal should use contextual selectors or a
|
||||||
|
documented request-local-label exception rather than durable IDs.
|
||||||
|
|
||||||
|
## Compatibility And Operational Effects
|
||||||
|
|
||||||
|
This work intentionally changes private prompt inputs, private structured
|
||||||
|
responses, and mapping semantics. It will invalidate affected checkpoints
|
||||||
|
through existing prompt, schema, projection, and policy fingerprints. No
|
||||||
|
manual checkpoint migration is required.
|
||||||
|
|
||||||
|
Durable D&D artifacts and generated-reference compatibility remain unchanged.
|
||||||
|
Operators do not receive new configuration fields or CLI controls. The feature
|
||||||
|
does not change PromptKit, provider routing, profile selection, retries,
|
||||||
|
concurrency, or public output placement.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
This work does not:
|
||||||
|
|
||||||
|
- remove canonical IDs from durable registries or occurrence artifacts;
|
||||||
|
- change occurrence categories, evidence rules, or registry identity policy;
|
||||||
|
- add fuzzy, probabilistic, or embedding-based entity resolution;
|
||||||
|
- allow registry provenance to substitute for occurrence evidence;
|
||||||
|
- introduce a general entity graph or cross-artifact identity framework;
|
||||||
|
- redesign unrelated D&D prompts or their schemas;
|
||||||
|
- implement the future generic deduplication normalizer; or
|
||||||
|
- add provider-specific prompt behavior.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
The target state is complete when:
|
||||||
|
|
||||||
|
- no maintained D&D prompt requires a model to reproduce a durable opaque
|
||||||
|
entity ID;
|
||||||
|
- current D&D reconciliation prompts no longer require opaque candidate keys;
|
||||||
|
- NPC, item, and location occurrence LLM outputs are resolved
|
||||||
|
deterministically into their unchanged durable artifacts;
|
||||||
|
- same-name location and reconciliation cases remain safe and unambiguous;
|
||||||
|
- invalid contextual selections preserve the existing extraction-failure or
|
||||||
|
normalization-fallback semantics appropriate to their stage;
|
||||||
|
- affected checkpoint identities change without altering public schema
|
||||||
|
versions;
|
||||||
|
- focused and repository-wide tests pass offline;
|
||||||
|
- the ADR, architecture invariant, D&D internal guide, LLM internal guide,
|
||||||
|
relevant integration contracts, and future roadmap accurately describe
|
||||||
|
their canonical portions of the implemented policy; and
|
||||||
|
- no unrelated code, prompt behavior, or public contract changes are included.
|
||||||
@@ -7,37 +7,6 @@ not as committed release dates.
|
|||||||
|
|
||||||
## Near-Term D&D Pipeline
|
## Near-Term D&D Pipeline
|
||||||
|
|
||||||
### Combat Enemy Ledger
|
|
||||||
|
|
||||||
- Add a D&D artifact that identifies enemies faced during combat and supports
|
|
||||||
an end-of-session encounter ledger.
|
|
||||||
- Track each enemy's observed state using a small controlled vocabulary such as
|
|
||||||
`active`, `killed`, `fled`, `captured`, or `incapacitated`, while preserving
|
|
||||||
an explicit unresolved state when the transcript does not establish an
|
|
||||||
outcome.
|
|
||||||
- Preserve the evidence for enemy participation and state changes rather than
|
|
||||||
inferring a terminal outcome from combat ending or an enemy disappearing
|
|
||||||
from the conversation.
|
|
||||||
- Define how repeated mentions, groups of unnamed enemies, summoned or allied
|
|
||||||
creatures, and the same enemy appearing in multiple combats affect identity
|
|
||||||
and ledger entries.
|
|
||||||
- Evaluate whether the ledger should be extracted directly, derived from
|
|
||||||
combat-turn artifacts, or use a sequential pipeline that consumes combat
|
|
||||||
turns and the normalized NPC registry as grounding references.
|
|
||||||
|
|
||||||
### Location Extraction
|
|
||||||
|
|
||||||
- Add a D&D artifact for locations visited by the party or otherwise mentioned
|
|
||||||
in the transcript.
|
|
||||||
- Distinguish observed visits from references, plans, recalled places, and
|
|
||||||
uncertain or inferred locations so a mention alone is not reported as a
|
|
||||||
visit.
|
|
||||||
- Preserve transcript evidence for each visit or mention and reconcile aliases,
|
|
||||||
nested places, and repeated appearances without collapsing distinct
|
|
||||||
locations that share a generic name.
|
|
||||||
- Define how the location artifact should ground later narrative reports and
|
|
||||||
whether future event artifacts should retain canonical location identities.
|
|
||||||
|
|
||||||
### Evaluate Spell Extraction And Normalization
|
### Evaluate Spell Extraction And Normalization
|
||||||
|
|
||||||
- Evaluate ordinary extraction retries and the completed normalization path
|
- Evaluate ordinary extraction retries and the completed normalization path
|
||||||
@@ -53,71 +22,6 @@ not as committed release dates.
|
|||||||
spell, combat, interaction, and scene-description lanes after real-world use.
|
spell, combat, interaction, and scene-description lanes after real-world use.
|
||||||
Add more complex chunking only in response to demonstrated failures.
|
Add more complex chunking only in response to demonstrated failures.
|
||||||
|
|
||||||
## Cross-Cutting LLM Runtime
|
|
||||||
|
|
||||||
### Deterministic Prompt Session Identity
|
|
||||||
|
|
||||||
- Replace the source-document-ID default for prompt sessions with one
|
|
||||||
predictable, procedurally generated session ID for the complete
|
|
||||||
source-processing workload.
|
|
||||||
- Preserve an explicit non-empty `--session-id` as the highest-precedence
|
|
||||||
override. Otherwise, derive the default only from the effective input module
|
|
||||||
identity and the exact raw input bytes.
|
|
||||||
- Use a versioned, bounded representation such as
|
|
||||||
`notarius:v1:<sha256(input-module + NUL + raw-input)>`. The exact encoding
|
|
||||||
must fit PromptKit's session length contract and must not embed source
|
|
||||||
content.
|
|
||||||
- Keep the derived session stable across runs, pipelines, selected lanes,
|
|
||||||
ordered steps, retries, resume, recomputation, LLM profiles, reasoning
|
|
||||||
overrides, and output, debug, or cache settings.
|
|
||||||
- Do not include file-backed references, generated references, reference
|
|
||||||
contents, or the composition of a reference bundle in session derivation.
|
|
||||||
References may change between prompt calls within one pipeline without
|
|
||||||
changing routing affinity.
|
|
||||||
- Resolve the authoritative session before checkpoint construction and use the
|
|
||||||
same value for checkpoint runtime identity, every prompt-facing module,
|
|
||||||
PromptKit's direct session field, the compatibility `session_id` prompt
|
|
||||||
variable, run-manifest metadata, and debug metadata.
|
|
||||||
- Keep routing identity separate from cache and checkpoint content identity.
|
|
||||||
Exact prompt prefixes, reference contents, model settings, and other
|
|
||||||
generation-affecting inputs must continue to participate in their existing
|
|
||||||
hashes and checkpoint fingerprints even though they do not change the
|
|
||||||
session.
|
|
||||||
- Treat the generated value as a provider-visible, stable pseudonymous
|
|
||||||
correlation identifier. Do not introduce an installation-specific HMAC or
|
|
||||||
secret unless a concrete multi-tenant or privacy requirement justifies
|
|
||||||
sacrificing deterministic identity across installations.
|
|
||||||
|
|
||||||
### Raise The Default Application-Wide LLM Limit
|
|
||||||
|
|
||||||
- Raise the default `concurrency.total_llm` value from 1 to 16 so ordinary
|
|
||||||
single-backend runs can use PromptKit's expected OpenRouter capacity and
|
|
||||||
lower-capacity local backends without an unnecessarily narrower Notarius
|
|
||||||
limit.
|
|
||||||
- Keep the Notarius application-wide scheduler mandatory and require
|
|
||||||
`total_llm` to remain a positive integer. Do not make the default unlimited:
|
|
||||||
endpoint-only profiles, an unrestricted local backend, injected clients, and
|
|
||||||
aggregate work across several backends may have no narrower PromptKit limit.
|
|
||||||
- Continue defaulting `concurrency.stage_workers.extract` to the effective
|
|
||||||
`total_llm`, making its default 16 as part of the same change. Preserve an
|
|
||||||
explicit lower extract-worker setting when an operator wants less queued or
|
|
||||||
concurrent extraction work.
|
|
||||||
- Define effective provider concurrency as the intersection of the Notarius
|
|
||||||
application-wide limit, the selected PromptKit backend limit when present,
|
|
||||||
and the work made available by stage execution. A Notarius limit of 16 does
|
|
||||||
not narrow a backend already limited to 16, while a local backend limited to
|
|
||||||
4 remains bounded at 4.
|
|
||||||
- Treat the default as an application-wide safety ceiling across profiles,
|
|
||||||
backends, modules, retries, and validators. A run that intentionally needs
|
|
||||||
the combined capacity of several backends may configure a higher
|
|
||||||
`total_llm` and an appropriate extract-worker count explicitly.
|
|
||||||
- Retain the existing configuration and environment override surfaces. Update
|
|
||||||
canonical configuration, operations, and internal documentation together
|
|
||||||
when the default changes.
|
|
||||||
- Reconsider decoupling the extract-worker default from `total_llm` only after
|
|
||||||
mixed-backend workloads demonstrate a need for a high global emergency
|
|
||||||
ceiling with a lower default work-production rate.
|
|
||||||
|
|
||||||
## Shared Normalization And Quality Work
|
## Shared Normalization And Quality Work
|
||||||
|
|
||||||
### Generic LLM-Assisted Deduplication
|
### Generic LLM-Assisted Deduplication
|
||||||
@@ -125,13 +29,15 @@ not as committed release dates.
|
|||||||
- Add a reusable normalizer that asks an LLM to identify duplicate sets in a
|
- Add a reusable normalizer that asks an LLM to identify duplicate sets in a
|
||||||
list and propose one replacement element for each set.
|
list and propose one replacement element for each set.
|
||||||
- Define the minimum domain-neutral input contract, initially an ordered list
|
- Define the minimum domain-neutral input contract, initially an ordered list
|
||||||
whose elements have stable unique IDs. Artifact-kind registrations or
|
whose elements retain stable unique IDs as internal deterministic state.
|
||||||
adapters may expose that structure without moving domain rules into the
|
Model proposals use contextual descriptors, or a specifically justified
|
||||||
generic package.
|
request-local short label, rather than durable IDs. Artifact-kind
|
||||||
|
registrations or adapters may expose that structure without moving domain
|
||||||
|
rules into the generic package.
|
||||||
- Keep mutation deterministic: parse and validate the model's duplicate groups,
|
- Keep mutation deterministic: parse and validate the model's duplicate groups,
|
||||||
require every referenced ID to exist, reject overlapping or malformed groups,
|
resolve every supplied descriptor or local label exactly, reject overlapping
|
||||||
prevent unrelated insertion or deletion, and apply only approved replacement
|
or malformed groups, prevent unrelated insertion or deletion, and apply only
|
||||||
operations in code.
|
approved replacement operations in code.
|
||||||
- Preserve provenance needed for audit and downstream validation, and emit
|
- Preserve provenance needed for audit and downstream validation, and emit
|
||||||
warnings describing every collapsed group.
|
warnings describing every collapsed group.
|
||||||
- Evaluate batching and context-window limits before applying the normalizer to
|
- Evaluate batching and context-window limits before applying the normalizer to
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,318 +0,0 @@
|
|||||||
# PromptKit v0.5 Integration And LLM Profile Policy
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This roadmap defines the target state for upgrading Notarius from PromptKit
|
|
||||||
v0.3.0 to v0.5.0 and adopting the upstream runtime and profile facilities that
|
|
||||||
directly improve Notarius. It also defines the application policy for stable,
|
|
||||||
domain-oriented LLM profile names, operator overrides, pipeline inheritance,
|
|
||||||
profile validation, provider defaults, checkpoint identity, and documentation.
|
|
||||||
|
|
||||||
The ordered work needed to reach this state belongs in
|
|
||||||
[the implementation plan](implementation.md). Current behavior remains defined
|
|
||||||
by the canonical documentation outside `docs/roadmap/` until the corresponding
|
|
||||||
work is implemented.
|
|
||||||
|
|
||||||
## Background
|
|
||||||
|
|
||||||
Notarius currently pins PromptKit v0.3.0. Its adapter prepares a request once
|
|
||||||
for debug material and then independently runs the original request, causing
|
|
||||||
PromptKit to prepare the same logical call a second time. The CLI validates an
|
|
||||||
explicit profile by preparing a synthetic prompt. PromptKit profile selection
|
|
||||||
can be repeated on individual module bindings or replaced for one invocation
|
|
||||||
with `--llm-profile`, but a configured pipeline cannot yet declare one inherited
|
|
||||||
profile policy.
|
|
||||||
|
|
||||||
PromptKit v0.4.0 and v0.5.0 add the upstream boundaries needed to improve these
|
|
||||||
areas:
|
|
||||||
|
|
||||||
- [v0.4.0](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/releases/v0.4.0.md)
|
|
||||||
adds opaque prepared executions, exact profile and prompt inspection, and a
|
|
||||||
typed backend-capacity error;
|
|
||||||
- [v0.5.0](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/releases/v0.5.0.md)
|
|
||||||
adds application fallback profile filesystems and stops sending unset
|
|
||||||
optional sampling controls as framework-selected provider values; and
|
|
||||||
- the [v0.5.0 format contract](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/formats.md)
|
|
||||||
defines the resulting profile-source and execution-setting precedence.
|
|
||||||
|
|
||||||
A source-compatibility test of the current Notarius repository against
|
|
||||||
PromptKit v0.5.0 completed successfully. The work is therefore primarily an
|
|
||||||
intentional runtime and configuration migration rather than a repair for a
|
|
||||||
breaking Go API change.
|
|
||||||
|
|
||||||
## Goals
|
|
||||||
|
|
||||||
- Pin and document PromptKit v0.5.0 as Notarius's supported upstream contract.
|
|
||||||
- Execute the exact prepared request snapshot whose safe details are recorded
|
|
||||||
in Notarius debug material.
|
|
||||||
- Validate configured PromptKit profiles through the upstream inspection API
|
|
||||||
without synthetic prompts, provider calls, or credential-value access.
|
|
||||||
- Give Notarius an application-owned, operator-overridable
|
|
||||||
`dnd-extraction` profile fallback.
|
|
||||||
- Let a pipeline choose one default LLM profile without repeating that ID on
|
|
||||||
every LLM-backed binding.
|
|
||||||
- Apply profile inheritance and run-wide overrides only where the resolved
|
|
||||||
module or validator can use an LLM.
|
|
||||||
- Preserve accurate checkpoint invalidation, effective profile provenance,
|
|
||||||
redaction, cancellation, concurrency, and provider-neutral module contracts.
|
|
||||||
- Provide operators with one clear deployment pattern for production,
|
|
||||||
development, and local profile definitions.
|
|
||||||
|
|
||||||
## Target End State
|
|
||||||
|
|
||||||
### PromptKit Runtime Boundary
|
|
||||||
|
|
||||||
Notarius depends on PromptKit v0.5.0 and uses its public APIs rather than
|
|
||||||
reimplementing source or execution resolution.
|
|
||||||
|
|
||||||
For each structured completion, the adapter:
|
|
||||||
|
|
||||||
1. builds one PromptKit run request from the provider-neutral Notarius request;
|
|
||||||
2. calls `PrepareExecution` once;
|
|
||||||
3. immediately arranges an idempotent `Discard` for every unexecuted handle;
|
|
||||||
4. obtains credential-redacted `Details` for debug and response metadata; and
|
|
||||||
5. calls `RunPrepared` so generation uses that exact frozen snapshot.
|
|
||||||
|
|
||||||
The debug prompt and successful result therefore describe the same selected
|
|
||||||
profile, rendered messages, input bytes, session, output contract, and effective
|
|
||||||
settings even when a filesystem-backed source changes concurrently. PromptKit
|
|
||||||
handle types remain private to `internal/framework/llm`.
|
|
||||||
|
|
||||||
PromptKit admission failures continue to match Notarius's provider-neutral
|
|
||||||
`ErrLLMCapacityExceeded` contract. When PromptKit supplies a `CapacityError`,
|
|
||||||
the adapter obtains the normalized backend ID through `errors.As` and may add it
|
|
||||||
to safe application-owned diagnostics without parsing upstream error wording.
|
|
||||||
The backend ID does not become a provider-specific module contract.
|
|
||||||
|
|
||||||
### Optional Provider Controls
|
|
||||||
|
|
||||||
Notarius accepts PromptKit v0.5.0's new behavior for `temperature`,
|
|
||||||
`max_tokens`, and `top_p`: an unset setting is omitted from compatible provider
|
|
||||||
requests and the provider chooses its own default. Notarius does not restore
|
|
||||||
PromptKit's former implicit `top_p: 1` value globally.
|
|
||||||
|
|
||||||
An operator who requires a particular value specifies it in the selected
|
|
||||||
PromptKit profile. The application fallback described below intentionally
|
|
||||||
leaves these controls unset. A human-reviewed D&D extraction comparison should
|
|
||||||
be performed after the upgrade, but paid or nondeterministic model output is
|
|
||||||
not part of the default automated test suite.
|
|
||||||
|
|
||||||
### Profile Inspection
|
|
||||||
|
|
||||||
Pipeline-aware configuration validation uses `Engine.InspectProfile` for every
|
|
||||||
effective explicit profile ID. It verifies that the profile exists, parses and
|
|
||||||
validates, resolves its backend and target, and is compatible with the engine's
|
|
||||||
registered backends. It does not create a synthetic prompt, load prompt inputs,
|
|
||||||
contact a provider, or require credential values to exist in the validation
|
|
||||||
process environment.
|
|
||||||
|
|
||||||
Credential availability is execution-time state. PromptKit preparation still
|
|
||||||
enforces the selected profile's credential contract before generation. This
|
|
||||||
keeps `notarius config validate` useful in build and deployment validation
|
|
||||||
environments where secrets are deliberately absent.
|
|
||||||
|
|
||||||
PromptKit construction for inspection and execution uses one shared internal
|
|
||||||
profile-source and backend-option path. The CLI does not expose PromptKit public
|
|
||||||
types across the Notarius LLM boundary merely to perform inspection.
|
|
||||||
|
|
||||||
`InspectPrompt` is not adopted merely because it exists. It remains available
|
|
||||||
for a later, separately defined module-to-prompt interface preflight if a
|
|
||||||
concrete validation requirement justifies that additional contract.
|
|
||||||
|
|
||||||
### Application And Operator Profile Sources
|
|
||||||
|
|
||||||
Notarius embeds one ordinary PromptKit YAML profile with the stable ID
|
|
||||||
`dnd-extraction`. It is an application fallback registered through
|
|
||||||
`WithFallbackProfileFS`, is owned by the D&D module family, and initially
|
|
||||||
preserves the current effective D&D baseline:
|
|
||||||
|
|
||||||
- backend: PromptKit's built-in `openrouter` backend;
|
|
||||||
- model: `openai/gpt-5.6-luna`;
|
|
||||||
- reasoning effort: unset, allowing OpenAI's backend to apply its default of
|
|
||||||
`medium`;
|
|
||||||
- generation timeout: 240 seconds;
|
|
||||||
- service tier: `flex`; and
|
|
||||||
- no application-selected `temperature`, `max_tokens`, or `top_p`.
|
|
||||||
|
|
||||||
All maintained D&D LLM prompt definitions use `dnd-extraction` as their
|
|
||||||
`default_profile`. The ID communicates workload intent rather than a provider,
|
|
||||||
model, or environment. Changing the embedded fallback is an intentional
|
|
||||||
Notarius execution-policy change and participates in checkpoint identity.
|
|
||||||
|
|
||||||
Effective profile definitions resolve in PromptKit's order:
|
|
||||||
|
|
||||||
1. programmatic in-memory profiles used by tests or explicit consumers;
|
|
||||||
2. the operator source configured by `promptkit.profile_file` or
|
|
||||||
`promptkit.profile_dir`;
|
|
||||||
3. the Notarius application fallback source; and
|
|
||||||
4. PromptKit's embedded built-in catalog.
|
|
||||||
|
|
||||||
Only an absent ID falls through to the next source. A matching profile is a
|
|
||||||
complete definition: fields are not merged with a lower-precedence definition,
|
|
||||||
and a malformed matching operator profile fails rather than silently selecting
|
|
||||||
the application fallback.
|
|
||||||
|
|
||||||
Production, development, and local deployments should normally provide
|
|
||||||
different complete definitions for the same `dnd-extraction` ID. An operator
|
|
||||||
source is optional because the application fallback keeps the maintained D&D
|
|
||||||
workflow usable, but a deployment that needs an intentional model or backend
|
|
||||||
policy should configure its own definition.
|
|
||||||
|
|
||||||
### Domain Ownership And Asset Assembly
|
|
||||||
|
|
||||||
The D&D fallback profile remains under `internal/modules/dnd` and is registered
|
|
||||||
by the D&D registrar, consistent with ADR-0004. Generic LLM plumbing knows how
|
|
||||||
to collect and flatten application fallback profile filesystems but contains no
|
|
||||||
D&D model or policy knowledge.
|
|
||||||
|
|
||||||
The shared asset registry detects invalid roots, unreadable sources, and
|
|
||||||
duplicate flattened paths. PromptKit remains responsible for strict profile
|
|
||||||
YAML parsing, duplicate profile-ID detection, source precedence, and effective
|
|
||||||
target resolution. The same assembled fallback source is supplied to runtime
|
|
||||||
execution and CLI profile inspection.
|
|
||||||
|
|
||||||
### Explicit Module Execution Metadata
|
|
||||||
|
|
||||||
Every registered input, chunk, extract, merge, normalize, and output module
|
|
||||||
declares one required execution class: `deterministic` or `llm_backed`.
|
|
||||||
Validator registrations continue to declare the same distinction through their
|
|
||||||
validator specifications.
|
|
||||||
|
|
||||||
The registered specification is authoritative for configuration resolution.
|
|
||||||
Current production classifications are:
|
|
||||||
|
|
||||||
- the D&D scene chunker, all D&D extractors, and the D&D NPC normalizer are
|
|
||||||
LLM-backed;
|
|
||||||
- the Seriatim input adapter, generic chunker, all current mergers, all other
|
|
||||||
current normalizers, and the JSON output encoder are deterministic; and
|
|
||||||
- current validators retain their declared classifications.
|
|
||||||
|
|
||||||
Missing or unsupported execution metadata is a registration error. Explicitly
|
|
||||||
assigning `llm_profile` to a deterministic module or validator is a pipeline
|
|
||||||
resolution error. The framework does not infer execution class by inspecting
|
|
||||||
domain package names or concrete implementation types at runtime.
|
|
||||||
|
|
||||||
The module specification replaces the chunk runner's special runtime
|
|
||||||
execution-class probe. Effective resolved bindings already express the result:
|
|
||||||
only LLM-backed bindings may retain a non-empty profile.
|
|
||||||
|
|
||||||
### Pipeline-Level Profile Default
|
|
||||||
|
|
||||||
Configuration version 4 gains one optional non-empty pipeline field:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
pipelines:
|
|
||||||
dnd-session:
|
|
||||||
llm_profile: dnd-extraction
|
|
||||||
```
|
|
||||||
|
|
||||||
No configuration-version increment is required because the field is additive
|
|
||||||
and existing files remain valid. An explicitly present blank value is invalid.
|
|
||||||
|
|
||||||
For every selected LLM-backed module and validator, the effective profile uses
|
|
||||||
this precedence:
|
|
||||||
|
|
||||||
1. non-empty run-wide `--llm-profile` override;
|
|
||||||
2. binding-specific `llm_profile`;
|
|
||||||
3. pipeline-level `llm_profile`; and
|
|
||||||
4. the prompt definition's `default_profile`, represented by an empty effective
|
|
||||||
Notarius binding profile.
|
|
||||||
|
|
||||||
The run-wide override and inherited pipeline default never attach to a
|
|
||||||
deterministic binding. Binding-specific exceptions remain available when one
|
|
||||||
operation needs a different cost, latency, quality, backend, or reasoning
|
|
||||||
policy.
|
|
||||||
|
|
||||||
Inheritance is resolved after module and validator selection, including
|
|
||||||
`--only` lane filtering, but before effective-pipeline validation, digest
|
|
||||||
construction, explicit-profile inspection, checkpoint construction,
|
|
||||||
preparation, execution, or provenance capture. Only profiles used by selected
|
|
||||||
LLM-backed bindings are inspected. An unused pipeline default in a pipeline
|
|
||||||
with no selected LLM-backed work does not require an otherwise unused profile
|
|
||||||
to exist.
|
|
||||||
|
|
||||||
The resolved pipeline contains effective binding profiles rather than a second
|
|
||||||
runtime inheritance mechanism. Two pipelines that differ only by spelling the
|
|
||||||
same effective policy once as a pipeline default and once on every LLM-backed
|
|
||||||
binding have the same semantic resolved digest. Changing an effective profile
|
|
||||||
changes the digest and applicable checkpoint identity.
|
|
||||||
|
|
||||||
### Provenance And Checkpoints
|
|
||||||
|
|
||||||
The PromptKit profile-source checkpoint fingerprint covers:
|
|
||||||
|
|
||||||
- the PromptKit v0.5.0 built-in profile catalog identity;
|
|
||||||
- exact application fallback profile asset content; and
|
|
||||||
- exact configured operator profile YAML content, when present.
|
|
||||||
|
|
||||||
The existing local-backend target fingerprint remains separate and continues
|
|
||||||
to exclude scheduling-only concurrency limits. Fingerprints contain hashes and
|
|
||||||
stable markers, not profile contents, filesystem paths, endpoints, credentials,
|
|
||||||
or other secrets.
|
|
||||||
|
|
||||||
Changing the PromptKit version, application fallback, operator profile, or
|
|
||||||
effective pipeline profile makes incompatible LLM checkpoints ineligible for
|
|
||||||
reuse. The dependency upgrade is expected to invalidate checkpoints produced
|
|
||||||
under v0.3.0.
|
|
||||||
|
|
||||||
Successful run manifests continue to record only profiles actually selected by
|
|
||||||
PromptKit, including their effective model, backend, and reasoning metadata.
|
|
||||||
Debug output reports the same effective execution snapshot used for generation.
|
|
||||||
|
|
||||||
### Operator Documentation And Examples
|
|
||||||
|
|
||||||
Canonical documentation clearly distinguishes:
|
|
||||||
|
|
||||||
- Notarius prompt and schema assets embedded in the application;
|
|
||||||
- Notarius application fallback profiles embedded in the application;
|
|
||||||
- PromptKit's own embedded built-in profiles; and
|
|
||||||
- operator profile files on the deployment filesystem.
|
|
||||||
|
|
||||||
The configuration reference owns the pipeline field, profile-source fields,
|
|
||||||
validation rules, and precedence. Operations owns deployment layout, working
|
|
||||||
directory behavior, credentials, and environment-specific profile management.
|
|
||||||
The PromptKit integration document owns the pinned upstream contract and
|
|
||||||
source-precedence boundary. Internal documents describe asset registration,
|
|
||||||
resolution, inspection, prepared execution, fingerprinting, and tests without
|
|
||||||
duplicating user-facing field definitions.
|
|
||||||
|
|
||||||
The maintained examples continue to include only the minimal and complete D&D
|
|
||||||
configurations. They use the stable `dnd-extraction` policy, and one maintained
|
|
||||||
PromptKit profile file under `examples/` demonstrates an operator override.
|
|
||||||
Examples remain secret-free and are validated without live provider calls.
|
|
||||||
|
|
||||||
## Out Of Scope
|
|
||||||
|
|
||||||
- Implementing the separate deterministic prompt-session identity roadmap
|
|
||||||
item.
|
|
||||||
- Changing the default `concurrency.total_llm` value; PromptKit's retained
|
|
||||||
OpenRouter capacity of 16 remains relevant to that separate item.
|
|
||||||
- Adding model evaluation as a deterministic or CI correctness gate.
|
|
||||||
- Automatically selecting production, development, or local environments.
|
|
||||||
Deployment configuration chooses the operator profile source.
|
|
||||||
- Profile inheritance, partial profile merging, or cross-profile aliases.
|
|
||||||
- Exposing PromptKit types to modules, validators, durable output contracts, or
|
|
||||||
public configuration structures.
|
|
||||||
- Adopting `InspectPrompt` without a separately justified prompt-interface
|
|
||||||
validation contract.
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- Notarius builds and its offline test suite passes with PromptKit v0.5.0.
|
|
||||||
- Every structured completion executes the exact snapshot used for safe debug
|
|
||||||
prompt details.
|
|
||||||
- Profile preflight uses profile inspection and no synthetic prompt.
|
|
||||||
- The embedded `dnd-extraction` fallback resolves without an operator source,
|
|
||||||
and a matching valid operator profile replaces it completely.
|
|
||||||
- Every production module has explicit, correct execution metadata.
|
|
||||||
- Pipeline, binding, CLI, and prompt-default precedence behaves as defined for
|
|
||||||
modules and validators, while deterministic bindings remain profile-free.
|
|
||||||
- Effective profiles participate in pipeline digests, profile inspection,
|
|
||||||
checkpoint identity, debug records, and run provenance at the appropriate
|
|
||||||
boundaries.
|
|
||||||
- The dependency and application fallback changes invalidate incompatible old
|
|
||||||
checkpoints without exposing profile or credential content.
|
|
||||||
- Canonical documentation and maintained examples accurately describe and
|
|
||||||
exercise the implemented operator workflow.
|
|
||||||
- Default tests remain deterministic, offline, credential-free, and focused on
|
|
||||||
Notarius-owned behavior rather than duplicating PromptKit's upstream suite.
|
|
||||||
@@ -35,28 +35,40 @@ pipelines:
|
|||||||
enabled: true
|
enabled: true
|
||||||
window_units: 3
|
window_units: 3
|
||||||
lanes:
|
lanes:
|
||||||
- item-events
|
- item-occurrences
|
||||||
- npcs
|
- item-registry
|
||||||
|
- location-registry
|
||||||
|
- location-occurrences
|
||||||
|
- npc-registry
|
||||||
- spells
|
- spells
|
||||||
- combat-turns
|
- combat-turns
|
||||||
- npc-interactions
|
- npc-occurrences
|
||||||
|
- enemy-events
|
||||||
steps:
|
steps:
|
||||||
# Establish session-wide reference artifacts alongside independent item events.
|
# Establish session-wide reference artifacts before their consumers.
|
||||||
- id: describe-session
|
- id: describe-session
|
||||||
artifacts:
|
artifacts:
|
||||||
item-events:
|
item-registry:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/item-events
|
module: dnd/item-registry
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/item-events
|
normalize: dnd/item-registry
|
||||||
npcs:
|
npc-registry:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/npcs
|
module: dnd/npc-registry
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize:
|
normalize:
|
||||||
module: dnd/npcs
|
module: dnd/npc-registry
|
||||||
|
retries: 2
|
||||||
|
location-registry:
|
||||||
|
extract:
|
||||||
|
module: dnd/location-registry
|
||||||
|
retries: 2
|
||||||
|
merge: appendorder
|
||||||
|
normalize:
|
||||||
|
module: dnd/location-registry
|
||||||
retries: 2
|
retries: 2
|
||||||
scene-descriptions:
|
scene-descriptions:
|
||||||
extract:
|
extract:
|
||||||
@@ -65,18 +77,32 @@ pipelines:
|
|||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/scene-descriptions
|
normalize: dnd/scene-descriptions
|
||||||
- id: extract-events
|
- id: extract-events
|
||||||
# Accepted NPC grounding and scene-description eligibility artifacts are
|
# Accepted registry artifacts and scene-description eligibility artifacts
|
||||||
# supplied in memory to their compatible consumers in this step.
|
# are supplied in memory to their compatible consumers in this step.
|
||||||
references:
|
references:
|
||||||
npcs:
|
location_registry:
|
||||||
artifact:
|
artifact:
|
||||||
step: describe-session
|
step: describe-session
|
||||||
lane: npcs
|
lane: location-registry
|
||||||
|
npc_registry:
|
||||||
|
artifact:
|
||||||
|
step: describe-session
|
||||||
|
lane: npc-registry
|
||||||
scene_descriptions:
|
scene_descriptions:
|
||||||
artifact:
|
artifact:
|
||||||
step: describe-session
|
step: describe-session
|
||||||
lane: scene-descriptions
|
lane: scene-descriptions
|
||||||
|
item_registry:
|
||||||
|
artifact:
|
||||||
|
step: describe-session
|
||||||
|
lane: item-registry
|
||||||
artifacts:
|
artifacts:
|
||||||
|
item-occurrences:
|
||||||
|
extract:
|
||||||
|
module: dnd/item-occurrences
|
||||||
|
retries: 2
|
||||||
|
merge: appendorder
|
||||||
|
normalize: dnd/item-occurrences
|
||||||
spells:
|
spells:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/spells
|
module: dnd/spells
|
||||||
@@ -95,9 +121,40 @@ pipelines:
|
|||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/combat-turns
|
normalize: dnd/combat-turns
|
||||||
npc-interactions:
|
npc-occurrences:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/npc-interactions
|
module: dnd/npc-occurrences
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/npc-interactions
|
normalize: dnd/npc-occurrences
|
||||||
|
location-occurrences:
|
||||||
|
extract:
|
||||||
|
module: dnd/location-occurrences
|
||||||
|
retries: 2
|
||||||
|
merge: appendorder
|
||||||
|
normalize: dnd/location-occurrences
|
||||||
|
- id: track-enemies
|
||||||
|
references:
|
||||||
|
npc_registry:
|
||||||
|
artifact:
|
||||||
|
step: describe-session
|
||||||
|
lane: npc-registry
|
||||||
|
scene_descriptions:
|
||||||
|
artifact:
|
||||||
|
step: describe-session
|
||||||
|
lane: scene-descriptions
|
||||||
|
combat_turns:
|
||||||
|
artifact:
|
||||||
|
step: extract-events
|
||||||
|
lane: combat-turns
|
||||||
|
npc_occurrences:
|
||||||
|
artifact:
|
||||||
|
step: extract-events
|
||||||
|
lane: npc-occurrences
|
||||||
|
artifacts:
|
||||||
|
enemy-events:
|
||||||
|
extract:
|
||||||
|
module: dnd/enemy-events
|
||||||
|
retries: 2
|
||||||
|
merge: appendorder
|
||||||
|
normalize: dnd/enemy-events
|
||||||
|
|||||||
@@ -249,10 +249,24 @@ func TestRunAutoReusesPlanWhenRunInputsChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
harness.mu.Lock()
|
harness.mu.Lock()
|
||||||
chunkCalls := harness.chunkCalls
|
chunkCalls := harness.chunkCalls
|
||||||
|
sessions := append([]string(nil), harness.sessionIDs...)
|
||||||
harness.mu.Unlock()
|
harness.mu.Unlock()
|
||||||
if chunkCalls != 1 {
|
if chunkCalls != 1 {
|
||||||
t.Fatalf("chunk calls across changed run inputs = %d, want 1", chunkCalls)
|
t.Fatalf("chunk calls across changed run inputs = %d, want 1", chunkCalls)
|
||||||
}
|
}
|
||||||
|
rawInput, err := os.ReadFile(roots.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wantSessionID, err := resolvePromptSessionID("", "test/input", rawInput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, sessionID := range sessions {
|
||||||
|
if sessionID != wantSessionID {
|
||||||
|
t.Fatalf("session IDs across reference changes = %#v, want %q", sessions, wantSessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
assertFile(t, filepath.Join(roots.plans, strings.TrimPrefix(stateTestDigest, "sha256:"), "plan.json"))
|
assertFile(t, filepath.Join(roots.plans, strings.TrimPrefix(stateTestDigest, "sha256:"), "plan.json"))
|
||||||
assertAnyFile(t, roots.output)
|
assertAnyFile(t, roots.output)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestProductionCombatConfigurationResolvesTypedLane(t *testing.T) {
|
|||||||
if !ok || codecSpec.Schema.ID != "notarius.dnd.combat_turns" || codecSpec.Schema.Version != "v1" {
|
if !ok || codecSpec.Schema.ID != "notarius.dnd.combat_turns" || codecSpec.Schema.Version != "v1" {
|
||||||
t.Fatalf("combat codec spec = %#v, want compatible durable schema", codecSpec)
|
t.Fatalf("combat codec spec = %#v, want compatible durable schema", codecSpec)
|
||||||
}
|
}
|
||||||
if !hasReferenceSlot(extractSpec.ReferenceSlots, "npcs") || !hasReferenceSlot(extractSpec.ReferenceSlots, "scene_descriptions") || !hasReferenceSlot(normalizeSpec.ReferenceSlots, "npcs") {
|
if !hasReferenceSlot(extractSpec.ReferenceSlots, "npc_registry") || !hasReferenceSlot(extractSpec.ReferenceSlots, "scene_descriptions") || !hasReferenceSlot(normalizeSpec.ReferenceSlots, "npc_registry") {
|
||||||
t.Fatalf("combat reference slots = %#v / %#v, want extraction scene and NPC slots plus normalization NPC slot", extractSpec.ReferenceSlots, normalizeSpec.ReferenceSlots)
|
t.Fatalf("combat reference slots = %#v / %#v, want extraction scene and NPC slots plus normalization NPC slot", extractSpec.ReferenceSlots, normalizeSpec.ReferenceSlots)
|
||||||
}
|
}
|
||||||
sceneSlot := referenceSlot(extractSpec.ReferenceSlots, "scene_descriptions")
|
sceneSlot := referenceSlot(extractSpec.ReferenceSlots, "scene_descriptions")
|
||||||
@@ -85,15 +85,15 @@ func TestProductionCombatConfigurationResolvesTypedLane(t *testing.T) {
|
|||||||
PipelineID: "dnd-combat",
|
PipelineID: "dnd-combat",
|
||||||
Catalog: catalog,
|
Catalog: catalog,
|
||||||
ReferenceOverrides: []pipeline.ReferenceBinding{
|
ReferenceOverrides: []pipeline.ReferenceBinding{
|
||||||
{Stage: pipeline.StageExtract, LaneID: "combat", SlotName: "npcs", Source: "npc-run/lanes/npcs.json", BindingSource: contracts.ReferenceBindingSourceCLI},
|
{Stage: pipeline.StageExtract, LaneID: "combat", SlotName: "npc_registry", Source: "npc-run/lanes/npc_registry.json", BindingSource: contracts.ReferenceBindingSourceCLI},
|
||||||
{Stage: pipeline.StageNormalize, LaneID: "combat", SlotName: "npcs", Source: "npc-run/lanes/npcs.json", BindingSource: contracts.ReferenceBindingSourceCLI},
|
{Stage: pipeline.StageNormalize, LaneID: "combat", SlotName: "npc_registry", Source: "npc-run/lanes/npc_registry.json", BindingSource: contracts.ReferenceBindingSourceCLI},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Resolve(bound references) error = %v, want nil", err)
|
t.Fatalf("Resolve(bound references) error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
boundLane := bound.ResolvedPipeline.Steps[0].ArtifactLanes[0]
|
boundLane := bound.ResolvedPipeline.Steps[0].ArtifactLanes[0]
|
||||||
if len(boundLane.ExtractReferences.Bindings) != 2 || len(boundLane.NormalizeReferences.Bindings) != 1 || !hasReferenceBinding(boundLane.ExtractReferences.Bindings, "npcs") || !hasReferenceBinding(boundLane.ExtractReferences.Bindings, "scene_descriptions") || !hasReferenceBinding(boundLane.NormalizeReferences.Bindings, "npcs") {
|
if len(boundLane.ExtractReferences.Bindings) != 2 || len(boundLane.NormalizeReferences.Bindings) != 1 || !hasReferenceBinding(boundLane.ExtractReferences.Bindings, "npc_registry") || !hasReferenceBinding(boundLane.ExtractReferences.Bindings, "scene_descriptions") || !hasReferenceBinding(boundLane.NormalizeReferences.Bindings, "npc_registry") {
|
||||||
t.Fatalf("bound combat references = %#v / %#v, want extraction scene and NPC bindings plus normalization NPC binding", boundLane.ExtractReferences, boundLane.NormalizeReferences)
|
t.Fatalf("bound combat references = %#v / %#v, want extraction scene and NPC bindings plus normalization NPC binding", boundLane.ExtractReferences, boundLane.NormalizeReferences)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
436
internal/cli/dnd_enemy_events_contract_test.go
Normal file
436
internal/cli/dnd_enemy_events_contract_test.go
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/evidencecontext"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/chunk/scenes"
|
||||||
|
itemoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemoccurrences"
|
||||||
|
itemregistrycodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemregistry"
|
||||||
|
locationoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationoccurrences"
|
||||||
|
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationregistry"
|
||||||
|
npcoccurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
|
combat "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/combatturns"
|
||||||
|
enemyevents "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/enemyevents"
|
||||||
|
itemoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemoccurrences"
|
||||||
|
itemregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemregistry"
|
||||||
|
locationoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
|
locations "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationregistry"
|
||||||
|
npcoccurrences "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
|
npcregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
|
scenedescriptions "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/scenedescriptions"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/spells"
|
||||||
|
enemyeventnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/enemyevents"
|
||||||
|
itemregistrynormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemregistry"
|
||||||
|
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationregistry"
|
||||||
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProductionEnemyEventConfigurationResolvesGeneratedHandoffs(t *testing.T) {
|
||||||
|
components := productionTestComponents(t)
|
||||||
|
cfg := loadMaintainedExample(t, repositoryPath("examples", "dnd-complete.config.yml"))
|
||||||
|
effective, err := cfg.Resolve(resolveInputForMaintainedExample(components, "dnd-session"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Resolve() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
materialized, _, err := pipeline.MaterializeReferences(effective.ResolvedPipeline, catalogFromRegistries(components.registries), pipeline.ReferenceMaterializationOptions{
|
||||||
|
ConfigPath: repositoryPath("examples", "dnd-complete.config.yml"),
|
||||||
|
WorkingDir: repositoryPath("examples"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MaterializeReferences() error = %v, want nil", err)
|
||||||
|
}
|
||||||
|
lane := referenceContractLane(t, materialized, "enemy-events")
|
||||||
|
if lane.ArtifactKind != dnd.EnemyEventListKind || lane.Extract.Module != enemyevents.Key || lane.Extract.Retries != 2 || lane.Merge.Module != pipeline.DefaultMergeModule || lane.Normalize.Module != enemyeventnormalize.Key {
|
||||||
|
t.Fatalf("enemy event lane = %#v, want typed production composition", lane)
|
||||||
|
}
|
||||||
|
for slot, want := range map[string]struct{ step, lane string }{
|
||||||
|
"npc_registry": {step: "describe-session", lane: "npc-registry"},
|
||||||
|
"scene_descriptions": {step: "describe-session", lane: "scene-descriptions"},
|
||||||
|
"combat_turns": {step: "extract-events", lane: "combat-turns"},
|
||||||
|
"npc_occurrences": {step: "extract-events", lane: "npc-occurrences"},
|
||||||
|
} {
|
||||||
|
binding, found := generatedReferenceBinding(lane.ExtractReferences.Bindings, slot)
|
||||||
|
if !found || binding.Artifact.Step != want.step || binding.Artifact.Lane != want.lane {
|
||||||
|
t.Fatalf("enemy event %s reference = %#v, want generated %s/%s artifact", slot, binding, want.step, want.lane)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if binding, found := generatedReferenceBinding(lane.NormalizeReferences.Bindings, "npc_registry"); !found || binding.Artifact.Step != "describe-session" || binding.Artifact.Lane != "npc-registry" {
|
||||||
|
t.Fatalf("enemy event normalizer NPC reference = %#v, want generated NPC artifact", binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog := catalogFromRegistries(components.registries)
|
||||||
|
extractSpec, ok := catalog.Extractors.Spec(enemyevents.Key)
|
||||||
|
if !ok || !reflect.DeepEqual(extractSpec.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(extractSpec.Provides, []string{"dnd.enemy_events"}) {
|
||||||
|
t.Fatalf("enemy event extractor spec = %#v, want source and artifact capabilities", extractSpec)
|
||||||
|
}
|
||||||
|
normalizeSpec, ok := catalog.Normalizers.SpecForArtifact(enemyeventnormalize.Key, dnd.EnemyEventListKind)
|
||||||
|
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
||||||
|
t.Fatalf("enemy event normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
||||||
|
}
|
||||||
|
for _, slot := range []string{"npc_registry", "scene_descriptions", "combat_turns", "npc_occurrences"} {
|
||||||
|
if !hasReferenceSlot(extractSpec.ReferenceSlots, slot) {
|
||||||
|
t.Fatalf("enemy event extractor slots = %#v, want %q", extractSpec.ReferenceSlots, slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasReferenceSlot(normalizeSpec.ReferenceSlots, "npc_registry") {
|
||||||
|
t.Fatalf("enemy event normalizer slots = %#v, want NPC registry", normalizeSpec.ReferenceSlots)
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := cfg.Pipelines["dnd-session"]
|
||||||
|
profile.Steps[2].References["npc_registry"] = pipeline.GeneratedReference("track-enemies", "enemy-events")
|
||||||
|
cfg.Pipelines["dnd-session"] = profile
|
||||||
|
if _, err := cfg.Resolve(resolveInputForMaintainedExample(components, "dnd-session")); err == nil || !strings.Contains(err.Error(), "earlier step") {
|
||||||
|
t.Fatalf("Resolve() error = %v, want future generated-reference rejection", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaintainedCompleteExamplePublishesRegistryBackedEntityOccurrences(t *testing.T) {
|
||||||
|
t.Chdir(repositoryPath())
|
||||||
|
outputRoot := filepath.Join(t.TempDir(), "output")
|
||||||
|
configPath := completeExampleConfigWithTemporaryCache(t)
|
||||||
|
client := &enemyEventLLMClient{}
|
||||||
|
options := productionCLIOptions(t)
|
||||||
|
options.Now = func() time.Time { return time.Unix(1700000000, 0).UTC() }
|
||||||
|
options.RunIDGenerator = func(time.Time) (string, error) { return productionRunID, nil }
|
||||||
|
options.UserCacheDir = func() (string, error) { return "", errors.New("user cache must not be used") }
|
||||||
|
options.LLMClientFactory = func(context.Context, config.Config, string, LLMRuntimeOverrides) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
||||||
|
return client, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout, stderr strings.Builder
|
||||||
|
code := RunWithOptions([]string{
|
||||||
|
"run", "dnd-session",
|
||||||
|
"--config", configPath,
|
||||||
|
"--input", repositoryPath("examples", "dnd-complete-transcript.json"),
|
||||||
|
"--chunk_cache", "bypass", "--output-dir", outputRoot, "--session-id", "enemy-event-session",
|
||||||
|
}, &stdout, &stderr, options)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
runRoot := filepath.Join(outputRoot, productionRunID)
|
||||||
|
index := readProductionJSON[exampleOutputIndex](t, filepath.Join(runRoot, "index.json"))
|
||||||
|
outputs := make(map[string]exampleOutputIndexEntry)
|
||||||
|
for _, entry := range index.OutputFiles {
|
||||||
|
outputs[entry.LaneID] = entry
|
||||||
|
}
|
||||||
|
enemyOutput := outputs["enemy-events"]
|
||||||
|
locationOutput := outputs["location-registry"]
|
||||||
|
locationOccurrenceOutput := outputs["location-occurrences"]
|
||||||
|
npcRegistryOutput := outputs["npc-registry"]
|
||||||
|
npcOccurrenceOutput := outputs["npc-occurrences"]
|
||||||
|
itemRegistryOutput := outputs["item-registry"]
|
||||||
|
itemOccurrenceOutput := outputs["item-occurrences"]
|
||||||
|
if enemyOutput.File != "lanes/enemy-events.json" || enemyOutput.SchemaID != "notarius.dnd.enemy_events" || enemyOutput.SchemaVersion != "v1" {
|
||||||
|
t.Fatalf("enemy event output = %#v, want typed enemy-event JSON", enemyOutput)
|
||||||
|
}
|
||||||
|
value := readProductionJSON[dnd.EnemyEventList](t, filepath.Join(runRoot, enemyOutput.File))
|
||||||
|
if len(value.Events) != 1 || value.Events[0].Name != "Kesh" || value.Events[0].Kind != dnd.EnemyEventKindFled || len(value.Events[0].SourceRefs) != 1 || value.Events[0].SourceRefs[0].SourceID != "session-ravenfall" || value.Events[0].SourceRefs[0].StartUnitID != 10 {
|
||||||
|
t.Fatalf("enemy event artifact = %#v, want source-linked Kesh fleeing event", value)
|
||||||
|
}
|
||||||
|
if locationOutput.File != "lanes/location-registry.json" || locationOutput.SchemaID != locationcodec.SchemaID || locationOutput.SchemaVersion != locationcodec.SchemaVersion {
|
||||||
|
t.Fatalf("location output = %#v, want typed location registry JSON", locationOutput)
|
||||||
|
}
|
||||||
|
locationsValue := readProductionJSON[dnd.LocationRegistry](t, filepath.Join(runRoot, locationOutput.File))
|
||||||
|
if len(locationsValue.Locations) != 2 || locationsValue.Locations[0].Name != "Moon Gate" || locationsValue.Locations[1].Name != "Moon Gate" || locationsValue.Locations[0].ID == locationsValue.Locations[1].ID {
|
||||||
|
t.Fatalf("location registry = %#v, want distinct source-grounded identities for same-name locations", locationsValue)
|
||||||
|
}
|
||||||
|
if locationOccurrenceOutput.File != "lanes/location-occurrences.json" || locationOccurrenceOutput.SchemaID != locationoccurrencecodec.SchemaID || locationOccurrenceOutput.SchemaVersion != locationoccurrencecodec.SchemaVersion {
|
||||||
|
t.Fatalf("location occurrence output = %#v, want typed occurrence JSON", locationOccurrenceOutput)
|
||||||
|
}
|
||||||
|
occurrencesValue := readProductionJSON[dnd.LocationOccurrenceList](t, filepath.Join(runRoot, locationOccurrenceOutput.File))
|
||||||
|
if len(occurrencesValue.Occurrences) != 2 || occurrencesValue.Occurrences[0].LocationID == occurrencesValue.Occurrences[1].LocationID || occurrencesValue.Occurrences[0].Name != "Moon Gate" || occurrencesValue.Occurrences[1].Name != "Moon Gate" {
|
||||||
|
t.Fatalf("location occurrences = %#v, want source-grounded references to distinct registry identities", occurrencesValue)
|
||||||
|
}
|
||||||
|
if npcRegistryOutput.File != "lanes/npc-registry.json" || npcRegistryOutput.SchemaID != npccodec.SchemaID || npcRegistryOutput.SchemaVersion != npccodec.SchemaVersion {
|
||||||
|
t.Fatalf("NPC registry output = %#v, want typed registry JSON", npcRegistryOutput)
|
||||||
|
}
|
||||||
|
npcRegistry := readProductionJSON[dnd.NPCRegistry](t, filepath.Join(runRoot, npcRegistryOutput.File))
|
||||||
|
if len(npcRegistry.NPCs) != 1 || npcRegistry.NPCs[0].Name != "Kesh" {
|
||||||
|
t.Fatalf("NPC registry = %#v, want Kesh identity", npcRegistry)
|
||||||
|
}
|
||||||
|
if npcOccurrenceOutput.File != "lanes/npc-occurrences.json" || npcOccurrenceOutput.SchemaID != npcoccurrencecodec.SchemaID || npcOccurrenceOutput.SchemaVersion != npcoccurrencecodec.SchemaVersion {
|
||||||
|
t.Fatalf("NPC occurrence output = %#v, want typed occurrence JSON", npcOccurrenceOutput)
|
||||||
|
}
|
||||||
|
npcOccurrences := readProductionJSON[dnd.NPCOccurrenceList](t, filepath.Join(runRoot, npcOccurrenceOutput.File))
|
||||||
|
if len(npcOccurrences.Occurrences) != 1 || npcOccurrences.Occurrences[0].NPCID != npcRegistry.NPCs[0].ID || npcOccurrences.Occurrences[0].Name != "Kesh" || len(npcOccurrences.Occurrences[0].SourceRefs) != 1 || npcOccurrences.Occurrences[0].SourceRefs[0].StartUnitID != 7 {
|
||||||
|
t.Fatalf("NPC occurrences = %#v, want independently evidenced Kesh registry grounding", npcOccurrences)
|
||||||
|
}
|
||||||
|
if itemRegistryOutput.File != "lanes/item-registry.json" || itemRegistryOutput.SchemaID != itemregistrycodec.SchemaID || itemRegistryOutput.SchemaVersion != itemregistrycodec.SchemaVersion {
|
||||||
|
t.Fatalf("item registry output = %#v, want typed registry JSON", itemRegistryOutput)
|
||||||
|
}
|
||||||
|
itemRegistry := readProductionJSON[dnd.ItemRegistry](t, filepath.Join(runRoot, itemRegistryOutput.File))
|
||||||
|
if len(itemRegistry.Items) != 1 || itemRegistry.Items[0].Name != "Moonblade" {
|
||||||
|
t.Fatalf("item registry = %#v, want Moonblade identity", itemRegistry)
|
||||||
|
}
|
||||||
|
if itemOccurrenceOutput.File != "lanes/item-occurrences.json" || itemOccurrenceOutput.SchemaID != itemoccurrencecodec.SchemaID || itemOccurrenceOutput.SchemaVersion != itemoccurrencecodec.SchemaVersion {
|
||||||
|
t.Fatalf("item occurrence output = %#v, want typed occurrence JSON", itemOccurrenceOutput)
|
||||||
|
}
|
||||||
|
itemOccurrences := readProductionJSON[dnd.ItemOccurrenceList](t, filepath.Join(runRoot, itemOccurrenceOutput.File))
|
||||||
|
if len(itemOccurrences.Occurrences) != 1 || itemOccurrences.Occurrences[0].ItemID != itemRegistry.Items[0].ID || itemOccurrences.Occurrences[0].Name != "Moonblade" || len(itemOccurrences.Occurrences[0].SourceRefs) != 1 || itemOccurrences.Occurrences[0].SourceRefs[0].StartUnitID != 5 {
|
||||||
|
t.Fatalf("item occurrences = %#v, want independently evidenced Moonblade registry grounding", itemOccurrences)
|
||||||
|
}
|
||||||
|
|
||||||
|
evidence := readProductionJSON[evidencecontext.Document](t, filepath.Join(runRoot, "evidence-context.json"))
|
||||||
|
for _, laneID := range []string{"enemy-events", "npc-registry", "npc-occurrences", "item-registry", "item-occurrences", "location-registry", "location-occurrences"} {
|
||||||
|
if !containsString(evidence.SelectedLanes, laneID) || !evidenceHasLane(evidence, laneID) {
|
||||||
|
t.Fatalf("evidence context = %#v, want direct %s evidence", evidence, laneID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requests := client.requestsFor(enemyevents.PromptID)
|
||||||
|
if len(requests) != 1 {
|
||||||
|
t.Fatalf("enemy event requests = %#v, want only the combat scene request", requests)
|
||||||
|
}
|
||||||
|
request := requests[0]
|
||||||
|
if request.SessionID != "enemy-event-session" {
|
||||||
|
t.Fatalf("enemy event session = %q, want shared session", request.SessionID)
|
||||||
|
}
|
||||||
|
for slot, required := range map[string]string{
|
||||||
|
"npc_registry": "Kesh",
|
||||||
|
"combat_turns": "Kesh",
|
||||||
|
"npc_occurrences": "Kesh",
|
||||||
|
} {
|
||||||
|
input, ok := request.Inputs[slot]
|
||||||
|
if !ok || !strings.Contains(string(input.Content), required) || strings.Contains(string(input.Content), "source_refs") || strings.Contains(string(input.Content), "start_unit_id") {
|
||||||
|
t.Fatalf("enemy event %s prompt input = %q, want compact source-free grounding", slot, input.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationRequests := client.requestsFor(locationoccurrences.PromptID)
|
||||||
|
if len(locationRequests) != 2 {
|
||||||
|
t.Fatalf("location occurrence requests = %#v, want one request per scene", locationRequests)
|
||||||
|
}
|
||||||
|
for _, request := range locationRequests {
|
||||||
|
registryInput := request.Inputs["location_registry"]
|
||||||
|
if !strings.Contains(string(registryInput.Content), "Moon Gate") || !strings.Contains(string(registryInput.Content), "registry_refs") || strings.Contains(string(registryInput.Content), `"id"`) || strings.Contains(string(registryInput.Content), "source_refs") {
|
||||||
|
t.Fatalf("location occurrence registry input = %q, want contextual selector grounding", registryInput.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, test := range []struct {
|
||||||
|
promptID string
|
||||||
|
slot string
|
||||||
|
name string
|
||||||
|
requiresIDs bool
|
||||||
|
}{
|
||||||
|
{promptID: npcoccurrences.PromptID, slot: "npc_registry", name: "Kesh"},
|
||||||
|
{promptID: itemoccurrences.PromptID, slot: "item_registry", name: "Moonblade"},
|
||||||
|
} {
|
||||||
|
requests := client.requestsFor(test.promptID)
|
||||||
|
if len(requests) != 2 {
|
||||||
|
t.Fatalf("%s requests = %#v, want one request per scene", test.promptID, requests)
|
||||||
|
}
|
||||||
|
for _, request := range requests {
|
||||||
|
registryInput := request.Inputs[test.slot]
|
||||||
|
hasID := strings.Contains(string(registryInput.Content), `"id"`)
|
||||||
|
if !strings.Contains(string(registryInput.Content), test.name) || hasID != test.requiresIDs || strings.Contains(string(registryInput.Content), "source_refs") {
|
||||||
|
t.Fatalf("%s registry input = %q, want source-free configured grounding", test.promptID, registryInput.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func completeExampleConfigWithTemporaryCache(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
content, err := os.ReadFile(repositoryPath("examples", "dnd-complete.config.yml"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cacheRoot := t.TempDir()
|
||||||
|
updated := strings.Replace(string(content), "directory: ./notarius-cache/chunk-plans", fmt.Sprintf("directory: %q", filepath.Join(cacheRoot, "chunk-plans")), 1)
|
||||||
|
updated = strings.Replace(updated, "directory: ./notarius-cache/checkpoints", fmt.Sprintf("directory: %q", filepath.Join(cacheRoot, "checkpoints")), 1)
|
||||||
|
for relative, absolute := range map[string]string{
|
||||||
|
"./dnd-party.txt": repositoryPath("examples", "dnd-party.txt"),
|
||||||
|
"./dnd-glossary.txt": repositoryPath("examples", "dnd-glossary.txt"),
|
||||||
|
"./dnd-spell-catalog.json": repositoryPath("examples", "dnd-spell-catalog.json"),
|
||||||
|
} {
|
||||||
|
updated = strings.ReplaceAll(updated, relative, fmt.Sprintf("%q", absolute))
|
||||||
|
}
|
||||||
|
path := filepath.Join(t.TempDir(), "dnd-complete.config.yml")
|
||||||
|
if err := os.WriteFile(path, []byte(updated), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
type enemyEventLLMClient struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
requests []contracts.StructuredCompletionRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *enemyEventLLMClient) CompleteStructured(ctx context.Context, request contracts.StructuredCompletionRequest, output any) (contracts.StructuredCompletionResponse, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, err
|
||||||
|
}
|
||||||
|
combatScene := strings.Contains(string(request.Inputs["transcript"].Content), "Roll initiative")
|
||||||
|
var content []byte
|
||||||
|
switch request.PromptID {
|
||||||
|
case scenes.PromptID:
|
||||||
|
content = []byte(`{"scenes":[{"start_unit_id":1,"end_unit_id":6},{"start_unit_id":7,"end_unit_id":11}]}`)
|
||||||
|
case npcregistry.PromptID:
|
||||||
|
if combatScene {
|
||||||
|
content = []byte(`{"npcs":[{"name":"Kesh","source_refs":[{"start_unit_id":7,"end_unit_id":7}]}]}`)
|
||||||
|
} else {
|
||||||
|
content = []byte(`{"npcs":[]}`)
|
||||||
|
}
|
||||||
|
case npcnormalize.PromptID:
|
||||||
|
content = []byte(`{"duplicate_groups":[]}`)
|
||||||
|
case scenedescriptions.PromptID:
|
||||||
|
kind, title := "narrative", "Arrival"
|
||||||
|
if combatScene {
|
||||||
|
kind, title = "combat", "Raiders attack"
|
||||||
|
}
|
||||||
|
content = []byte(fmt.Sprintf(`{"kind":%q,"title":%q,"summary":"session scene"}`, kind, title))
|
||||||
|
case locations.PromptID:
|
||||||
|
unitID := 1
|
||||||
|
if combatScene {
|
||||||
|
unitID = 7
|
||||||
|
}
|
||||||
|
content = []byte(fmt.Sprintf(`{"locations":[{"name":"Moon Gate","source_refs":[{"start_unit_id":%d,"end_unit_id":%d}]}]}`, unitID, unitID))
|
||||||
|
case locationnormalize.PromptID:
|
||||||
|
content = []byte(`{"duplicate_groups":[]}`)
|
||||||
|
case spells.PromptID:
|
||||||
|
content = []byte(`{"spell_casts":[]}`)
|
||||||
|
case itemregistry.PromptID:
|
||||||
|
if combatScene {
|
||||||
|
content = []byte(`{"items":[]}`)
|
||||||
|
} else {
|
||||||
|
content = []byte(`{"items":[{"name":"Moonblade","source_refs":[{"start_unit_id":5,"end_unit_id":5}]}]}`)
|
||||||
|
}
|
||||||
|
case itemregistrynormalize.PromptID:
|
||||||
|
content = []byte(`{"duplicate_groups":[]}`)
|
||||||
|
case itemoccurrences.PromptID:
|
||||||
|
if combatScene {
|
||||||
|
content = []byte(`{"occurrences":[]}`)
|
||||||
|
} else {
|
||||||
|
var registry struct {
|
||||||
|
Items []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"items"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(request.Inputs["item_registry"].Content, ®istry); err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("decode generated item registry: %w", err)
|
||||||
|
}
|
||||||
|
if len(registry.Items) != 1 || registry.Items[0].Name != "Moonblade" {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("generated item registry has %d items, want 1", len(registry.Items))
|
||||||
|
}
|
||||||
|
content = []byte(`{"occurrences":[{"name":"Moonblade","kind":"discovered","quantity":null,"from":null,"to":null,"source_refs":[{"start_segment":5,"end_segment":5}]}]}`)
|
||||||
|
}
|
||||||
|
case combat.PromptID:
|
||||||
|
content = []byte(`{"combat_turns":[{"actor":"Kesh","turn_kind":"turn","source_refs":[{"start_unit_id":8,"end_unit_id":8}]}]}`)
|
||||||
|
case npcoccurrences.PromptID:
|
||||||
|
if combatScene {
|
||||||
|
var registry struct {
|
||||||
|
NPCs []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"npcs"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(request.Inputs["npc_registry"].Content, ®istry); err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("decode generated NPC registry: %w", err)
|
||||||
|
}
|
||||||
|
if len(registry.NPCs) == 0 || registry.NPCs[0].Name != "Kesh" {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("generated NPC registry has no NPCs")
|
||||||
|
}
|
||||||
|
content = []byte(`{"occurrences":[{"name":"Kesh","kind":"combat_opponent","source_refs":[{"start_unit_id":7,"end_unit_id":7}]}]}`)
|
||||||
|
} else {
|
||||||
|
content = []byte(`{"occurrences":[]}`)
|
||||||
|
}
|
||||||
|
case locationoccurrences.PromptID:
|
||||||
|
var registry struct {
|
||||||
|
Locations []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
RegistryRefs []struct {
|
||||||
|
StartUnitID int `json:"start_unit_id"`
|
||||||
|
EndUnitID int `json:"end_unit_id"`
|
||||||
|
} `json:"registry_refs"`
|
||||||
|
} `json:"locations"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(request.Inputs["location_registry"].Content, ®istry); err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("decode generated location registry: %w", err)
|
||||||
|
}
|
||||||
|
if len(registry.Locations) == 0 {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("generated location registry has no locations")
|
||||||
|
}
|
||||||
|
unitID := 1
|
||||||
|
location := registry.Locations[0]
|
||||||
|
if combatScene {
|
||||||
|
unitID = 7
|
||||||
|
if len(registry.Locations) > 1 {
|
||||||
|
location = registry.Locations[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registryRefs, err := json.Marshal(location.RegistryRefs)
|
||||||
|
if err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("encode location selector: %w", err)
|
||||||
|
}
|
||||||
|
content = []byte(fmt.Sprintf(`{"occurrences":[{"name":%q,"registry_refs":%s,"kind":"visited","source_refs":[{"start_unit_id":%d,"end_unit_id":%d}]}]}`, location.Name, registryRefs, unitID, unitID))
|
||||||
|
case enemyevents.PromptID:
|
||||||
|
content = []byte(`{"events":[{"name":"Kesh","kind":"fled","source_refs":[{"start_unit_id":10,"end_unit_id":10}]}]}`)
|
||||||
|
default:
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("unexpected prompt %q", request.PromptID)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(content, output); err != nil {
|
||||||
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate fake structured target: %w", err)
|
||||||
|
}
|
||||||
|
client.mu.Lock()
|
||||||
|
client.requests = append(client.requests, request)
|
||||||
|
client.mu.Unlock()
|
||||||
|
return contracts.StructuredCompletionResponse{Content: content, Provider: "test", Model: "deterministic", ProfileID: request.ProfileID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client *enemyEventLLMClient) requestsFor(promptID string) []contracts.StructuredCompletionRequest {
|
||||||
|
client.mu.Lock()
|
||||||
|
defer client.mu.Unlock()
|
||||||
|
var requests []contracts.StructuredCompletionRequest
|
||||||
|
for _, request := range client.requests {
|
||||||
|
if request.PromptID == promptID {
|
||||||
|
requests = append(requests, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requests
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsString(values []string, want string) bool {
|
||||||
|
for _, value := range values {
|
||||||
|
if value == want {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func evidenceHasLane(value evidencecontext.Document, laneID string) bool {
|
||||||
|
for _, context := range value.Contexts {
|
||||||
|
for _, reference := range context.EvidenceRefs {
|
||||||
|
if reference.LaneID == laneID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func generatedReferenceBinding(bindings []pipeline.ReferenceBinding, slotName string) (pipeline.ReferenceBinding, bool) {
|
||||||
|
for _, binding := range bindings {
|
||||||
|
if binding.SlotName == slotName && binding.Artifact != nil {
|
||||||
|
return binding, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pipeline.ReferenceBinding{}, false
|
||||||
|
}
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
|
||||||
interactioncodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcinteractions"
|
|
||||||
interactionextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcinteractions"
|
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
|
|
||||||
interactionnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcinteractions"
|
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestProductionNPCInteractionPipelineResolvesAndPrepares(t *testing.T) {
|
|
||||||
components := productionTestComponents(t)
|
|
||||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.GeneratedReference("npcs", "npcs")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(resolved.Steps) != 2 || len(resolved.Steps[1].ArtifactLanes) != 1 {
|
|
||||||
t.Fatalf("resolved pipeline = %#v", resolved)
|
|
||||||
}
|
|
||||||
lane := resolved.Steps[1].ArtifactLanes[0]
|
|
||||||
if lane.ArtifactKind != dnd.NPCInteractionListKind || lane.Extract.Module != interactionextract.Key || lane.Normalize.Module != interactionnormalize.Key {
|
|
||||||
t.Fatalf("interaction lane = %#v", lane)
|
|
||||||
}
|
|
||||||
for _, bindings := range [][]pipeline.ReferenceBinding{lane.ExtractReferences.Bindings, lane.NormalizeReferences.Bindings} {
|
|
||||||
if len(bindings) != 1 || bindings[0].SlotName != "npcs" || bindings[0].Artifact == nil || bindings[0].Artifact.Step != "npcs" || bindings[0].Artifact.Lane != "npcs" {
|
|
||||||
t.Fatalf("generated bindings = %#v", bindings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := pipeline.Prepare(resolved, components.registries, pipeline.ModuleDependencies{LLM: &productionFakeLLMClient{}}); err != nil {
|
|
||||||
t.Fatalf("Prepare() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
catalog := catalogFromRegistries(components.registries)
|
|
||||||
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCInteractionListKind)
|
|
||||||
if !ok || codecSpec.Schema.ID != interactioncodec.SchemaID || codecSpec.Schema.Version != interactioncodec.SchemaVersion {
|
|
||||||
t.Fatalf("NPC interaction codec spec = %#v", codecSpec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProductionNPCInteractionReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
|
||||||
components := productionTestComponents(t)
|
|
||||||
catalog := catalogFromRegistries(components.registries)
|
|
||||||
laterProfile := npcInteractionProfile(pipeline.GeneratedReference("npcs", "npcs"))
|
|
||||||
laterProfile.Steps[0].ID = "seed"
|
|
||||||
laterProfile.Steps[0].Artifacts["seed"] = laterProfile.Steps[0].Artifacts["npcs"]
|
|
||||||
delete(laterProfile.Steps[0].Artifacts, "npcs")
|
|
||||||
laterProfile.Steps = append(laterProfile.Steps, pipeline.PipelineStepProfile{ID: "future", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
|
||||||
"npcs": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
|
||||||
}})
|
|
||||||
laterProfile.Steps[1].References["npcs"] = pipeline.GeneratedReference("future", "npcs")
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
profile pipeline.PipelineProfile
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{name: "missing", profile: npcInteractionProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
|
||||||
{name: "same step", profile: npcInteractionProfile(pipeline.GeneratedReference("interactions", "interactions")), want: "earlier step"},
|
|
||||||
{name: "later step", profile: laterProfile, want: "earlier step"},
|
|
||||||
{name: "wrong artifact kind", profile: npcInteractionProfile(pipeline.GeneratedReference("npcs", "npcs")), want: "does not accept artifact kind"},
|
|
||||||
}
|
|
||||||
tests[3].profile.Steps[0].Artifacts["npcs"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("dnd/spells")}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
_, err := pipeline.ResolvePipeline(test.profile, pipeline.ResolveOptions{}, catalog)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), test.want) {
|
|
||||||
t.Fatalf("ResolvePipeline() error = %v, want %q", err, test.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProductionNPCInteractionReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
|
||||||
components := productionTestComponents(t)
|
|
||||||
catalog := catalogFromRegistries(components.registries)
|
|
||||||
root := t.TempDir()
|
|
||||||
for _, test := range []struct {
|
|
||||||
name string
|
|
||||||
file string
|
|
||||||
content string
|
|
||||||
prepare bool
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{name: "media type", file: "registry.txt", content: "not JSON", want: "media type"},
|
|
||||||
{name: "artifact schema", file: "registry.json", content: `{"npcs":[{"name":"missing required fields"}]}`, prepare: true, want: "NPC registry"},
|
|
||||||
} {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
path := filepath.Join(root, test.file)
|
|
||||||
if err := os.WriteFile(path, []byte(test.content), 0o600); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
resolved, err := pipeline.ResolvePipeline(npcInteractionProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
|
||||||
}
|
|
||||||
materialized, _, err := pipeline.MaterializeReferences(resolved, catalog, pipeline.ReferenceMaterializationOptions{})
|
|
||||||
if !test.prepare {
|
|
||||||
if err == nil || !strings.Contains(err.Error(), test.want) {
|
|
||||||
t.Fatalf("MaterializeReferences() error = %v, want %q", err, test.want)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("MaterializeReferences() error = %v", err)
|
|
||||||
}
|
|
||||||
if _, err := pipeline.Prepare(materialized, components.registries, pipeline.ModuleDependencies{LLM: &productionFakeLLMClient{}}); err == nil || !strings.Contains(err.Error(), test.want) {
|
|
||||||
t.Fatalf("Prepare() error = %v, want %q", err, test.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func npcInteractionProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
|
||||||
profile := pipeline.PipelineProfile{
|
|
||||||
ID: "dnd-npc-interactions",
|
|
||||||
Input: pipeline.Binding("seriatim"),
|
|
||||||
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
|
||||||
Output: pipeline.Binding("json"),
|
|
||||||
Steps: []pipeline.PipelineStepProfile{
|
|
||||||
{ID: "npcs", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
|
||||||
"npcs": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
|
||||||
}},
|
|
||||||
{ID: "interactions", References: map[string]pipeline.ReferenceSource{"npcs": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
|
||||||
"interactions": {Extract: pipeline.Binding(interactionextract.Key), Normalize: pipeline.Binding(interactionnormalize.Key)},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return profile
|
|
||||||
}
|
|
||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcs"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcs"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProductionNPCConfigurationResolvesTypedLane(t *testing.T) {
|
func TestProductionNPCConfigurationResolvesTypedLane(t *testing.T) {
|
||||||
@@ -28,7 +28,7 @@ func TestProductionNPCConfigurationResolvesTypedLane(t *testing.T) {
|
|||||||
t.Fatalf("artifact lanes = %#v, want one NPC lane", effective.ResolvedPipeline.Steps[0].ArtifactLanes)
|
t.Fatalf("artifact lanes = %#v, want one NPC lane", effective.ResolvedPipeline.Steps[0].ArtifactLanes)
|
||||||
}
|
}
|
||||||
lane := effective.ResolvedPipeline.Steps[0].ArtifactLanes[0]
|
lane := effective.ResolvedPipeline.Steps[0].ArtifactLanes[0]
|
||||||
if lane.ID != "npcs" || lane.ArtifactKind != dnd.NPCListKind || lane.Extract.Module != npcextract.Key || lane.Extract.Retries != 2 || lane.Merge.Module != pipeline.DefaultMergeModule || lane.Normalize.Module != npcnormalize.Key {
|
if lane.ID != "npc_registry" || lane.ArtifactKind != dnd.NPCRegistryKind || lane.Extract.Module != npcextract.Key || lane.Extract.Retries != 2 || lane.Merge.Module != pipeline.DefaultMergeModule || lane.Normalize.Module != npcnormalize.Key {
|
||||||
t.Fatalf("resolved NPC lane = %#v, want typed production composition", lane)
|
t.Fatalf("resolved NPC lane = %#v, want typed production composition", lane)
|
||||||
}
|
}
|
||||||
if len(lane.ExtractReferences.Bindings) != 0 || len(lane.NormalizeReferences.Bindings) != 0 {
|
if len(lane.ExtractReferences.Bindings) != 0 || len(lane.NormalizeReferences.Bindings) != 0 {
|
||||||
@@ -36,36 +36,36 @@ func TestProductionNPCConfigurationResolvesTypedLane(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extractSpec, ok := catalog.Extractors.Spec(npcextract.Key)
|
extractSpec, ok := catalog.Extractors.Spec(npcextract.Key)
|
||||||
if !ok || !reflect.DeepEqual(extractSpec.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(extractSpec.Provides, []string{"dnd.npcs"}) {
|
if !ok || !reflect.DeepEqual(extractSpec.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(extractSpec.Provides, []string{"dnd.npc_registry"}) {
|
||||||
t.Fatalf("NPC extractor spec = %#v, want source and artifact capabilities", extractSpec)
|
t.Fatalf("NPC extractor spec = %#v, want source and artifact capabilities", extractSpec)
|
||||||
}
|
}
|
||||||
mergeSpec, ok := catalog.Mergers.SpecForArtifact(pipeline.DefaultMergeModule, dnd.NPCListKind)
|
mergeSpec, ok := catalog.Mergers.SpecForArtifact(pipeline.DefaultMergeModule, dnd.NPCRegistryKind)
|
||||||
if !ok || !reflect.DeepEqual(mergeSpec.Provides, []string{"merged"}) {
|
if !ok || !reflect.DeepEqual(mergeSpec.Provides, []string{"merged"}) {
|
||||||
t.Fatalf("NPC merger spec = %#v, want merged capability", mergeSpec)
|
t.Fatalf("NPC merger spec = %#v, want merged capability", mergeSpec)
|
||||||
}
|
}
|
||||||
normalizeSpec, ok := catalog.Normalizers.SpecForArtifact(npcnormalize.Key, dnd.NPCListKind)
|
normalizeSpec, ok := catalog.Normalizers.SpecForArtifact(npcnormalize.Key, dnd.NPCRegistryKind)
|
||||||
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
if !ok || !reflect.DeepEqual(normalizeSpec.Requires, []string{"merged"}) || !reflect.DeepEqual(normalizeSpec.Provides, []string{"normalized"}) {
|
||||||
t.Fatalf("NPC normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
t.Fatalf("NPC normalizer spec = %#v, want merged/normalized capabilities", normalizeSpec)
|
||||||
}
|
}
|
||||||
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCListKind)
|
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCRegistryKind)
|
||||||
if !ok || codecSpec.Kind != dnd.NPCListKind || codecSpec.Schema.ID != npccodec.SchemaID || codecSpec.Schema.Version != npccodec.SchemaVersion {
|
if !ok || codecSpec.Kind != dnd.NPCRegistryKind || codecSpec.Schema.ID != npccodec.SchemaID || codecSpec.Schema.Version != npccodec.SchemaVersion {
|
||||||
t.Fatalf("NPC codec spec = %#v, want typed v1 durable schema", codecSpec)
|
t.Fatalf("NPC codec spec = %#v, want typed v1 durable schema", codecSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantExtractChain := []pipeline.ModuleBinding{
|
wantExtractChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
pipeline.Binding("extract/dnd/npcs/shape"),
|
pipeline.Binding("extract/dnd/npc-registry/shape"),
|
||||||
pipeline.Binding("extract/dnd/npcs/source_refs"),
|
pipeline.Binding("extract/dnd/npc-registry/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npcs/source_relatedness"),
|
pipeline.Binding("extract/dnd/npc-registry/source_relatedness"),
|
||||||
}
|
}
|
||||||
wantNormalizeChain := []pipeline.ModuleBinding{
|
wantNormalizeChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
pipeline.Binding("extract/dnd/npcs/shape"),
|
pipeline.Binding("extract/dnd/npc-registry/shape"),
|
||||||
pipeline.Binding("normalize/dnd/npcs/identity"),
|
pipeline.Binding("normalize/dnd/npc-registry/identity"),
|
||||||
pipeline.Binding("extract/dnd/npcs/source_refs"),
|
pipeline.Binding("extract/dnd/npc-registry/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npcs/source_relatedness"),
|
pipeline.Binding("extract/dnd/npc-registry/source_relatedness"),
|
||||||
}
|
}
|
||||||
if got := validatorChain(effective.ResolvedPipeline, pipeline.StageExtract, npcextract.Key); !reflect.DeepEqual(got, wantExtractChain) {
|
if got := validatorChain(effective.ResolvedPipeline, pipeline.StageExtract, npcextract.Key); !reflect.DeepEqual(got, wantExtractChain) {
|
||||||
t.Fatalf("NPC extract chain = %#v, want %#v", got, wantExtractChain)
|
t.Fatalf("NPC extract chain = %#v, want %#v", got, wantExtractChain)
|
||||||
@@ -90,16 +90,16 @@ func TestProductionNPCConfigurationValidatesOptionsReferencesAndPlacement(t *tes
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
||||||
lane := profile.Artifacts["npcs"]
|
lane := profile.Artifacts["npc_registry"]
|
||||||
lane.Extract.Options = map[string]any{"unexpected": true}
|
lane.Extract.Options = map[string]any{"unexpected": true}
|
||||||
profile.Artifacts["npcs"] = lane
|
profile.Artifacts["npc_registry"] = lane
|
||||||
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
|
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
|
||||||
t.Fatalf("unknown extractor option error = %v, want strict option rejection", err)
|
t.Fatalf("unknown extractor option error = %v, want strict option rejection", err)
|
||||||
}
|
}
|
||||||
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
||||||
lane := profile.Artifacts["npcs"]
|
lane := profile.Artifacts["npc_registry"]
|
||||||
lane.Normalize.Options = map[string]any{"unexpected": true}
|
lane.Normalize.Options = map[string]any{"unexpected": true}
|
||||||
profile.Artifacts["npcs"] = lane
|
profile.Artifacts["npc_registry"] = lane
|
||||||
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
|
}); err == nil || !strings.Contains(err.Error(), "unknown option") {
|
||||||
t.Fatalf("unknown normalizer option error = %v, want strict option rejection", err)
|
t.Fatalf("unknown normalizer option error = %v, want strict option rejection", err)
|
||||||
}
|
}
|
||||||
@@ -113,9 +113,9 @@ func TestProductionNPCConfigurationValidatesOptionsReferencesAndPlacement(t *tes
|
|||||||
t.Fatalf("optional NPC references error = %v, want resolution success", err)
|
t.Fatalf("optional NPC references error = %v, want resolution success", err)
|
||||||
}
|
}
|
||||||
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
if err := resolve(func(profile *pipeline.PipelineProfile) {
|
||||||
lane := profile.Artifacts["npcs"]
|
lane := profile.Artifacts["npc_registry"]
|
||||||
lane.Validators = []pipeline.ModuleBinding{pipeline.Binding("normalize/dnd/npcs/identity")}
|
lane.Validators = []pipeline.ModuleBinding{pipeline.Binding("normalize/dnd/npc-registry/identity")}
|
||||||
profile.Artifacts["npcs"] = lane
|
profile.Artifacts["npc_registry"] = lane
|
||||||
}); err == nil || !strings.Contains(err.Error(), "artifact lane level") {
|
}); err == nil || !strings.Contains(err.Error(), "artifact lane level") {
|
||||||
t.Fatalf("lane-level validator error = %v, want invalid placement rejection", err)
|
t.Fatalf("lane-level validator error = %v, want invalid placement rejection", err)
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ func productionNPCContractConfig() config.Config {
|
|||||||
Input: pipeline.Binding("seriatim"),
|
Input: pipeline.Binding("seriatim"),
|
||||||
Chunk: pipeline.Binding(pipeline.DefaultChunkModule),
|
Chunk: pipeline.Binding(pipeline.DefaultChunkModule),
|
||||||
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
"npcs": {
|
"npc_registry": {
|
||||||
Extract: pipeline.ModuleBinding{Module: npcextract.Key, Retries: 2},
|
Extract: pipeline.ModuleBinding{Module: npcextract.Key, Retries: 2},
|
||||||
Normalize: pipeline.Binding(npcnormalize.Key),
|
Normalize: pipeline.Binding(npcnormalize.Key),
|
||||||
},
|
},
|
||||||
|
|||||||
217
internal/cli/dnd_occurrences_contract_test.go
Normal file
217
internal/cli/dnd_occurrences_contract_test.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
||||||
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||||
|
itemregistrycodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/itemregistry"
|
||||||
|
locationregistrycodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationregistry"
|
||||||
|
occurrencecodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcoccurrences"
|
||||||
|
npcregistrycodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcregistry"
|
||||||
|
itemoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/itemoccurrences"
|
||||||
|
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
||||||
|
occurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcoccurrences"
|
||||||
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
||||||
|
itemidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/items/identity"
|
||||||
|
locationidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/identity"
|
||||||
|
itemoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/itemoccurrences"
|
||||||
|
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
||||||
|
occurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcoccurrences"
|
||||||
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
||||||
|
npcidentity "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProductionNPCOccurrencePipelineResolvesAndPrepares(t *testing.T) {
|
||||||
|
components := productionTestComponents(t)
|
||||||
|
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(resolved.Steps) != 2 || len(resolved.Steps[1].ArtifactLanes) != 1 {
|
||||||
|
t.Fatalf("resolved pipeline = %#v", resolved)
|
||||||
|
}
|
||||||
|
lane := resolved.Steps[1].ArtifactLanes[0]
|
||||||
|
if lane.ArtifactKind != dnd.NPCOccurrenceListKind || lane.Extract.Module != occurrenceextract.Key || lane.Normalize.Module != occurrencenormalize.Key {
|
||||||
|
t.Fatalf("occurrence lane = %#v", lane)
|
||||||
|
}
|
||||||
|
for _, bindings := range [][]pipeline.ReferenceBinding{lane.ExtractReferences.Bindings, lane.NormalizeReferences.Bindings} {
|
||||||
|
if len(bindings) != 1 || bindings[0].SlotName != "npc_registry" || bindings[0].Artifact == nil || bindings[0].Artifact.Step != "npc-registry" || bindings[0].Artifact.Lane != "npc_registry" {
|
||||||
|
t.Fatalf("generated bindings = %#v", bindings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := pipeline.Prepare(resolved, components.registries, pipeline.ModuleDependencies{LLM: &productionFakeLLMClient{}}); err != nil {
|
||||||
|
t.Fatalf("Prepare() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog := catalogFromRegistries(components.registries)
|
||||||
|
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCOccurrenceListKind)
|
||||||
|
if !ok || codecSpec.Schema.ID != occurrencecodec.SchemaID || codecSpec.Schema.Version != occurrencecodec.SchemaVersion {
|
||||||
|
t.Fatalf("NPC occurrence codec spec = %#v", codecSpec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProductionNPCOccurrenceReferencesRequireEarlierCompatibleProducer(t *testing.T) {
|
||||||
|
components := productionTestComponents(t)
|
||||||
|
catalog := catalogFromRegistries(components.registries)
|
||||||
|
laterProfile := npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry"))
|
||||||
|
laterProfile.Steps[0].ID = "seed"
|
||||||
|
laterProfile.Steps[0].Artifacts["seed"] = laterProfile.Steps[0].Artifacts["npc_registry"]
|
||||||
|
delete(laterProfile.Steps[0].Artifacts, "npc_registry")
|
||||||
|
laterProfile.Steps = append(laterProfile.Steps, pipeline.PipelineStepProfile{ID: "future", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
|
"npc_registry": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
||||||
|
}})
|
||||||
|
laterProfile.Steps[1].References["npc_registry"] = pipeline.GeneratedReference("future", "npc_registry")
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
profile pipeline.PipelineProfile
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "missing", profile: npcOccurrenceProfile(pipeline.ReferenceSource{}), want: "source must not be empty"},
|
||||||
|
{name: "same step", profile: npcOccurrenceProfile(pipeline.GeneratedReference("occurrences", "occurrences")), want: "earlier step"},
|
||||||
|
{name: "later step", profile: laterProfile, want: "earlier step"},
|
||||||
|
{name: "wrong artifact kind", profile: npcOccurrenceProfile(pipeline.GeneratedReference("npc-registry", "npc_registry")), want: "does not accept artifact kind"},
|
||||||
|
}
|
||||||
|
tests[3].profile.Steps[0].Artifacts["npc_registry"] = pipeline.ArtifactLaneProfile{Extract: pipeline.Binding("dnd/spells")}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
_, err := pipeline.ResolvePipeline(test.profile, pipeline.ResolveOptions{}, catalog)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
||||||
|
t.Fatalf("ResolvePipeline() error = %v, want %q", err, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProductionNPCOccurrenceReferencesRejectIncompatibleExternalRegistries(t *testing.T) {
|
||||||
|
components := productionTestComponents(t)
|
||||||
|
catalog := catalogFromRegistries(components.registries)
|
||||||
|
root := t.TempDir()
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
file string
|
||||||
|
content string
|
||||||
|
prepare bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "media type", file: "registry.txt", content: "not JSON", want: "media type"},
|
||||||
|
{name: "artifact schema", file: "registry.json", content: `{"npcs":[{"name":"missing required fields"}]}`, prepare: true, want: "NPC registry"},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
path := filepath.Join(root, test.file)
|
||||||
|
if err := os.WriteFile(path, []byte(test.content), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
resolved, err := pipeline.ResolvePipeline(npcOccurrenceProfile(pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||||
|
}
|
||||||
|
materialized, _, err := pipeline.MaterializeReferences(resolved, catalog, pipeline.ReferenceMaterializationOptions{})
|
||||||
|
if !test.prepare {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), test.want) {
|
||||||
|
t.Fatalf("MaterializeReferences() error = %v, want %q", err, test.want)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MaterializeReferences() error = %v", err)
|
||||||
|
}
|
||||||
|
if _, err := pipeline.Prepare(materialized, components.registries, pipeline.ModuleDependencies{LLM: &productionFakeLLMClient{}}); err == nil || !strings.Contains(err.Error(), test.want) {
|
||||||
|
t.Fatalf("Prepare() error = %v, want %q", err, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProductionOccurrencePipelinesAcceptCompatibleExternalRegistries(t *testing.T) {
|
||||||
|
components := productionTestComponents(t)
|
||||||
|
catalog := catalogFromRegistries(components.registries)
|
||||||
|
reference := source.SourceRef{SourceID: "external-registry", StartUnitID: 1, EndUnitID: 1}
|
||||||
|
npcContent, err := npcregistrycodec.New().Encode(dnd.NPCRegistry{NPCs: []dnd.NPC{{
|
||||||
|
ID: npcidentity.DeriveID("Mira Thorn"), Name: "Mira Thorn", SourceRefs: []source.SourceRef{reference},
|
||||||
|
}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
locationContent, err := locationregistrycodec.New().Encode(dnd.LocationRegistry{Locations: []dnd.Location{{
|
||||||
|
ID: locationidentity.DeriveID("Moon Gate", []source.SourceRef{reference}), Name: "Moon Gate", SourceRefs: []source.SourceRef{reference},
|
||||||
|
}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
itemContent, err := itemregistrycodec.New().Encode(dnd.ItemRegistry{Items: []dnd.Item{{
|
||||||
|
ID: itemidentity.DeriveID("Moonblade"), Name: "Moonblade", SourceRefs: []source.SourceRef{reference},
|
||||||
|
}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
root := t.TempDir()
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
slot string
|
||||||
|
extractor string
|
||||||
|
normalizer string
|
||||||
|
content []byte
|
||||||
|
}{
|
||||||
|
{name: "NPC", slot: "npc_registry", extractor: occurrenceextract.Key, normalizer: occurrencenormalize.Key, content: npcContent},
|
||||||
|
{name: "location", slot: "location_registry", extractor: locationoccurrenceextract.Key, normalizer: locationoccurrencenormalize.Key, content: locationContent},
|
||||||
|
{name: "item", slot: "item_registry", extractor: itemoccurrenceextract.Key, normalizer: itemoccurrencenormalize.Key, content: itemContent},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
path := filepath.Join(root, strings.ToLower(test.name)+"-registry.json")
|
||||||
|
if err := os.WriteFile(path, test.content, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
resolved, err := pipeline.ResolvePipeline(externalOccurrenceProfile(test.slot, test.extractor, test.normalizer, pipeline.ExternalReference(path)), pipeline.ResolveOptions{}, catalog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolvePipeline() error = %v", err)
|
||||||
|
}
|
||||||
|
materialized, warnings, err := pipeline.MaterializeReferences(resolved, catalog, pipeline.ReferenceMaterializationOptions{})
|
||||||
|
if err != nil || len(warnings) != 0 {
|
||||||
|
t.Fatalf("MaterializeReferences() error = %v warnings = %#v", err, warnings)
|
||||||
|
}
|
||||||
|
if _, err := pipeline.Prepare(materialized, components.registries, pipeline.ModuleDependencies{LLM: &productionFakeLLMClient{}}); err != nil {
|
||||||
|
t.Fatalf("Prepare() error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalOccurrenceProfile(slot, extractor, normalizer string, reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
||||||
|
return pipeline.PipelineProfile{
|
||||||
|
ID: "external-registry-occurrences",
|
||||||
|
Input: pipeline.Binding("seriatim"),
|
||||||
|
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
||||||
|
Output: pipeline.Binding("json"),
|
||||||
|
Steps: []pipeline.PipelineStepProfile{{
|
||||||
|
ID: "occurrences",
|
||||||
|
References: map[string]pipeline.ReferenceSource{slot: reference},
|
||||||
|
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
|
"occurrences": {Extract: pipeline.Binding(extractor), Normalize: pipeline.Binding(normalizer)},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func npcOccurrenceProfile(reference pipeline.ReferenceSource) pipeline.PipelineProfile {
|
||||||
|
profile := pipeline.PipelineProfile{
|
||||||
|
ID: "dnd-npc-occurrences",
|
||||||
|
Input: pipeline.Binding("seriatim"),
|
||||||
|
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
||||||
|
Output: pipeline.Binding("json"),
|
||||||
|
Steps: []pipeline.PipelineStepProfile{
|
||||||
|
{ID: "npc-registry", Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
|
"npc_registry": {Extract: pipeline.Binding(npcextract.Key), Normalize: pipeline.Binding(npcnormalize.Key)},
|
||||||
|
}},
|
||||||
|
{ID: "occurrences", References: map[string]pipeline.ReferenceSource{"npc_registry": reference}, Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
|
"occurrences": {Extract: pipeline.Binding(occurrenceextract.Key), Normalize: pipeline.Binding(occurrencenormalize.Key)},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return profile
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user