Document D&D extractor contract

This commit is contained in:
2026-07-24 14:46:19 +00:00
parent 1aa30a73db
commit a1b76093ce
4 changed files with 48 additions and 8 deletions

View File

@@ -64,12 +64,13 @@ lanes. The canonical ordering and cache-boundary policy is documented in
contracts expose only Notarius structured-completion types, not Scriptorium contracts expose only Notarius structured-completion types, not Scriptorium
public types. public types.
The shared `ChunkPromptMaterial` helper owns common transcript material The shared `PrepareChunkExtraction` helper owns common extraction preflight and
preparation for the spell, NPC, combat-turn, and NPC-interaction extractors. It clones supplied transcript material preparation for the spell, NPC, combat-turn,
source metadata, falls back to the materialized chunk when content is absent, NPC-interaction, and scene-description extractors. It validates common request
checks that content remains chunk-identical, and fills only the common default state, clones supplied source metadata, falls back to the materialized chunk
fields. Extractors retain their request validation and wrap helper errors with when content is absent, checks that content remains chunk-identical, and fills
their module context. only the common default fields. Extractors retain receiver, dependency, and
lane-specific checks locally and wrap helper errors with their module context.
Reference material may inform a module or prompt but must not become source Reference material may inform a module or prompt but must not become source
evidence. The resolver and materializer behavior is described in evidence. The resolver and materializer behavior is described in
@@ -575,6 +576,37 @@ When adding a production module or validator:
Do not add the extension to `docs/development.md`; that file routes by task and Do not add the extension to `docs/development.md`; that file routes by task and
does not inventory implementations. does not inventory implementations.
### D&D Extractor Contract
New D&D extractors preserve these package-owned responsibilities:
- Reject unknown options unless an option namespace is intentionally
extensible, and use shared common preflight while retaining receiver,
dependency, and lane-specific checks locally.
- Return independently owned results and exposed metadata that callers may
safely mutate.
- Keep the private response DTO, structural response schema and its identity,
provider-response mapping, durable artifact conversion, and lane diagnostics
in the owning package.
- Include every stable semantic input that can change durable output in
checkpoint identity. Consider prompt, schema, mapping, canonicalization,
prepared reference projections, identity, normalization, and trimming where
applicable.
- Add focused behavioral coverage where the lane's risks warrant it, including
construction and registration, option rejection, preflight, provider
failures, structured decoding, mapping and ownership, prompt
role/input/cache order, and checkpoint invalidation.
Prompt ordering, shared-asset ownership, cache boundaries, and private-schema
rules are defined in [LLM Runtime](llm.md#dd-extraction-prompt-ordering-and-cache-boundaries).
[Pipeline Internals](pipeline.md#reference-materialization) owns reference
materialization, and its [checkpoint hooks](pipeline.md#checkpoint-and-debug-hooks)
define checkpoint behavior. Follow [Architecture](../policy/architecture.md#source-and-domain-boundaries)
for ownership boundaries and the [Testing Policy](../policy/testing.md) when
selecting durable coverage. This contract intentionally does not prescribe
prompt prose or length, hashes, test counts, filenames, fixture layouts, or
generic implementation builders.
## Tests To Inspect ## Tests To Inspect
- Package-local `*_test.go` files under the module or validator being changed. - Package-local `*_test.go` files under the module or validator being changed.

View File

@@ -14,7 +14,6 @@ import (
const ( const (
Key = "dnd/combat-turns" Key = "dnd/combat-turns"
ArtifactType = "dnd.combat_turn"
mappingPolicy = "dnd.combat_turns.extract_mapping.v2" mappingPolicy = "dnd.combat_turns.extract_mapping.v2"
) )

View File

@@ -14,7 +14,6 @@ import (
) )
const Key = "dnd/spells" const Key = "dnd/spells"
const ArtifactType = "dnd.spell_cast"
const SchemaVersion = "v1" const SchemaVersion = "v1"
const mappingPolicy = "dnd.spells.extract_mapping.v2" const mappingPolicy = "dnd.spells.extract_mapping.v2"
@@ -122,6 +121,9 @@ func (e *Extractor) ReferenceSlots() []contracts.ReferenceSlot {
} }
func (e *Extractor) ManifestMetadata() map[string]any { func (e *Extractor) ManifestMetadata() map[string]any {
if e == nil {
return nil
}
metadata := map[string]any{ metadata := map[string]any{
"prompt_id": PromptID, "prompt_id": PromptID,
"prompt_version": SchemaVersion, "prompt_version": SchemaVersion,

View File

@@ -174,6 +174,13 @@ func TestExtractorManifestMetadataIncludesLLMSchemaProvenance(t *testing.T) {
} }
} }
func TestNilExtractorManifestMetadata(t *testing.T) {
var extractor *Extractor
if metadata := extractor.ManifestMetadata(); metadata != nil {
t.Fatalf("nil extractor metadata = %#v, want nil", metadata)
}
}
func TestExtractPassesReferencesAsPromptInputs(t *testing.T) { func TestExtractPassesReferencesAsPromptInputs(t *testing.T) {
client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{}}} client := &fakeSpellsLLMClient{response: extractionResponse{SpellCasts: []spellCastResponse{}}}
req := extractionRequest() req := extractionRequest()