21 KiB
Implementation Plan
Objective
Implement D&D Combat Scene Semantic Validation:
add the first production D&D LLM-backed validator as an optional extract-stage
validator for dnd/scene-descriptions, while preserving the existing chunk
plan, durable artifacts, downstream combat gate, maintained examples, and
production default validator chains.
This plan is ordered and decision-complete. Each numbered stage is scoped for one gpt-5.6-terra implementation prompt. Complete and validate each stage before beginning the next one.
Decisions Shared By All Stages
- Scene classification remains owned by the per-chunk
dnd/scene-descriptionsextractor. Do not annotatednd/scenesplans or add a chunk-stage validator. - Add the typed validator package
internal/modules/dnd/validate/scenedescriptions/combat_semanticswith keyextract/dnd/scene-descriptions/combat_semantics, registered fordnd.SceneDescriptionListKindwith execution classllm_backed. - The validator is selectable through the existing validator override but is absent from all production default chains. It reviews only combat versus non-combat, not title, summary, non-combat subtype, or boundary quality.
- Reuse the injected scheduled
StructuredLLMClient, existing profile precedence, PromptKit structural-repair budget, validator execution retries, producer semantic retries, and validation failure policy. Add no parallel provider, scheduler, retry, or configuration mechanism. - Use prompt ID
dnd.scene_descriptions.validate_combat, default profilednd-extraction, and private prompt/schema versionv1. - The LLM response has exactly two required fields:
verdictandexplanation. Verdict is one ofapproved,combat_should_be_added, orcombat_should_be_removed. Reject unknown fields, optional properties, anduniqueItems. Require a trimmed, nonblank explanation of at most 512 Unicode code points in both schema and deterministic interpretation. - Map rejections to application-owned reason codes
scene_active_combat_not_classifiedandscene_combat_classification_unsupported. Codes, validator keys, opaque IDs, and source coordinates are never model instructions. - Transcript unit IDs may remain in ordinary source presentation, but the model must not reproduce them. Candidate presentation contains only the proposed kind; application code owns IDs, ranges, mapping, and provenance.
- PromptKit may repair structural validator output. A final malformed, inconsistent, blank, or oversized validator response is an execution failure, not a semantic rejection. Do not recursively validate or provide semantic feedback to the validator's own LLM call.
- Rejection guidance tells the producer either to return
kind: combator to choose the appropriatenarrative,recap, ormetakind, and includes the bounded transcript-grounded explanation. It contains no internal code. - Keep durable schemas at their current versions and retain the exact
combatgates in combat-turn and enemy-event extraction. - Default tests are deterministic, offline, and credential-free. Use fakes at the structured LLM boundary. Do not add exact prompt-length, token-count, or complete prose snapshot tests.
- No ADR is required. This feature applies the architecture and ADRs 0012, 0014, and 0015 without changing stage ownership or a durable contract.
Stage 1: Extract The Shared Combat Policy
✅ Complete
Goal
Give the existing extractor and future validator one byte-identical owner for the combat decision policy without changing the extractor contract or the common extraction prompt prefix.
Work
- Add
assets/dnd/shared/prompts/common-dnd-scene-combat-policy.mdand move into it the current combat definition, mixed-chunk precedence, and explicit non-examples: planning, threats, hostile dialogue, aftermath, recollection, and rules discussion without active encounter play. - Remove those rules from the local scene-description instructions. Retain
the exactly-one-scene task, all four supported kind names, the
narrative/recap/metadefinitions and precedence, and title/summary policy. The rendered prompt must remain complete and non-repetitive. - Add the shared file to the scene extractor's
PromptAssetManifestandprompt.yaml. Preserve the extraction-wide prefix through the cached transcript. Place the combat-policy message after the transcript and before local instructions so other extraction lanes retain an identical prefix. - Update prompt asset and composition tests to prove the shared policy appears once in the correct relative position. Use semantic sentinels rather than a full prompt snapshot or exact size assertion.
- Do not change the prompt ID/version, response schema, extractor logic, artifact, or validator chains.
Inspect
assets/dnd/shared/prompts/assets/dnd/scene-descriptions/prompts/internal/modules/dnd/extract/scenedescriptions/prompt_assets.gointernal/modules/dnd/extract/scenedescriptions/prompt_assets_test.gointernal/modules/dnd/register/prompt_cache_test.go
Acceptance And Validation
- One embedded shared asset owns the combat policy.
- The scene prompt still prepares with complete classification instructions.
- All extraction prompts retain the same prefix through the transcript.
- No brittle change-detector test is added.
Run:
go test ./internal/modules/dnd/extract/scenedescriptions
go test ./internal/modules/dnd/register
git diff --check
This stage is small enough for one implementation prompt.
Stage 2: Add Validator Prompt And Schema Assets
✅ Complete
Goal
Create and locally verify the private PromptKit contract before implementing semantic logic or global registration.
Work
-
Create
assets/dnd/scene-descriptions/validate/combat-semantics/with:prompts/prompt.yaml;prompts/instructions.md;prompts/proposed-kind.md; andschemas/dnd_scene_combat_semantics_llm.v1.json.
-
Define required inputs
transcript(application/json) andproposed_kind(text/plain). Order messages as system, shared combat policy, stable local validator instructions, variable proposed kind, and final variable shared chunk transcript. Mark only the two variable messages ephemeral. Do not select campaign references or the identity fragment. -
Define the verdict semantics precisely.
approvedapproves only the proposed combat status, not the whole scene description. -
Define a strict object schema with
additionalProperties: false, both fields inrequired, the three-value verdict enum, and explanationminLength: 1andmaxLength: 512. Include no confidence, ID, range, diagnostics, or correction field. -
Add package-local asset/schema plumbing in
combat_semantics, following existing D&D manifests. Use:- schema key
dnd_scene_combat_semantics_llm; - schema ID
notarius.dnd.scene_descriptions.combat_semantics.llm; and - schema name
notarius_dnd_scene_combat_semantics_llm_v1.
Expose
RegisterPromptAssets, cached asset metadata, and mutation-safe schema loading. Do not register them at the D&D composition root yet. - schema key
-
Add focused tests that prepare the embedded prompt, require both inputs, accept all valid verdicts, and reject missing, extra, mistyped, blank, and oversized fields. Metadata tests must prove hashes are available without exposing raw assets.
Inspect
- existing scene-description extractor asset and schema code
- analogous D&D semantic-reconciliation prompt assets
internal/framework/llminternal/framework/promptfs
Acceptance And Validation
- The package-local embedded prompt and schema prepare offline.
- All schema fields are required and no
uniqueItemsappears. - The prompt contains only the policy, proposed kind, and transcript needed for this decision and selects rather than copies the shared policy.
- No global registration changes occur yet.
Run:
go test ./internal/modules/dnd/validate/scenedescriptions/combat_semantics
go test ./internal/modules/dnd/extract/scenedescriptions
git diff --check
This stage is small enough for one implementation prompt.
Stage 3: Implement The Typed Validator
Goal
Implement package-local validator construction, preconditions, structured completion, verdict interpretation, corrections, fingerprints, and behavioral tests.
Work
-
Define the module key, two reason codes, zero-field strict
Options, private response type, and validator holding only the injected client and immutable prompt/schema/policy metadata. Construction fails contextually for a nil LLM dependency. -
Register through a typed builder consuming
request.Dependencies.LLM. Retain no unrelated references or mutable request maps. -
Before calling the model, require extract stage, non-nil source and chunk, valid chunk-scoped source input, exactly one scene satisfying the existing extract shape contract, and scene ID/range equal to the current chunk. Violations are execution errors, not approval or skip.
-
Call
CompleteStructuredonce per execution with validator key as stage name; the selected prompt/version; request profile, session ID, and repair pointer; cloned chunk source input astranscript; and only the scene kind string asproposed_kind. Do not forward references, scene prose, identifiers, ranges, reason codes, or semantic correction. -
Interpret output deterministically:
- trim and enforce the 512-code-point explanation bound;
- approve
approvedfor either proposed combat status; - accept
combat_should_be_addedonly for a non-combat proposal; - accept
combat_should_be_removedonly for a combat proposal; and - treat every unknown or inconsistent result as execution failure.
-
Build bounded
ValidationResultmessages and semantic correction guidance. Approved results carry no rejection message or quality diagnostic. -
Implement
Name,ExecutionClass,Spec, manifest metadata, checkpoint fingerprints, and strict option validation. A validator spec does not declare a producer correction protocol. -
Add consolidated table-driven tests with a recording fake LLM for approvals, both rejection directions, inconsistent results, explanation bounds, input preconditions, completion error/cancellation, request-field forwarding, model-visible input exclusions, option rejection, and metadata ownership.
Inspect
internal/framework/contracts/{contracts,typed_pipeline}.gointernal/framework/pipeline/{construction,validator_registry}.gointernal/modules/dnd/validate/scenedescriptions/{shape,source_refs}- existing LLM-backed D&D extractor and normalizer packages
Acceptance And Validation
- The package is usable without provider-specific types or global state.
- It cannot judge a wrong-stage, aggregate, malformed, or wrong-chunk value.
- Semantic rejection and execution failure remain distinct.
- Guidance is meaningful and contains no opaque identifiers or internal codes.
- Consequential contracts are tested offline through the package boundary.
Run:
go test ./internal/modules/dnd/validate/scenedescriptions/...
go vet ./internal/modules/dnd/validate/scenedescriptions/...
git diff --check
This stage is small enough for one implementation prompt.
Stage 4: Register And Resolve The Optional Validator
Goal
Expose the module through production D&D composition and prove explicit configuration works without changing defaults.
Work
- Register the validator builder in
internal/modules/dnd/register/validators.goand its assets ininternal/modules/dnd/register/modules.go, preserving deterministic registration and contextual errors. - Extend registrar/catalog tests to prove the typed kind, execution class, production prompt/schema availability, and absence from both scene default chains.
- Add one focused resolution/preparation case whose scene extract override
repeats the current default validators in order and appends the semantic
validator last. Prove validator
llm_profile,structured_output_repair_attempts, andretriesreach the prepared LLM-backed validator through existing machinery. - Add a negative case for selecting this typed validator against an incompatible artifact kind if existing typed-registry tests do not already exercise the concrete production key. Rely on generic tests for all other validator-binding rules.
- Do not alter the minimal or complete D&D example configurations.
Inspect
internal/modules/dnd/register/{validators,modules,chains}.gointernal/modules/dnd/register/register_test.gointernal/modules/dnd/register/prompt_cache_test.go- typed validator resolution/preparation tests under
internal/framework/pipeline
Acceptance And Validation
- Production registration exposes the validator and prompt.
- An explicit compatible override resolves before execution.
- Default chain membership/order and maintained examples are unchanged.
- Incompatible selection still fails during resolution or preparation.
Run:
go test ./internal/modules/dnd/register
go test ./internal/framework/pipeline
go test ./internal/cli
git diff --check
This stage is small enough for one implementation prompt.
Stage 5: Prove Feedback-Aware Producer Correction
Goal
Prove the assembled D&D path can reject a well-formed wrong scene kind, guide the scene producer, and accept a corrected replacement without leaking internal identifiers.
Work
-
Add one focused test under
internal/modules/integration(or extend the narrowest assembled scene test) with a stateful fake structured client that distinguishes scene producer and semantic validator prompt IDs. -
Configure the current deterministic scene extract chain followed by the semantic validator, with a positive producer retry budget and no unnecessary validator execution retries.
-
Use a chunk containing clear active combat. Return a valid non-combat scene first,
combat_should_be_addedfrom the validator, a corrected combat scene after feedback, and approval on revalidation. -
Assert outcomes rather than private choreography:
- the accepted normalized scene is
combat; - superseded attempts leave no rejection or process warning;
- the producer receives the exact latest defective model candidate and one semantic correction request;
- guidance directs
kind: combatand includes the bounded explanation; - guidance excludes validator key, reason code, chunk ID, and source range; and
- the corrected candidate traverses the complete validator chain again.
- the accepted normalized scene is
-
Keep unsupported-combat verdict mapping owned by Stage 3 package tests unless adding it to this integration test is materially cheaper than a duplicate assembled setup.
-
Rely on existing framework tests for aggregate guidance, exhaustion,
warn_continue, validator retries, and checkpoint ineligibility. Change generic framework code only if this test exposes a real defect, and report the scope expansion explicitly.
Inspect
internal/modules/integration/- framework extract-correction and semantic-correction tests
internal/cli/assembled_spell_pipeline_contract_test.gointernal/modules/dnd/extract/scenedescriptions/
Acceptance And Validation
- One deterministic assembled test proves D&D semantic correction and revalidation.
- Only the latest defective response and semantic guidance reach the producer.
- No internal code or deterministic identifier reaches correction text.
- Existing framework budgets and policies remain the only orchestration path.
Run:
go test ./internal/modules/integration
go test ./internal/framework/pipeline
go test ./internal/modules/dnd/extract/scenedescriptions
git diff --check
This stage is small enough for one implementation prompt.
Stage 6: Add Evaluation Material And Documentation
Goal
Provide accurate opt-in configuration guidance and a bounded human-reviewed corpus for evaluation before any future default-chain proposal.
Work
- Add a synthetic, non-sensitive corpus at
internal/modules/dnd/validate/scenedescriptions/combat_semantics/testdata/evaluation_cases.json. Each case has a human-readable unique name, transcript units, proposed kind, expected verdict, and reviewer rationale. Cover active combat, setup and aftermath around combat, multi-phase combat, rules interruption, planning, threats, hostile dialogue, aftermath without combat, recap/recollection, and sustained out-of-character discussion. - Add one cheap fixture-contract test for strict decoding, unique/nonblank names, nonblank input, supported proposed kinds/verdicts, and representation of all verdict classes. Do not assert the exact fixture count or treat fake responses as a measurement of model quality.
- Document a manual or explicitly opt-in provider evaluation procedure in
docs/internal/dnd.md. Require recording false acceptance, false rejection, producer correction success, added calls, latency, and token use before default-chain promotion. Do not add live calls to the default suite. - Update
docs/config.md, the canonical configuration owner, to list the key, state its LLM-backed opt-in status, and show the smallest illustrative override that repeats the existing scene extract chain and appends the semantic validator. Emphasize that an override replaces the chain and that a positive producer retry budget is required if rejection should trigger a corrected scene-description attempt. Link to the existing binding table instead of repeating profile, structural-repair, producer-retry, or validator-retry rules. - Update
docs/internal/dnd.mdto describe the module boundary, prompt-policy ownership, verdict mapping, deterministic correction mapping, and difference from deferred boundary-coherence review. Link to configuration and generic retry/failure owners rather than duplicating volatile contracts. - Update
docs/internal/overview.mdordocs/internal/modules.mdonly if its implemented inventory would otherwise become false. Do not add an ADR, a third example, or a durable integration contract change.
Inspect
docs/{config,internal/dnd,internal/modules,internal/overview}.md- all files under
docs/policy/ - the two maintained D&D example configurations
Acceptance And Validation
- The corpus is bounded, synthetic, strict, and covers the intended cases without masquerading as a provider quality test.
- Configuration documents one correct opt-in path and preserves replacement semantics.
- Canonical documentation owners describe only behavior implemented in Stages 1–5, with no volatile duplication or default-enabled claim.
- All new relative Markdown links resolve manually; the repository currently has no automated documentation link checker.
Run:
go test ./internal/modules/dnd/validate/scenedescriptions/combat_semantics
go test ./internal/modules/dnd/register ./internal/framework/pipeline ./internal/cli
git diff --check
This stage is small enough for one implementation prompt.
Stage 7: Final Verification And Scope Audit
Goal
Verify the feature as one coherent change and leave it ready for a separate post-implementation review.
Work
- Compare the complete diff with the feature roadmap and every stage above. Implement or report every unmet acceptance criterion; do not silently defer required work.
- Confirm by inspection that the validator is registered but absent from
default chains/examples; both consumers select the shared combat policy; no
duplicate policy remains; its schema has all fields required and no
uniqueItems; model-facing content requests no opaque IDs or internal codes; it uses the scheduled client and existing budgets; and downstream exact-combat gates are unchanged. - Format changed Go files. Remove only redundant tests, stale comments, unused helpers, or roadmap-inconsistent documentation found during this review; do not perform unrelated refactoring.
- Run the full validation suite. If an environmental limitation blocks a command, run unaffected checks and record the exact limitation rather than weakening tests.
- Leave both roadmap documents in place for the post-implementation audit. Do not promote the validator or retire the roadmaps.
Acceptance And Validation
- Focused and repository-wide checks pass.
- No durable schema, chunk plan, default chain, example, or downstream gate changed outside the roadmap.
- The worktree contains no binary, credential, private transcript, ignored debris, or unrelated modification.
- Documentation paths and links resolve, and the repository is ready for completeness and code-quality review.
Run:
gofmt -w <changed-go-files>
go test ./...
go test -race ./...
go vet ./...
go build ./cmd/notarius
git diff --check
git status --short --untracked-files=all
This stage is small enough for one implementation prompt.
Open Questions
None. The feature roadmap and this plan fix the stage, scope, schema, prompt ownership, reason-code mapping, configuration status, failure semantics, evaluation gate, and documentation ownership. Default-chain promotion and chunk-boundary semantic review are explicitly outside this plan.