Compare commits
5 Commits
v0.3.0
...
987c9691c6
| Author | SHA1 | Date | |
|---|---|---|---|
| 987c9691c6 | |||
| 9e4b989e53 | |||
| 6bdebcb5e2 | |||
| ad36d534a4 | |||
| 73d6e184ab |
3
.codebase-memory/.gitattributes
vendored
Normal file
3
.codebase-memory/.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Auto-generated by codebase-memory-mcp
|
||||||
|
# Prevent merge conflicts on compressed artifact
|
||||||
|
graph.db.zst merge=ours binary
|
||||||
11
.codebase-memory/artifact.json
Normal file
11
.codebase-memory/artifact.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 2,
|
||||||
|
"commit": "9e4b989e53d65efa614b5eedd8530caed12f60b5",
|
||||||
|
"indexed_at": "2026-07-27T18:27:55Z",
|
||||||
|
"project": "home-eric-Workspace-notarius",
|
||||||
|
"nodes": 6356,
|
||||||
|
"edges": 35132,
|
||||||
|
"original_size": 26017792,
|
||||||
|
"compressed_size": 4386397,
|
||||||
|
"compression_level": 3
|
||||||
|
}
|
||||||
BIN
.codebase-memory/graph.db.zst
Normal file
BIN
.codebase-memory/graph.db.zst
Normal file
Binary file not shown.
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,6 @@
|
|||||||
notarius
|
notarius
|
||||||
notarius-output
|
notarius-output
|
||||||
workspace/
|
workspace/
|
||||||
.codebase-memory/
|
|
||||||
|
|
||||||
# ---> Go
|
# ---> Go
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
@@ -74,3 +73,4 @@ Icon
|
|||||||
Network Trash Folder
|
Network Trash Folder
|
||||||
Temporary Items
|
Temporary Items
|
||||||
.apdisk
|
.apdisk
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
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 NPC, location, and item registries; their
|
transcript JSON and can produce scene descriptions, item and currency events,
|
||||||
source-grounded occurrences; scene descriptions, combat turns, enemy events,
|
NPC identities, combat turns, NPC interactions, and spell casts.
|
||||||
and spell casts.
|
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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" }}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
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,45 +0,0 @@
|
|||||||
id: dnd.item_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: item_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: ./item-registry.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./instructions.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
output:
|
|
||||||
format: json
|
|
||||||
validation_mode: json_schema
|
|
||||||
schema_path: dnd_item_occurrences_llm.v1.json
|
|
||||||
repair_attempts: 0
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
||||||
"$id": "notarius.dnd.item_occurrences.llm",
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["occurrences"],
|
|
||||||
"properties": {
|
|
||||||
"occurrences": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["name", "kind", "quantity", "from", "to", "source_refs"],
|
|
||||||
"properties": {
|
|
||||||
"name": {"type": "string"},
|
|
||||||
"kind": {"type": "string"},
|
|
||||||
"quantity": {"type": ["integer", "null"]},
|
|
||||||
"from": {"type": ["string", "null"]},
|
|
||||||
"to": {"type": ["string", "null"]},
|
|
||||||
"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"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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,40 +0,0 @@
|
|||||||
id: dnd.item_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_item_registry_llm.v1.json
|
|
||||||
repair_attempts: 0
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"$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"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
Determine whether candidates identify the same item type or unique designation
|
|
||||||
using their contextual labels and cited transcript windows. Do not treat nearby
|
|
||||||
evidence, similar objects, or a shared owner as sufficient.
|
|
||||||
|
|
||||||
Keep currency denominations and materially different item types separate. Keep
|
|
||||||
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.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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: ./sharedassets/protocol.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./instructions.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
- role: user
|
|
||||||
content_file: ./sharedassets/candidates.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./sharedassets/transcript-windows.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
output:
|
|
||||||
format: json
|
|
||||||
validation_mode: json_schema
|
|
||||||
schema_path: semantic_reconciliation_llm.v1.json
|
|
||||||
repair_attempts: 0
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
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" }}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"$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"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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,32 +0,0 @@
|
|||||||
{
|
|
||||||
"$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"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
Determine whether candidates identify the same physical place using their
|
|
||||||
contextual labels and cited transcript windows. Do not treat matching names,
|
|
||||||
nearby evidence, nested places, or generic labels as sufficient.
|
|
||||||
|
|
||||||
Keep parent and child places separate, as well as similarly named places and
|
|
||||||
uncertain aliases.
|
|
||||||
|
|
||||||
When selecting a canonical display name, prefer the clearest established name.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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: ./sharedassets/protocol.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./instructions.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
- role: user
|
|
||||||
content_file: ./sharedassets/candidates.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./sharedassets/transcript-windows.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
output:
|
|
||||||
format: json
|
|
||||||
validation_mode: json_schema
|
|
||||||
schema_path: semantic_reconciliation_llm.v1.json
|
|
||||||
repair_attempts: 0
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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,19 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
Determine whether candidates refer to the same individual using their
|
|
||||||
contextual labels and cited transcript windows. Preserve distinct individuals
|
|
||||||
even when their names are similar or their contextual descriptions are
|
|
||||||
identical.
|
|
||||||
|
|
||||||
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,5 +0,0 @@
|
|||||||
id: dnd-extraction
|
|
||||||
backend: openrouter
|
|
||||||
model: openai/gpt-5.6-luna
|
|
||||||
timeout_seconds: 240
|
|
||||||
service_tier: flex
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
Transcript units are the only evidence for extracted events and factual claims.
|
|
||||||
Every reported factual claim must be supported by cited transcript units. Use
|
|
||||||
integer `start_unit_id` and `end_unit_id` values from the transcript.
|
|
||||||
|
|
||||||
When supporting evidence is non-contiguous, use multiple narrow ranges rather
|
|
||||||
than a broad range that bridges unrelated conversation.
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
You process Dungeons & Dragons gameplay transcripts.
|
|
||||||
|
|
||||||
As input, you will receive one or more portions of a transcript. The transcript 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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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" }}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
The complete ordered transcript of this Dungeons & Dragons gameplay session is provided below.
|
|
||||||
|
|
||||||
{{ input "transcript" }}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
The spell catalog for this extraction is provided below as JSON. Each entry
|
|
||||||
lists a `canonical_name` and its recognized `aliases`. If the transcript uses
|
|
||||||
an alias, select that entry's `canonical_name`. Return spell names using the
|
|
||||||
canonical spelling exactly; never return an alias as a spell name.
|
|
||||||
|
|
||||||
{{ input "spell_catalog" }}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Candidate material:
|
|
||||||
|
|
||||||
{{ input "candidates" }}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Identify only high-confidence duplicate entities among the supplied candidates.
|
|
||||||
|
|
||||||
Preserve distinct entities even when their names are similar. Treat contextual descriptions and transcript evidence as supporting material, not as permission to merge ambiguous records.
|
|
||||||
|
|
||||||
When several records are duplicates, choose as canonical the candidate with the clearest stable identity. Prefer a complete proper name over an abbreviation, and prefer an unadorned proper name over one with incidental descriptors unless the evidence establishes those descriptors as part of the name. A longer name is not inherently more canonical.
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
id: generic.semantic_reconciliation
|
|
||||||
version: "v1"
|
|
||||||
inputs:
|
|
||||||
- name: candidates
|
|
||||||
required: true
|
|
||||||
content_type: application/json
|
|
||||||
- name: transcript
|
|
||||||
required: true
|
|
||||||
content_type: application/json
|
|
||||||
messages:
|
|
||||||
- role: system
|
|
||||||
content_file: ./system.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./protocol.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./instructions.md
|
|
||||||
cache_control:
|
|
||||||
type: ephemeral
|
|
||||||
- role: user
|
|
||||||
content_file: ./candidates.md
|
|
||||||
- role: user
|
|
||||||
content_file: ./transcript-windows.md
|
|
||||||
output:
|
|
||||||
format: json
|
|
||||||
validation_mode: json_schema
|
|
||||||
schema_path: semantic_reconciliation_llm.v1.json
|
|
||||||
repair_attempts: 0
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
Use only the positive integer `candidate_id` values supplied in the candidate material.
|
|
||||||
|
|
||||||
Return a duplicate group only when the evidence supports that every selected candidate describes the same underlying entity. Each group must contain at least two distinct candidate IDs, and its `canonical_candidate_id` must be one of those IDs. A candidate may appear in at most one group.
|
|
||||||
|
|
||||||
Omit uncertain matches and candidates that should remain distinct. Do not invent candidates or infer an ID from list position. An empty `duplicate_groups` array is valid.
|
|
||||||
|
|
||||||
The response must conform exactly to the selected JSON schema. Return IDs only: do not copy candidate names, evidence, transcript text, source identifiers, or source ranges into the response.
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
You reconcile structured records that may describe the same underlying entity.
|
|
||||||
Follow the supplied protocol and return only the requested structured result.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Transcript evidence windows:
|
|
||||||
|
|
||||||
{{ input "transcript" }}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
||||||
"$id": "notarius.generic.semantic_reconciliation.llm",
|
|
||||||
"title": "notarius_semantic_reconciliation_llm_v1",
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["duplicate_groups"],
|
|
||||||
"properties": {
|
|
||||||
"duplicate_groups": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["candidate_ids", "canonical_candidate_id"],
|
|
||||||
"properties": {
|
|
||||||
"candidate_ids": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 2,
|
|
||||||
"items": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"canonical_candidate_id": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// 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 — its asset-co-location rule is superseded by [ADR-0011](0011-centralize-llm-assets.md); its domain-first module packaging decision remains accepted.
|
**Status:** Accepted
|
||||||
**Date:** 2026-07-13
|
**Date:** 2026-07-13
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
# ADR-0010: Use workload-oriented LLM profile defaults
|
|
||||||
|
|
||||||
**Status:** Accepted
|
|
||||||
**Date:** 2026-08-03
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
LLM-backed D&D operations share an execution-policy choice, but repeating a
|
|
||||||
provider or model-named profile on every module binding ties pipeline structure
|
|
||||||
to a deployment decision. Different environments may require different model,
|
|
||||||
backend, timeout, or reasoning settings while retaining the same workload.
|
|
||||||
|
|
||||||
Notarius also needs a usable default for maintained D&D prompts without making
|
|
||||||
an operator profile mandatory. That default must remain owned by the D&D
|
|
||||||
family, while generic LLM infrastructure stays unaware of domain-specific
|
|
||||||
policy.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Pipelines may name one workload-oriented default profile, inherited only by
|
|
||||||
selected LLM-backed bindings and validators. Binding-level profile IDs remain
|
|
||||||
intentional exceptions, and the run-wide CLI profile override has highest
|
|
||||||
precedence.
|
|
||||||
|
|
||||||
The D&D family owns an embedded fallback profile named `dnd-extraction`.
|
|
||||||
Operators may provide a complete profile with the same ID through a PromptKit
|
|
||||||
filesystem source. PromptKit selects the higher-precedence matching definition;
|
|
||||||
Notarius does not merge profile documents. Production, development, and local
|
|
||||||
deployments can therefore use different execution policy behind one unchanged
|
|
||||||
pipeline ID.
|
|
||||||
|
|
||||||
## Alternatives considered
|
|
||||||
|
|
||||||
- Repeat a model-named profile on every binding. This makes routine deployment
|
|
||||||
policy changes noisy and obscures the shared workload intent.
|
|
||||||
- Require every deployment to install a profile file. This adds configuration
|
|
||||||
friction and leaves maintained D&D prompts without an application-owned
|
|
||||||
fallback.
|
|
||||||
- Put D&D profile policy in generic LLM infrastructure. This breaks domain
|
|
||||||
ownership and makes generic code depend on one workload.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
Pipeline configuration expresses workload intent rather than a specific
|
|
||||||
provider or model. Operators can replace the complete execution policy without
|
|
||||||
editing bindings, while binding-level and run-wide exceptions remain available.
|
|
||||||
Profile changes affect resolved pipeline and checkpoint identity, so they may
|
|
||||||
intentionally cause work to be recomputed. The D&D fallback becomes a
|
|
||||||
maintained application execution-policy asset.
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# 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-typed-interfaces-with-two-zone-data-model.md) and the distinction
|
|
||||||
between disambiguating references and source evidence in
|
|
||||||
[ADR-0009](0009-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.
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
# ADR-0013: Use request-local candidate handles for semantic reconciliation
|
|
||||||
|
|
||||||
**Status:** Accepted
|
|
||||||
**Date:** 2026-08-09
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
Several typed normalize stage modules need semantic reconciliation after
|
|
||||||
deterministic preprocessing: a model can judge whether source-backed candidates
|
|
||||||
refer to the same underlying entity, while application code remains responsible
|
|
||||||
for constructing the normalized artifact. Requiring the model to reproduce a
|
|
||||||
candidate's full contextual selector makes the response larger and introduces
|
|
||||||
avoidable formatting, ordering, and transcription failure modes.
|
|
||||||
|
|
||||||
Reconciliation must preserve the exact typed artifact boundary established by
|
|
||||||
[ADR-0003](0003-typed-interfaces-with-two-zone-data-model.md), the domain-neutral
|
|
||||||
framework and concrete-domain dependency direction established by
|
|
||||||
[ADR-0004](0004-package-modules-by-domain.md), and the distinction in
|
|
||||||
[ADR-0009](0009-minimal-evidence-grounded-extraction-artifacts.md) between source
|
|
||||||
evidence and auxiliary identity context. It also needs a concrete, narrowly
|
|
||||||
scoped application of the request-local-label exception allowed by
|
|
||||||
[ADR-0012](0012-resolve-opaque-entity-identifiers-deterministically.md).
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Semantic reconciliation will be a domain-neutral framework mechanism used by
|
|
||||||
typed normalize stage modules. A consuming artifact family will retain
|
|
||||||
ownership of its typed records, identity rules, consolidation policy, durable
|
|
||||||
IDs, and domain warnings; the framework mechanism will not infer those rules
|
|
||||||
from arbitrary data.
|
|
||||||
|
|
||||||
For each reconciliation request, deterministic code will assign every eligible
|
|
||||||
model-visible candidate a contiguous, one-based integer handle. The model may
|
|
||||||
receive the candidate's contextual label, source references, and bounded source
|
|
||||||
context needed to judge identity, but its structured response will identify
|
|
||||||
candidates only by those supplied handles. A handle is local to one request,
|
|
||||||
does not represent entity identity, and must never enter a durable artifact or
|
|
||||||
be used to derive a durable ID.
|
|
||||||
|
|
||||||
The model will propose duplicate groups and select one supplied member of each
|
|
||||||
group as canonical. Deterministic code will resolve the handles through the
|
|
||||||
retained request mapping, validate the complete proposal, discard unsafe
|
|
||||||
groups, and apply only validated groups through typed domain-owned policy. The
|
|
||||||
model will not synthesize replacement records or directly mutate an artifact.
|
|
||||||
|
|
||||||
Every reconciliation prompt will combine a mandatory framework-owned protocol
|
|
||||||
and safety policy with an explicitly selected semantic policy. The semantic
|
|
||||||
policy may be the conservative generic policy or a domain-owned policy, but it
|
|
||||||
cannot replace the shared response protocol or deterministic safety boundary.
|
|
||||||
|
|
||||||
## Alternatives considered
|
|
||||||
|
|
||||||
- Return durable application IDs. Opaque IDs do not help semantic judgment,
|
|
||||||
expose application identity mechanics, and make model output reproduce data
|
|
||||||
that deterministic code already owns.
|
|
||||||
- Return names alone or copied contextual selectors. Names can be ambiguous,
|
|
||||||
while reproducing labels and source ranges adds response complexity and
|
|
||||||
creates mismatches without adding semantic information. Request-local
|
|
||||||
handles preserve exact selection without either failure mode.
|
|
||||||
- Ask the model to return synthesized canonical replacement records. This
|
|
||||||
would transfer typed artifact construction, provenance consolidation, and
|
|
||||||
durable identity policy to a probabilistic boundary.
|
|
||||||
- Reconcile reflection-discovered fields or arbitrary JSON. This would weaken
|
|
||||||
the typed artifact contract and move domain semantics into generic code.
|
|
||||||
- Hide reconciliation inside extraction or another stage. This would obscure
|
|
||||||
stage ownership and create cross-stage behavior outside the fixed pipeline;
|
|
||||||
reconciliation remains explicit normalize-stage behavior.
|
|
||||||
- Let each domain replace the complete prompt protocol. This would duplicate
|
|
||||||
safety mechanics and allow domain policy to bypass the common response and
|
|
||||||
validation contract.
|
|
||||||
|
|
||||||
## Consequences
|
|
||||||
|
|
||||||
Model responses become smaller and easier to validate, while deterministic
|
|
||||||
application code retains authority over identity, provenance, ordering, and
|
|
||||||
typed artifact construction. The framework requires a request-local mapping,
|
|
||||||
bounded context preparation, a private integer response contract, proposal
|
|
||||||
assessment, and shared prompt assets. Each consuming artifact family still
|
|
||||||
requires a typed adapter for its irreducibly domain-specific rules.
|
|
||||||
|
|
||||||
Request-local handles are deliberately unsuitable for persistence, logging as
|
|
||||||
entity identity, checkpoint contracts, or cross-request correlation. Changes
|
|
||||||
to shared protocol and policy assets must participate in the normal prompt,
|
|
||||||
schema, and checkpoint fingerprint mechanisms.
|
|
||||||
|
|
||||||
Acceptance of this decision does not imply that the shared mechanism or its
|
|
||||||
consumer migrations are implemented. The
|
|
||||||
[feature roadmap](../roadmap/semantic-reconciliation.md) owns target behavior
|
|
||||||
and status, and the
|
|
||||||
[implementation plan](../roadmap/implementation.md) owns delivery sequence
|
|
||||||
until the work is complete.
|
|
||||||
27
docs/cli.md
27
docs/cli.md
@@ -39,33 +39,16 @@ pipeline ID and **--input** are required.
|
|||||||
| **--debug** | Retain a debug bundle for this run. |
|
| **--debug** | Retain a debug bundle for this run. |
|
||||||
| **--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** | Override effective LLM-capable module bindings with one configured profile. |
|
||||||
| **--session-id id** | Override the generated prompt session identifier with a non-empty value for LLM-backed module calls. |
|
| **--session-id id** | Supply a non-empty prompt session identifier to 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. |
|
|
||||||
| **--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. |
|
||||||
| **--without-reference selector** | Remove a configured optional reference binding. Repeatable. |
|
| **--without-reference selector** | Remove a configured optional reference binding. Repeatable. |
|
||||||
|
|
||||||
**--chunk_cache** accepts only **auto**, **bypass**, or **refresh**.
|
**--chunk_cache** accepts only **auto**, **bypass**, or **refresh**.
|
||||||
**--debug-dir**, **--output-dir**, **--session-id**, and
|
**--debug-dir**, **--output-dir**, **--session-id**, and
|
||||||
**--reasoning-effort**, and **--recompute-step** reject explicit empty values.
|
**--recompute-step** reject explicit empty values. **--recompute-step**
|
||||||
**--reasoning-effort** and **--clear-reasoning-effort** are mutually exclusive.
|
requires **--resume**; checkpoint requirements and reuse behavior are
|
||||||
When neither is present, reasoning effort comes from the selected PromptKit
|
documented in [Operations](operations.md).
|
||||||
profile. These controls apply to the shared run client, including retries and
|
|
||||||
LLM-backed validators, and do not modify configuration or profile files.
|
|
||||||
Persistent reasoning settings remain a PromptKit profile concern.
|
|
||||||
**--recompute-step** requires **--resume**; checkpoint requirements and reuse
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
197
docs/config.md
197
docs/config.md
@@ -1,7 +1,7 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
This is the canonical reference for Notarius configuration. Configuration files
|
This is the canonical reference for Notarius configuration. Configuration files
|
||||||
are YAML and must declare version 4. They select pipelines and their modules;
|
are YAML and must declare version 3. They select pipelines and their modules;
|
||||||
the [CLI reference](cli.md) owns invocation syntax, and
|
the [CLI reference](cli.md) owns invocation syntax, and
|
||||||
[Operations](operations.md) owns run-state procedures.
|
[Operations](operations.md) owns run-state procedures.
|
||||||
|
|
||||||
@@ -32,8 +32,7 @@ override the fields listed below.
|
|||||||
single-lane Seriatim-to-spell pipeline.
|
single-lane Seriatim-to-spell pipeline.
|
||||||
- [Complete D&D configuration](../examples/dnd-complete.config.yml) uses
|
- [Complete D&D configuration](../examples/dnd-complete.config.yml) uses
|
||||||
ordered steps, all implemented D&D lanes, generated references, state
|
ordered steps, all implemented D&D lanes, generated references, state
|
||||||
settings, bounded LLM concurrency, and the maintained
|
settings, and bounded LLM concurrency.
|
||||||
[operator profile](../examples/profiles/dnd-extraction.yml).
|
|
||||||
|
|
||||||
Use these complete files as starting points rather than combining the
|
Use these complete files as starting points rather than combining the
|
||||||
illustrative fragments in this reference.
|
illustrative fragments in this reference.
|
||||||
@@ -46,8 +45,8 @@ other than **version** is optional.
|
|||||||
|
|
||||||
| Field | Type | Default | Rules |
|
| Field | Type | Default | Rules |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| **version** | integer | none | Required; must be 4. |
|
| **version** | integer | none | Required; must be 3. |
|
||||||
| **promptkit** | object | none | Profile source and optional local-backend configuration. |
|
| **scriptorium** | object | none | Profile source configuration. |
|
||||||
| **pipelines** | map | empty | Maps pipeline IDs to pipeline definitions. |
|
| **pipelines** | map | empty | Maps pipeline IDs to pipeline definitions. |
|
||||||
| **concurrency** | object | see below | Global LLM and extraction limits. |
|
| **concurrency** | object | see below | Global LLM and extraction limits. |
|
||||||
| **output** | object | see below | Published output settings. |
|
| **output** | object | see below | Published output settings. |
|
||||||
@@ -58,7 +57,7 @@ Built-in defaults are:
|
|||||||
|
|
||||||
| Field | Default |
|
| Field | Default |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **concurrency.total_llm** | 16 |
|
| **concurrency.total_llm** | 1 |
|
||||||
| **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** |
|
||||||
@@ -70,85 +69,20 @@ Built-in defaults are:
|
|||||||
An empty cache directory in YAML deliberately selects the corresponding
|
An empty cache directory in YAML deliberately selects the corresponding
|
||||||
per-user root. An explicit empty output or debug directory is invalid.
|
per-user root. An explicit empty output or debug directory is invalid.
|
||||||
|
|
||||||
## PromptKit Profiles
|
## Scriptorium Profiles
|
||||||
|
|
||||||
The optional **promptkit** object selects one source of profile definitions and
|
The optional **scriptorium** object selects one source of profile definitions:
|
||||||
may register one conventional local OpenAI-compatible backend:
|
|
||||||
|
|
||||||
~~~yaml
|
|
||||||
version: 4
|
|
||||||
|
|
||||||
promptkit:
|
|
||||||
profile_dir: ./profiles
|
|
||||||
# profile_file: ./profiles.yml
|
|
||||||
local_backend:
|
|
||||||
endpoint: http://localhost:8000/v1
|
|
||||||
concurrency_limit: 2
|
|
||||||
~~~
|
|
||||||
|
|
||||||
| Field | Type | Rules |
|
| Field | Type | Rules |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| **profile_dir** | string | Non-empty directory containing profile files. |
|
| **profile_dir** | string | Non-empty directory containing profile files. |
|
||||||
| **profile_file** | string | Non-empty profile file. |
|
| **profile_file** | string | Non-empty profile file. |
|
||||||
| **local_backend** | object | Optional registration for the conventional PromptKit backend ID **local**. |
|
|
||||||
| **local_backend.endpoint** | string | Required when **local_backend** is present; absolute HTTP or HTTPS URL with a host. |
|
|
||||||
| **local_backend.concurrency_limit** | integer | Optional non-negative limit; defaults to 0. |
|
|
||||||
|
|
||||||
Set at most one of **profile_dir** and **profile_file**. Relative values use
|
Set at most one of these fields. Profile IDs used by a binding must be available
|
||||||
the process working directory, not the configuration file's directory. The
|
from the selected Scriptorium profile source when the pipeline is resolved.
|
||||||
complete example's `./examples/profiles/dnd-extraction.yml` value is therefore
|
Keep credentials out of this file: configure a profile to read its credential
|
||||||
valid when Notarius is launched from the repository root; use an absolute path
|
from an environment variable, then set that environment variable only in the
|
||||||
for services and containers.
|
run environment.
|
||||||
|
|
||||||
An operator source is optional. For a requested ID, PromptKit checks the
|
|
||||||
configured operator source first, then Notarius's embedded fallback profiles,
|
|
||||||
then its own built-in catalog. A matching profile is complete: it replaces a
|
|
||||||
lower-precedence definition rather than merging with it. The maintained
|
|
||||||
[`dnd-extraction` operator profile](../examples/profiles/dnd-extraction.yml)
|
|
||||||
is a secret-free deployment artifact; production, development, and local
|
|
||||||
deployments can each provide a complete definition with that same workload ID.
|
|
||||||
Use workload-oriented IDs for new profiles instead of model names.
|
|
||||||
[Operations](operations.md#promptkit-profile-deployment) owns the deployment
|
|
||||||
workflow and credential-handling guidance.
|
|
||||||
|
|
||||||
When **local_backend** is present, its endpoint is trimmed and must use HTTP or
|
|
||||||
HTTPS case-insensitively, be absolute, and have a non-empty host. URL paths are
|
|
||||||
allowed. User information, queries, and fragments are rejected. A zero
|
|
||||||
**concurrency_limit** leaves the local backend unrestricted inside PromptKit;
|
|
||||||
a positive value limits simultaneous local generations. The application-wide
|
|
||||||
**concurrency.total_llm** limit still applies in both cases. Neither local
|
|
||||||
backend field has an environment override. Omitting **local_backend** registers
|
|
||||||
nothing and preserves existing built-in and endpoint-only profile behavior.
|
|
||||||
|
|
||||||
A file-backed PromptKit profile selects the registration by its case-sensitive
|
|
||||||
backend ID:
|
|
||||||
|
|
||||||
~~~yaml
|
|
||||||
id: local-summary
|
|
||||||
backend: local
|
|
||||||
model: example-model
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Keep credentials out of the local-backend object. A PromptKit profile may name
|
|
||||||
its credential environment variable through `api_key_env`; set that variable
|
|
||||||
only in the run environment. PromptKit owns the
|
|
||||||
[pinned profile-file format](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/formats.md).
|
|
||||||
The [PromptKit upstream boundary](integrations/pkg-promptkit.md) identifies the
|
|
||||||
supported package API, and [Operations](operations.md#operational-limits)
|
|
||||||
describes the effective concurrency layers.
|
|
||||||
|
|
||||||
`notarius config validate --pipeline <id>` resolves the selected pipeline and
|
|
||||||
inspects every explicit effective profile without contacting a provider or
|
|
||||||
requiring credential values. It rejects absent, malformed, or incompatible
|
|
||||||
profiles before a run prepares modules. Credential availability is checked only
|
|
||||||
when a generation is prepared.
|
|
||||||
|
|
||||||
## Migrating Version 3 Configuration
|
|
||||||
|
|
||||||
Version 3 files are not decoded or rewritten. Change **version: 3** to
|
|
||||||
**version: 4** and rename the top-level **scriptorium:** section to
|
|
||||||
**promptkit:**. Version 4 decoding is strict, so a remaining **scriptorium**
|
|
||||||
field is rejected as unknown.
|
|
||||||
|
|
||||||
## Operational Environment Variables
|
## Operational Environment Variables
|
||||||
|
|
||||||
@@ -204,7 +138,6 @@ Each **pipelines** entry has a unique, non-empty ID and the following shape:
|
|||||||
~~~yaml
|
~~~yaml
|
||||||
pipelines:
|
pipelines:
|
||||||
dnd-session:
|
dnd-session:
|
||||||
llm_profile: dnd-extraction
|
|
||||||
input: seriatim
|
input: seriatim
|
||||||
chunk: generic
|
chunk: generic
|
||||||
output: json
|
output: json
|
||||||
@@ -217,7 +150,6 @@ pipelines:
|
|||||||
|
|
||||||
| Field | Type | Default | Rules |
|
| Field | Type | Default | Rules |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| **llm_profile** | string | none | Optional non-empty default PromptKit profile ID for selected LLM-backed bindings and validators. An explicitly present blank value is invalid. |
|
|
||||||
| **input** | module binding | none | Required. |
|
| **input** | module binding | none | Required. |
|
||||||
| **chunk** | module binding | **generic** | Optional. |
|
| **chunk** | module binding | **generic** | Optional. |
|
||||||
| **output** | module binding | **json** | Optional. |
|
| **output** | module binding | **json** | Optional. |
|
||||||
@@ -231,12 +163,6 @@ needs a unique non-empty **id**, an **artifacts** map, and may have
|
|||||||
**references**. A lane ID must not appear more than once in a pipeline,
|
**references**. A lane ID must not appear more than once in a pipeline,
|
||||||
including across explicit steps.
|
including across explicit steps.
|
||||||
|
|
||||||
For each selected LLM-backed binding or validator, profile selection occurs
|
|
||||||
after module, validator, and `--only` lane selection. It uses the
|
|
||||||
run-level **--llm-profile** value first, then the binding's **llm_profile**,
|
|
||||||
then the pipeline's **llm_profile**, and finally the PromptKit default.
|
|
||||||
Deterministic bindings do not receive these defaults or run overrides.
|
|
||||||
|
|
||||||
A lane has these fields:
|
A lane has these fields:
|
||||||
|
|
||||||
| Field | Type | Default | Rules |
|
| Field | Type | Default | Rules |
|
||||||
@@ -264,7 +190,7 @@ Use an object for fields:
|
|||||||
~~~yaml
|
~~~yaml
|
||||||
extract:
|
extract:
|
||||||
module: dnd/spells
|
module: dnd/spells
|
||||||
llm_profile: dnd-extraction
|
llm_profile: gemini-2-flash
|
||||||
retries: 2
|
retries: 2
|
||||||
references:
|
references:
|
||||||
spell_catalog: ./dnd-spell-catalog.json
|
spell_catalog: ./dnd-spell-catalog.json
|
||||||
@@ -273,7 +199,7 @@ extract:
|
|||||||
| Binding field | Type | Default | Rules |
|
| Binding field | Type | Default | Rules |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| **module** | string | none | Required for an object binding. Must be a registered compatible key. |
|
| **module** | string | none | Required for an object binding. Must be a registered compatible key. |
|
||||||
| **llm_profile** | string | none | Optional non-empty PromptKit profile ID for an LLM-backed binding. It overrides the pipeline default unless the run supplies **--llm-profile**. |
|
| **llm_profile** | string | none | Optional non-empty Scriptorium profile ID. |
|
||||||
| **retries** | integer | 0 | Non-negative additional attempts for chunk, extract, merge, and normalize bindings. |
|
| **retries** | integer | 0 | Non-negative additional attempts for chunk, extract, merge, and normalize bindings. |
|
||||||
| **options** | object | none | Must satisfy the selected module. |
|
| **options** | object | none | Must satisfy the selected module. |
|
||||||
| **references** | map | none | Valid only on chunk, extract, merge, and normalize bindings. |
|
| **references** | map | none | Valid only on chunk, extract, merge, and normalize bindings. |
|
||||||
@@ -283,8 +209,7 @@ Omitting **validators** uses the registered chain. **validators: []** selects
|
|||||||
an empty chain; a non-empty list replaces the chain in the listed order.
|
an empty chain; a non-empty list replaces the chain in the listed order.
|
||||||
Validator bindings accept only **module**, **llm_profile**, and **options**.
|
Validator bindings accept only **module**, **llm_profile**, and **options**.
|
||||||
They reject **references**, **retries**, and nested **validators**. Deterministic
|
They reject **references**, **retries**, and nested **validators**. Deterministic
|
||||||
validators reject an explicit **llm_profile**. Deterministic module bindings
|
validators reject an explicit **llm_profile**.
|
||||||
also reject an explicit **llm_profile**.
|
|
||||||
|
|
||||||
The **json** output module accepts optional **include_chunk_map** and
|
The **json** output module accepts optional **include_chunk_map** and
|
||||||
**evidence_context** settings:
|
**evidence_context** settings:
|
||||||
@@ -298,7 +223,7 @@ output:
|
|||||||
enabled: true
|
enabled: true
|
||||||
window_units: 3
|
window_units: 3
|
||||||
lanes:
|
lanes:
|
||||||
- npc-registry
|
- npcs
|
||||||
- spells
|
- spells
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
@@ -319,8 +244,7 @@ Unknown outer or nested option fields are rejected, as are incompatible YAML
|
|||||||
types. The allowlist remains valid when a run uses lane filtering: a configured
|
types. The allowlist remains valid when a run uses lane filtering: a configured
|
||||||
lane that is not active for that invocation simply contributes no evidence.
|
lane that is not active for that invocation simply contributes no evidence.
|
||||||
Evidence publication is opt-in because it can persist source text and metadata.
|
Evidence publication is opt-in because it can persist source text and metadata.
|
||||||
When enabled, it publishes the selected source-unit excerpt defined by the
|
Its payload contract is [Published Evidence Context](integrations/evidence-context.md).
|
||||||
[Published Evidence Context contract](integrations/evidence-context.md).
|
|
||||||
|
|
||||||
## References And Ordered Handoffs
|
## References And Ordered Handoffs
|
||||||
|
|
||||||
@@ -333,15 +257,15 @@ step:
|
|||||||
steps:
|
steps:
|
||||||
- id: describe-session
|
- id: describe-session
|
||||||
artifacts:
|
artifacts:
|
||||||
npc-registry:
|
npcs:
|
||||||
extract: dnd/npc-registry
|
extract: dnd/npcs
|
||||||
normalize: dnd/npc-registry
|
normalize: dnd/npcs
|
||||||
- id: extract-events
|
- id: extract-events
|
||||||
references:
|
references:
|
||||||
npc_registry:
|
npcs:
|
||||||
artifact:
|
artifact:
|
||||||
step: describe-session
|
step: describe-session
|
||||||
lane: npc-registry
|
lane: npcs
|
||||||
artifacts:
|
artifacts:
|
||||||
spells:
|
spells:
|
||||||
extract: dnd/spells
|
extract: dnd/spells
|
||||||
@@ -371,38 +295,14 @@ 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). |
|
||||||
| **location_registry** | Required normalized location registry for location-occurrence extraction and normalization. |
|
| **npcs** | Normalized NPC registry. Optional for spells and combat turns; required for NPC interactions. |
|
||||||
| **item_registry** | Required normalized item registry for item-occurrence extraction and normalization. |
|
| **scene_descriptions** | Required normalized scene-description artifact for combat-turn extraction. |
|
||||||
| **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 occurrences require **npc_registry** for both extraction and
|
**roster**. NPC interactions require **npcs** 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 **npc_registry**. Location occurrences
|
normalized combat-turn module may use optional **npcs**. The complete example
|
||||||
require **location_registry** for extraction and normalization. Item occurrences require
|
shows generated **npcs** and **scene_descriptions** bindings.
|
||||||
**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
|
||||||
|
|
||||||
@@ -410,29 +310,18 @@ generated bindings.
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Input | **seriatim** |
|
| Input | **seriatim** |
|
||||||
| Chunk | **generic**, **dnd/scenes** |
|
| Chunk | **generic**, **dnd/scenes** |
|
||||||
| 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** |
|
| Extract | **dnd/spells**, **dnd/npcs**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions** |
|
||||||
| Merge | **appendorder** |
|
| Merge | **appendorder** |
|
||||||
| 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** |
|
| Normalize | **noop**, **dnd/spells**, **dnd/npcs**, **dnd/combat-turns**, **dnd/item-events**, **dnd/npc-interactions**, **dnd/scene-descriptions** |
|
||||||
| 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),
|
||||||
[NPC registry](integrations/dnd-npc-registry-artifacts.md),
|
[NPCs](integrations/dnd-npc-artifacts.md),
|
||||||
[NPC occurrences](integrations/dnd-npc-occurrence-artifacts.md),
|
[NPC interactions](integrations/dnd-npc-interaction-artifacts.md),
|
||||||
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
[combat turns](integrations/dnd-combat-turn-artifacts.md),
|
||||||
[item registry](integrations/dnd-item-registry-artifacts.md),
|
[item events](integrations/dnd-item-event-artifacts.md), and
|
||||||
[item occurrences](integrations/dnd-item-occurrence-artifacts.md),
|
[scene descriptions](integrations/dnd-scene-description-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
|
||||||
|
|
||||||
@@ -442,15 +331,11 @@ 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** |
|
||||||
| NPC registry | **extract/dnd/npc-registry/shape**, **extract/dnd/npc-registry/source_refs**, **extract/dnd/npc-registry/source_relatedness**, **normalize/dnd/npc-registry/identity** |
|
| NPCs | **extract/dnd/npcs/shape**, **extract/dnd/npcs/source_refs**, **extract/dnd/npcs/source_relatedness**, **normalize/dnd/npcs/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 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** |
|
| 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 registry | **extract/dnd/item-registry/shape**, **extract/dnd/item-registry/source_refs**, **extract/dnd/item-registry/source_relatedness**, **normalize/dnd/item-registry/identity** |
|
| 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** |
|
||||||
| 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
|
||||||
@@ -459,15 +344,11 @@ 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 |
|
||||||
| 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 |
|
| 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 |
|
||||||
| 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 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 |
|
| 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 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 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 |
|
||||||
| 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,13 +27,10 @@ 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. Notarius generates a stable prompt session for the resolved
|
reference files. When a stable prompt session identifier or references are
|
||||||
input module and exact input bytes. Pass **--session-id** only when intentionally
|
needed, pass the supported CLI flags. Supply credentials through Notarius's
|
||||||
grouping different invocations under a different session. Supply credentials
|
documented configuration and environment mechanisms, never as command-line
|
||||||
through Notarius's documented configuration and environment mechanisms, never
|
arguments or generated secret-bearing configuration.
|
||||||
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,10 +52,10 @@ contract. The JSON bundle contract links to the available lane contracts.
|
|||||||
If `index.json` has an `evidence_context` descriptor, treat it as a
|
If `index.json` has an `evidence_context` descriptor, treat it as a
|
||||||
pipeline-wide artifact rather than a lane entry. Verify its six descriptor
|
pipeline-wide artifact rather than a lane entry. Verify its six descriptor
|
||||||
fields before decoding the linked file according to the [Published Evidence
|
fields before decoding the linked file according to the [Published Evidence
|
||||||
Context contract](../integrations/evidence-context.md). Decode its top-level
|
Context contract](../integrations/evidence-context.md). Use each
|
||||||
source-unit array as a reading excerpt. Obtain authoritative citations and lane
|
`evidence_refs` entry as the citation to source material. Its surrounding
|
||||||
provenance from the normalized lane artifacts; the excerpt has neither and its
|
context range and included units explain the citation, but do not widen or
|
||||||
nearby units do not widen a lane artifact's cited source reference.
|
replace the cited source reference.
|
||||||
|
|
||||||
A zero exit status may still report rejected outputs, warnings, or absent
|
A zero exit status may still report rejected outputs, warnings, or absent
|
||||||
lanes. The caller decides which lane IDs are required for its own work and
|
lanes. The caller decides which lane IDs are required for its own work and
|
||||||
@@ -73,5 +70,5 @@ them. Treat the input, output bundle, cache, debug bundle, and captured process
|
|||||||
logs as potentially sensitive data. Apply the caller's access controls and
|
logs as potentially sensitive data. Apply the caller's access controls and
|
||||||
retention policy, and avoid copying secrets into arguments, logs, or
|
retention policy, and avoid copying secrets into arguments, logs, or
|
||||||
provenance records. An evidence-context artifact contains source-unit text and
|
provenance records. An evidence-context artifact contains source-unit text and
|
||||||
metadata and can cover most of an input; preserve and share it only when that
|
metadata, and selected lanes can cover most of an input; preserve and share it
|
||||||
source content is authorized for the recipient.
|
only when that source content is authorized for the recipient.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ implemented component map.
|
|||||||
| Configuration loading, resolution, or user-visible configuration behavior | [Configuration Internals](internal/configuration.md) and [Configuration](config.md) | The internal guide owns loading and resolution mechanics; the reference owns the configuration contract. |
|
| Configuration loading, resolution, or user-visible configuration behavior | [Configuration Internals](internal/configuration.md) and [Configuration](config.md) | The internal guide owns loading and resolution mechanics; the reference owns the configuration contract. |
|
||||||
| Pipeline resolution or execution | [Pipeline Internals](internal/pipeline.md) | It documents profiles, references, validation, retries, checkpoints, and runner behavior. |
|
| Pipeline resolution or execution | [Pipeline Internals](internal/pipeline.md) | It documents profiles, references, validation, retries, checkpoints, and runner behavior. |
|
||||||
| Production modules or validators | [Module Internals](internal/modules.md), [D&D Module Internals](internal/dnd.md), and [D&D integration contracts](integrations/) | The generic guide owns extension mechanics, the D&D guide owns shared family conventions, and the contracts own durable output shapes. |
|
| Production modules or validators | [Module Internals](internal/modules.md), [D&D Module Internals](internal/dnd.md), and [D&D integration contracts](integrations/) | The generic guide owns extension mechanics, the D&D guide owns shared family conventions, and the contracts own durable output shapes. |
|
||||||
| LLM clients, prompts, schemas, profiles, or scheduling | [LLM Runtime](internal/llm.md) | It documents the transport boundary and PromptKit integration. |
|
| LLM clients, prompts, schemas, profiles, or scheduling | [LLM Runtime](internal/llm.md) | It documents the transport boundary and Scriptorium integration. |
|
||||||
| Output, cache, resume, or debug artifacts | [Run State Internals](internal/state.md), [Operations](operations.md), and [Configuration](config.md) | These separate implementation details, operator behavior, and configuration contracts. |
|
| Output, cache, resume, or debug artifacts | [Run State Internals](internal/state.md), [Operations](operations.md), and [Configuration](config.md) | These separate implementation details, operator behavior, and configuration contracts. |
|
||||||
| External input formats, artifact schemas, or durable output files | [Integration Contracts](integrations/) | Integration documents define external and durable data contracts. |
|
| External input formats, artifact schemas, or durable output files | [Integration Contracts](integrations/) | Integration documents define external and durable data contracts. |
|
||||||
| Proposed or unimplemented behavior | [Roadmap](roadmap/) | Future work belongs only in roadmap documentation until implemented. |
|
| Proposed or unimplemented behavior | [Roadmap](roadmap/) | Future work belongs only in roadmap documentation until implemented. |
|
||||||
|
|||||||
@@ -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 registry artifact](dnd-npc-registry-artifacts.md) can ground an
|
An optional normalized [NPC artifact](dnd-npc-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,9 +63,7 @@ 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-occurrence artifact](dnd-npc-occurrence-artifacts.md) records
|
The [NPC-interaction artifact](dnd-npc-interaction-artifacts.md) records
|
||||||
broader NPC occurrences. The [enemy-event artifact](dnd-enemy-event-artifacts.md)
|
broader NPC occurrences. The [JSON output contract](json-output.md) defines
|
||||||
uses combat turns as grounding only; turns do not establish an enemy event or
|
publication, and [D&D module internals](../internal/dnd.md) describes routing
|
||||||
its outcome. The [JSON output contract](json-output.md) defines publication,
|
and validation mechanics.
|
||||||
and [D&D module internals](../internal/dnd.md) describes routing and validation
|
|
||||||
mechanics.
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
# 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).
|
|
||||||
78
docs/integrations/dnd-item-event-artifacts.md
Normal file
78
docs/integrations/dnd-item-event-artifacts.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# 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.
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# 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.
|
|
||||||
69
docs/integrations/dnd-npc-artifacts.md
Normal file
69
docs/integrations/dnd-npc-artifacts.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# 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 Occurrence Artifact
|
# D&D NPC Interaction Artifact
|
||||||
|
|
||||||
This contract defines the durable occurrence list produced by
|
This contract defines the durable occurrence list produced by
|
||||||
`dnd/npc-occurrences`. It records discrete, source-grounded occurrences with
|
`dnd/npc-interactions`. It records discrete, source-grounded interactions 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,37 +9,35 @@ or summarize the session.
|
|||||||
|
|
||||||
| Property | Value |
|
| Property | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Artifact kind | `dnd/npc-occurrence-list` |
|
| Artifact kind | `dnd/npc-interaction-list` |
|
||||||
| Schema ID | `notarius.dnd.npc_occurrences` |
|
| Schema ID | `notarius.dnd.npc_interactions` |
|
||||||
| Schema name | `notarius_dnd_npc_occurrences_v1` |
|
| Schema name | `notarius_dnd_npc_interactions_v1` |
|
||||||
| Schema version | `v1` |
|
| Schema version | `v1` |
|
||||||
| Media type | `application/json` |
|
| Media type | `application/json` |
|
||||||
|
|
||||||
`v1` is a strict JSON object with required `occurrences`; the array may be
|
`v1` is a strict JSON object with required `interactions`; the array may be
|
||||||
empty. Occurrence and source-reference objects reject unknown fields. An
|
empty. Interaction 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 occurrence has these required fields:
|
Each interaction 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 occurrence categories below. |
|
| `kind` | One of the interaction 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 occurrence is confined to its accepted chunk.
|
for an interaction is confined to its accepted chunk.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"occurrences": [
|
"interactions": [
|
||||||
{
|
{
|
||||||
"npc_id": "npc:sha256:example",
|
|
||||||
"name": "Mira Thorn",
|
"name": "Mira Thorn",
|
||||||
"kind": "dialogue",
|
"kind": "dialogue",
|
||||||
"source_refs": [
|
"source_refs": [
|
||||||
@@ -50,7 +48,7 @@ for an occurrence is confined to its accepted chunk.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Occurrence categories
|
## Interaction categories
|
||||||
|
|
||||||
| Kind | Meaning |
|
| Kind | Meaning |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -67,24 +65,14 @@ for uncertain classification.
|
|||||||
|
|
||||||
## Identity, evidence, and order
|
## Identity, evidence, and order
|
||||||
|
|
||||||
The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md)
|
The required normalized [NPC artifact](dnd-npc-artifacts.md) resolves `name`.
|
||||||
supplies names-only contextual grounding to the model. Notarius resolves the
|
Registry references are provenance only and never replace an interaction's own
|
||||||
selected name and writes the exact `{npc_id, name}` pair. An unknown or
|
evidence. Normalization canonicalizes recognized registry names, orders and
|
||||||
ambiguous selection rejects the complete model result; normalization does not
|
deduplicates exact source references, then orders interactions by valid source
|
||||||
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 NPC ID, canonical name, kind, and complete valid evidence
|
Only entries with the same 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. The [enemy-event artifact](dnd-enemy-event-artifacts.md) consumes
|
occurrences and the [JSON output contract](json-output.md) for publication.
|
||||||
only `combat_opponent` occurrences as grounding; they never establish an enemy
|
Pipeline mechanics are described in [D&D module internals](../internal/dnd.md).
|
||||||
event or outcome. The [JSON output contract](json-output.md) defines
|
|
||||||
publication. Pipeline mechanics are described in
|
|
||||||
[D&D module internals](../internal/dnd.md).
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# 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,9 +62,8 @@ 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) and
|
The [combat-turn artifact](dnd-combat-turn-artifacts.md) uses an exact matching
|
||||||
[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 their evidence. Publication is defined by the
|
reference never become combat 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 registry artifact](dnd-npc-registry-artifacts.md) can ground a
|
The optional normalized [NPC artifact](dnd-npc-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`.
|
||||||
|
|
||||||
|
|||||||
@@ -67,12 +67,6 @@ including a collision with the embedded catalog. Matching uses the catalog’s
|
|||||||
case, whitespace, and apostrophe normalization, so authors should avoid names
|
case, whitespace, and apostrophe normalization, so authors should avoid names
|
||||||
or aliases that normalize to another spell.
|
or aliases that normalize to another spell.
|
||||||
|
|
||||||
Spell extraction receives the effective catalog as deterministic canonical-name
|
|
||||||
and alias pairs. An alias in the transcript selects its associated canonical
|
|
||||||
name; the extractor is instructed to return that canonical spelling. The
|
|
||||||
projection contains no catalog source metadata or provenance, and aliases
|
|
||||||
remain recognition context rather than transcript evidence.
|
|
||||||
|
|
||||||
The overlay is a recognition aid only. The durable spell-artifact schema and
|
The overlay is a recognition aid only. The durable spell-artifact schema and
|
||||||
source-evidence rules are defined by the
|
source-evidence rules are defined by the
|
||||||
[D&D spell artifact contract](dnd-spell-artifacts.md).
|
[D&D spell artifact contract](dnd-spell-artifacts.md).
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
# Published Evidence Context
|
# Published Evidence Context
|
||||||
|
|
||||||
This contract defines the optional `source/evidence-context` artifact emitted
|
This contract defines the optional `source/evidence-context` artifact emitted
|
||||||
by the production JSON output. It is a selected source-unit excerpt for
|
by the production JSON output. Its configuration is owned by
|
||||||
convenient reading alongside normalized lane artifacts; it is not a second
|
[Configuration](../config.md#module-bindings-and-validators); its logical-file
|
||||||
citation or provenance model. Its configuration is owned by
|
discovery is owned by [Published JSON Output](json-output.md).
|
||||||
[Configuration](../config.md#module-bindings-and-validators), and its
|
|
||||||
logical-file discovery is owned by [Published JSON Output](json-output.md).
|
|
||||||
|
|
||||||
## Identity And Discovery
|
## Identity And Discovery
|
||||||
|
|
||||||
@@ -28,13 +26,35 @@ its absence means evidence publication was not enabled for that bundle.
|
|||||||
|
|
||||||
## Payload
|
## Payload
|
||||||
|
|
||||||
The v1 payload is a top-level JSON array of generic source units. There is no
|
The v1 payload is a JSON object with required `source_id`, `source_digest`,
|
||||||
wrapper, source-level metadata, context grouping, lane identifier, or evidence
|
`window_units`, `selected_lanes`, and `contexts` fields. `selected_lanes` and
|
||||||
reference in the payload. An enabled configuration with no contributing
|
`contexts` are always arrays; an enabled configuration with no accepted direct
|
||||||
accepted evidence publishes `[]`.
|
evidence publishes `contexts: []`.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"source_digest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||||
|
"window_units": 1,
|
||||||
|
"selected_lanes": ["npcs", "spells"],
|
||||||
|
"contexts": [
|
||||||
|
{
|
||||||
|
"context_ref": {
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"start_unit_id": 10,
|
||||||
|
"end_unit_id": 20
|
||||||
|
},
|
||||||
|
"evidence_refs": [
|
||||||
|
{
|
||||||
|
"lane_id": "spells",
|
||||||
|
"source_ref": {
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"start_unit_id": 10,
|
||||||
|
"end_unit_id": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"units": [
|
||||||
{
|
{
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"kind": "transcript_segment",
|
"kind": "transcript_segment",
|
||||||
@@ -55,53 +75,42 @@ accepted evidence publishes `[]`.
|
|||||||
"end_unit_id": 20
|
"end_unit_id": 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Each source unit has required `id`, `kind`, `text`, and self `ref` fields.
|
Each context requires `context_ref`, `evidence_refs`, and `units` arrays.
|
||||||
`ref` contains `source_id`, `start_unit_id`, and `end_unit_id`, and both unit
|
`context_ref` identifies the first and last included unit. Each evidence entry
|
||||||
endpoints identify that unit's `id`. A unit may also contain source-owned
|
contains a selected `lane_id` and an original `source_ref`. A unit uses the
|
||||||
`metadata`, an open-ended JSON object. Fixed unit and reference fields are
|
existing source-unit shape: required `id`, `kind`, `text`, and self `ref`, plus
|
||||||
strict: consumers must reject unknown fixed fields, malformed units, invalid
|
optional JSON-object `metadata`. Fixed payload objects reject unknown fields;
|
||||||
self-references, units whose `source_id` differs from other units in the same
|
unit metadata may contain application-defined JSON values.
|
||||||
excerpt, and a payload that is not the array described here.
|
|
||||||
|
|
||||||
The excerpt preserves each selected unit exactly as represented by the
|
## Citations And Context
|
||||||
validated generic source document. It does not add evidence-context-specific
|
|
||||||
annotations or reshape source-owned metadata.
|
|
||||||
|
|
||||||
## Selection And Citations
|
`evidence_refs` are the authoritative citations. They identify the direct
|
||||||
|
references emitted by accepted normalized artifacts. `context_ref` and the
|
||||||
|
units collection include those cited units plus nearby source units selected by
|
||||||
|
the configured window. They are explanatory context, not widened citations.
|
||||||
|
|
||||||
The framework obtains direct source references only through typed evidence
|
Only accepted outputs from the configured lane allowlist contribute. Rejected,
|
||||||
projections of accepted normalized artifacts in the configured lane allowlist.
|
failed, absent, and lane-filtered outputs do not contribute. The artifact never
|
||||||
It validates each reference against the current source document, expands its
|
contains raw input bytes, prompts, model responses, auxiliary reference
|
||||||
range by `window_units` source-unit positions on each side, clamps at document
|
content, credentials, or filesystem paths.
|
||||||
boundaries, and takes the union of all expanded ranges. The output contains
|
|
||||||
each selected source unit once in source-document position order, regardless
|
|
||||||
of numeric unit IDs. Repeated references, overlapping windows, and citations
|
|
||||||
from multiple lanes do not duplicate a unit. Rejected, failed, absent,
|
|
||||||
inactive, and unselected lanes contribute nothing.
|
|
||||||
|
|
||||||
Normalized lane artifacts remain authoritative for citations and for which lane
|
## Ordering And Compatibility
|
||||||
cited a range. The excerpt has no lane attribution and must not be used to
|
|
||||||
reconstruct it. Its included nearby units provide reading context only; they
|
|
||||||
do not widen any citation in a lane artifact.
|
|
||||||
|
|
||||||
The excerpt contains at most every generic source unit once. It can therefore
|
The selected lane allowlist is lexical. Contexts and units are in source
|
||||||
equal the complete generic source document when coverage is broad or the
|
document position order, not numeric unit-ID order. Direct evidence entries
|
||||||
window is large. No byte-, token-, or compression-size guarantee is made, and
|
are deterministically ordered by lane and source reference. Overlapping or
|
||||||
the framework does not truncate the excerpt to meet an arbitrary size limit.
|
contiguous windows merge, and each source unit appears at most once in the
|
||||||
|
resulting contexts.
|
||||||
## Consumer Responsibilities And Data Handling
|
|
||||||
|
|
||||||
The artifact is additive to the JSON bundle and is not a lane payload,
|
The artifact is additive to the JSON bundle and is not a lane payload,
|
||||||
normalized-output count, checkpoint, or generated reference. Consumers that
|
normalized-output count, checkpoint, or generated reference. Consumers that
|
||||||
do not need it must tolerate an absent descriptor. Consumers that do use it
|
do not need it must tolerate the absent optional descriptor. Consumers that do
|
||||||
should validate the descriptor and payload before use, retain the artifact with
|
use it should preserve the artifact and its schema identity with the run
|
||||||
its schema identity when needed for a run record, and read citations from the
|
provenance, and should treat its source text and metadata as sensitive durable
|
||||||
corresponding normalized lane artifacts.
|
content.
|
||||||
|
|
||||||
The excerpt contains source-unit text and source-owned metadata and is durable
|
|
||||||
output. Treat it as sensitive source content, apply appropriate access controls
|
|
||||||
and retention, and do not assume its selected form is materially smaller or
|
|
||||||
less sensitive than the original input.
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ root for the logical discovery described here.
|
|||||||
| `warnings.json` | Accepted-output and run warnings. |
|
| `warnings.json` | Accepted-output and run warnings. |
|
||||||
| `lanes/<safe-lane-id>.json` | One normalized artifact payload for each lane. |
|
| `lanes/<safe-lane-id>.json` | One normalized artifact payload for each lane. |
|
||||||
| `chunk-map.json` | Optional accepted chunk map, when its export is enabled and available. |
|
| `chunk-map.json` | Optional accepted chunk map, when its export is enabled and available. |
|
||||||
| `evidence-context.json` | Optional selected source-unit excerpt, when evidence publication is enabled. |
|
| `evidence-context.json` | Optional source-context artifact, when evidence publication is enabled. |
|
||||||
|
|
||||||
JSON files are pretty-printed with a trailing newline. Lane payloads are
|
JSON files are pretty-printed with a trailing newline. Lane payloads are
|
||||||
accepted only when their media type is `application/json`.
|
accepted only when their media type is `application/json`.
|
||||||
@@ -71,15 +71,11 @@ 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), [NPC registry](dnd-npc-registry-artifacts.md),
|
[spells](dnd-spell-artifacts.md), [NPCs](dnd-npc-artifacts.md),
|
||||||
[NPC occurrences](dnd-npc-occurrence-artifacts.md),
|
[NPC interactions](dnd-npc-interaction-artifacts.md),
|
||||||
[combat turns](dnd-combat-turn-artifacts.md),
|
[combat turns](dnd-combat-turn-artifacts.md),
|
||||||
[item registry](dnd-item-registry-artifacts.md),
|
[item events](dnd-item-event-artifacts.md), and
|
||||||
[item occurrences](dnd-item-occurrence-artifacts.md),
|
[scene descriptions](dnd-scene-description-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`
|
||||||
|
|
||||||
@@ -102,26 +98,6 @@ 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
|
|
||||||
provenance:
|
|
||||||
|
|
||||||
| Field | Required | Meaning |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `id` | Yes | Selected PromptKit profile identifier. |
|
|
||||||
| `provider` | No | Notarius adapter provider identifier. |
|
|
||||||
| `model` | No | Effective provider model identifier. |
|
|
||||||
| `backend_id` | No | Effective PromptKit backend registration identifier. Endpoint-only profiles omit it. |
|
|
||||||
| `reasoning_effort` | No | Effective opaque provider reasoning setting. An empty or explicitly cleared setting is omitted. |
|
|
||||||
|
|
||||||
These values describe observed execution; they are not a backend-registration
|
|
||||||
interface. Entries that differ by backend or effective reasoning remain
|
|
||||||
distinct even when their profile, provider, and model are otherwise equal.
|
|
||||||
|
|
||||||
## Rejections And Warnings
|
## Rejections And Warnings
|
||||||
|
|
||||||
`rejected.json` is always an object with a `rejected` array. Each entry has
|
`rejected.json` is always an object with a `rejected` array. Each entry has
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
# PromptKit Integration
|
|
||||||
|
|
||||||
Notarius pins
|
|
||||||
[`gitea.maximumdirect.net/eric/promptkit` v0.5.0](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0)
|
|
||||||
as its in-process prompt engine. The upstream
|
|
||||||
[Go package consumer guide](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/consumers/pkg-promptkit.md)
|
|
||||||
owns the public engine API, and the upstream
|
|
||||||
[format reference](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/docs/formats.md)
|
|
||||||
owns prompt, profile, and schema file contracts.
|
|
||||||
|
|
||||||
## Supported Boundary
|
|
||||||
|
|
||||||
Notarius relies on the root `promptkit` package to:
|
|
||||||
|
|
||||||
- construct an `Engine` with filesystem-backed prompt, schema, and optional
|
|
||||||
operator and application-fallback profile sources;
|
|
||||||
- prepare one frozen execution from a `RunRequest` with named inline artifacts,
|
|
||||||
variables, a direct session ID, prompt identity, and profile selection, then
|
|
||||||
record credential-redacted details and run that exact execution;
|
|
||||||
- return rendered debug material, validated structured output, selected
|
|
||||||
profile, backend, effective model metadata, and token usage;
|
|
||||||
- register the optional conventional `local` backend through `BackendLocal`,
|
|
||||||
`LocalBackend`, and `WithBackend`;
|
|
||||||
- distinguish structured-output validation failure from execution failure; and
|
|
||||||
- identify a missing explicit profile through `ErrProfileNotFound` and backend
|
|
||||||
admission exhaustion through `ErrCapacityExceeded`.
|
|
||||||
|
|
||||||
The pinned
|
|
||||||
[`BackendLocal`, `LocalBackend`, and `WithBackend` API](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.5.0/backends.go)
|
|
||||||
owns the registration and backend-capacity contract.
|
|
||||||
|
|
||||||
For one completion, the adapter calls `PrepareExecution`, takes a
|
|
||||||
caller-owned `Details` snapshot, and calls `RunPrepared` for that same opaque
|
|
||||||
prepared execution. It defers `Discard` for every unexecuted handle. Explicit
|
|
||||||
profile preflight uses `Engine.InspectProfile`; it does not prepare a synthetic
|
|
||||||
prompt. PromptKit's prepared handle, inspection result, and capacity-error
|
|
||||||
types stay inside the Notarius LLM adapter.
|
|
||||||
|
|
||||||
When a PromptKit profile and runtime override leave `temperature`, `max_tokens`,
|
|
||||||
or `top_p` unset, Notarius leaves that control unset as well. Compatible
|
|
||||||
providers therefore apply their own defaults; an operator that requires a
|
|
||||||
specific sampling value must select it explicitly in the profile or runtime
|
|
||||||
override.
|
|
||||||
|
|
||||||
Notarius does not use PromptKit's optional `ArtifactReader`. It materializes
|
|
||||||
source and reference content itself and supplies owned inline artifacts at the
|
|
||||||
adapter boundary. It also retains responsibility for pipeline retries,
|
|
||||||
scheduling, debug persistence, redaction, profile provenance, and conversion
|
|
||||||
from private model responses into durable domain artifacts.
|
|
||||||
|
|
||||||
Notarius sends one stable effective session through PromptKit's direct session
|
|
||||||
field, which is authoritative for provider session behavior. It also retains
|
|
||||||
the same value as the `session_id` prompt variable for maintained prompt
|
|
||||||
compatibility. The generated identifier is 76 ASCII characters, within
|
|
||||||
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
|
|
||||||
setting as optional run-manifest provenance. Endpoint-only profiles have no
|
|
||||||
backend ID. Debug prompt material also retains the selected backend ID and
|
|
||||||
PromptKit's stable lower-case `effective_model_params` JSON, which may include
|
|
||||||
`backend_id`. Notarius production configuration exposes one optional
|
|
||||||
conventional `local` registration. It does not expose a general user-defined
|
|
||||||
PromptKit backend registry. Endpoint-only profiles remain supported unchanged.
|
|
||||||
|
|
||||||
Notarius retains its application-wide scheduled client around the PromptKit
|
|
||||||
adapter. PromptKit may apply a narrower limit for the selected backend;
|
|
||||||
endpoint-only profiles have no such backend limit. The adapter translates
|
|
||||||
PromptKit capacity rejection into the provider-neutral Notarius
|
|
||||||
`ErrLLMCapacityExceeded` contract. It may include the normalized selected
|
|
||||||
backend ID in safe diagnostic context, without exposing PromptKit's capacity
|
|
||||||
error type, and leaves retries to the calling pipeline stage.
|
|
||||||
|
|
||||||
## Profile Sources And Compatibility
|
|
||||||
|
|
||||||
Notarius gives PromptKit the configured operator profile source, registered
|
|
||||||
application fallback profile assets, and optional backend registration through
|
|
||||||
the same construction path for inspection and execution. PromptKit owns the
|
|
||||||
resulting source precedence and strict profile parsing: a matching operator
|
|
||||||
profile is a complete replacement for a fallback or built-in profile, while an
|
|
||||||
invalid matching document fails instead of falling through. The operator
|
|
||||||
configuration and deployment workflow are defined in
|
|
||||||
[Configuration](../config.md#promptkit-profiles) and
|
|
||||||
[Operations](../operations.md#promptkit-profile-deployment).
|
|
||||||
|
|
||||||
Notarius supports this boundary against PromptKit v0.5.0. Its fallback source,
|
|
||||||
prepared-execution, inspection, and typed capacity APIs are used as public
|
|
||||||
upstream contracts; other PromptKit APIs or file-format behavior are not
|
|
||||||
implicitly supported. A dependency upgrade requires reviewing the adapter,
|
|
||||||
profile-source construction, and this compatibility statement against the
|
|
||||||
pinned upstream documentation.
|
|
||||||
|
|
||||||
## Notarius Ownership
|
|
||||||
|
|
||||||
[LLM Runtime Internals](../internal/llm.md) describes how Notarius mounts
|
|
||||||
module assets, maps its transport-neutral completion contract, prepares and
|
|
||||||
executes requests, validates output, records provenance, captures debug
|
|
||||||
material, redacts errors, and preserves timeout ownership.
|
|
||||||
[D&D Module Internals](../internal/dnd.md) owns the embedded
|
|
||||||
`dnd-extraction` fallback profile and the maintained D&D prompt defaults.
|
|
||||||
[Configuration](../config.md#promptkit-profiles) defines how a Notarius
|
|
||||||
configuration selects one PromptKit profile source and optionally registers
|
|
||||||
the conventional local backend.
|
|
||||||
|
|
||||||
PromptKit API or format changes outside this boundary are not implicitly
|
|
||||||
supported. Updating the pinned version requires reviewing the adapter and
|
|
||||||
profile/configuration contracts against the upstream documentation.
|
|
||||||
@@ -38,14 +38,9 @@ in [Configuration Internals](configuration.md).
|
|||||||
|
|
||||||
Configuration validation without a selected pipeline checks structural
|
Configuration validation without a selected pipeline checks structural
|
||||||
configuration only. Validation with a selected pipeline also builds the
|
configuration only. Validation with a selected pipeline also builds the
|
||||||
effective catalog, resolves the pipeline, and verifies every explicit effective
|
effective catalog, resolves the pipeline, and verifies explicitly selected
|
||||||
PromptKit profile. Selected LLM-backed input, chunk, lane, output, and validator
|
Scriptorium profiles. Pipeline listing validates configuration before returning
|
||||||
profiles are inspected
|
normalized, sorted identifiers.
|
||||||
against the configured PromptKit source and backend registrations without
|
|
||||||
loading a prompt or performing generation, so an unknown or invalid profile
|
|
||||||
fails before pipeline preparation. Credential availability remains an
|
|
||||||
execution-time concern. Pipeline listing validates configuration before
|
|
||||||
returning normalized, sorted identifiers.
|
|
||||||
|
|
||||||
## Production Composition
|
## Production Composition
|
||||||
|
|
||||||
@@ -56,24 +51,12 @@ catalog used for resolution and the concrete constructors used for preparation.
|
|||||||
Tests may provide a catalog or registries instead; production code must not
|
Tests may provide a catalog or registries instead; production code must not
|
||||||
silently merge an injected partial catalog with production registrations.
|
silently merge an injected partial catalog with production registrations.
|
||||||
|
|
||||||
The production LLM factory builds one PromptKit-backed client from the resolved
|
The production LLM factory builds the Scriptorium-backed client from resolved
|
||||||
**promptkit.profile_dir** or **promptkit.profile_file** source, attaches the
|
configuration, creates one scheduler from the effective global LLM limit, and
|
||||||
profile-provenance recorder, creates one scheduler from the effective global
|
wraps the client before it reaches modules. Registration and LLM construction
|
||||||
LLM limit, and wraps the client before it reaches modules. Registration and LLM
|
errors are returned before a pipeline is prepared. Concrete module keys and
|
||||||
construction errors are returned before a pipeline is prepared. Configuration
|
validator chains are public configuration choices and remain documented in
|
||||||
field definitions remain in [Configuration](../config.md#promptkit-profiles);
|
[Configuration](../config.md).
|
||||||
the D&D registrar's fallback profile assets and the adapter mechanics remain in
|
|
||||||
[LLM Runtime](llm.md).
|
|
||||||
|
|
||||||
The factory also accepts `LLMRuntimeOverrides`, whose reasoning pointer
|
|
||||||
preserves inherit, replace, and clear states across the composition boundary.
|
|
||||||
Run orchestration constructs this value from the mutually exclusive
|
|
||||||
`--reasoning-effort` and `--clear-reasoning-effort` controls. Absence preserves
|
|
||||||
a nil pointer, replacement is trimmed, and clear uses a non-nil empty string.
|
|
||||||
The same override reaches the one shared production client, checkpoint
|
|
||||||
identity, and debug invocation metadata. Persistent reasoning configuration
|
|
||||||
remains owned by PromptKit profiles; Notarius configuration has no reasoning
|
|
||||||
field.
|
|
||||||
|
|
||||||
## Run Orchestration
|
## Run Orchestration
|
||||||
|
|
||||||
@@ -85,15 +68,12 @@ handoff:
|
|||||||
2. create and validate a safe run identity, then allocate a debug bundle only
|
2. create and validate a safe run identity, then allocate a debug bundle only
|
||||||
when requested;
|
when requested;
|
||||||
3. build the effective catalog, resolve requested reference changes, resolve
|
3. build the effective catalog, resolve requested reference changes, resolve
|
||||||
the effective pipeline, and inspect its explicit effective PromptKit
|
the effective pipeline, and verify explicit Scriptorium 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, and prepared modules;
|
5. construct registries, the scheduled LLM client, prepared modules, and the
|
||||||
6. read the source input once, resolve its effective session from the explicit
|
requested cache/checkpoint collaborators;
|
||||||
override or resolved input module and raw bytes, then construct requested
|
6. read the source input and invoke the framework runner; and
|
||||||
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.
|
||||||
|
|
||||||
@@ -104,13 +84,6 @@ 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
|
||||||
|
|||||||
@@ -39,22 +39,14 @@ This establishes the public precedence order without giving environment input a
|
|||||||
second file schema. Loading and application reject malformed YAML, unsupported
|
second file schema. Loading and application reject malformed YAML, unsupported
|
||||||
file versions, unknown fields, invalid values, and identifiers that are empty
|
file versions, unknown fields, invalid values, and identifiers that are empty
|
||||||
or collide after whitespace normalization. The file application also makes the
|
or collide after whitespace normalization. The file application also makes the
|
||||||
effective extraction-worker default follow the effective LLM limit. A present
|
effective extraction-worker default follow the effective LLM limit.
|
||||||
PromptKit local-backend object requires and trims its endpoint, defaults its
|
|
||||||
omitted concurrency limit to zero, and is copied so the parsed file model
|
|
||||||
cannot alias the populated **Config**. A pipeline `llm_profile` is
|
|
||||||
presence-aware: omission remains empty, while a present blank value is
|
|
||||||
rejected and a non-empty file value is trimmed before it reaches **Config**.
|
|
||||||
|
|
||||||
**Config.Validate** checks configuration-only invariants before resolution. It
|
**Config.Validate** checks configuration-only invariants before resolution. It
|
||||||
rejects incompatible profile sources, invalid state-surface values, unsupported
|
rejects incompatible profile sources, invalid state-surface values, unsupported
|
||||||
concurrency settings, malformed bindings and references, invalid retries, and
|
concurrency settings, malformed bindings and references, invalid retries, and
|
||||||
invalid pipeline, step, or lane structure. PromptKit local-backend validation
|
invalid pipeline, step, or lane structure. Its errors retain the closest known
|
||||||
accepts only an absolute HTTP or HTTPS endpoint with a host and no user
|
pipeline, lane, and binding context. It deliberately does not require modules
|
||||||
information, query, or fragment, and rejects a negative local concurrency
|
to be registered: that requires a catalog and belongs to resolution.
|
||||||
limit. Its errors retain the closest known pipeline, lane, and binding context.
|
|
||||||
It deliberately does not require modules to be registered: that requires a
|
|
||||||
catalog and belongs to resolution.
|
|
||||||
|
|
||||||
The exact user-selectable values and validation rules are defined in
|
The exact user-selectable values and validation rules are defined in
|
||||||
[Configuration](../config.md). Keep additions to the file model, an
|
[Configuration](../config.md). Keep additions to the file model, an
|
||||||
@@ -64,15 +56,13 @@ environment override, its validation, and that reference in the same change.
|
|||||||
|
|
||||||
**Config.Resolve** first recomputes derived concurrency defaults and validates
|
**Config.Resolve** first recomputes derived concurrency defaults and validates
|
||||||
the configuration. It normalizes the requested pipeline ID, copies the selected
|
the configuration. It normalizes the requested pipeline ID, copies the selected
|
||||||
profile, and passes the non-empty command-level LLM profile override, requested
|
profile, applies a non-empty command-level LLM profile override to the
|
||||||
lane selection, and reference changes to the framework resolver.
|
LLM-capable stage bindings, and calls the framework resolver with the requested
|
||||||
|
lane selection and reference changes.
|
||||||
|
|
||||||
After module and validator selection, the resolver applies the effective
|
The command-level override does not replace an explicitly selected validator
|
||||||
profile policy to LLM-backed bindings only: command override, binding profile,
|
profile. Validator bindings remain part of the resolved validator chain and
|
||||||
pipeline profile, then the prompt default. Deterministic bindings remain
|
are resolved under their own declared configuration.
|
||||||
profile-free, and no second inheritance decision occurs during execution. The
|
|
||||||
public field definitions and precedence are owned by
|
|
||||||
[Configuration](../config.md#pipelines).
|
|
||||||
|
|
||||||
The framework resolver supplies defaults, selects lanes, resolves validator
|
The framework resolver supplies defaults, selects lanes, resolves validator
|
||||||
chains, checks registered module and artifact compatibility, validates module
|
chains, checks registered module and artifact compatibility, validates module
|
||||||
@@ -81,8 +71,7 @@ options, and returns the fixed ordered pipeline shape. The resulting
|
|||||||
changes, a clone of the input configuration, and the resolved pipeline.
|
changes, a clone of the input configuration, and the resolved pipeline.
|
||||||
Callers may therefore retain or modify their input slices and maps without
|
Callers may therefore retain or modify their input slices and maps without
|
||||||
changing the resolved result, and later consumers cannot mutate the original
|
changing the resolved result, and later consumers cannot mutate the original
|
||||||
configuration through the effective value. This ownership includes the nested
|
configuration through the effective value.
|
||||||
PromptKit local-backend value.
|
|
||||||
|
|
||||||
Resolution failures stop before module construction and source parsing. They
|
Resolution failures stop before module construction and source parsing. They
|
||||||
include an error path for an unconfigured pipeline, missing module, missing
|
include an error path for an unconfigured pipeline, missing module, missing
|
||||||
@@ -94,8 +83,7 @@ runtime error class described in the [CLI reference](../cli.md#output-streams-an
|
|||||||
|
|
||||||
The framework assigns the resolved pipeline a deterministic SHA-256 digest
|
The framework assigns the resolved pipeline a deterministic SHA-256 digest
|
||||||
after defaults, lane selection, module bindings, reference bindings, validator
|
after defaults, lane selection, module bindings, reference bindings, validator
|
||||||
chains, effective LLM profiles, and artifact schema identity have been
|
chains, and artifact schema identity have been resolved. The digest excludes
|
||||||
resolved. The digest excludes
|
|
||||||
its own stored value. It identifies resolved composition rather than raw YAML
|
its own stored value. It identifies resolved composition rather than raw YAML
|
||||||
bytes, a debug payload, or all runtime state. The CLI records it as invocation
|
bytes, a debug payload, or all runtime state. The CLI records it as invocation
|
||||||
provenance before execution; cache and checkpoint identity have additional
|
provenance before execution; cache and checkpoint identity have additional
|
||||||
@@ -106,12 +94,9 @@ Configuration summaries must use **Redacted**, **RedactedSummaryPayload**, or
|
|||||||
Those methods copy every binding and nested option container, replace values
|
Those methods copy every binding and nested option container, replace values
|
||||||
whose key is credential-shaped with **[REDACTED]**, and omit materialized
|
whose key is credential-shaped with **[REDACTED]**, and omit materialized
|
||||||
reference content while retaining safe binding and reference provenance. The
|
reference content while retaining safe binding and reference provenance. The
|
||||||
payload must not alias the source configuration or resolved pipeline.
|
payload must not alias the source configuration or resolved pipeline. This
|
||||||
PromptKit's local endpoint and concurrency limit are preserved as non-secret
|
redaction is deliberately narrow: it protects configuration summaries and does
|
||||||
configuration metadata in the independently owned summary; the object contains
|
not authorize recording arbitrary environment values or provider requests.
|
||||||
no credential value. This redaction is deliberately narrow: it protects
|
|
||||||
configuration summaries and does not authorize recording arbitrary environment
|
|
||||||
values or provider requests.
|
|
||||||
|
|
||||||
## Invariants To Preserve
|
## Invariants To Preserve
|
||||||
|
|
||||||
|
|||||||
@@ -7,34 +7,25 @@ selectable keys, bindings, reference syntax, and default validator chains.
|
|||||||
|
|
||||||
## Durable Artifact Contracts
|
## Durable Artifact Contracts
|
||||||
|
|
||||||
The ten lanes have separate durable wire contracts. This guide deliberately
|
The six 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) |
|
||||||
| NPC registry | [NPC registry artifacts](../integrations/dnd-npc-registry-artifacts.md) |
|
| NPCs | [NPC artifacts](../integrations/dnd-npc-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 occurrences | [item-occurrence artifacts](../integrations/dnd-item-occurrence-artifacts.md) |
|
| Item events | [item-event artifacts](../integrations/dnd-item-event-artifacts.md) |
|
||||||
| Item registry | [item-registry artifacts](../integrations/dnd-item-registry-artifacts.md) |
|
| NPC interactions | [NPC-interaction artifacts](../integrations/dnd-npc-interaction-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
|
||||||
|
|
||||||
The D&D registrar registers the family’s artifact codecs, extractors, typed
|
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, and default
|
||||||
profile asset, and default validator chains. Each extractor and normalizer has
|
validator chains. Each extractor and normalizer has a stable module spec,
|
||||||
a stable module spec, explicit execution class, strict option decoding, and a
|
strict option decoding, and a typed builder. Configuration remains the
|
||||||
typed builder. Scene chunking, every extractor, and NPC, location, and item-registry
|
canonical owner of the exact keys and validator order.
|
||||||
normalization are registered as `llm_backed`; the remaining current D&D mergers
|
|
||||||
and normalizers are `deterministic`. The metadata is available to catalog inspection and
|
|
||||||
resolved-pipeline debug data and determines which selected bindings inherit the
|
|
||||||
pipeline profile. Configuration remains the canonical owner of the exact keys,
|
|
||||||
profile precedence, and validator order.
|
|
||||||
|
|
||||||
Private structured-LLM response schemas are deliberately minimal. They reject
|
Private structured-LLM response schemas are deliberately minimal. They reject
|
||||||
invalid JSON structure, missing required fields, incompatible types, and
|
invalid JSON structure, missing required fields, incompatible types, and
|
||||||
@@ -44,69 +35,19 @@ the contracts above define durable data.
|
|||||||
|
|
||||||
## Prompt Construction
|
## Prompt Construction
|
||||||
|
|
||||||
D&D LLM-facing content lives beneath `assets/dnd/`. Each module contributes a
|
D&D extractors assemble prompts from an ordered manifest of shared and
|
||||||
local `prompt.yaml` declaration and `instructions.md`; input-specific files
|
module-owned assets. Reuse the shared D&D system, evidence, identity,
|
||||||
such as a catalog, registry, grounding projection, or candidate collection are
|
reference, and transcript assets instead of copying their text into individual
|
||||||
local only when that module needs them. New extractor content uses its feature
|
modules. A manifest’s declared sequence, including cache-control placement, is
|
||||||
subtree, while families with both extraction and normalization content use their
|
part of the prompt behavior, and the chunk transcript is the final message.
|
||||||
`extract` and `normalize` subtrees. Shared visual-provenance fragments use
|
Preserve that order when changing an extractor or its assets so prompt-cache
|
||||||
the `common-dnd-` prefix. Production lane code belongs with its D&D codec,
|
behavior remains stable.
|
||||||
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
|
All extractors use the shared prompt-input preparation rules. The current chunk
|
||||||
assets are selected, their mount paths, their message order, cache controls,
|
is copied into transcript material; player, party, glossary, and compatible
|
||||||
and the files included in its prompt fingerprint. Shared fragments belong to
|
campaign references are context for disambiguation, not source evidence.
|
||||||
the D&D shared implementation and are selected by name rather than copied into
|
Reference prompt material is canonically ordered before it is rendered, which
|
||||||
module directories. The root `assets` package is a content-only boundary; its
|
keeps equivalent inputs stable across runs.
|
||||||
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
|
|
||||||
profile. The D&D registrar registers the fallback, while an operator can
|
|
||||||
replace it with a complete profile of the same ID from the configured PromptKit
|
|
||||||
source. Deployment profile selection is documented in
|
|
||||||
[Configuration](../config.md#promptkit-profiles).
|
|
||||||
|
|
||||||
The D&D transcript assets have distinct consumers. Scene chunking consumes the
|
|
||||||
complete-session `common-dnd-transcript-full.md`, while extraction prompts
|
|
||||||
consume the current-chunk `common-dnd-transcript-chunk.md`. NPC, location, and
|
|
||||||
item normalization instead mount the generic semantic-reconciliation
|
|
||||||
candidate and transcript-window presentation assets. Player, party, glossary,
|
|
||||||
and compatible campaign references provide disambiguating context only when
|
|
||||||
declared by the active prompt; they never establish evidence. Reference
|
|
||||||
material is canonically ordered before rendering so equivalent inputs remain
|
|
||||||
stable.
|
|
||||||
|
|
||||||
Extraction prompts render the common system and identity messages first, then
|
|
||||||
cached campaign references and the cached chunk transcript. Evidence policy and
|
|
||||||
any lane-specific registry, catalog, or grounding projection follow that
|
|
||||||
prefix. The final module instructions message is ephemeral. This keeps the
|
|
||||||
reusable extraction prefix identical while preserving the lane-specific suffix.
|
|
||||||
|
|
||||||
Scene chunking intentionally uses a different order: system, cached campaign
|
|
||||||
references, uncached module instructions, then the final ephemeral full
|
|
||||||
transcript. Entity normalization also has its own order: D&D system, mandatory
|
|
||||||
generic protocol, ephemeral domain semantic instructions, generic candidate
|
|
||||||
presentation, and final ephemeral generic transcript windows. These orders and
|
|
||||||
cache controls are prompt behavior; change them only through the owning
|
|
||||||
manifest and prompt declaration.
|
|
||||||
|
|
||||||
## Evidence, Candidates, And Normalization
|
## Evidence, Candidates, And Normalization
|
||||||
|
|
||||||
@@ -119,63 +60,19 @@ result.
|
|||||||
|
|
||||||
Default chains keep responsibilities separate: structural validators assess the
|
Default chains keep responsibilities separate: structural validators assess the
|
||||||
candidate, source-reference validators resolve cited ranges against the current
|
candidate, source-reference validators resolve cited ranges against the current
|
||||||
source and require extraction evidence to stay within the current chunk,
|
source, durable-schema validation checks an approved representation, and
|
||||||
durable-schema validation checks an approved representation, and
|
|
||||||
relatedness validators report advisory evidence concerns. The configured order
|
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).
|
||||||
|
|
||||||
Enemy-event extraction additionally rejects a second `engaged` observation for
|
Normalizers are deterministic for spells, combat turns, item events, NPC
|
||||||
the same comparison identity within one scene-scoped result. Normalization may
|
interactions, and scene descriptions. They canonicalize display values and
|
||||||
combine results from distinct scenes, so it intentionally does not apply that
|
evidence, use source-document order for stable output, and issue bounded
|
||||||
rule. Configuration owns the exact validator key and chain position.
|
warnings for changes or collapsed duplicates. The NPC normalizer is the
|
||||||
|
intentional exception: it first produces a deterministic candidate set, then
|
||||||
Normalizers are deterministic for spells, combat turns, item occurrences, NPC
|
uses a bounded structured-LLM proposal to reconcile identity groups. Invalid
|
||||||
occurrences, scene descriptions, enemy events, and location occurrences. They
|
or unusable proposals retain the deterministic result and surface retry or
|
||||||
canonicalize display values and evidence, use source-document order for stable
|
fallback diagnostics; the model does not directly replace durable records.
|
||||||
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.
|
|
||||||
|
|
||||||
## Semantic Registry Reconciliation
|
|
||||||
|
|
||||||
The three registry normalizers instantiate the domain-neutral
|
|
||||||
`internal/framework/semanticreconcile` engine with default bounds. Each
|
|
||||||
eligible candidate receives a contiguous, one-based `candidate_id` for that
|
|
||||||
request. The model sees that handle, the candidate label and source-free
|
|
||||||
evidence ranges, plus bounded transcript windows; it returns only duplicate
|
|
||||||
groups of supplied handles and one supplied canonical handle per group. It
|
|
||||||
never returns names, evidence, durable IDs, or replacement records. Identical
|
|
||||||
labels and evidence remain independently selectable because their handles are
|
|
||||||
distinct.
|
|
||||||
|
|
||||||
The generic core owns the mandatory handle protocol, candidate and transcript
|
|
||||||
presentation, the private response schema, source-reference validation,
|
|
||||||
candidate and combined-material limits, structured completion, proposal
|
|
||||||
assessment, stable group ordering, and typed plan-application mechanics. The
|
|
||||||
D&D prompt contributes its system message and registry-specific semantic
|
|
||||||
instructions. The generic registrar registers the shared prompt and schema;
|
|
||||||
the D&D registrar registers each consuming prompt and the fallback profile.
|
|
||||||
|
|
||||||
Fewer than two eligible candidates skips the LLM without a semantic warning.
|
|
||||||
An exceeded bound also skips the call and preserves the deterministic
|
|
||||||
preprocessed registry, adding the registry's bounded fallback warning. Invalid
|
|
||||||
structured output or discarded proposal groups use the normalizer's existing
|
|
||||||
retry contract; retry exhaustion preserves the safe deterministic or
|
|
||||||
partially applied result and emits its bounded fallback warning. Provider,
|
|
||||||
transport, cancellation, and context-material failures remain execution
|
|
||||||
errors.
|
|
||||||
|
|
||||||
Application remains typed and registry-owned. All three policies select the
|
|
||||||
canonical member's normalized display name, union member evidence in source
|
|
||||||
order, preserve ungrouped records, and derive durable identity only after
|
|
||||||
consolidation. NPC IDs derive from the final name. Item IDs also derive from
|
|
||||||
the final name, and a typed guard prevents currency aliases from crossing
|
|
||||||
denominations or mixing currency with non-currency records. Location IDs
|
|
||||||
derive from the final name and final evidence, preserving same-name,
|
|
||||||
parent/child, and distinct physical-place identities. Registry warning scopes,
|
|
||||||
reason codes, and postconditions remain outside the generic core.
|
|
||||||
|
|
||||||
## Generated References And Grounding
|
## Generated References And Grounding
|
||||||
|
|
||||||
@@ -185,55 +82,30 @@ 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 and item registry consumers receive names-only grounding. Location
|
NPC registries are names-only grounding projections: they may canonicalize
|
||||||
consumers receive a contextual selector containing the canonical name and the
|
actors for spells and combat turns and are required for NPC interactions, but
|
||||||
registry references needed to distinguish same-name places. The calling module
|
they do not supply evidence. Scene-description registries are eligibility-only
|
||||||
resolves those supplied selections locally and maps them into the unchanged
|
projections: they retain the current chunk’s classification data, not scene
|
||||||
durable ID/name pair; an unknown or ambiguous selection rejects the complete
|
prose or evidence, and exist to route combat extraction.
|
||||||
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. |
|
||||||
| 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. |
|
| NPCs | Does not consume an NPC registry. 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 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. |
|
| Item events | Uses campaign context for disambiguation but has no NPC-registry or scene-description dependency. |
|
||||||
| 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 interactions | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. |
|
||||||
| 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# LLM Runtime Internals
|
# LLM Runtime Internals
|
||||||
|
|
||||||
`internal/framework/llm` is Notarius’s provider-independent structured
|
`internal/framework/llm` is Notarius’s provider-independent structured
|
||||||
completion boundary. It adapts framework requests to PromptKit, bounds
|
completion boundary. It adapts framework requests to Scriptorium, bounds
|
||||||
provider calls, assembles registered prompt and schema assets, records selected
|
provider calls, assembles registered prompt and schema assets, records selected
|
||||||
profiles, and redacts provider errors. The architectural boundary is defined in
|
profiles, and redacts provider errors. The architectural boundary is defined in
|
||||||
[Architecture](../policy/architecture.md#llm-boundary); profile sources,
|
[Architecture](../policy/architecture.md#llm-boundary); profile sources,
|
||||||
credentials, and concurrency settings belong in
|
credentials, and concurrency settings belong in
|
||||||
[Configuration](../config.md#promptkit-profiles) and
|
[Configuration](../config.md#scriptorium-profiles) and
|
||||||
[Configuration](../config.md#concurrency-output-cache-and-debug).
|
[Configuration](../config.md#concurrency-output-cache-and-debug).
|
||||||
|
|
||||||
## Structured Completion Boundary
|
## Structured Completion Boundary
|
||||||
@@ -24,86 +24,25 @@ 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
|
`ScriptoriumClient` validates the request target and prompt identity, maps each
|
||||||
application identity; PromptKit and this adapter do not own entity identity.
|
named material to a Scriptorium inline artifact while preserving its origin URI,
|
||||||
|
forwards session and profile selection, then prepares and runs the prompt. It
|
||||||
|
returns Scriptorium’s validated raw bytes rather than re-encoding the decoded
|
||||||
|
target. An empty optional material is represented as one space so its named
|
||||||
|
input is retained by Scriptorium.
|
||||||
|
|
||||||
`PromptKitClient` validates the request target and prompt identity, maps each
|
An empty request profile lets the prompt select its configured default. The CLI
|
||||||
named material to a PromptKit inline artifact while preserving its origin URI,
|
prepares every explicitly selected binding profile before a run begins, so a
|
||||||
passes the supplied request session through to PromptKit's direct per-run
|
missing explicit profile fails before stage execution. Calls record the profile
|
||||||
session field, retains the same value as the `session_id` prompt variable for
|
actually selected by Scriptorium; the recorder deduplicates non-secret profile
|
||||||
maintained prompt compatibility, and forwards profile selection. It does not
|
identity, provider, and model values for manifest use.
|
||||||
derive or replace session values; the CLI owns that policy. It then creates one
|
|
||||||
frozen prepared execution, captures its caller-owned credential-redacted
|
|
||||||
details for debug material, and executes that exact snapshot through
|
|
||||||
PromptKit's prepared-execution boundary. The direct field
|
|
||||||
is authoritative for provider session behavior. A session ID is a stable,
|
|
||||||
non-secret correlation identifier and may be exposed to providers and provider
|
|
||||||
observability. The adapter returns PromptKit’s validated raw bytes rather than
|
|
||||||
re-encoding the decoded target. An empty optional material is represented as
|
|
||||||
one space so its named input is retained by PromptKit.
|
|
||||||
|
|
||||||
Client construction may also receive a run-wide reasoning-effort override from
|
|
||||||
the CLI factory boundary. The adapter copies the caller-owned pointer and
|
|
||||||
creates a fresh PromptKit execution override for each request: a nil pointer
|
|
||||||
inherits the selected profile, a non-empty value replaces it, and an empty
|
|
||||||
value clears inherited reasoning. The CLI's mutually exclusive
|
|
||||||
`--reasoning-effort` and `--clear-reasoning-effort` controls select those
|
|
||||||
states. With neither flag, profile behavior remains unchanged. Because
|
|
||||||
production constructs one shared client, the selected state applies uniformly
|
|
||||||
to module calls, retries, and LLM-backed validators for the whole run.
|
|
||||||
|
|
||||||
An empty request profile lets the prompt select its configured default. Before a
|
|
||||||
run begins, the CLI asks the adapter to inspect every explicit profile on the
|
|
||||||
resolved selected LLM-backed bindings and validators, including inherited
|
|
||||||
pipeline profiles. Inspection resolves the profile and its selected backend and
|
|
||||||
target without loading a prompt, reading credentials, admitting capacity, or
|
|
||||||
contacting a provider, so a missing or invalid explicit profile fails before
|
|
||||||
stage execution while a valid `api_key_env` may remain unset. Calls record the
|
|
||||||
profile actually selected by PromptKit. The recorder trims and deduplicates
|
|
||||||
non-secret profile identity, provider, model, selected backend ID, and
|
|
||||||
effective reasoning values for manifest use. Entries that differ in backend or
|
|
||||||
reasoning remain distinct and deterministically ordered. Endpoint-only profiles
|
|
||||||
retain an empty backend ID, which the published JSON omits. Successful
|
|
||||||
completion responses and recorded profile manifests identify the adapter
|
|
||||||
provider as `promptkit`.
|
|
||||||
|
|
||||||
The CLI's profile-inspection engine and the production adapter use the same
|
|
||||||
profile-source construction to apply the configured profile directory or file,
|
|
||||||
the optional registered fallback profile assets, and the optional conventional
|
|
||||||
`local` backend. Preflight therefore resolves the same profile sources and
|
|
||||||
backend membership as runtime without performing generation. Fallback assets
|
|
||||||
are mounted only when at least one source is registered. The production D&D
|
|
||||||
registrar contributes its `dnd-extraction` fallback, and the maintained D&D
|
|
||||||
prompts select that logical ID by default. PromptKit owns source precedence and
|
|
||||||
profile parsing: an operator-provided matching profile takes precedence over a
|
|
||||||
fallback profile without Notarius merging either document.
|
|
||||||
When the registration is absent, a profile selecting `backend: local` fails
|
|
||||||
inspection instead of falling back to a built-in or endpoint-only target.
|
|
||||||
|
|
||||||
Before execution, the adapter also contributes a non-secret checkpoint
|
|
||||||
fingerprint for the effective PromptKit profile source. It combines the
|
|
||||||
identity of PromptKit's compiled-in profile catalog with a deterministic digest
|
|
||||||
of every YAML profile in the configured profile directory, or of the configured
|
|
||||||
profile file, and a deterministic digest of the flattened fallback profile
|
|
||||||
assets. The fingerprint contains neither profile content nor source paths. It
|
|
||||||
covers inherited pipeline profiles, explicit binding profiles, and
|
|
||||||
prompt-selected defaults, so changing a model or other profile setting cannot
|
|
||||||
reuse checkpoints created under the
|
|
||||||
prior profile source. This cache identity is independent of durable
|
|
||||||
profile provenance: run manifests continue to list only profiles actually
|
|
||||||
observed during LLM calls. When the local backend is registered, a second
|
|
||||||
fingerprint hashes its trimmed endpoint behind a stable marker. Changing that
|
|
||||||
semantic execution target invalidates checkpoint reuse. The raw endpoint is not
|
|
||||||
stored in checkpoint identity, and the local concurrency limit is excluded
|
|
||||||
because it changes scheduling rather than execution semantics.
|
|
||||||
|
|
||||||
## Shared Provider-Call Limit
|
## Shared Provider-Call Limit
|
||||||
|
|
||||||
Production construction creates one PromptKit client and wraps it in one
|
Production construction creates one Scriptorium client and wraps it in one
|
||||||
scheduled client. The scheduler has a fixed, positive permit limit, serves
|
scheduled client. The scheduler has a fixed, positive permit limit, serves
|
||||||
queued calls in FIFO order, and removes a queued call when its context is
|
queued calls in FIFO order, and removes a queued call when its context is
|
||||||
cancelled. It rechecks the caller context after admission and before dispatch.
|
cancelled. A granted permit is released exactly once on every completion path.
|
||||||
A granted permit is released exactly once on every completion path.
|
|
||||||
|
|
||||||
The scheduled wrapper surrounds every `CompleteStructured` call, so concurrent
|
The scheduled wrapper surrounds every `CompleteStructured` call, so concurrent
|
||||||
lanes, pipeline retries, and LLM-backed validators share the same provider-call
|
lanes, pipeline retries, and LLM-backed validators share the same provider-call
|
||||||
@@ -112,57 +51,22 @@ worker counts cannot exceed the configured LLM limit. The configuration field
|
|||||||
and its effective default are owned by
|
and its effective default are owned by
|
||||||
[Configuration](../config.md#concurrency-output-cache-and-debug).
|
[Configuration](../config.md#concurrency-output-cache-and-debug).
|
||||||
|
|
||||||
PromptKit applies a second, independent admission limit when the selected
|
|
||||||
profile names a limited backend. It sits beneath the Notarius scheduled client,
|
|
||||||
so it may narrow but cannot expand the application-wide limit. Built-in
|
|
||||||
OpenRouter profiles select PromptKit's reserved backend and its upstream
|
|
||||||
capacity policy. A positive configured local-backend limit bounds active local
|
|
||||||
generations inside PromptKit; zero leaves that backend unlimited there.
|
|
||||||
Endpoint-only profiles do not select a PromptKit backend and remain limited
|
|
||||||
only by the Notarius scheduler.
|
|
||||||
|
|
||||||
## Prompt And Schema Assets
|
## Prompt And Schema Assets
|
||||||
|
|
||||||
An `AssetRegistry` collects prompt, schema, and optional fallback-profile
|
An `AssetRegistry` collects prompt and schema filesystems from production module
|
||||||
filesystems from production module families. It flattens registered roots into
|
families. It flattens registered roots into the Scriptorium filesystems and
|
||||||
the corresponding PromptKit filesystems and rejects invalid roots, unreadable
|
rejects invalid roots, unreadable assets, duplicate paths, and missing prompt
|
||||||
assets, duplicate paths, and missing prompt or schema files during preparation.
|
or schema files during preparation. The framework’s `promptfs` helper combines
|
||||||
Fallback assets receive a safe content digest for checkpoint identity; raw
|
module-owned prompt files with reusable domain fragments without making the
|
||||||
paths and bytes are never included. The framework’s `promptfs` helper combines
|
|
||||||
module-selected prompt files with reusable domain fragments without making the
|
|
||||||
framework depend on D&D content.
|
framework depend on D&D content.
|
||||||
|
|
||||||
LLM-facing content is embedded once by the root `assets` package. Each consumer
|
Each LLM-backed module owns its prompt declaration, package-specific assets,
|
||||||
uses only its scoped subtree, while the module retains ownership of its prompt
|
and private response schema. Shared D&D wording is owned by the D&D shared
|
||||||
declaration, ordered manifest, private response-schema identity, and
|
asset package; the detailed D&D conventions are in
|
||||||
registration. Shared D&D fragments are selected by D&D's shared implementation;
|
[D&D Module Internals](dnd.md). The mounted prompt assets used by a module also
|
||||||
the detailed convention is in [D&D Module Internals](dnd.md). This physical
|
determine its prompt fingerprint. Schema loaders validate JSON, attach identity
|
||||||
arrangement and its data-only boundary are defined by
|
and digest metadata, make defensive copies, and expose diagnostics without raw
|
||||||
[Architecture](../policy/architecture.md) and
|
schema bytes.
|
||||||
[ADR-0011](../adr/0011-centralize-llm-assets.md), rather than by this runtime
|
|
||||||
guide.
|
|
||||||
|
|
||||||
The generic registrar is the sole production registration owner for the
|
|
||||||
semantic-reconciliation default prompt and private response schema. The
|
|
||||||
domain-neutral reconciliation package also exposes only its mandatory protocol
|
|
||||||
and candidate/transcript presentation files for domain prompt manifests. D&D
|
|
||||||
registry normalizers mount those files while retaining ownership and hashing
|
|
||||||
of their D&D system message, semantic instructions, and complete prompt
|
|
||||||
declaration. The response schema is therefore registered once even though
|
|
||||||
several typed normalizers select it.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Semantic-reconciliation normalizers extend this identity with the shared
|
|
||||||
response-schema digest, framework policy version, and complete limit-policy
|
|
||||||
digest. Their manifest metadata records the same content-free prompt, schema,
|
|
||||||
policy, and limit identities together with domain identity and normalization
|
|
||||||
policies. Request-local handles, source material, proposal content, and raw
|
|
||||||
asset bytes are not checkpoint metadata.
|
|
||||||
|
|
||||||
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
|
||||||
@@ -172,45 +76,30 @@ contract. Durable formats and compatibility rules remain in the
|
|||||||
## Prompt Maintenance And Backend Caching
|
## Prompt Maintenance And Backend Caching
|
||||||
|
|
||||||
Prompt message order and shared asset bytes are runtime behavior. Backend cache
|
Prompt message order and shared asset bytes are runtime behavior. Backend cache
|
||||||
reuse depends on identical preceding roles, rendered bytes, and cache-control
|
reuse depends on the same preceding messages and content, not merely equivalent
|
||||||
metadata—not merely equivalent meaning. Keep reusable shared assets
|
meaning. Keep reusable shared assets byte-identical and keep stable material
|
||||||
byte-identical and preserve each prompt’s declared ordering and cache controls
|
before the inputs that vary per request wherever a prompt’s declared sequence
|
||||||
when editing it.
|
supports caching. Preserve the existing manifest order and cache-control hints
|
||||||
|
when editing a prompt.
|
||||||
|
|
||||||
For sibling prompts that can reuse the same source material, order universal
|
D&D extraction manifests place the changing chunk transcript at the end of the
|
||||||
shared context first, request source material next, and module-specific
|
prompt after their reusable context. Scene chunking and NPC normalization use
|
||||||
suffixes last. Put a cache boundary at a reusable prefix that is useful to the
|
their own declared message sequences because their inputs and work differ. The
|
||||||
backend. Redundant intermediate cache boundaries do not extend that reusable
|
family-specific asset and ordering rules belong in [D&D Module Internals](dnd.md).
|
||||||
prefix and add no value.
|
Do not add tests that enforce a fixed message-prefix length; prompt-asset tests
|
||||||
|
should instead verify the meaningful asset sequence, inputs, and cache controls
|
||||||
Prompt-family owners may choose a different sequence when their inputs and
|
of the prompt being changed.
|
||||||
reuse pattern differ. The D&D family’s extraction, scene-chunking, and NPC
|
|
||||||
normalization policies are maintained in [D&D Module Internals](dnd.md#prompt-construction).
|
|
||||||
Do not add tests that enforce prompt prose; prompt tests should verify the
|
|
||||||
meaningful input placement and cache controls of the prompt being changed.
|
|
||||||
|
|
||||||
## Validation, Repair, And Retries
|
## Validation, Repair, And Retries
|
||||||
|
|
||||||
PromptKit performs prompt rendering, provider execution, and the prompt’s
|
Scriptorium performs prompt rendering, provider execution, and the prompt’s
|
||||||
structured-output validation. The adapter reports an empty result, validation
|
structured-output validation. The adapter reports an empty result, validation
|
||||||
failure, empty structured body, or decode failure as
|
failure, empty structured body, or decode failure as
|
||||||
`ErrInvalidStructuredOutput`, while retaining the returned raw bytes and debug
|
`ErrInvalidStructuredOutput`, while retaining the returned raw bytes and debug
|
||||||
material when they exist. Provider failures remain operational errors rather
|
material when they exist. Provider failures remain operational errors rather
|
||||||
than output-validation failures. Apart from documented context, capacity, and
|
than output-validation failures.
|
||||||
invalid-output categories, provider error values and types do not cross the
|
|
||||||
adapter error chain; callers receive only a credential-redacted diagnostic.
|
|
||||||
|
|
||||||
When PromptKit rejects backend admission before generation, the adapter maps
|
Prompt-declared repair is executed within Scriptorium’s structured-output flow.
|
||||||
`promptkit.ErrCapacityExceeded` to
|
|
||||||
`contracts.ErrLLMCapacityExceeded`, retaining prompt context and a redacted
|
|
||||||
upstream diagnostic without exposing the PromptKit sentinel or capacity-error
|
|
||||||
type as a framework contract. When supplied, the normalized selected backend
|
|
||||||
ID appears only in that safe application-owned diagnostic context. A canceled
|
|
||||||
caller context takes precedence. The adapter does not retry capacity failures;
|
|
||||||
the pipeline's existing binding attempt policy sees the operational error and
|
|
||||||
decides whether to rerun the complete operation.
|
|
||||||
|
|
||||||
Prompt-declared repair is executed within PromptKit’s structured-output flow.
|
|
||||||
The current production D&D prompt manifests set repair attempts to zero. That
|
The current production D&D prompt manifests set repair attempts to zero. That
|
||||||
setting does not replace pipeline retry behavior: a binding’s configured retry
|
setting does not replace pipeline retry behavior: a binding’s configured retry
|
||||||
count reruns its stage attempt after an error or rejection, and an exhausted
|
count reruns its stage attempt after an error or rejection, and an exhausted
|
||||||
@@ -219,42 +108,22 @@ attempt lifecycle, validation chains, and retry diagnostics; see
|
|||||||
[Pipeline Internals](pipeline.md#validation-retries-and-output) and the
|
[Pipeline Internals](pipeline.md#validation-retries-and-output) and the
|
||||||
[binding reference](../config.md#module-bindings-and-validators).
|
[binding reference](../config.md#module-bindings-and-validators).
|
||||||
|
|
||||||
## Timeout Ownership
|
|
||||||
|
|
||||||
The caller context remains the outer cancellation authority. PromptKit applies
|
|
||||||
a positive effective generation timeout as an inner request deadline; an
|
|
||||||
explicit zero disables only that generation deadline. The HTTP client timeout
|
|
||||||
is a separate transport-wide cap. Notarius forwards the caller context and
|
|
||||||
does not install another timeout wrapper around PromptKit.
|
|
||||||
|
|
||||||
The selected PromptKit profile owns generation settings. Notarius binding
|
|
||||||
retries remain outside the adapter and repeat the complete module operation
|
|
||||||
and validation chain. PromptKit does not add a provider retry loop.
|
|
||||||
Operator-facing behavior is summarized in
|
|
||||||
[Operations](../operations.md#operational-limits), and the pinned upstream
|
|
||||||
contract is identified in
|
|
||||||
[PromptKit Integration](../integrations/pkg-promptkit.md).
|
|
||||||
|
|
||||||
## Observability And Redaction
|
## Observability And Redaction
|
||||||
|
|
||||||
When debug recording is enabled, the pipeline decorates the shared client. The
|
When debug recording is enabled, the pipeline decorates the shared client. The
|
||||||
wrapper records prepared prompt and response material, timing, selected profile
|
wrapper records prepared prompt and response material, timing, selected profile
|
||||||
and backend, effective model parameters, and call identifiers in the run’s
|
and model, and call identifiers in the run’s debug bundle, including material
|
||||||
debug bundle, including material available from a failed structured completion.
|
available from a failed structured completion. For a successful completion, a
|
||||||
Effective parameters use PromptKit's stable lower-case JSON field names and may
|
debug-write failure is surfaced; when the completion already failed, its call
|
||||||
include `backend_id`. For a successful completion, a debug-write failure is
|
error remains the result. Debug-bundle location, retention, and handling are
|
||||||
surfaced; when the completion already failed, its call error remains the
|
operational concerns documented in [Operations](../operations.md#debug-bundles).
|
||||||
result. Debug-bundle location, retention, and handling are operational concerns
|
|
||||||
documented in [Operations](../operations.md#debug-bundles).
|
|
||||||
|
|
||||||
Run manifests receive selected profile summaries, including optional effective
|
Run manifests receive selected profile summaries and component identities, not
|
||||||
backend and reasoning provenance, and component identities—not prompt, schema,
|
prompt, schema, source, reference, or response content. Provider error text is
|
||||||
source, reference, or response content. The published field semantics belong
|
wrapped with prompt context and bearer credentials are redacted before it
|
||||||
to the [JSON output contract](../integrations/json-output.md#manifestjson).
|
crosses the runtime boundary. Known-secret redaction is available to other
|
||||||
Provider error text is wrapped with prompt context and bearer credentials are
|
runtime collaborators; it does not make prompt or response contents safe for
|
||||||
redacted before it crosses the runtime boundary. Known-secret redaction is
|
general logging.
|
||||||
available to other runtime collaborators; it does not make prompt or response
|
|
||||||
contents safe for general logging.
|
|
||||||
|
|
||||||
## Failure Boundaries
|
## Failure Boundaries
|
||||||
|
|
||||||
@@ -262,8 +131,6 @@ contents safe for general logging.
|
|||||||
sources, invalid asset registration, or a non-positive scheduler limit.
|
sources, invalid asset registration, or a non-positive scheduler limit.
|
||||||
- Preparation failures, unavailable explicit profiles, provider failures, and
|
- Preparation failures, unavailable explicit profiles, provider failures, and
|
||||||
context cancellation propagate to the calling stage with context.
|
context cancellation propagate to the calling stage with context.
|
||||||
- Backend admission exhaustion is a provider-neutral operational error and is
|
|
||||||
not classified as invalid structured output or validator rejection.
|
|
||||||
- Malformed or schema-invalid provider output is classified separately as
|
- Malformed or schema-invalid provider output is classified separately as
|
||||||
invalid structured output so the module or pipeline can apply its own retry
|
invalid structured output so the module or pipeline can apply its own retry
|
||||||
and rejection policy.
|
and rejection policy.
|
||||||
|
|||||||
@@ -12,15 +12,9 @@ exceptions. See [D&D Module Internals](dnd.md) rather than adding them here.
|
|||||||
|
|
||||||
A module is a typed implementation registered for one pipeline stage. Its
|
A module is a typed implementation registered for one pipeline stage. Its
|
||||||
`ModuleSpec` is the public-to-the-framework declaration of its stable key,
|
`ModuleSpec` is the public-to-the-framework declaration of its stable key,
|
||||||
stage, execution class, required and provided capabilities, artifact kind, and
|
stage, required and provided capabilities, artifact kind, and accepted
|
||||||
accepted reference slots. The execution class states whether a module is
|
reference slots. The framework uses that declaration to resolve a configured
|
||||||
`deterministic` or `llm_backed`; registries retain it for catalog inspection and
|
binding before it builds the implementation.
|
||||||
resolved-pipeline debug data without constructing the module. The framework
|
|
||||||
uses the declaration to resolve a configured binding before it builds the
|
|
||||||
implementation. After selection, the resolver applies profile inheritance only
|
|
||||||
to bindings whose declared execution class is `llm_backed` and rejects a
|
|
||||||
binding-specific profile on a deterministic module. The user-facing precedence
|
|
||||||
contract belongs in [Configuration](../config.md#pipelines).
|
|
||||||
|
|
||||||
Implementations that accept options must provide both an option validator and
|
Implementations that accept options must provide both an option validator and
|
||||||
a builder. The validator is used while resolving configuration; the builder
|
a builder. The validator is used while resolving configuration; the builder
|
||||||
@@ -42,72 +36,33 @@ generic source references and must use the codec's exact Go type. It does not
|
|||||||
interpret surrounding context or publish files; the pipeline validates the
|
interpret surrounding context or publish files; the pipeline validates the
|
||||||
capability during preparation and the output boundary owns publication. See
|
capability during preparation and the output boundary owns publication. See
|
||||||
the [Published Evidence Context contract](../integrations/evidence-context.md)
|
the [Published Evidence Context contract](../integrations/evidence-context.md)
|
||||||
for the durable source-unit excerpt. Lane artifacts retain citation and lane
|
for the durable result.
|
||||||
provenance; the framework does not add either to that published excerpt.
|
|
||||||
|
|
||||||
An artifact family is broader than a module: it owns the cohesive domain
|
|
||||||
feature across its artifact type, codec, stage modules, validators, prompt
|
|
||||||
policy, schemas, identity helpers, and reference projections. An extractor and
|
|
||||||
normalizer in one artifact family remain independently registered modules in
|
|
||||||
their respective pipeline stages. This ownership vocabulary does not create a
|
|
||||||
new registry or change the fixed pipeline.
|
|
||||||
|
|
||||||
## Production Composition
|
## Production Composition
|
||||||
|
|
||||||
Production composition is intentionally split by family:
|
Production composition is intentionally split by family:
|
||||||
|
|
||||||
- The generic registrar provides the unit chunker, generic JSON validators,
|
- The generic registrar provides the unit chunker, generic JSON validators,
|
||||||
JSON output encoder, and shared semantic-reconciliation prompt and response
|
and JSON output encoder.
|
||||||
schema assets.
|
|
||||||
- The Seriatim registrar provides the transcript input adapter. Its external
|
- The Seriatim registrar provides the transcript input adapter. Its external
|
||||||
input behavior is defined by the [Seriatim contract](../integrations/seriatim.md).
|
input behavior is defined by the [Seriatim contract](../integrations/seriatim.md).
|
||||||
- The D&D registrar provides its codecs, extractors, mergers, normalizers,
|
- The D&D registrar provides its codecs, extractors, mergers, normalizers,
|
||||||
validators, prompt assets, fallback profile asset, and default chains. Its behavioral conventions
|
validators, prompt assets, and default chains. Its behavioral conventions
|
||||||
are documented in [D&D Module Internals](dnd.md).
|
are documented in [D&D Module Internals](dnd.md).
|
||||||
|
|
||||||
The CLI owns the composition that invokes these registrars. A module package
|
The CLI owns the composition that invokes these registrars. A module package
|
||||||
may register its own family but must not assemble the CLI or make framework
|
may register its own family but must not assemble the CLI or make framework
|
||||||
packages depend on production extensions.
|
packages depend on production extensions.
|
||||||
|
|
||||||
## Semantic Reconciliation
|
|
||||||
|
|
||||||
`internal/framework/semanticreconcile` is a domain-neutral strategy used by a
|
|
||||||
typed normalize module; it is not itself a selectable stage module. A
|
|
||||||
source-backed artifact-family normalizer projects its deterministic records
|
|
||||||
into contextual candidates and owned typed record envelopes, supplies its
|
|
||||||
chosen prompt identity and resolved LLM profile, and constructs an engine with
|
|
||||||
explicit limits. The core filters invalid evidence, assigns contiguous
|
|
||||||
request-local integer handles, renders bounded candidate and transcript
|
|
||||||
materials, invokes the structured-completion boundary, and assesses the
|
|
||||||
returned duplicate groups into a stable non-overlapping plan.
|
|
||||||
|
|
||||||
The normalizer then applies that plan through a typed `ApplicationPolicy`. The
|
|
||||||
core preserves ungrouped records, contribution order, and provenance while the
|
|
||||||
artifact family owns group guards, field and evidence consolidation, durable
|
|
||||||
ID derivation, retry and fallback presentation, warnings, and postconditions.
|
|
||||||
Request-local handles do not enter the typed value or durable artifact. Fewer
|
|
||||||
than two eligible candidates skips model invocation; exceeding a candidate or
|
|
||||||
combined-material bound preserves the deterministic result under the family's
|
|
||||||
fallback policy. Provider, transport, cancellation, and context-construction
|
|
||||||
failures remain execution errors.
|
|
||||||
|
|
||||||
The core supplies a conservative generic prompt and the single private
|
|
||||||
response schema. A domain prompt may substitute its semantic instructions but
|
|
||||||
mounts the core-owned protocol and candidate/transcript presentation assets.
|
|
||||||
Prompt, schema, policy, and limit identities participate in manifest metadata
|
|
||||||
and checkpoint fingerprints. The generic registrar owns production
|
|
||||||
registration of those shared assets; a consuming domain registrar owns only
|
|
||||||
its domain prompt.
|
|
||||||
|
|
||||||
## Adding Or Changing A Module
|
## Adding Or Changing A Module
|
||||||
|
|
||||||
1. Choose the pipeline stage and the typed artifact boundary. Put external
|
1. Choose the pipeline stage and the typed artifact boundary. Put external
|
||||||
input or durable artifact formats in the relevant integration contract,
|
input or durable artifact formats in the relevant integration contract,
|
||||||
not in this guide or in a private LLM response type.
|
not in this guide or in a private LLM response type.
|
||||||
2. Define a stable `ModuleSpec` with an explicit execution class, the exact
|
2. Define a stable `ModuleSpec` with the exact capabilities and reference
|
||||||
capabilities, and reference slots needed for the operation. Model a
|
slots needed for the operation. Model a producer/consumer handoff as an
|
||||||
producer/consumer handoff as an artifact-compatible slot; configuration
|
artifact-compatible slot; configuration then chooses an external file or a
|
||||||
then chooses an external file or a generated binding.
|
generated binding.
|
||||||
3. Implement strict option decoding, construction, and the typed stage
|
3. Implement strict option decoding, construction, and the typed stage
|
||||||
interface. Preserve caller ownership: do not retain mutable request data
|
interface. Preserve caller ownership: do not retain mutable request data
|
||||||
and return defensive copies where an implementation exposes stored data.
|
and return defensive copies where an implementation exposes stored data.
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ 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. |
|
||||||
| Semantic reconciliation | **internal/framework/semanticreconcile** | Bounded source-backed candidate preparation, request-local handle proposals, deterministic assessment, typed plan application, and reconciliation identity metadata; see [Module Internals](modules.md#semantic-reconciliation) and [D&D Module Internals](dnd.md#semantic-registry-reconciliation). |
|
|
||||||
| 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. |
|
||||||
|
|
||||||
@@ -50,9 +48,8 @@ the CLI composition boundary.
|
|||||||
composition, and path safety.
|
composition, and path safety.
|
||||||
- [LLM Runtime](llm.md): structured completion, scheduling, prompt assets,
|
- [LLM Runtime](llm.md): structured completion, scheduling, prompt assets,
|
||||||
profiles, and secret handling.
|
profiles, and secret handling.
|
||||||
- [Module Internals](modules.md): generic extension registration, artifact
|
- [Module Internals](modules.md): generic extension registration, module
|
||||||
families, module construction, semantic reconciliation, validation, and
|
construction, validation, and reference mechanics.
|
||||||
reference mechanics.
|
|
||||||
- [D&D Module Internals](dnd.md): shared D&D extractor conventions, generated
|
- [D&D Module Internals](dnd.md): shared D&D extractor conventions, generated
|
||||||
reference projections, and lane-specific exceptions. Durable D&D and
|
reference projections, and lane-specific exceptions. Durable D&D and
|
||||||
Seriatim data shapes remain in the [integration contracts](../integrations/).
|
Seriatim data shapes remain in the [integration contracts](../integrations/).
|
||||||
|
|||||||
@@ -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, a supplied prompt session, and state/debug
|
dependencies, input bytes, and state/debug collaborators. It returns logical
|
||||||
collaborators. It returns logical output files, normalized artifacts, recorded
|
output files, normalized artifacts, recorded rejections and warnings, manifest
|
||||||
rejections and warnings, manifest provenance, and checkpoint decisions. The
|
provenance, and checkpoint decisions. The CLI owns process arguments,
|
||||||
CLI owns process arguments, configuration discovery, session resolution,
|
configuration discovery, physical roots, and placement of returned output
|
||||||
physical roots, and placement of returned output files.
|
files.
|
||||||
|
|
||||||
The framework has one fixed shape:
|
The framework has one fixed shape:
|
||||||
|
|
||||||
@@ -34,11 +34,6 @@ requested lanes where that is supported, resolves validator chains, checks
|
|||||||
module capabilities and typed artifact compatibility, validates options, and
|
module capabilities and typed artifact compatibility, validates options, and
|
||||||
assigns a deterministic resolved-composition digest. The resolved pipeline
|
assigns a deterministic resolved-composition digest. The resolved pipeline
|
||||||
contains bindings and declared reference targets, not external reference bytes.
|
contains bindings and declared reference targets, not external reference bytes.
|
||||||
After selection, the resolver applies command, binding, and pipeline profile
|
|
||||||
precedence to LLM-backed bindings and validators only; prompt defaults remain
|
|
||||||
an empty resolved binding profile. Deterministic bindings remain profile-free.
|
|
||||||
These effective values are part of the digest, so execution and checkpoint
|
|
||||||
consumers do not repeat profile inheritance.
|
|
||||||
Configuration resolution supplies the selected profile and catalog; see
|
Configuration resolution supplies the selected profile and catalog; see
|
||||||
[Configuration Internals](configuration.md).
|
[Configuration Internals](configuration.md).
|
||||||
|
|
||||||
@@ -46,18 +41,16 @@ External reference materialization happens before preparation. The materializer
|
|||||||
checks that each slot is declared by the selected module, resolves a file path
|
checks that each slot is declared by the selected module, resolves a file path
|
||||||
relative to the correct configuration or working-directory origin, reads
|
relative to the correct configuration or working-directory origin, reads
|
||||||
UTF-8 text, verifies media type and size limits, and retains bounded
|
UTF-8 text, verifies media type and size limits, and retains bounded
|
||||||
provenance. For a positive slot limit, it reads at most the limit plus one byte
|
provenance. A generated-artifact selector remains declared but has no bytes
|
||||||
and rejects overflow before retaining content. A generated-artifact selector
|
until its producing step completes.
|
||||||
remains declared but has no bytes until its producing step completes.
|
|
||||||
|
|
||||||
Preparation is the construction boundary. It validates the resolved shape and
|
Preparation is the construction boundary. It validates the resolved shape and
|
||||||
registry set, clones the resolved data, then constructs the input adapter,
|
registry set, clones the resolved data, then constructs the input adapter,
|
||||||
chunker, stage-local validators, every typed lane, and output encoder. Each
|
chunker, stage-local validators, every typed lane, and output encoder with
|
||||||
registered builder receives its own cloned build request immediately before its
|
cloned options, references, and shared dependencies. It also collects stable
|
||||||
module-owned code runs. Preparation also collects stable checkpoint
|
checkpoint fingerprints. Missing registrations, incompatible typed entries,
|
||||||
fingerprints. Missing registrations, incompatible typed entries, nil
|
nil implementations, and constructor failures are reported before source
|
||||||
implementations, and constructor failures are reported before source parsing
|
parsing or any stage operation begins.
|
||||||
or any stage operation begins.
|
|
||||||
|
|
||||||
An output encoder can opt into source-evidence publication through its output
|
An output encoder can opt into source-evidence publication through its output
|
||||||
policy. Preparation keeps the configured lane allowlist and active lanes
|
policy. Preparation keeps the configured lane allowlist and active lanes
|
||||||
@@ -86,10 +79,6 @@ 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
|
||||||
@@ -117,12 +106,9 @@ for started workers, and prevents output encoding.
|
|||||||
|
|
||||||
Every chunk, extract, merge, and normalize candidate passes its resolved
|
Every chunk, extract, merge, and normalize candidate passes its resolved
|
||||||
validator chain. Validators receive immutable canonical input appropriate to
|
validator chain. Validators receive immutable canonical input appropriate to
|
||||||
their target: chunks, codec-decoded typed candidates, or serialized codec
|
their target: chunks, typed values, or serialized codec bytes. They may
|
||||||
bytes. Each typed validator receives a newly decoded value from the one
|
approve, approve with warnings, reject, or fail. A rejection is an ordinary
|
||||||
candidate serialization for that attempt, while serialized validators receive
|
pipeline result; a validator error is a framework error.
|
||||||
separately owned representation bytes and schema metadata. They may approve,
|
|
||||||
approve with warnings, reject, or fail. A rejection is an ordinary pipeline
|
|
||||||
result; a validator error is a framework error.
|
|
||||||
|
|
||||||
The runner applies the binding's retry policy around a stage operation and its
|
The runner applies the binding's retry policy around a stage operation and its
|
||||||
complete validation chain. It preserves warnings only from the final accepted
|
complete validation chain. It preserves warnings only from the final accepted
|
||||||
|
|||||||
@@ -39,17 +39,12 @@ codecs, loader, and recorder. The CLI constructs a recorder whenever checkpoint
|
|||||||
recording is enabled and constructs a loader only for a `--resume` invocation.
|
recording is enabled and constructs a loader only for a `--resume` invocation.
|
||||||
Identity incorporates explicit stable semantic fingerprints collected from
|
Identity incorporates explicit stable semantic fingerprints collected from
|
||||||
prepared modules and validators in addition to configuration, input,
|
prepared modules and validators in addition to configuration, input,
|
||||||
references, runtime overrides, observed LLM profiles, and the LLM runtime's
|
references, runtime overrides, and LLM profiles.
|
||||||
non-secret effective profile-source identity. A profile source change therefore
|
|
||||||
causes a cold miss even when the configured profile ID remains unchanged.
|
|
||||||
The serialized
|
The serialized
|
||||||
`workspace_schema_version` identifiers are frozen wire-compatibility fields;
|
`workspace_schema_version` identifiers are frozen wire-compatibility fields;
|
||||||
they do not describe a current public state surface.
|
they do not describe a current public state surface.
|
||||||
|
|
||||||
Ordered-step lane checkpoints include the step identity in their storage scope.
|
Ordered-step lane checkpoints include the step identity in their storage scope.
|
||||||
Accepted step and lane identities are encoded injectively before becoming
|
|
||||||
filesystem path components, while ordinary safe identifiers retain their
|
|
||||||
readable paths.
|
|
||||||
When a later lane consumes a generated artifact, its dependency fingerprints
|
When a later lane consumes a generated artifact, its dependency fingerprints
|
||||||
include the producer's artifact kind, complete schema identity, media type,
|
include the producer's artifact kind, complete schema identity, media type,
|
||||||
canonical content digest, and size. Ordinary resume compares those fingerprints
|
canonical content digest, and size. Ordinary resume compares those fingerprints
|
||||||
|
|||||||
@@ -42,42 +42,6 @@ evidence publication. Apply an appropriate umask and output-root access policy
|
|||||||
before enabling that option; the requested output modes alone may not be
|
before enabling that option; the requested output modes alone may not be
|
||||||
suitable for transcript-bearing bundles.
|
suitable for transcript-bearing bundles.
|
||||||
|
|
||||||
## PromptKit Profile Deployment
|
|
||||||
|
|
||||||
Profile deployment has four distinct layers:
|
|
||||||
|
|
||||||
| Layer | Owner | Operational role |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Prompts and schemas | Notarius module families | Embedded request and structured-output definitions. They are not deployment profile files. |
|
|
||||||
| Fallback profiles | Notarius module families | Embedded application defaults, including D&D's `dnd-extraction` profile. |
|
|
||||||
| Built-in profiles | PromptKit | Upstream catalog entries available when no higher-precedence source defines an ID. |
|
|
||||||
| Operator profiles | Deployment filesystem | Complete environment-specific definitions selected by `promptkit.profile_file` or `promptkit.profile_dir`. |
|
|
||||||
|
|
||||||
The maintained D&D pipeline uses the workload ID `dnd-extraction`. The
|
|
||||||
embedded fallback makes that ID usable without an operator file. Production,
|
|
||||||
development, and local deployments can each install a different complete
|
|
||||||
definition for the same ID, retaining the pipeline while choosing their own
|
|
||||||
model, backend, timeout, or reasoning policy. An operator definition wins over
|
|
||||||
the fallback; it is not merged with it. The configuration field and full
|
|
||||||
precedence rules are owned by [Configuration](config.md#promptkit-profiles).
|
|
||||||
|
|
||||||
Use a profile source owned by the service account, keep it readable only by
|
|
||||||
the intended operator, and supply provider credentials through the service
|
|
||||||
environment—not in the Notarius configuration or profile YAML. The maintained
|
|
||||||
[operator profile](../examples/profiles/dnd-extraction.yml) is secret-free and
|
|
||||||
can be copied as a format starting point. Validate a deployment without a
|
|
||||||
provider call or credentials:
|
|
||||||
|
|
||||||
~~~sh
|
|
||||||
notarius config validate --config /etc/notarius/config.yml --pipeline dnd-session
|
|
||||||
~~~
|
|
||||||
|
|
||||||
Profile paths are currently resolved from the process working directory, not
|
|
||||||
from the configuration file. The complete example's
|
|
||||||
`./examples/profiles/dnd-extraction.yml` path is valid for a repository-root
|
|
||||||
invocation only. Use absolute paths such as
|
|
||||||
`/etc/notarius/profiles/dnd-extraction.yml` for services and containers.
|
|
||||||
|
|
||||||
## Run Lifecycle
|
## Run Lifecycle
|
||||||
|
|
||||||
Use the [run command](cli.md#run) to start a pipeline. A valid invocation loads
|
Use the [run command](cli.md#run) to start a pipeline. A valid invocation loads
|
||||||
@@ -110,9 +74,9 @@ are defined in [Accepted Chunk Map](integrations/chunk-map.md). An optional
|
|||||||
[evidence context](integrations/evidence-context.md) contains source-unit text
|
[evidence context](integrations/evidence-context.md) contains source-unit text
|
||||||
and metadata. It is not a cache or debug artifact: retain it with the output
|
and metadata. It is not a cache or debug artifact: retain it with the output
|
||||||
bundle only for as long as consumers need it, and apply source-content access
|
bundle only for as long as consumers need it, and apply source-content access
|
||||||
controls to the entire bundle. Its selected source-unit excerpt may include
|
controls to the entire bundle. Selected lanes may collectively cite most of a
|
||||||
every source unit once when coverage is broad or its configured window is
|
transcript, so a broad allowlist can make the evidence artifact nearly as
|
||||||
large, so do not assume a byte or token reduction or reduced sensitivity.
|
sensitive and large as the source itself.
|
||||||
|
|
||||||
## Chunk-Plan Cache
|
## Chunk-Plan Cache
|
||||||
|
|
||||||
@@ -153,15 +117,9 @@ compatible recorded work. A resume request fails when checkpoint recording is
|
|||||||
disabled. Without **--resume**, a recording-enabled run executes normally and
|
disabled. Without **--resume**, a recording-enabled run executes normally and
|
||||||
does not load checkpoint state. Compatibility includes the resolved pipeline,
|
does not load checkpoint state. Compatibility includes the resolved pipeline,
|
||||||
input, selected lanes, runtime overrides, reference provenance, LLM-profile
|
input, selected lanes, runtime overrides, reference provenance, LLM-profile
|
||||||
provenance, the effective PromptKit profile-source fingerprint, and
|
provenance, and prepared-component fingerprints. A changed identity produces a
|
||||||
prepared-component fingerprints. When a local PromptKit backend is configured,
|
cold miss; Notarius does not migrate, rewrite, or delete older checkpoint
|
||||||
compatibility also includes a non-secret fingerprint of its endpoint. Changing
|
directories.
|
||||||
profile content or the local endpoint causes a cold miss; changing only the
|
|
||||||
local concurrency limit does not. A changed identity produces a cold miss;
|
|
||||||
Notarius does not migrate, rewrite, or delete older checkpoint directories.
|
|
||||||
Reasoning-effort inheritance, replacement, and explicit clearing are distinct
|
|
||||||
runtime identities, so checkpoints created under one state are not reused by
|
|
||||||
either of the others.
|
|
||||||
|
|
||||||
Checkpoint state is confined below an identity-specific path:
|
Checkpoint state is confined below an identity-specific path:
|
||||||
|
|
||||||
@@ -221,9 +179,7 @@ warning, checkpoint, chunk-plan, and terminal reporting artifacts. The trace
|
|||||||
contains allowlisted application diagnostic records and can include source or
|
contains allowlisted application diagnostic records and can include source or
|
||||||
derived application data. Neither surface is a cache input. Do not treat a
|
derived application data. Neither surface is a cache input. Do not treat a
|
||||||
debug bundle as safe to share merely because its configuration summary is
|
debug bundle as safe to share merely because its configuration summary is
|
||||||
redacted. Invocation metadata omits reasoning effort when it is inherited,
|
redacted.
|
||||||
records the replacement value when one is supplied, and records an empty value
|
|
||||||
when inherited reasoning was explicitly cleared.
|
|
||||||
|
|
||||||
Notarius never creates debug state without an explicit request and never
|
Notarius never creates debug state without an explicit request and never
|
||||||
automatically deletes a requested bundle. If allocation succeeds, the command
|
automatically deletes a requested bundle. If allocation succeeds, the command
|
||||||
@@ -251,51 +207,10 @@ or automatic cleanup command.
|
|||||||
|
|
||||||
## Operational Limits
|
## Operational Limits
|
||||||
|
|
||||||
Provider execution settings and the generation timeout come from the selected
|
Provider retries and timeouts are supplied by the selected Scriptorium profile.
|
||||||
PromptKit profile. The invocation-only **--reasoning-effort** and
|
Module retry settings and concurrency limits are configuration contracts; see
|
||||||
**--clear-reasoning-effort** controls may replace or clear that profile setting
|
[module bindings](config.md#module-bindings-and-validators) and
|
||||||
for all LLM-backed calls in one run without changing the profile. PromptKit
|
|
||||||
v0.5.0 does not add a provider retry loop. Notarius binding retries rerun the
|
|
||||||
complete module operation and validation chain as defined by
|
|
||||||
[module bindings](config.md#module-bindings-and-validators).
|
|
||||||
|
|
||||||
Timeouts are layered. Caller cancellation is the outer authority. A positive
|
|
||||||
effective generation timeout adds an inner request deadline, while zero
|
|
||||||
disables only that generation deadline. The HTTP client timeout remains a
|
|
||||||
transport-wide cap. Notarius does not add another timeout around PromptKit.
|
|
||||||
The pinned upstream boundary and profile-format links are in
|
|
||||||
[PromptKit Integration](integrations/pkg-promptkit.md).
|
|
||||||
|
|
||||||
Concurrency has two independent layers. Notarius **total_llm** defaults to 16
|
|
||||||
and is the application-wide provider-call limit shared by all backends,
|
|
||||||
modules, retries, and validators. PromptKit may impose a narrower admission
|
|
||||||
limit for the selected backend. The effective active-generation bound is the
|
|
||||||
intersection of the Notarius limit, any PromptKit backend limit, and work made
|
|
||||||
available by the pipeline. Built-in OpenRouter profiles use PromptKit's
|
|
||||||
upstream backend limit; endpoint-only profiles have no PromptKit backend limit
|
|
||||||
and remain bounded by Notarius. For the configured local backend, a zero
|
|
||||||
**concurrency_limit** leaves only the Notarius scheduler as a call limit. A
|
|
||||||
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
|
|
||||||
admission behavior. When a PromptKit backend has admitted all active and queued
|
|
||||||
work, a new call fails as capacity exhaustion before generation. The adapter
|
|
||||||
maps that failure to Notarius's existing provider-neutral capacity error and
|
|
||||||
does not retry it. The calling stage's configured retry policy applies
|
|
||||||
normally, and the run fails if those attempts are exhausted. Caller
|
|
||||||
cancellation remains authoritative. Configuration contracts are documented
|
|
||||||
under [PromptKit profiles](config.md#promptkit-profiles) and
|
|
||||||
[concurrency](config.md#concurrency-output-cache-and-debug). Extract-worker
|
[concurrency](config.md#concurrency-output-cache-and-debug). Extract-worker
|
||||||
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.
|
|
||||||
|
|||||||
@@ -24,12 +24,6 @@ DAGs or a general workflow language. Every stage remains explicit; general
|
|||||||
chunking, merging, or normalization behavior must not be hidden inside an
|
chunking, merging, or normalization behavior must not be hidden inside an
|
||||||
extractor.
|
extractor.
|
||||||
|
|
||||||
A stage module is one configured implementation of one pipeline stage. An
|
|
||||||
artifact family is the cohesive domain feature that owns an artifact across
|
|
||||||
the explicit stages and supporting codecs, validators, prompts, identity
|
|
||||||
rules, and reference projections. Artifact-family ownership does not combine
|
|
||||||
stages or alter the fixed pipeline.
|
|
||||||
|
|
||||||
Input and chunking are pipeline-wide. Each selected artifact lane owns its
|
Input and chunking are pipeline-wide. Each selected artifact lane owns its
|
||||||
extract, merge, and normalize stages, and the output stage aggregates the run's
|
extract, merge, and normalize stages, and the output stage aggregates the run's
|
||||||
lane outcomes.
|
lane outcomes.
|
||||||
@@ -45,24 +39,11 @@ implementations. Domain-neutral model and framework layers provide reusable
|
|||||||
policy, contracts, and orchestration. Concrete input, pipeline, output, and
|
policy, contracts, and orchestration. Concrete input, pipeline, output, and
|
||||||
validation extensions depend inward on those generic layers.
|
validation extensions depend inward on those generic layers.
|
||||||
|
|
||||||
Semantic reconciliation is one such domain-neutral framework mechanism. It
|
|
||||||
prepares bounded source context, invokes a shared model-judgment protocol,
|
|
||||||
validates proposals, and applies safe plans through typed policies supplied by
|
|
||||||
the consuming artifact family. It does not own domain identity, durable IDs,
|
|
||||||
warning semantics, or artifact construction rules.
|
|
||||||
|
|
||||||
Generic layers must not depend on production extensions. Concrete extensions
|
Generic layers must not depend on production extensions. Concrete extensions
|
||||||
must not compose the application or take ownership of process behavior. The
|
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;
|
||||||
@@ -92,12 +73,6 @@ 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
|
||||||
@@ -195,17 +170,6 @@ 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. Semantic reconciliation may instead expose
|
|
||||||
contiguous, one-based candidate handles that exist only for one request;
|
|
||||||
deterministic code resolves them before typed application, and they never
|
|
||||||
become durable identity. This is the approved request-local-label application
|
|
||||||
of [ADR-0012](../adr/0012-resolve-opaque-entity-identifiers-deterministically.md)
|
|
||||||
recorded by
|
|
||||||
[ADR-0013](../adr/0013-use-request-local-candidate-handles-for-semantic-reconciliation.md).
|
|
||||||
|
|
||||||
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.
|
||||||
@@ -213,8 +177,7 @@ individual modules.
|
|||||||
The application-wide LLM scheduler bounds actual provider calls independently
|
The application-wide LLM scheduler bounds actual provider calls independently
|
||||||
of framework worker limits. Every LLM-backed module, retry, and validator uses
|
of framework worker limits. Every LLM-backed module, retry, and validator uses
|
||||||
the single injected scheduled client, including work performed by overlapping
|
the single injected scheduled client, including work performed by overlapping
|
||||||
lanes. Provider runtime adapters may enforce a narrower backend-specific limit
|
lanes.
|
||||||
beneath this mandatory application-wide scheduler.
|
|
||||||
|
|
||||||
## Configuration And Provenance
|
## Configuration And Provenance
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
226
docs/roadmap/evidence.md
Normal file
226
docs/roadmap/evidence.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Published Evidence Context
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Implemented.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Let downstream consumers build narrative reports from normalized artifacts
|
||||||
|
without separately parsing the original transcript or resolving source-unit
|
||||||
|
references themselves.
|
||||||
|
|
||||||
|
The production JSON output optionally publishes one deterministic, deduplicated
|
||||||
|
evidence-context artifact containing the transcript units relevant to explicitly
|
||||||
|
selected normalized lanes. Existing lane payloads remain the canonical semantic
|
||||||
|
results and retain their precise source references.
|
||||||
|
|
||||||
|
## Desired End State
|
||||||
|
|
||||||
|
When evidence-context publication is enabled, a consumer can:
|
||||||
|
|
||||||
|
1. discover one versioned evidence-context document through `index.json`;
|
||||||
|
2. obtain the union of source units needed to understand evidence cited by the
|
||||||
|
selected normalized lanes;
|
||||||
|
3. distinguish each artifact's direct evidence references from surrounding
|
||||||
|
units included only for narrative context;
|
||||||
|
4. retain speaker, timestamp, and other accepted source-unit metadata needed to
|
||||||
|
interpret the transcript; and
|
||||||
|
5. produce a narrative report without receiving duplicated transcript text in
|
||||||
|
every lane payload.
|
||||||
|
|
||||||
|
This is deterministic output projection. It does not invoke an LLM, change
|
||||||
|
normalization, or make surrounding context part of an artifact's evidence.
|
||||||
|
|
||||||
|
## Configuration Policy
|
||||||
|
|
||||||
|
Evidence publication is configured on the production JSON output module. The
|
||||||
|
intended configuration shape is:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
output:
|
||||||
|
module: json
|
||||||
|
options:
|
||||||
|
evidence_context:
|
||||||
|
enabled: true
|
||||||
|
window_units: 3
|
||||||
|
lanes:
|
||||||
|
- combat-turns
|
||||||
|
- item-events
|
||||||
|
- npc-interactions
|
||||||
|
- npcs
|
||||||
|
- spells
|
||||||
|
```
|
||||||
|
|
||||||
|
- Omitting `evidence_context` disables publication. When the object is present,
|
||||||
|
`enabled` is required.
|
||||||
|
- `enabled: false` accepts no `lanes` or `window_units` fields, preventing
|
||||||
|
silently ignored configuration.
|
||||||
|
- `lanes` is a required, non-empty allowlist of configured final lane IDs when
|
||||||
|
evidence publication is enabled. Values are trimmed, unique, and normalized
|
||||||
|
to lexical order.
|
||||||
|
- `window_units` is a non-negative integer and defaults to `3`. Zero publishes
|
||||||
|
only directly referenced units.
|
||||||
|
- Unknown lanes, duplicate lane IDs, and selected lanes whose artifact kind
|
||||||
|
cannot expose source evidence fail configuration resolution or pipeline
|
||||||
|
preparation.
|
||||||
|
- A selected lane that completes without a normalized output contributes no
|
||||||
|
evidence and does not make an otherwise successful run fail.
|
||||||
|
- Invocation-level lane filtering does not invalidate the configured allowlist.
|
||||||
|
Allowlisted lanes excluded from the effective run contribute nothing, while
|
||||||
|
the evidence document still records the configured allowlist.
|
||||||
|
|
||||||
|
The allowlist is intentional safety and stability policy. Scene descriptions
|
||||||
|
and other broad-range lanes are excluded unless named expressly. Adding a new
|
||||||
|
pipeline lane never silently increases output size or publishes more transcript
|
||||||
|
content.
|
||||||
|
|
||||||
|
## Evidence Collection Boundary
|
||||||
|
|
||||||
|
Evidence collection applies to accepted final normalized artifacts from the
|
||||||
|
selected lanes. It must not inspect arbitrary serialized JSON for fields named
|
||||||
|
`source_ref` or `source_refs`, and the generic JSON output module must not
|
||||||
|
depend on D&D artifact types.
|
||||||
|
|
||||||
|
Artifact-kind registrations expose their source references through an explicit
|
||||||
|
typed projection contract. The framework uses that contract to assemble a
|
||||||
|
domain-neutral evidence request containing:
|
||||||
|
|
||||||
|
- the accepted generic source document;
|
||||||
|
- the selected lane and artifact identities; and
|
||||||
|
- defensive copies of their direct source references.
|
||||||
|
|
||||||
|
The output stage owns publication of the resulting logical artifact. Generic
|
||||||
|
framework code owns range validation, position-based expansion, and union
|
||||||
|
logic. Domain-specific adapters own only the extraction of evidence references
|
||||||
|
from their typed artifacts.
|
||||||
|
|
||||||
|
Both plural-reference artifacts and singular-reference artifacts, such as
|
||||||
|
scene descriptions, can participate through the same projection contract.
|
||||||
|
They do so only when their configured lane is allowlisted.
|
||||||
|
|
||||||
|
## Range Expansion And Deduplication
|
||||||
|
|
||||||
|
For every valid direct source reference:
|
||||||
|
|
||||||
|
1. resolve its endpoints through source-document positions, not numeric
|
||||||
|
unit-ID arithmetic;
|
||||||
|
2. expand the range by `window_units` positions on each side;
|
||||||
|
3. clip the expanded range at document boundaries; and
|
||||||
|
4. union overlapping or contiguous expanded ranges.
|
||||||
|
|
||||||
|
Published contexts and units remain in source-document order. Each source unit
|
||||||
|
appears at most once in a merged context. Original direct references remain
|
||||||
|
unchanged and are associated with their contributing lane IDs so consumers can
|
||||||
|
tell why a context was included.
|
||||||
|
|
||||||
|
The projector must not silently omit or repair an invalid reference that
|
||||||
|
reaches this boundary. Such a value violates the accepted normalized-artifact
|
||||||
|
contract and causes output projection to fail with a content-safe error.
|
||||||
|
|
||||||
|
No implicit coverage limit truncates selected evidence. If the allowlisted
|
||||||
|
lanes collectively cite most or all of a transcript, the evidence document may
|
||||||
|
contain most or all of it. The explicit lane allowlist is the control that
|
||||||
|
prevents a broad lane such as scene descriptions from doing so accidentally.
|
||||||
|
|
||||||
|
## Durable Evidence Artifact
|
||||||
|
|
||||||
|
The JSON bundle gains one optional, non-lane artifact with these durable
|
||||||
|
identities:
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| Logical file | `evidence-context.json` |
|
||||||
|
| Index descriptor | `evidence_context` |
|
||||||
|
| Artifact kind | `source/evidence-context` |
|
||||||
|
| Media type | `application/json` |
|
||||||
|
| Schema ID | `notarius.source.evidence_context` |
|
||||||
|
| Schema name | `notarius_source_evidence_context_v1` |
|
||||||
|
| Schema version | `v1` |
|
||||||
|
|
||||||
|
The descriptor in `index.json` carries the artifact and schema identities,
|
||||||
|
analogous to the existing chunk-map descriptor. The artifact is present
|
||||||
|
whenever evidence publication is enabled, including when its context collection
|
||||||
|
is empty.
|
||||||
|
|
||||||
|
The document contains:
|
||||||
|
|
||||||
|
- the source document ID and semantic digest;
|
||||||
|
- the effective window size;
|
||||||
|
- the sorted configured lane allowlist;
|
||||||
|
- an ordered context collection;
|
||||||
|
- each context's expanded start and end unit IDs;
|
||||||
|
- the original direct references and contributing lane IDs covered by that
|
||||||
|
context; and
|
||||||
|
- the ordered accepted source units, including unit ID, kind, text,
|
||||||
|
self-reference, and metadata.
|
||||||
|
|
||||||
|
Expanded context bounds are navigation aids, not citations. The original
|
||||||
|
references embedded in each context remain the authoritative direct evidence.
|
||||||
|
The evidence artifact is discovered separately from lane payloads and does not
|
||||||
|
increase the normalized-lane count reported by the runner or subprocess
|
||||||
|
receipt.
|
||||||
|
|
||||||
|
## Failure And Publication Semantics
|
||||||
|
|
||||||
|
- Evidence projection occurs only after selected normalized outputs are known
|
||||||
|
and before the output encoder returns its logical files.
|
||||||
|
- Projection or encoding failure is an output-stage framework error; the CLI
|
||||||
|
does not publish a partially assembled output bundle.
|
||||||
|
- Rejected or absent lane outputs contribute nothing. Their attempted values
|
||||||
|
and source references must not be published through this artifact.
|
||||||
|
- Context generation is deterministic for the same source document, selected
|
||||||
|
normalized outputs, lane allowlist, and window size.
|
||||||
|
- Existing output, checkpoint, warning, rejection, debug, and subprocess
|
||||||
|
success semantics remain unchanged.
|
||||||
|
|
||||||
|
## Sensitivity And Size
|
||||||
|
|
||||||
|
Unlike the current chunk map, the evidence artifact contains transcript text
|
||||||
|
and source-unit metadata. Enabling it therefore creates additional durable
|
||||||
|
sensitive data and may materially increase bundle size.
|
||||||
|
|
||||||
|
The implemented configuration, operations, integration, and consumer documents
|
||||||
|
state that:
|
||||||
|
|
||||||
|
- evidence publication is opt-in;
|
||||||
|
- output permissions and retention must be appropriate for source content;
|
||||||
|
- selecting broad or numerous lanes can publish most of the transcript; and
|
||||||
|
- the artifact must not contain raw input bytes, LLM prompts or responses,
|
||||||
|
auxiliary reference content, credentials, debug-only data, or filesystem
|
||||||
|
paths.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Evidence publication is disabled by default and leaves existing bundles
|
||||||
|
unchanged.
|
||||||
|
- Enabling it requires an explicit non-empty lane allowlist.
|
||||||
|
- References from all selected successful lanes contribute to one deduplicated
|
||||||
|
document.
|
||||||
|
- Non-monotonic unit IDs are expanded and ordered correctly by document
|
||||||
|
position.
|
||||||
|
- Overlapping windows share one ordered copy of each included source unit.
|
||||||
|
- Direct references remain distinguishable from added context.
|
||||||
|
- Scene descriptions cannot contribute unless their lane is explicitly
|
||||||
|
allowlisted.
|
||||||
|
- Invalid selected lanes and unsupported artifact kinds fail before execution;
|
||||||
|
invalid accepted references fail output projection rather than being ignored.
|
||||||
|
- Empty selected-lane results produce a valid empty evidence artifact.
|
||||||
|
- Existing D&D lane schemas, normalized-output counts, and source-reference
|
||||||
|
semantics do not change.
|
||||||
|
- Generic framework and output packages do not depend on D&D types or parse
|
||||||
|
artifact JSON heuristically.
|
||||||
|
- The published contract and operational documentation clearly describe source
|
||||||
|
sensitivity, discovery, compatibility, and retention.
|
||||||
|
|
||||||
|
## Out Of Scope
|
||||||
|
|
||||||
|
- Embedding transcript units directly into each D&D record or lane payload.
|
||||||
|
- Replacing precise source references with expanded context ranges.
|
||||||
|
- Automatically including every configured lane.
|
||||||
|
- An explicit full-transcript publication mode.
|
||||||
|
- LLM summarization, retrieval, ranking, or narrative generation.
|
||||||
|
- Per-record window sizes or lane-specific window sizes.
|
||||||
|
- CLI overrides for evidence configuration.
|
||||||
|
- Reading rejected attempts, debug artifacts, auxiliary references, or prior
|
||||||
|
output bundles as evidence sources.
|
||||||
@@ -24,52 +24,26 @@ not as committed release dates.
|
|||||||
|
|
||||||
## Shared Normalization And Quality Work
|
## Shared Normalization And Quality Work
|
||||||
|
|
||||||
The implemented source-backed core and initial D&D registry adoption are
|
### Generic LLM-Assisted Deduplication
|
||||||
described by [Module Internals](../internal/modules.md#semantic-reconciliation)
|
|
||||||
and
|
|
||||||
[D&D Module Internals](../internal/dnd.md#semantic-registry-reconciliation).
|
|
||||||
The [Semantic Reconciliation Roadmap](semantic-reconciliation.md) retains the
|
|
||||||
original feature scope; the sections below keep broader extensions deferred.
|
|
||||||
|
|
||||||
### Large-Collection Semantic Reconciliation
|
- Add a reusable normalizer that asks an LLM to identify duplicate sets in a
|
||||||
|
list and propose one replacement element for each set.
|
||||||
|
- Define the minimum domain-neutral input contract, initially an ordered list
|
||||||
|
whose elements have stable unique 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,
|
||||||
|
require every referenced ID to exist, reject overlapping or malformed groups,
|
||||||
|
prevent unrelated insertion or deletion, and apply only approved replacement
|
||||||
|
operations in code.
|
||||||
|
- Preserve provenance needed for audit and downstream validation, and emit
|
||||||
|
warnings describing every collapsed group.
|
||||||
|
- Evaluate batching and context-window limits before applying the normalizer to
|
||||||
|
large artifact collections.
|
||||||
|
|
||||||
- Evaluate deterministic candidate blocking only after representative registry
|
The model may use its own domain knowledge to judge semantic duplication; the
|
||||||
inputs exceed the active roadmap's bounded single-request limits. Blocking
|
generic implementation is responsible only for the common proposal contract,
|
||||||
should use cheap, explainable signals to form plausible comparison sets while
|
safety checks, and deterministic application of accepted changes.
|
||||||
preserving the possibility that a duplicate appears outside a lexical name
|
|
||||||
match.
|
|
||||||
- Define correctness for candidates that appear in more than one block,
|
|
||||||
conflicting canonical selections, transitive identity across blocks, retry
|
|
||||||
isolation, and deterministic final ordering before implementation.
|
|
||||||
- Prefer a reconciliation graph or union plan with explicit conflict checks
|
|
||||||
over arbitrary fixed-size slices. Never silently treat a batch boundary as
|
|
||||||
evidence that two candidates are distinct.
|
|
||||||
- Record per-request bounds, block provenance, model calls, discarded
|
|
||||||
proposals, and final group derivation well enough to audit a collapse.
|
|
||||||
|
|
||||||
### Operator-Selected Semantic Policies
|
|
||||||
|
|
||||||
- Consider allowing an operator to select an approved semantic-policy prompt
|
|
||||||
for a typed reconciliation module without replacing the shared protocol,
|
|
||||||
response schema, or deterministic safety rules.
|
|
||||||
- Define the trusted asset source, configuration syntax, compatibility checks,
|
|
||||||
startup validation, provenance, prompt fingerprinting, checkpoint effects,
|
|
||||||
and support boundary before exposing the option.
|
|
||||||
- Prefer selection among registered, typed-policy-compatible prompt assets over
|
|
||||||
arbitrary filesystem prompt paths. Do not add this flexibility until an
|
|
||||||
operator workflow requires it; artifact-family-owned policy remains simpler
|
|
||||||
and safer for the initial implementation.
|
|
||||||
|
|
||||||
### Broader Reconciliation Inputs And Module Selection
|
|
||||||
|
|
||||||
- Revisit alternate context providers when a concrete non-source-backed entity
|
|
||||||
collection needs semantic reconciliation. Any extension must preserve the
|
|
||||||
same request-local identity, deterministic proposal validation, provenance,
|
|
||||||
and typed application guarantees.
|
|
||||||
- Consider a selectable generic normalizer only if Notarius gains a real
|
|
||||||
domain-neutral typed artifact contract that can safely support it. Do not
|
|
||||||
weaken exact artifact registration or introduce reflection-based arbitrary
|
|
||||||
JSON mutation merely to expose a universal module key.
|
|
||||||
|
|
||||||
### Validation And Review
|
### Validation And Review
|
||||||
|
|
||||||
@@ -126,18 +100,6 @@ checkpoint reuse, when an older artifact may be decoded or adapted, and when a
|
|||||||
producer or all dependents must be recomputed. Do not add a general migration
|
producer or all dependents must be recomputed. Do not add a general migration
|
||||||
framework until an actual contract change requires one.
|
framework until an actual contract change requires one.
|
||||||
|
|
||||||
### Artifact-family-oriented physical packaging
|
|
||||||
|
|
||||||
[ADR-0004](../adr/0004-package-modules-by-domain.md) currently groups production
|
|
||||||
extensions by domain and then by pipeline stage. After artifact-family
|
|
||||||
ownership terminology is established and more families span extraction,
|
|
||||||
normalization, validation, codecs, references, and assets, reassess whether a
|
|
||||||
feature-first physical layout would improve navigation and reduce scattered
|
|
||||||
changes enough to justify a repository-wide package migration. Any change must
|
|
||||||
address Go dependency cycles, registrar ownership, stable public module keys,
|
|
||||||
and supersession of the affected ADR-0004 decision. Conceptual artifact-family
|
|
||||||
ownership does not by itself require this move.
|
|
||||||
|
|
||||||
## Blue-Sky Platform And Operations
|
## Blue-Sky Platform And Operations
|
||||||
|
|
||||||
These ideas are intentionally less specified. Promote one into an earlier
|
These ideas are intentionally less specified. Promote one into an earlier
|
||||||
|
|||||||
486
docs/roadmap/implementation.md
Normal file
486
docs/roadmap/implementation.md
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
# Published Evidence Context Implementation Plan
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Completed.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Implement the accepted [Published Evidence Context](evidence.md) roadmap as an
|
||||||
|
optional, deterministic extension of the production JSON output. The completed
|
||||||
|
work must publish one deduplicated source-context artifact for an explicit set
|
||||||
|
of successful normalized lanes while leaving existing lane payloads,
|
||||||
|
normalization, checkpointing, subprocess results, and disabled output bundles
|
||||||
|
unchanged.
|
||||||
|
|
||||||
|
Complete the stages below in order. Each stage must leave its affected packages
|
||||||
|
passing before the next begins. Do not implement per-record hydration, implicit
|
||||||
|
all-lane collection, a full-transcript mode, LLM processing, or any other
|
||||||
|
roadmap item marked out of scope.
|
||||||
|
|
||||||
|
## Decisions And Invariants
|
||||||
|
|
||||||
|
- Evidence publication is output policy. Normalizers continue to return
|
||||||
|
semantic artifacts with precise source references and do not receive
|
||||||
|
hydration responsibilities.
|
||||||
|
- The framework operates on the accepted generic `source.SourceDocument` and
|
||||||
|
typed artifact projections. It must not inspect serialized JSON for
|
||||||
|
`source_ref` or `source_refs`, and generic packages must not depend on D&D
|
||||||
|
types.
|
||||||
|
- The selected lane allowlist is explicit, non-empty, and globally addressed by
|
||||||
|
resolved lane ID. Pipeline resolution already guarantees lane IDs are unique
|
||||||
|
across ordered steps.
|
||||||
|
- The framework decodes accepted serialized normalize outputs through their
|
||||||
|
registered artifact codecs before invoking typed evidence projectors. This
|
||||||
|
supports both fresh and checkpoint-reused normalize outputs without retaining
|
||||||
|
a second typed result channel.
|
||||||
|
- Expansion uses source-document positions. Numeric unit IDs are identities,
|
||||||
|
not sequence numbers.
|
||||||
|
- Direct source references are never widened or rewritten. Expanded ranges are
|
||||||
|
context bounds only.
|
||||||
|
- Rejected, failed, and absent normalized lane outputs contribute no evidence.
|
||||||
|
- Evidence output is sensitive durable source content, not cache or debug
|
||||||
|
state. It contains accepted source units only and never raw input bytes,
|
||||||
|
prompts, model responses, auxiliary references, paths, or credentials.
|
||||||
|
- The optional `evidence_context` index field is an additive v1 JSON-bundle
|
||||||
|
change. Existing D&D artifact schemas and the subprocess receipt do not
|
||||||
|
change.
|
||||||
|
|
||||||
|
## Stage 1: Add Typed Evidence Capability And Resolve Output Policy
|
||||||
|
|
||||||
|
Add a dedicated artifact-evidence registry under the pipeline framework:
|
||||||
|
|
||||||
|
- `pipeline.ArtifactEvidenceRegistry` stores one typed projector per artifact
|
||||||
|
kind.
|
||||||
|
- `pipeline.ArtifactEvidenceProjector[T]` is
|
||||||
|
`func(T) []source.SourceRef`.
|
||||||
|
- `pipeline.RegisterArtifactEvidence[T](registry, kind, projector)` accepts a
|
||||||
|
non-empty kind and non-nil projector, records the exact Go type for `T`, and
|
||||||
|
rejects duplicate kinds.
|
||||||
|
- The erased projection boundary checks the exact registered type, invokes the
|
||||||
|
projector, and returns a defensive copy of its references.
|
||||||
|
- The registry exposes only the discovery and projection operations required by
|
||||||
|
resolution, preparation, and execution; do not expose its mutable entries.
|
||||||
|
|
||||||
|
Add the registry to `pipeline.Registries` and `pipeline.ModuleCatalog`, including
|
||||||
|
CLI catalog conversion, production construction, empty-set detection, and
|
||||||
|
test registry helpers. A nil evidence registry remains valid when evidence
|
||||||
|
publication is disabled. Production construction and the D&D registrar require
|
||||||
|
and populate it.
|
||||||
|
|
||||||
|
Define these framework-level output-policy contracts:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type EvidenceContextPolicy struct {
|
||||||
|
Enabled bool
|
||||||
|
WindowUnits int
|
||||||
|
LaneIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvidenceContextPolicyProvider interface {
|
||||||
|
EvidenceContextPolicy() EvidenceContextPolicy
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Provider and prepared-pipeline boundaries defensively copy `LaneIDs`.
|
||||||
|
|
||||||
|
Add an optional output-profile option-validation callback to
|
||||||
|
`OutputEncoderRegistry`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type OutputProfileOptionContext struct {
|
||||||
|
LaneIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputProfileOptionValidator func(
|
||||||
|
OutputProfileOptionContext,
|
||||||
|
map[string]any,
|
||||||
|
) error
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `RegisterBuilderWithProfileValidation(spec, validateOptions,
|
||||||
|
validateProfile, builder)` and make existing output registration methods
|
||||||
|
delegate to it with no profile callback. The registry passes defensive copies
|
||||||
|
to validation. The callback receives the complete configured lane-ID set before
|
||||||
|
invocation-level `--only` filtering. The production JSON output uses it only to
|
||||||
|
prove that every configured evidence lane exists. Keep the extension generic:
|
||||||
|
the pipeline supplies lane identities, while the output module interprets its
|
||||||
|
own options. Build that lane set from the normalized legacy-or-steps profile
|
||||||
|
before selection and reject duplicate configured lane IDs through the existing
|
||||||
|
pipeline identity rules.
|
||||||
|
|
||||||
|
Extend the production JSON output options with the nested
|
||||||
|
`evidence_context` object:
|
||||||
|
|
||||||
|
- omission disables the feature;
|
||||||
|
- `enabled` is required when the object is present;
|
||||||
|
- `enabled: false` permits no `lanes` or `window_units` fields;
|
||||||
|
- `enabled: true` requires a non-empty `lanes` array;
|
||||||
|
- lane values are strings, trimmed, non-empty, unique after trimming, and
|
||||||
|
normalized to lexical order;
|
||||||
|
- `window_units` is an optional non-negative integer with default `3`; and
|
||||||
|
- outer and nested unknown fields and incompatible YAML value types remain
|
||||||
|
strict configuration errors.
|
||||||
|
|
||||||
|
The JSON encoder implements the policy provider from its decoded immutable
|
||||||
|
options. Pipeline resolution invokes its profile validator against all
|
||||||
|
configured steps, so an unknown evidence lane fails even when another lane is
|
||||||
|
selected with `--only`.
|
||||||
|
|
||||||
|
During `pipeline.Prepare`, after constructing the output encoder:
|
||||||
|
|
||||||
|
1. obtain and defensively normalize an enabled policy;
|
||||||
|
2. intersect its configured IDs with the effective prepared lanes, treating
|
||||||
|
allowlisted lanes removed by invocation-level filtering as inactive;
|
||||||
|
3. require an artifact-evidence registration for each active lane kind;
|
||||||
|
4. prove that its projector Go type exactly matches the active lane's registered
|
||||||
|
artifact codec type; and
|
||||||
|
5. retain an immutable private evidence plan on `PreparedPipeline`.
|
||||||
|
|
||||||
|
Duplicate or empty provider values, a missing evidence registry for an active
|
||||||
|
lane, unsupported active artifact kinds, and type mismatches fail preparation
|
||||||
|
with pipeline/output/lane context. A disabled or non-participating output
|
||||||
|
encoder creates no evidence plan and preserves existing preparation behavior.
|
||||||
|
The private plan retains both the full configured allowlist for publication and
|
||||||
|
the active lane/projector intersection for execution.
|
||||||
|
|
||||||
|
Register D&D evidence projectors for all six current artifact kinds. Each
|
||||||
|
projector returns copies of the artifact's direct references in record order:
|
||||||
|
spells, NPCs, combat turns, item events, NPC interactions, and the singular
|
||||||
|
reference from each scene description. Scene descriptions gain capability but
|
||||||
|
remain excluded unless their configured lane ID is allowlisted.
|
||||||
|
|
||||||
|
Stage tests:
|
||||||
|
|
||||||
|
- Registry tests cover nil, blank, duplicate, exact-type, defensive-copy, and
|
||||||
|
deterministic discovery behavior.
|
||||||
|
- JSON option tests cover disabled, enabled/default-window, explicit zero
|
||||||
|
window, normalization, duplicates, unknown fields, and invalid types.
|
||||||
|
- Preparation tests cover selected lanes across steps, unknown lanes,
|
||||||
|
unsupported kinds, projector/codec type mismatch, disabled behavior, and
|
||||||
|
defensive policy ownership.
|
||||||
|
- Resolution/preparation tests prove a valid allowlist survives `--only`, an
|
||||||
|
excluded lane contributes no active projector, and a genuinely unknown
|
||||||
|
configured lane still fails profile resolution.
|
||||||
|
- D&D registration tests prove every production D&D artifact kind has the
|
||||||
|
expected evidence capability without testing individual field loops
|
||||||
|
redundantly.
|
||||||
|
- One table-driven D&D projector test supplies representative values for all
|
||||||
|
six artifact kinds and proves plural and singular references are copied
|
||||||
|
without aliasing or semantic rewriting.
|
||||||
|
|
||||||
|
Stage completion:
|
||||||
|
|
||||||
|
- `go test ./internal/framework/pipeline`
|
||||||
|
- `go test ./internal/modules/generic/output/json`
|
||||||
|
- `go test ./internal/modules/dnd/register`
|
||||||
|
- `go test ./internal/cli`
|
||||||
|
|
||||||
|
## Stage 2: Define And Build The Evidence-Context Artifact
|
||||||
|
|
||||||
|
Add a domain-neutral `internal/framework/evidencecontext` package that owns the
|
||||||
|
durable model, JSON Schema, strict codec, projection algorithm, and these exact
|
||||||
|
identities:
|
||||||
|
|
||||||
|
- artifact kind `source/evidence-context`;
|
||||||
|
- media type `application/json`;
|
||||||
|
- schema ID `notarius.source.evidence_context`;
|
||||||
|
- schema name `notarius_source_evidence_context_v1`; and
|
||||||
|
- schema version `v1`.
|
||||||
|
|
||||||
|
Use these package-level model and build contracts:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Document struct {
|
||||||
|
SourceID string
|
||||||
|
SourceDigest string
|
||||||
|
WindowUnits int
|
||||||
|
SelectedLanes []string
|
||||||
|
Contexts []Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
ContextRef source.SourceRef
|
||||||
|
EvidenceRefs []EvidenceRef
|
||||||
|
Units []source.SourceUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
type EvidenceRef struct {
|
||||||
|
LaneID string
|
||||||
|
SourceRef source.SourceRef
|
||||||
|
}
|
||||||
|
|
||||||
|
type LaneEvidence struct {
|
||||||
|
LaneID string
|
||||||
|
SourceRefs []source.SourceRef
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildRequest struct {
|
||||||
|
Source *source.SourceDocument
|
||||||
|
WindowUnits int
|
||||||
|
SelectedLanes []string
|
||||||
|
LaneEvidence []LaneEvidence
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply the JSON field names shown below. Provide `Build(BuildRequest)`,
|
||||||
|
`Serialize(BuildRequest)`, and a `Codec` with the same identity/encode/decode
|
||||||
|
responsibilities as the chunk-map codec.
|
||||||
|
|
||||||
|
The v1 payload has this exact shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"source_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"window_units": 0,
|
||||||
|
"selected_lanes": ["npcs"],
|
||||||
|
"contexts": [
|
||||||
|
{
|
||||||
|
"context_ref": {
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"start_unit_id": 13,
|
||||||
|
"end_unit_id": 13
|
||||||
|
},
|
||||||
|
"evidence_refs": [
|
||||||
|
{
|
||||||
|
"lane_id": "npcs",
|
||||||
|
"source_ref": {
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"start_unit_id": 13,
|
||||||
|
"end_unit_id": 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"units": [
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"kind": "transcript_segment",
|
||||||
|
"text": "The party meets Rowan.",
|
||||||
|
"ref": {
|
||||||
|
"source_id": "session-alpha",
|
||||||
|
"start_unit_id": 13,
|
||||||
|
"end_unit_id": 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All displayed fields are required. `selected_lanes`, `contexts`,
|
||||||
|
`evidence_refs`, and `units` encode as arrays rather than `null`; `contexts`
|
||||||
|
may be empty. Each unit uses the existing `source.SourceUnit` JSON shape with
|
||||||
|
required `id`, `kind`, `text`, and `ref`, plus optional JSON-shaped `metadata`.
|
||||||
|
Fixed objects reject unknown fields; metadata remains an arbitrary JSON object.
|
||||||
|
|
||||||
|
The builder accepts the validated source document, selected lane IDs, effective
|
||||||
|
window, and lane-attributed direct references, then:
|
||||||
|
|
||||||
|
1. requires a non-negative window and a non-empty, trimmed, unique selected
|
||||||
|
lane set, then stores that set in lexical order;
|
||||||
|
2. requires every `LaneEvidence.LaneID` to belong to the selected set;
|
||||||
|
3. validates the source document, recomputes its semantic digest, and requires
|
||||||
|
it to equal `SourceDocument.Digest`;
|
||||||
|
4. validates every direct reference against one `source.DocumentIndex`;
|
||||||
|
5. deduplicates exact `(lane_id, source_ref)` contributions;
|
||||||
|
6. resolves endpoints to document positions;
|
||||||
|
7. expands each side without integer overflow and clips at document bounds;
|
||||||
|
8. sorts by expanded document position with deterministic lane/reference
|
||||||
|
tie-breakers;
|
||||||
|
9. merges overlapping or position-contiguous expanded intervals;
|
||||||
|
10. unions and deterministically sorts each merged context's direct
|
||||||
|
contributions; and
|
||||||
|
11. deep-clones the corresponding source units and JSON-shaped metadata.
|
||||||
|
|
||||||
|
Contexts are disjoint and ordered by document position, so a source unit occurs
|
||||||
|
at most once in the document. `context_ref` identifies the first and last
|
||||||
|
included units; `evidence_refs` retains only original citations. An empty
|
||||||
|
reference collection produces the same source identity, window, sorted
|
||||||
|
allowlist, and an explicit empty contexts array.
|
||||||
|
|
||||||
|
Projection failures identify only structural scope such as lane and reference
|
||||||
|
position. They must not include source text, metadata values, raw serialized
|
||||||
|
artifacts, or unrelated paths.
|
||||||
|
|
||||||
|
The codec must validate its model before encoding, produce deterministic JSON,
|
||||||
|
strictly decode the checked-in schema, and return independently owned values.
|
||||||
|
The JSON output encoder remains responsible for pretty-printing the logical
|
||||||
|
file with its standard trailing newline. Follow the existing chunk-map
|
||||||
|
package's separation between model, builder, codec, schema asset, and contract
|
||||||
|
tests where useful, without coupling the two artifact formats.
|
||||||
|
|
||||||
|
Stage tests:
|
||||||
|
|
||||||
|
- A table-driven builder suite covers zero and nonzero windows, boundary
|
||||||
|
clipping, non-monotonic unit IDs, separate gaps, overlapping and contiguous
|
||||||
|
windows, duplicate contributions, multiple lanes, stable ordering, empty
|
||||||
|
contexts, invalid selected/contributing lanes, source-digest mismatch, and
|
||||||
|
invalid references.
|
||||||
|
- Ownership tests prove output mutation cannot affect the source document or
|
||||||
|
projector inputs, including nested metadata.
|
||||||
|
- Codec tests cover round trip, required arrays, schema identity, malformed and
|
||||||
|
trailing JSON, unknown fixed fields, invalid ordering/ranges, mismatched
|
||||||
|
source identities, and independently owned decoded metadata.
|
||||||
|
- Use structured assertions and a compact valid fixture; do not add a large
|
||||||
|
transcript golden file.
|
||||||
|
|
||||||
|
Stage completion:
|
||||||
|
|
||||||
|
- `go test ./internal/framework/evidencecontext`
|
||||||
|
|
||||||
|
## Stage 3: Integrate Projection With Runner Output
|
||||||
|
|
||||||
|
Extend `contracts.OutputRequest` with an optional
|
||||||
|
`EvidenceContext *SerializedArtifact` field and clone it at every ownership
|
||||||
|
handoff, following the existing chunk-map pointer pattern.
|
||||||
|
|
||||||
|
After lane execution and final manifest population, but before invoking the
|
||||||
|
output encoder, the runner must:
|
||||||
|
|
||||||
|
1. skip all work when the prepared evidence plan is absent;
|
||||||
|
2. index accepted `NormalizeOutputs` by their globally unique lane IDs and fail
|
||||||
|
on an internal duplicate rather than silently overwrite it;
|
||||||
|
3. for each selected lane with an output, verify its source and artifact kind,
|
||||||
|
decode it through the prepared artifact codec registry, and invoke the
|
||||||
|
prepared typed projector;
|
||||||
|
4. build and serialize the evidence document through the evidence-context
|
||||||
|
package; and
|
||||||
|
5. pass a defensive serialized-artifact copy to the output encoder.
|
||||||
|
|
||||||
|
Selected lanes without normalized output contribute nothing. Normalize
|
||||||
|
rejections remain successful pipeline outcomes; evidence projection does not
|
||||||
|
inspect rejected candidates. An invalid accepted reference, incompatible
|
||||||
|
serialized artifact, projection type failure, or evidence serialization failure
|
||||||
|
is an output-stage framework error before logical files are returned or
|
||||||
|
physically published.
|
||||||
|
|
||||||
|
At this external-content consumption boundary, do not propagate artifact-codec
|
||||||
|
or metadata-cloning errors with `%w` when their text could contain artifact
|
||||||
|
fields or source metadata. Return fixed, actionable categories scoped by lane
|
||||||
|
and operation; detailed codec errors remain available to direct trusted
|
||||||
|
callers and their focused tests.
|
||||||
|
|
||||||
|
Add an allowlisted debug summary containing only evidence artifact identity,
|
||||||
|
selected lanes, window, context count, unit count, and source digest. Do not
|
||||||
|
duplicate transcript text or source-unit metadata into a new evidence-specific
|
||||||
|
debug envelope. Existing normalized-output debug behavior remains unchanged.
|
||||||
|
|
||||||
|
The output artifact is not a normalized lane, generated reference, checkpoint,
|
||||||
|
or manifest normalized-output entry. It does not alter normalized-output,
|
||||||
|
rejection, or warning counts. Resume continues to reuse normalized checkpoints;
|
||||||
|
evidence is deterministically rebuilt during the always-executed output stage.
|
||||||
|
|
||||||
|
Stage tests:
|
||||||
|
|
||||||
|
- Runner tests use a real codec, evidence projector, and small capturing output
|
||||||
|
encoder to prove selected-lane union, absent/rejected lane omission, invalid
|
||||||
|
accepted-reference failure, output-request defensive ownership, and no work
|
||||||
|
when disabled.
|
||||||
|
- Include one checkpoint-reused normalized-output case to prove evidence is
|
||||||
|
reconstructed identically without retaining typed normalize values.
|
||||||
|
- Confirm projection failures prevent output encoding and return a failed
|
||||||
|
manifest without changing rejection semantics.
|
||||||
|
|
||||||
|
Stage completion:
|
||||||
|
|
||||||
|
- `go test ./internal/framework/pipeline`
|
||||||
|
|
||||||
|
## Stage 4: Publish Through The JSON Bundle
|
||||||
|
|
||||||
|
Teach the production JSON encoder to recognize the optional evidence-context
|
||||||
|
artifact, verify its exact kind, media type, schema identity, schema digest, and
|
||||||
|
payload validity through the evidence-context codec, and emit:
|
||||||
|
|
||||||
|
- logical file `evidence-context.json`; and
|
||||||
|
- optional `index.json` descriptor field `evidence_context`.
|
||||||
|
|
||||||
|
The descriptor uses the same six fields as `chunk_map`:
|
||||||
|
`artifact_kind`, `file`, `media_type`, `schema_id`, `schema_name`, and
|
||||||
|
`schema_version`. Refactor the encoder's private descriptor representation only
|
||||||
|
as needed to share that shape; do not change the existing `chunk_map` wire
|
||||||
|
contract. Evidence output is ordered with the encoder's other fixed logical
|
||||||
|
files, remains a non-lane artifact, and is present with an empty contexts array
|
||||||
|
when enabled but no selected lane produces references.
|
||||||
|
|
||||||
|
The JSON encoder's validation boundary returns a fixed content-safe evidence
|
||||||
|
artifact error rather than propagating decoder or schema diagnostics that could
|
||||||
|
echo transcript text or metadata. Direct evidence-context codec tests retain
|
||||||
|
detailed structural errors.
|
||||||
|
|
||||||
|
Update the maintained complete D&D configuration to enable evidence context
|
||||||
|
with window `3` for `item-events`, `npcs`, `spells`, `combat-turns`, and
|
||||||
|
`npc-interactions`. Deliberately omit `scene-descriptions`. Keep the minimal
|
||||||
|
configuration disabled by omission.
|
||||||
|
|
||||||
|
Stage tests:
|
||||||
|
|
||||||
|
- JSON encoder tests own descriptor shape, exact logical filename, identity
|
||||||
|
checking, empty evidence publication, and disabled bundle stability.
|
||||||
|
- One assembled production D&D test uses multiple selected lanes with
|
||||||
|
overlapping references and non-monotonic unit IDs, decodes the published
|
||||||
|
artifact through its production codec, and proves union/deduplication and
|
||||||
|
scene-description exclusion.
|
||||||
|
- A second narrow case explicitly allowlists a scene-description lane to prove
|
||||||
|
capability is opt-in rather than hard-coded exclusion.
|
||||||
|
- Existing index, chunk-map, lane, manifest, warning, and rejection tests remain
|
||||||
|
the owners of their current formats; do not repeat their full matrices.
|
||||||
|
|
||||||
|
Stage completion:
|
||||||
|
|
||||||
|
- `go test ./internal/modules/generic/output/json`
|
||||||
|
- `go test ./internal/modules/dnd/...`
|
||||||
|
- `go test ./internal/modules/integration`
|
||||||
|
- `go test ./internal/cli`
|
||||||
|
|
||||||
|
## Stage 5: Publish Current-Behavior Documentation
|
||||||
|
|
||||||
|
After implementation and behavioral tests pass, update canonical documentation:
|
||||||
|
|
||||||
|
- `docs/config.md` owns the nested JSON output options, defaults, strict
|
||||||
|
validation, required lane allowlist, and a small configuration snippet.
|
||||||
|
- A new `docs/integrations/evidence-context.md` owns the complete v1 payload,
|
||||||
|
identities, direct-evidence versus context semantics, ordering,
|
||||||
|
compatibility, and a compact valid example.
|
||||||
|
- `docs/integrations/json-output.md` owns the optional logical file and
|
||||||
|
`index.json` descriptor; link to the evidence contract rather than repeating
|
||||||
|
its payload.
|
||||||
|
- `docs/operations.md` owns durable source-content sensitivity, permissions,
|
||||||
|
retention, and the possibility that selected lanes cover most of a
|
||||||
|
transcript.
|
||||||
|
- `docs/consumers/subprocess.md` explains discovery through the optional index
|
||||||
|
descriptor and requires consumers to treat `evidence_refs`, not expanded
|
||||||
|
context bounds, as citations.
|
||||||
|
- `docs/policy/architecture.md` records the generic typed evidence-projection
|
||||||
|
boundary and output ownership without adding D&D or wire-format detail.
|
||||||
|
- `docs/internal/pipeline.md` and `docs/internal/modules.md` describe the typed
|
||||||
|
evidence registry, preparation checks, reconstruction from serialized
|
||||||
|
normalize outputs, and output-stage ownership without restating public wire
|
||||||
|
fields.
|
||||||
|
|
||||||
|
Update only the smallest orientation links needed for discoverability. Do not
|
||||||
|
add a CLI flag, configuration environment override, or duplicate the complete
|
||||||
|
configuration outside `examples/`.
|
||||||
|
|
||||||
|
After all current-behavior documentation is accurate:
|
||||||
|
|
||||||
|
- set [the feature roadmap](evidence.md) status to `Implemented`;
|
||||||
|
- set this plan's status to `Completed`; and
|
||||||
|
- leave the integration and configuration documents, not either roadmap, as
|
||||||
|
the canonical implemented contract.
|
||||||
|
|
||||||
|
Final verification:
|
||||||
|
|
||||||
|
- `git diff --check`
|
||||||
|
- `go test ./...`
|
||||||
|
- `go vet ./...`
|
||||||
|
- `go build ./cmd/notarius`
|
||||||
|
- `go test -race ./internal/framework/evidencecontext ./internal/framework/pipeline ./internal/modules/generic/output/json ./internal/modules/dnd/... ./internal/modules/integration ./internal/cli`
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
None. The plan fixes the configuration shape and defaults, typed projection
|
||||||
|
boundary, preparation timing, durable schema and identities, range-union
|
||||||
|
algorithm, failure semantics, JSON discovery, D&D coverage, documentation
|
||||||
|
ownership, and test boundaries.
|
||||||
208
docs/roadmap/subprocess.md
Normal file
208
docs/roadmap/subprocess.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# Subprocess Integration Contract
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Implemented.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Make Notarius straightforward to invoke as a subprocess from an orchestrator
|
||||||
|
such as Narratio. A caller should be able to run a configured pipeline, discover
|
||||||
|
the published output bundle without parsing human prose or scanning a
|
||||||
|
directory, and hand selected structured artifacts to a later stage.
|
||||||
|
|
||||||
|
This work strengthens the public CLI boundary. It does not turn Notarius into a
|
||||||
|
Go library, embed Narratio-specific behavior, or change pipeline execution and
|
||||||
|
artifact semantics.
|
||||||
|
|
||||||
|
## Desired End State
|
||||||
|
|
||||||
|
A subprocess caller can:
|
||||||
|
|
||||||
|
1. validate a Notarius configuration and selected pipeline before execution;
|
||||||
|
2. invoke `notarius run` with explicit input, output-root, session, and
|
||||||
|
reference arguments;
|
||||||
|
3. request one versioned, machine-readable success result on standard output;
|
||||||
|
4. use that result to locate the published output bundle;
|
||||||
|
5. discover normalized lane payloads through the bundle's authoritative
|
||||||
|
`index.json`;
|
||||||
|
6. distinguish process failure from successful partial pipeline outcomes; and
|
||||||
|
7. record Notarius run provenance in its own manifest without depending on
|
||||||
|
internal packages, cache formats, debug formats, or human-readable messages.
|
||||||
|
|
||||||
|
The existing human-oriented command output remains the default for interactive
|
||||||
|
use.
|
||||||
|
|
||||||
|
## Machine-Readable Run Result
|
||||||
|
|
||||||
|
`notarius run` supports `--json`. On success, the flag makes standard output
|
||||||
|
contain exactly one JSON object followed by a newline. No human-oriented status
|
||||||
|
line is mixed into that stream.
|
||||||
|
|
||||||
|
The result uses the schema identity `notarius.run-result.v1` and contains:
|
||||||
|
|
||||||
|
| Field | Presence | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `schema_version` | Required | Exactly `notarius.run-result.v1`. |
|
||||||
|
| `run_id` | Required | The Notarius run identifier. |
|
||||||
|
| `pipeline_id` | Required | The effective pipeline identifier. |
|
||||||
|
| `output_directory` | Required | Absolute path to the successfully published output bundle. |
|
||||||
|
| `index_file` | Required for the production JSON output | Logical bundle path `index.json`. |
|
||||||
|
| `normalized_output_count` | Required | Number of final normalized lane outputs returned by the pipeline. |
|
||||||
|
| `rejected_output_count` | Required | Number of recorded rejected outputs. |
|
||||||
|
| `warning_count` | Required | Number of final run warnings returned by the pipeline. |
|
||||||
|
| `validation_status` | Required | The run manifest's final validation status without reinterpretation. |
|
||||||
|
| `debug_directory` | Optional | Absolute debug-bundle path when debug capture was requested and completed. |
|
||||||
|
|
||||||
|
An illustrative successful result is:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schema_version": "notarius.run-result.v1",
|
||||||
|
"run_id": "run-1770000000000000000-0123456789abcdef0123456789abcdef",
|
||||||
|
"pipeline_id": "dnd-session",
|
||||||
|
"output_directory": "/srv/narratio/runs/session-7/notarius/run-1770000000000000000-0123456789abcdef0123456789abcdef",
|
||||||
|
"index_file": "index.json",
|
||||||
|
"normalized_output_count": 6,
|
||||||
|
"rejected_output_count": 2,
|
||||||
|
"warning_count": 1,
|
||||||
|
"validation_status": "approved"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The receipt is a discovery and summary document, not a duplicate output
|
||||||
|
envelope. It does not embed lane payloads, rejection entries, warnings, the run
|
||||||
|
manifest, or output-file contents. Consumers use `index_file` and the existing
|
||||||
|
published JSON output contract for those records.
|
||||||
|
|
||||||
|
The result contract must tolerate future additive optional fields. Any
|
||||||
|
incompatible field or semantic change requires a new run-result schema version.
|
||||||
|
|
||||||
|
## Stream, Publication, And Failure Semantics
|
||||||
|
|
||||||
|
Machine-readable output is emitted only after:
|
||||||
|
|
||||||
|
- the pipeline has completed without a framework error;
|
||||||
|
- all logical output files have been successfully published;
|
||||||
|
- requested debug terminal reporting has completed; and
|
||||||
|
- all result fields are known.
|
||||||
|
|
||||||
|
Writing or encoding the machine-readable result is part of successful command
|
||||||
|
completion. Failure to write it produces the existing runtime-failure exit
|
||||||
|
class.
|
||||||
|
|
||||||
|
With `--json`:
|
||||||
|
|
||||||
|
- successful stdout is exclusively the run-result JSON document;
|
||||||
|
- successful warnings remain on stderr under the existing CLI contract;
|
||||||
|
- syntax and runtime errors retain their existing exit statuses and stderr
|
||||||
|
diagnostics;
|
||||||
|
- consumers treat stdout as a valid result only when the process exits with
|
||||||
|
status 0; failures before result writing emit no result, while a failure
|
||||||
|
during the stdout write may leave incomplete bytes that must be ignored; and
|
||||||
|
- human-readable diagnostic wording is not promoted into a machine contract.
|
||||||
|
|
||||||
|
Without `--json`, current interactive stdout and stderr behavior remains
|
||||||
|
unchanged.
|
||||||
|
|
||||||
|
Successful runs may contain rejected outputs or omit some normalized lanes.
|
||||||
|
That remains a valid pipeline outcome. The run result reports counts, while
|
||||||
|
`index.json`, `rejected.json`, and `warnings.json` remain authoritative for
|
||||||
|
details. Notarius will not add a generic `--fail-on-rejection` policy as part
|
||||||
|
of this work.
|
||||||
|
|
||||||
|
## Output Discovery And Consumer Responsibilities
|
||||||
|
|
||||||
|
The production JSON encoder's `index.json` remains the authoritative mapping
|
||||||
|
from lane IDs to published payloads. A subprocess consumer should:
|
||||||
|
|
||||||
|
- resolve `index_file` beneath `output_directory` and reject path escape;
|
||||||
|
- locate expected outputs by `lane_id`, not by guessing filenames;
|
||||||
|
- check each selected descriptor's media type and schema identity;
|
||||||
|
- decode payloads according to their published integration contracts;
|
||||||
|
- decide which lanes are required or optional for its own later stages; and
|
||||||
|
- retain rejection, warning, and manifest files when they are needed for
|
||||||
|
provenance or review.
|
||||||
|
|
||||||
|
For Narratio, required report inputs and partial-success policy remain Narratio
|
||||||
|
stage configuration and orchestration concerns. Notarius does not acquire
|
||||||
|
knowledge of Narratio stages, manifests, workspace layout, publication policy,
|
||||||
|
or report formats.
|
||||||
|
|
||||||
|
## Invocation Guidance
|
||||||
|
|
||||||
|
The consumer documentation recommends that subprocess callers:
|
||||||
|
|
||||||
|
- use `notarius config validate --pipeline` as an optional preflight;
|
||||||
|
- pass explicit absolute paths for the input, configuration, output root, and
|
||||||
|
CLI-supplied references;
|
||||||
|
- use a stable, non-secret prompt session identifier when useful for provider
|
||||||
|
routing or caching;
|
||||||
|
- capture stdout and stderr separately;
|
||||||
|
- supply credentials through the configured environment mechanism rather than
|
||||||
|
command arguments or generated configuration containing secret values;
|
||||||
|
- place output, cache, debug, and subprocess logs under intentional
|
||||||
|
sensitivity and retention policies; and
|
||||||
|
- treat the Notarius manifest and run-result receipt as provenance while
|
||||||
|
leaving the caller's own manifest authoritative for its stage lifecycle.
|
||||||
|
|
||||||
|
Notarius configuration remains owned by Notarius. An orchestrator may select a
|
||||||
|
configuration and pass supported operational overrides, but should not
|
||||||
|
duplicate the complete Notarius configuration schema.
|
||||||
|
|
||||||
|
## Documentation End State
|
||||||
|
|
||||||
|
- `docs/cli.md` owns `run --json`, stream behavior, and exit semantics;
|
||||||
|
- a new `docs/integrations/run-result.md` owns the versioned run-result wire
|
||||||
|
contract and compatibility policy;
|
||||||
|
- `docs/integrations/json-output.md` remains the sole owner of output-bundle
|
||||||
|
discovery and lane publication;
|
||||||
|
- a new `docs/consumers/subprocess.md` provides the task-oriented invocation and
|
||||||
|
consumption workflow; and
|
||||||
|
- `docs/internal/cli.md` describes how the CLI constructs and emits the result
|
||||||
|
only after successful publication.
|
||||||
|
|
||||||
|
Other documents should link to these owners instead of repeating volatile
|
||||||
|
fields or command details.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- An ordinary successful `run` retains its existing human-readable output.
|
||||||
|
- A successful `run --json` emits one valid `notarius.run-result.v1` document
|
||||||
|
and no human prose on stdout.
|
||||||
|
- Relative configured or overridden output and debug roots are reported as
|
||||||
|
absolute bundle paths.
|
||||||
|
- The receipt identifies the production JSON bundle entry point without
|
||||||
|
copying its lane descriptors or payloads.
|
||||||
|
- Warning-bearing and rejection-bearing runs remain successful and report
|
||||||
|
accurate counts.
|
||||||
|
- Syntax, configuration, provider, pipeline, publication, debug, and result
|
||||||
|
writing failures retain the correct nonzero exit class. Consumers are
|
||||||
|
explicitly required to ignore stdout from a nonzero invocation.
|
||||||
|
- The implementation does not expose internal Go types or couple generic CLI
|
||||||
|
code to D&D or Narratio concepts.
|
||||||
|
- Public and internal documentation assigns each new contract to one canonical
|
||||||
|
owner.
|
||||||
|
- Offline behavioral tests protect the structured-output contract, default
|
||||||
|
human behavior, absolute path reporting, stream separation, and failure to
|
||||||
|
serialize or write the success result without duplicating lower-level output
|
||||||
|
encoder tests.
|
||||||
|
|
||||||
|
## Out Of Scope
|
||||||
|
|
||||||
|
The following may be useful later but are not prerequisites for the Narratio
|
||||||
|
integration:
|
||||||
|
|
||||||
|
- a result-file flag in addition to machine-readable stdout;
|
||||||
|
- a JSON failure envelope or stable machine-readable error taxonomy;
|
||||||
|
- caller-supplied Notarius run IDs or exact output-bundle paths;
|
||||||
|
- a generic `--fail-on-rejection` or required-lane CLI policy;
|
||||||
|
- a public Go client package or importable Narratio adapter;
|
||||||
|
- Narratio stage, configuration, manifest, or report-generation changes;
|
||||||
|
- `notarius version --json`;
|
||||||
|
- installable or queryable artifact JSON Schemas;
|
||||||
|
- signal-aware CLI contexts and graceful SIGINT or SIGTERM handling;
|
||||||
|
- packaged release artifacts and a broader application-versioning policy.
|
||||||
|
|
||||||
|
These items should be promoted only in response to a demonstrated integration
|
||||||
|
need rather than bundled into the initial subprocess contract.
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
version: 4
|
version: 3
|
||||||
promptkit:
|
|
||||||
profile_file: ./examples/profiles/dnd-extraction.yml
|
|
||||||
concurrency:
|
concurrency:
|
||||||
total_llm: 2
|
total_llm: 2
|
||||||
stage_workers:
|
stage_workers:
|
||||||
@@ -18,7 +16,6 @@ debug:
|
|||||||
directory: ./notarius-debug
|
directory: ./notarius-debug
|
||||||
pipelines:
|
pipelines:
|
||||||
dnd-session:
|
dnd-session:
|
||||||
llm_profile: dnd-extraction
|
|
||||||
input: seriatim
|
input: seriatim
|
||||||
# Stable campaign context is shared by every module that accepts these slots.
|
# Stable campaign context is shared by every module that accepts these slots.
|
||||||
references:
|
references:
|
||||||
@@ -35,40 +32,29 @@ pipelines:
|
|||||||
enabled: true
|
enabled: true
|
||||||
window_units: 3
|
window_units: 3
|
||||||
lanes:
|
lanes:
|
||||||
- item-occurrences
|
- item-events
|
||||||
- item-registry
|
- npcs
|
||||||
- location-registry
|
|
||||||
- location-occurrences
|
|
||||||
- npc-registry
|
|
||||||
- spells
|
- spells
|
||||||
- combat-turns
|
- combat-turns
|
||||||
- npc-occurrences
|
- npc-interactions
|
||||||
- enemy-events
|
|
||||||
steps:
|
steps:
|
||||||
# Establish session-wide reference artifacts before their consumers.
|
# Establish session-wide reference artifacts alongside independent item events.
|
||||||
- id: describe-session
|
- id: describe-session
|
||||||
artifacts:
|
artifacts:
|
||||||
item-registry:
|
item-events:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/item-registry
|
module: dnd/item-events
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/item-registry
|
normalize: dnd/item-events
|
||||||
npc-registry:
|
npcs:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/npc-registry
|
module: dnd/npcs
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize:
|
normalize:
|
||||||
module: dnd/npc-registry
|
module: dnd/npcs
|
||||||
retries: 2
|
llm_profile: gemini-2-flash
|
||||||
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:
|
||||||
@@ -77,32 +63,18 @@ pipelines:
|
|||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/scene-descriptions
|
normalize: dnd/scene-descriptions
|
||||||
- id: extract-events
|
- id: extract-events
|
||||||
# Accepted registry artifacts and scene-description eligibility artifacts
|
# Accepted NPC grounding and scene-description eligibility artifacts are
|
||||||
# are supplied in memory to their compatible consumers in this step.
|
# supplied in memory to their compatible consumers in this step.
|
||||||
references:
|
references:
|
||||||
location_registry:
|
npcs:
|
||||||
artifact:
|
artifact:
|
||||||
step: describe-session
|
step: describe-session
|
||||||
lane: location-registry
|
lane: npcs
|
||||||
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
|
||||||
@@ -121,40 +93,9 @@ pipelines:
|
|||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/combat-turns
|
normalize: dnd/combat-turns
|
||||||
npc-occurrences:
|
npc-interactions:
|
||||||
extract:
|
extract:
|
||||||
module: dnd/npc-occurrences
|
module: dnd/npc-interactions
|
||||||
retries: 2
|
retries: 2
|
||||||
merge: appendorder
|
merge: appendorder
|
||||||
normalize: dnd/npc-occurrences
|
normalize: dnd/npc-interactions
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
version: 4
|
version: 3
|
||||||
pipelines:
|
pipelines:
|
||||||
dnd-session:
|
dnd-session:
|
||||||
llm_profile: dnd-extraction
|
|
||||||
input: seriatim
|
input: seriatim
|
||||||
artifacts:
|
artifacts:
|
||||||
spells:
|
spells:
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
id: dnd-extraction
|
|
||||||
backend: openrouter
|
|
||||||
model: openai/gpt-5.6-luna
|
|
||||||
timeout_seconds: 240
|
|
||||||
service_tier: flex
|
|
||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module gitea.maximumdirect.net/eric/notarius
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.maximumdirect.net/eric/promptkit v0.5.0
|
gitea.maximumdirect.net/eric/scriptorium v0.11.1
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -1,9 +1,13 @@
|
|||||||
gitea.maximumdirect.net/eric/promptkit v0.5.0 h1:jnpazLyyNhWrB2xzwwtUkNUfktkTdkENTwuSPnKiYrc=
|
gitea.maximumdirect.net/eric/scriptorium v0.11.0 h1:rjvbt9FTaWHxYlHq7QlUzmMVUt3QdbTmeCkmH81N//o=
|
||||||
gitea.maximumdirect.net/eric/promptkit v0.5.0/go.mod h1:R95NM6fbMDGDC0/UomgnSBP6ui2ns+8SZb8bESNvrDQ=
|
gitea.maximumdirect.net/eric/scriptorium v0.11.0/go.mod h1:FQ5lEuNxmrQyNgIomkpZdxvfTC0jWjbXYuq3tbJWF64=
|
||||||
|
gitea.maximumdirect.net/eric/scriptorium v0.11.1 h1:zBKtB3+fP8FcHGI8DJD99CiTL6crAGitBhWtE+xYJHc=
|
||||||
|
gitea.maximumdirect.net/eric/scriptorium v0.11.1/go.mod h1:FQ5lEuNxmrQyNgIomkpZdxvfTC0jWjbXYuq3tbJWF64=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
|
||||||
)
|
|
||||||
|
|
||||||
const invalidEnemyEventExtractorKey = "test/dnd/invalid-enemy-events"
|
|
||||||
|
|
||||||
func TestAssembledEnemyEventLaneRejectsInvalidFinalArtifactDespiteValidatorOverrides(t *testing.T) {
|
|
||||||
components := productionTestComponents(t)
|
|
||||||
if err := pipeline.RegisterExtractor[dnd.EnemyEventList](components.registries.Extractors, pipeline.ModuleSpec{
|
|
||||||
Key: invalidEnemyEventExtractorKey,
|
|
||||||
Stage: pipeline.StageExtract,
|
|
||||||
ExecutionClass: contracts.ExecutionClassDeterministic,
|
|
||||||
Requires: []string{"chunks", "source.transcript"},
|
|
||||||
Provides: []string{"dnd.enemy_events"},
|
|
||||||
ArtifactKind: dnd.EnemyEventListKind,
|
|
||||||
}, func() (contracts.Extractor[dnd.EnemyEventList], error) {
|
|
||||||
return invalidEnemyEventExtractor{}, nil
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("register extractor: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
accept := pipeline.ValidatorOverride{Set: true, Validators: []pipeline.ModuleBinding{pipeline.Binding("generic/always_accept")}}
|
|
||||||
resolved, err := pipeline.ResolvePipeline(pipeline.PipelineProfile{
|
|
||||||
ID: "assembled-invalid-enemy-events",
|
|
||||||
Input: pipeline.Binding("seriatim"),
|
|
||||||
Chunk: pipeline.ModuleBinding{Module: "generic", Options: map[string]any{"max_units": 1}},
|
|
||||||
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
|
||||||
"enemy-events": {
|
|
||||||
Extract: pipeline.ModuleBinding{Module: invalidEnemyEventExtractorKey, Validators: accept},
|
|
||||||
Normalize: pipeline.ModuleBinding{Module: pipeline.DefaultNormalizeModule, Validators: accept},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Output: pipeline.Binding("json"),
|
|
||||||
}, pipeline.ResolveOptions{}, catalogFromRegistries(components.registries))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ResolvePipeline() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prepared, err := pipeline.Prepare(resolved, components.registries, pipeline.ModuleDependencies{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Prepare() error = %v", err)
|
|
||||||
}
|
|
||||||
_, err = pipeline.New().Run(context.Background(), pipeline.RunInput{
|
|
||||||
Prepared: prepared,
|
|
||||||
RawInput: readRepositoryFile(t, "examples", "seriatim-minimal-transcript.json"),
|
|
||||||
ChunkCacheMode: pipeline.ChunkCacheBypass,
|
|
||||||
})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "serialize accepted extract output") || !strings.Contains(err.Error(), "must not exceed") {
|
|
||||||
t.Fatalf("Run() error = %v, want final durable range rejection", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type invalidEnemyEventExtractor struct{}
|
|
||||||
|
|
||||||
func (invalidEnemyEventExtractor) Key() string { return invalidEnemyEventExtractorKey }
|
|
||||||
|
|
||||||
func (invalidEnemyEventExtractor) ReferenceSlots() []contracts.ReferenceSlot { return nil }
|
|
||||||
|
|
||||||
func (invalidEnemyEventExtractor) Extract(ctx context.Context, req contracts.TypedExtractionRequest) (contracts.TypedExtractionResult[dnd.EnemyEventList], error) {
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
return contracts.TypedExtractionResult[dnd.EnemyEventList]{}, err
|
|
||||||
}
|
|
||||||
if req.Source == nil {
|
|
||||||
return contracts.TypedExtractionResult[dnd.EnemyEventList]{}, errors.New("assembled extractor requires source")
|
|
||||||
}
|
|
||||||
return contracts.TypedExtractionResult[dnd.EnemyEventList]{Value: dnd.EnemyEventList{Events: []dnd.EnemyEvent{{
|
|
||||||
Name: "Ashfang",
|
|
||||||
Kind: dnd.EnemyEventKindEngaged,
|
|
||||||
SourceRefs: []source.SourceRef{{SourceID: req.Source.ID, StartUnitID: 2, EndUnitID: 1}},
|
|
||||||
}}}}, nil
|
|
||||||
}
|
|
||||||
@@ -134,7 +134,7 @@ func TestAssembledSpellPipelineHonorsNormalizeValidatorOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAssembledSpellPipelinePromotesTerminalUnknownSpellWarning(t *testing.T) {
|
func TestAssembledSpellPipelineRejectsUnknownSpellWithoutPromotingAttemptWarning(t *testing.T) {
|
||||||
registries, resolved, _ := assembledSpellPipeline(t, assembledSpellPipelineOptions{unknownSpell: true})
|
registries, resolved, _ := assembledSpellPipeline(t, assembledSpellPipelineOptions{unknownSpell: true})
|
||||||
prepared, err := pipeline.Prepare(resolved, registries, pipeline.ModuleDependencies{})
|
prepared, err := pipeline.Prepare(resolved, registries, pipeline.ModuleDependencies{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,8 +161,10 @@ func TestAssembledSpellPipelinePromotesTerminalUnknownSpellWarning(t *testing.T)
|
|||||||
if !reflect.DeepEqual(rejectedFile.Rejected, output.Rejected) {
|
if !reflect.DeepEqual(rejectedFile.Rejected, output.Rejected) {
|
||||||
t.Fatalf("rejected file = %#v, run rejections = %#v, want durable rejection diagnostic", rejectedFile.Rejected, output.Rejected)
|
t.Fatalf("rejected file = %#v, run rejections = %#v, want durable rejection diagnostic", rejectedFile.Rejected, output.Rejected)
|
||||||
}
|
}
|
||||||
if len(output.Warnings) != 1 || output.Warnings[0].ReasonCode != spellnormalize.ReasonCodeSpellNameUnresolved || output.Warnings[0].Scope != "spell_casts[0]" {
|
for _, warning := range output.Warnings {
|
||||||
t.Fatalf("warnings = %#v, want terminal normalize catalog warning", output.Warnings)
|
if warning.ReasonCode == spellnormalize.ReasonCodeSpellNameUnresolved {
|
||||||
|
t.Fatalf("warnings = %#v, want rejected-attempt warning to remain non-durable", output.Warnings)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +215,6 @@ func assembledSpellPipeline(t *testing.T, options assembledSpellPipelineOptions)
|
|||||||
if err := pipeline.RegisterExtractor[dnd.SpellList](components.registries.Extractors, pipeline.ModuleSpec{
|
if err := pipeline.RegisterExtractor[dnd.SpellList](components.registries.Extractors, pipeline.ModuleSpec{
|
||||||
Key: assembledSpellExtractorKey,
|
Key: assembledSpellExtractorKey,
|
||||||
Stage: pipeline.StageExtract,
|
Stage: pipeline.StageExtract,
|
||||||
ExecutionClass: contracts.ExecutionClassDeterministic,
|
|
||||||
Requires: []string{"chunks", "source.transcript"},
|
Requires: []string{"chunks", "source.transcript"},
|
||||||
Provides: []string{"dnd.spell_casts"},
|
Provides: []string{"dnd.spell_casts"},
|
||||||
ArtifactKind: dnd.SpellListKind,
|
ArtifactKind: dnd.SpellListKind,
|
||||||
|
|||||||
@@ -249,24 +249,10 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,13 +143,24 @@ func isEmptyRegistries(registries pipeline.Registries) bool {
|
|||||||
registries.Outputs == nil
|
registries.Outputs == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productionLLMClientFactory(ctx context.Context, cfg config.Config, profileID string) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
assets, err := productionPromptAssets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return buildProductionLLMClient(ctx, cfg, profileID, assets)
|
||||||
|
}
|
||||||
|
|
||||||
func productionLLMClientFactoryWithAssets(assets *llm.AssetRegistry) LLMClientFactory {
|
func productionLLMClientFactoryWithAssets(assets *llm.AssetRegistry) LLMClientFactory {
|
||||||
return func(ctx context.Context, cfg config.Config, profileID string, overrides LLMRuntimeOverrides) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
return func(ctx context.Context, cfg config.Config, profileID string) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
||||||
return buildProductionLLMClient(ctx, cfg, profileID, overrides, assets)
|
return buildProductionLLMClient(ctx, cfg, profileID, assets)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID string, overrides LLMRuntimeOverrides, assets *llm.AssetRegistry) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID string, assets *llm.AssetRegistry) (contracts.StructuredLLMClient, []artifacts.LLMProfileManifest, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -157,16 +168,14 @@ func buildProductionLLMClient(ctx context.Context, cfg config.Config, profileID
|
|||||||
return nil, nil, fmt.Errorf("production asset registry must not be nil")
|
return nil, nil, fmt.Errorf("production asset registry must not be nil")
|
||||||
}
|
}
|
||||||
recorder := llm.NewLLMProfileRecorder()
|
recorder := llm.NewLLMProfileRecorder()
|
||||||
client, err := llm.NewPromptKitClient(llm.PromptKitClientConfig{
|
client, err := llm.NewScriptoriumClient(llm.ScriptoriumClientConfig{
|
||||||
ProfileDir: cfg.PromptKit.ProfileDir,
|
ProfileDir: cfg.Scriptorium.ProfileDir,
|
||||||
ProfileFile: cfg.PromptKit.ProfileFile,
|
ProfileFile: cfg.Scriptorium.ProfileFile,
|
||||||
LocalBackend: mapPromptKitLocalBackend(cfg.PromptKit.LocalBackend),
|
|
||||||
Assets: assets,
|
Assets: assets,
|
||||||
Recorder: recorder,
|
Recorder: recorder,
|
||||||
ReasoningEffort: overrides.ReasoningEffort,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("create PromptKit-backed LLM client: %w", err)
|
return nil, nil, fmt.Errorf("create Scriptorium-backed LLM client: %w", err)
|
||||||
}
|
}
|
||||||
scheduler, err := llm.NewScheduler(cfg.Concurrency.TotalLLM)
|
scheduler, err := llm.NewScheduler(cfg.Concurrency.TotalLLM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -154,23 +154,6 @@ func TestConfigValidateResolvesPipelineAndChecksSelection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigValidatePipelineDefaultProfileIsOffline(t *testing.T) {
|
|
||||||
configPath := writeCommandConfigContent(t, `version: 4
|
|
||||||
pipelines:
|
|
||||||
demo:
|
|
||||||
llm_profile: dnd-extraction
|
|
||||||
input: seriatim
|
|
||||||
artifacts:
|
|
||||||
spells:
|
|
||||||
extract: dnd/spells
|
|
||||||
`)
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
code := RunWithOptions([]string{"config", "validate", "--config", configPath, "--pipeline", "demo"}, &stdout, &stderr, Options{})
|
|
||||||
if code != 0 || !strings.Contains(stdout.String(), "valid for pipeline \"demo\"") || stderr.Len() != 0 {
|
|
||||||
t.Fatalf("code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPipelinesListSortsNormalizedIDsInTextAndJSON(t *testing.T) {
|
func TestPipelinesListSortsNormalizedIDsInTextAndJSON(t *testing.T) {
|
||||||
configPath := writeCommandConfig(t, " zeta ", "alpha")
|
configPath := writeCommandConfig(t, " zeta ", "alpha")
|
||||||
options := commandContractOptions(t)
|
options := commandContractOptions(t)
|
||||||
@@ -231,13 +214,13 @@ func commandContractOptionsWithLookup(t *testing.T, lookup func(string) (string,
|
|||||||
|
|
||||||
func writeCommandConfig(t *testing.T, firstID, secondID string) string {
|
func writeCommandConfig(t *testing.T, firstID, secondID string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
content := fmt.Sprintf("version: 4\npipelines:\n %q:\n input: seriatim\n %q:\n input: seriatim\n", firstID, secondID)
|
content := fmt.Sprintf("version: 3\npipelines:\n %q:\n input: seriatim\n %q:\n input: seriatim\n", firstID, secondID)
|
||||||
return writeCommandConfigContent(t, content)
|
return writeCommandConfigContent(t, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeResolvableCommandConfig(t *testing.T) string {
|
func writeResolvableCommandConfig(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return writeCommandConfigContent(t, `version: 4
|
return writeCommandConfigContent(t, `version: 3
|
||||||
pipelines:
|
pipelines:
|
||||||
demo:
|
demo:
|
||||||
input: seriatim
|
input: seriatim
|
||||||
|
|||||||
@@ -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, "npc_registry") || !hasReferenceSlot(extractSpec.ReferenceSlots, "scene_descriptions") || !hasReferenceSlot(normalizeSpec.ReferenceSlots, "npc_registry") {
|
if !hasReferenceSlot(extractSpec.ReferenceSlots, "npcs") || !hasReferenceSlot(extractSpec.ReferenceSlots, "scene_descriptions") || !hasReferenceSlot(normalizeSpec.ReferenceSlots, "npcs") {
|
||||||
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: "npc_registry", Source: "npc-run/lanes/npc_registry.json", BindingSource: contracts.ReferenceBindingSourceCLI},
|
{Stage: pipeline.StageExtract, 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},
|
{Stage: pipeline.StageNormalize, LaneID: "combat", SlotName: "npcs", Source: "npc-run/lanes/npcs.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, "npc_registry") || !hasReferenceBinding(boundLane.ExtractReferences.Bindings, "scene_descriptions") || !hasReferenceBinding(boundLane.NormalizeReferences.Bindings, "npc_registry") {
|
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") {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,433 +0,0 @@
|
|||||||
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"))
|
|
||||||
if len(evidence) == 0 {
|
|
||||||
t.Fatalf("evidence context = %#v, want selected source-unit evidence", evidence)
|
|
||||||
}
|
|
||||||
seenEvidenceUnits := make(map[int]struct{}, len(evidence))
|
|
||||||
for _, unit := range evidence {
|
|
||||||
if unit.Ref.SourceID != "session-ravenfall" || unit.Ref.StartUnitID != unit.ID || unit.Ref.EndUnitID != unit.ID {
|
|
||||||
t.Fatalf("evidence unit = %#v, want unchanged source-unit self-reference", unit)
|
|
||||||
}
|
|
||||||
if _, exists := seenEvidenceUnits[unit.ID]; exists {
|
|
||||||
t.Fatalf("evidence context = %#v, want each source unit once", evidence)
|
|
||||||
}
|
|
||||||
seenEvidenceUnits[unit.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_unit_id":5,"end_unit_id":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 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
|
|
||||||
}
|
|
||||||
135
internal/cli/dnd_interactions_contract_test.go
Normal file
135
internal/cli/dnd_interactions_contract_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
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/npcregistry"
|
npccodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/npcs"
|
||||||
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcregistry"
|
npcextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/npcs"
|
||||||
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcregistry"
|
npcnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/npcs"
|
||||||
)
|
)
|
||||||
|
|
||||||
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 != "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 {
|
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 {
|
||||||
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.npc_registry"}) {
|
if !ok || !reflect.DeepEqual(extractSpec.Requires, []string{"chunks", "source.transcript"}) || !reflect.DeepEqual(extractSpec.Provides, []string{"dnd.npcs"}) {
|
||||||
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.NPCRegistryKind)
|
mergeSpec, ok := catalog.Mergers.SpecForArtifact(pipeline.DefaultMergeModule, dnd.NPCListKind)
|
||||||
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.NPCRegistryKind)
|
normalizeSpec, ok := catalog.Normalizers.SpecForArtifact(npcnormalize.Key, dnd.NPCListKind)
|
||||||
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.NPCRegistryKind)
|
codecSpec, ok := catalog.ArtifactCodecs.Spec(dnd.NPCListKind)
|
||||||
if !ok || codecSpec.Kind != dnd.NPCRegistryKind || codecSpec.Schema.ID != npccodec.SchemaID || codecSpec.Schema.Version != npccodec.SchemaVersion {
|
if !ok || codecSpec.Kind != dnd.NPCListKind || 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/npc-registry/shape"),
|
pipeline.Binding("extract/dnd/npcs/shape"),
|
||||||
pipeline.Binding("extract/dnd/npc-registry/source_refs"),
|
pipeline.Binding("extract/dnd/npcs/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npc-registry/source_relatedness"),
|
pipeline.Binding("extract/dnd/npcs/source_relatedness"),
|
||||||
}
|
}
|
||||||
wantNormalizeChain := []pipeline.ModuleBinding{
|
wantNormalizeChain := []pipeline.ModuleBinding{
|
||||||
pipeline.Binding("generic/valid_json"),
|
pipeline.Binding("generic/valid_json"),
|
||||||
pipeline.Binding("extract/dnd/npc-registry/shape"),
|
pipeline.Binding("extract/dnd/npcs/shape"),
|
||||||
pipeline.Binding("normalize/dnd/npc-registry/identity"),
|
pipeline.Binding("normalize/dnd/npcs/identity"),
|
||||||
pipeline.Binding("extract/dnd/npc-registry/source_refs"),
|
pipeline.Binding("extract/dnd/npcs/source_refs"),
|
||||||
pipeline.Binding("generic/valid_json_schema"),
|
pipeline.Binding("generic/valid_json_schema"),
|
||||||
pipeline.Binding("extract/dnd/npc-registry/source_relatedness"),
|
pipeline.Binding("extract/dnd/npcs/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["npc_registry"]
|
lane := profile.Artifacts["npcs"]
|
||||||
lane.Extract.Options = map[string]any{"unexpected": true}
|
lane.Extract.Options = map[string]any{"unexpected": true}
|
||||||
profile.Artifacts["npc_registry"] = lane
|
profile.Artifacts["npcs"] = 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["npc_registry"]
|
lane := profile.Artifacts["npcs"]
|
||||||
lane.Normalize.Options = map[string]any{"unexpected": true}
|
lane.Normalize.Options = map[string]any{"unexpected": true}
|
||||||
profile.Artifacts["npc_registry"] = lane
|
profile.Artifacts["npcs"] = 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["npc_registry"]
|
lane := profile.Artifacts["npcs"]
|
||||||
lane.Validators = []pipeline.ModuleBinding{pipeline.Binding("normalize/dnd/npc-registry/identity")}
|
lane.Validators = []pipeline.ModuleBinding{pipeline.Binding("normalize/dnd/npcs/identity")}
|
||||||
profile.Artifacts["npc_registry"] = lane
|
profile.Artifacts["npcs"] = 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{
|
||||||
"npc_registry": {
|
"npcs": {
|
||||||
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),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,217 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
|
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
@@ -30,7 +28,7 @@ func TestProductionSceneDescriptionWorkflow(t *testing.T) {
|
|||||||
Output: pipeline.Binding("json"),
|
Output: pipeline.Binding("json"),
|
||||||
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
Artifacts: map[string]pipeline.ArtifactLaneProfile{
|
||||||
"scene-descriptions": {
|
"scene-descriptions": {
|
||||||
Extract: pipeline.ModuleBinding{Module: sceneextract.Key, LLMProfile: "scene-description-profile"},
|
Extract: pipeline.Binding(sceneextract.Key),
|
||||||
Normalize: pipeline.Binding(scenenormalize.Key),
|
Normalize: pipeline.Binding(scenenormalize.Key),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -47,8 +45,7 @@ func TestProductionSceneDescriptionWorkflow(t *testing.T) {
|
|||||||
t.Fatalf("resolved references = %#v / %#v, want no generated or required references", lane.ExtractReferences, lane.NormalizeReferences)
|
t.Fatalf("resolved references = %#v / %#v, want no generated or required references", lane.ExtractReferences, lane.NormalizeReferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
llmClient := &sceneDescriptionLLM{}
|
prepared, err := pipeline.Prepare(effective.ResolvedPipeline, components.registries, pipeline.ModuleDependencies{LLM: sceneDescriptionLLM{}})
|
||||||
prepared, err := pipeline.Prepare(effective.ResolvedPipeline, components.registries, pipeline.ModuleDependencies{LLM: llmClient})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Prepare() error = %v", err)
|
t.Fatalf("Prepare() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -63,14 +60,6 @@ func TestProductionSceneDescriptionWorkflow(t *testing.T) {
|
|||||||
if output.Manifest.ValidationStatus != "approved" || len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 1 {
|
if output.Manifest.ValidationStatus != "approved" || len(output.Rejected) != 0 || len(output.NormalizeOutputs) != 1 {
|
||||||
t.Fatalf("run output = %#v, want one approved normalized artifact", output)
|
t.Fatalf("run output = %#v, want one approved normalized artifact", output)
|
||||||
}
|
}
|
||||||
wantProfiles := []artifacts.LLMProfileManifest{{
|
|
||||||
ID: "scene-description-profile",
|
|
||||||
Provider: "promptkit",
|
|
||||||
Model: "deterministic",
|
|
||||||
}}
|
|
||||||
if !reflect.DeepEqual(output.Manifest.LLMProfiles, wantProfiles) {
|
|
||||||
t.Fatalf("manifest LLM profiles = %#v, want %#v", output.Manifest.LLMProfiles, wantProfiles)
|
|
||||||
}
|
|
||||||
normalizedOutput := output.NormalizeOutputs[0]
|
normalizedOutput := output.NormalizeOutputs[0]
|
||||||
if normalizedOutput.NormalizerKey != scenenormalize.Key || normalizedOutput.Artifact.Kind != dnd.SceneDescriptionListKind || normalizedOutput.Artifact.Schema.ID != scenecodec.SchemaID || normalizedOutput.Artifact.Schema.Name != scenecodec.SchemaName || normalizedOutput.Artifact.Schema.Version != scenecodec.SchemaVersion {
|
if normalizedOutput.NormalizerKey != scenenormalize.Key || normalizedOutput.Artifact.Kind != dnd.SceneDescriptionListKind || normalizedOutput.Artifact.Schema.ID != scenecodec.SchemaID || normalizedOutput.Artifact.Schema.Name != scenecodec.SchemaName || normalizedOutput.Artifact.Schema.Version != scenecodec.SchemaVersion {
|
||||||
t.Fatalf("normalized output = %#v, want registered durable scene-description schema", normalizedOutput)
|
t.Fatalf("normalized output = %#v, want registered durable scene-description schema", normalizedOutput)
|
||||||
@@ -96,12 +85,9 @@ func TestProductionSceneDescriptionWorkflow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type sceneDescriptionLLM struct {
|
type sceneDescriptionLLM struct{}
|
||||||
mu sync.Mutex
|
|
||||||
profile *artifacts.LLMProfileManifest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *sceneDescriptionLLM) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
func (sceneDescriptionLLM) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, err
|
return contracts.StructuredCompletionResponse{}, err
|
||||||
}
|
}
|
||||||
@@ -121,27 +107,5 @@ func (client *sceneDescriptionLLM) CompleteStructured(ctx context.Context, req c
|
|||||||
if err := json.Unmarshal([]byte(content), out); err != nil {
|
if err := json.Unmarshal([]byte(content), out); err != nil {
|
||||||
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate structured response: %w", err)
|
return contracts.StructuredCompletionResponse{}, fmt.Errorf("populate structured response: %w", err)
|
||||||
}
|
}
|
||||||
profile := artifacts.LLMProfileManifest{
|
return contracts.StructuredCompletionResponse{Content: []byte(content), Provider: "test", Model: "deterministic", ProfileID: req.ProfileID}, nil
|
||||||
ID: req.ProfileID,
|
|
||||||
Provider: "promptkit",
|
|
||||||
Model: "deterministic",
|
|
||||||
}
|
|
||||||
client.mu.Lock()
|
|
||||||
client.profile = &profile
|
|
||||||
client.mu.Unlock()
|
|
||||||
return contracts.StructuredCompletionResponse{
|
|
||||||
Content: []byte(content),
|
|
||||||
Provider: profile.Provider,
|
|
||||||
Model: profile.Model,
|
|
||||||
ProfileID: profile.ID,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *sceneDescriptionLLM) LLMProfileManifests() []artifacts.LLMProfileManifest {
|
|
||||||
client.mu.Lock()
|
|
||||||
defer client.mu.Unlock()
|
|
||||||
if client.profile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []artifacts.LLMProfileManifest{*client.profile}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ import (
|
|||||||
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
||||||
"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"
|
||||||
locationoccurrenceextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationoccurrences"
|
|
||||||
locationextract "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/extract/locationregistry"
|
|
||||||
locationoccurrencenormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationoccurrences"
|
|
||||||
locationnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/locationregistry"
|
|
||||||
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
spellnormalize "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/normalize/spells"
|
||||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/seriatim/input/transcript"
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/seriatim/input/transcript"
|
||||||
)
|
)
|
||||||
@@ -28,9 +24,6 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
|||||||
for _, example := range maintainedExampleFiles(t) {
|
for _, example := range maintainedExampleFiles(t) {
|
||||||
t.Run(example.name, func(t *testing.T) {
|
t.Run(example.name, func(t *testing.T) {
|
||||||
cfg := loadMaintainedExample(t, example.path)
|
cfg := loadMaintainedExample(t, example.path)
|
||||||
if example.name == "complete" && (cfg.Concurrency.TotalLLM != 2 || cfg.Concurrency.StageWorkers["extract"] != 2) {
|
|
||||||
t.Fatalf("complete example concurrency = %#v, want explicit limits of 2", cfg.Concurrency)
|
|
||||||
}
|
|
||||||
raw, err := os.ReadFile(example.transcriptPath)
|
raw, err := os.ReadFile(example.transcriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read maintained transcript %q: %v", example.transcriptPath, err)
|
t.Fatalf("read maintained transcript %q: %v", example.transcriptPath, err)
|
||||||
@@ -58,55 +51,21 @@ func TestMaintainedExamplesLoadResolveAndList(t *testing.T) {
|
|||||||
t.Fatalf("materialize maintained example references for %q: %v", pipelineID, err)
|
t.Fatalf("materialize maintained example references for %q: %v", pipelineID, err)
|
||||||
}
|
}
|
||||||
if example.name == "complete" {
|
if example.name == "complete" {
|
||||||
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-registry,location-registry,npc-registry,scene-descriptions|extract-events:combat-turns,item-occurrences,location-occurrences,npc-occurrences,spells|track-enemies:enemy-events" {
|
if got := exampleStepLaneIDs(materialized); strings.Join(got, "|") != "describe-session:item-events,npcs,scene-descriptions|extract-events:combat-turns,npc-interactions,spells" {
|
||||||
t.Fatalf("complete example steps and lanes = %v, want the documented D&D extractor composition", got)
|
t.Fatalf("complete example steps and lanes = %v, want every D&D extractor in the documented two-step composition", got)
|
||||||
}
|
|
||||||
locationLane := referenceContractLane(t, materialized, "location-registry")
|
|
||||||
if locationLane.ArtifactKind != dnd.LocationRegistryKind || locationLane.Extract.Module != locationextract.Key || locationLane.Extract.Retries != 2 || locationLane.Merge.Module != pipeline.DefaultMergeModule || locationLane.Normalize.Module != locationnormalize.Key || locationLane.Normalize.Retries != 2 {
|
|
||||||
t.Fatalf("location lane = %#v, want typed registry composition", locationLane)
|
|
||||||
}
|
|
||||||
occurrenceLane := referenceContractLane(t, materialized, "location-occurrences")
|
|
||||||
if occurrenceLane.ArtifactKind != dnd.LocationOccurrenceListKind || occurrenceLane.Extract.Module != locationoccurrenceextract.Key || occurrenceLane.Extract.Retries != 2 || occurrenceLane.Merge.Module != pipeline.DefaultMergeModule || occurrenceLane.Normalize.Module != locationoccurrencenormalize.Key {
|
|
||||||
t.Fatalf("location occurrence lane = %#v, want typed occurrence composition", occurrenceLane)
|
|
||||||
}
|
|
||||||
for _, target := range []pipeline.ResolvedReferenceTarget{occurrenceLane.ExtractReferences, occurrenceLane.NormalizeReferences} {
|
|
||||||
binding, found := generatedReferenceBinding(target.Bindings, "location_registry")
|
|
||||||
if !found || binding.Artifact.Step != "describe-session" || binding.Artifact.Lane != "location-registry" {
|
|
||||||
t.Fatalf("location occurrence %s reference = %#v, want generated location registry", target.Stage, binding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, slot := range []string{"party", "glossary"} {
|
|
||||||
if len(occurrenceLane.ExtractReferences.ReferenceSet.Slots[slot].Items) != 1 {
|
|
||||||
t.Fatalf("location occurrence extractor %s reference was not materialized: %#v", slot, occurrenceLane.ExtractReferences)
|
|
||||||
}
|
|
||||||
if _, found := occurrenceLane.NormalizeReferences.ReferenceSet.Slots[slot]; found {
|
|
||||||
t.Fatalf("location occurrence normalizer unexpectedly consumes %s: %#v", slot, occurrenceLane.NormalizeReferences)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
spellLane := referenceContractLane(t, materialized, "spells")
|
spellLane := referenceContractLane(t, materialized, "spells")
|
||||||
if len(spellLane.ExtractReferences.ReferenceSet.Slots["spell_catalog"].Items) != 1 ||
|
if len(spellLane.ExtractReferences.ReferenceSet.Slots["spell_catalog"].Items) != 1 ||
|
||||||
len(spellLane.NormalizeReferences.ReferenceSet.Slots["spell_catalog"].Items) != 1 {
|
len(spellLane.NormalizeReferences.ReferenceSet.Slots["spell_catalog"].Items) != 1 {
|
||||||
t.Fatalf("complete example spell catalog reference was not materialized: %#v", spellLane)
|
t.Fatalf("complete example spell catalog reference was not materialized: %#v", spellLane)
|
||||||
}
|
}
|
||||||
itemOccurrenceLane := referenceContractLane(t, materialized, "item-occurrences")
|
itemEventLane := referenceContractLane(t, materialized, "item-events")
|
||||||
for _, references := range []pipeline.ResolvedReferenceTarget{itemOccurrenceLane.ExtractReferences, itemOccurrenceLane.NormalizeReferences} {
|
for _, references := range []pipeline.ResolvedReferenceTarget{itemEventLane.ExtractReferences, itemEventLane.NormalizeReferences} {
|
||||||
if _, found := references.ReferenceSet.Slots["npc_registry"]; found {
|
if _, found := references.ReferenceSet.Slots["npcs"]; found {
|
||||||
t.Fatalf("item occurrence lane unexpectedly depends on generated NPCs: %#v", itemOccurrenceLane)
|
t.Fatalf("item event lane unexpectedly depends on generated NPCs: %#v", itemEventLane)
|
||||||
}
|
}
|
||||||
if _, found := references.ReferenceSet.Slots["scene_descriptions"]; found {
|
if _, found := references.ReferenceSet.Slots["scene_descriptions"]; found {
|
||||||
t.Fatalf("item occurrence lane unexpectedly depends on generated scene descriptions: %#v", itemOccurrenceLane)
|
t.Fatalf("item event lane unexpectedly depends on generated scene descriptions: %#v", itemEventLane)
|
||||||
}
|
|
||||||
}
|
|
||||||
enemyEventLane := referenceContractLane(t, materialized, "enemy-events")
|
|
||||||
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(enemyEventLane.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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,37 +94,6 @@ func TestMaintainedConfigurationExampleSet(t *testing.T) {
|
|||||||
if got := strings.Join(names, ","); got != "dnd-complete.config.yml,dnd-minimal.config.yml" {
|
if got := strings.Join(names, ","); got != "dnd-complete.config.yml,dnd-minimal.config.yml" {
|
||||||
t.Fatalf("maintained configuration examples = %q, want only the minimal and complete D&D examples", got)
|
t.Fatalf("maintained configuration examples = %q, want only the minimal and complete D&D examples", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
profileEntries, err := os.ReadDir(repositoryPath("examples", "profiles"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
names = names[:0]
|
|
||||||
for _, entry := range profileEntries {
|
|
||||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".yml") {
|
|
||||||
names = append(names, entry.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
if got := strings.Join(names, ","); got != "dnd-extraction.yml" {
|
|
||||||
t.Fatalf("maintained operator profiles = %q, want dnd-extraction.yml", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMaintainedExamplesValidateEffectiveProfilesOffline(t *testing.T) {
|
|
||||||
t.Chdir(repositoryPath())
|
|
||||||
t.Setenv("OPENROUTER_API_KEY", "")
|
|
||||||
for _, example := range maintainedExampleFiles(t) {
|
|
||||||
t.Run(example.name, func(t *testing.T) {
|
|
||||||
var stdout, stderr strings.Builder
|
|
||||||
code := RunWithOptions([]string{
|
|
||||||
"config", "validate", "--config", example.path, "--pipeline", "dnd-session",
|
|
||||||
}, &stdout, &stderr, Options{})
|
|
||||||
if code != 0 || stderr.Len() != 0 || !strings.Contains(stdout.String(), `valid for pipeline "dnd-session"`) {
|
|
||||||
t.Fatalf("code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func exampleStepLaneIDs(resolved pipeline.ResolvedPipeline) []string {
|
func exampleStepLaneIDs(resolved pipeline.ResolvedPipeline) []string {
|
||||||
@@ -283,12 +211,6 @@ func TestMaintainedMalformedInputOnlyRecordsDebugFailureWhenRequested(t *testing
|
|||||||
if report.Succeeded || report.PipelineID != "dnd-session" {
|
if report.Succeeded || report.PipelineID != "dnd-session" {
|
||||||
t.Fatalf("failure report = %#v, want failed dnd-session report", report)
|
t.Fatalf("failure report = %#v, want failed dnd-session report", report)
|
||||||
}
|
}
|
||||||
invocation := readProductionJSON[debugbundle.Invocation](t, filepath.Join(bundle, "summary", "invocation.json"))
|
|
||||||
manifest := readProductionJSON[artifacts.RunManifest](t, filepath.Join(bundle, "summary", "run-manifest.json"))
|
|
||||||
manifestSession, found := manifest.Metadata["session_id"]
|
|
||||||
if invocation.SessionID == "" || !found || manifestSession != invocation.SessionID {
|
|
||||||
t.Fatalf("failed run sessions: invocation=%q manifest=%#v metadata=%#v", invocation.SessionID, manifestSession, manifest.Metadata)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user