Plan combat scene validation

This commit is contained in:
2026-08-27 23:26:59 +00:00
parent 61436d7c18
commit 0f2b23dce1
3 changed files with 793 additions and 41 deletions

View File

@@ -0,0 +1,488 @@
# Implementation Plan
## Objective
Implement [D&D Combat Scene Semantic Validation](combat-scene-validation.md):
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-descriptions` extractor. Do not annotate `dnd/scenes` plans or add
a chunk-stage validator.
- Add the typed validator package
`internal/modules/dnd/validate/scenedescriptions/combat_semantics` with key
`extract/dnd/scene-descriptions/combat_semantics`, registered for
`dnd.SceneDescriptionListKind` with execution class `llm_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 profile
`dnd-extraction`, and private prompt/schema version `v1`.
- The LLM response has exactly two required fields: `verdict` and
`explanation`. Verdict is one of `approved`, `combat_should_be_added`, or
`combat_should_be_removed`. Reject unknown fields, optional properties, and
`uniqueItems`. 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_classified` and
`scene_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: combat` or to
choose the appropriate `narrative`, `recap`, or `meta` kind, and includes the
bounded transcript-grounded explanation. It contains no internal code.
- Keep durable schemas at their current versions and retain the exact `combat`
gates 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
### 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
1. Add `assets/dnd/shared/prompts/common-dnd-scene-combat-policy.md` and 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.
2. Remove those rules from the local scene-description instructions. Retain
the exactly-one-scene task, all four supported kind names, the
`narrative`/`recap`/`meta` definitions and precedence, and title/summary
policy. The rendered prompt must remain complete and non-repetitive.
3. Add the shared file to the scene extractor's `PromptAssetManifest` and
`prompt.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.
4. 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.
5. 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.go`
- `internal/modules/dnd/extract/scenedescriptions/prompt_assets_test.go`
- `internal/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:
```sh
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
### Goal
Create and locally verify the private PromptKit contract before implementing
semantic logic or global registration.
### Work
1. Create
`assets/dnd/scene-descriptions/validate/combat-semantics/` with:
- `prompts/prompt.yaml`;
- `prompts/instructions.md`;
- `prompts/proposed-kind.md`; and
- `schemas/dnd_scene_combat_semantics_llm.v1.json`.
2. Define required inputs `transcript` (`application/json`) and
`proposed_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.
3. Define the verdict semantics precisely. `approved` approves only the
proposed combat status, not the whole scene description.
4. Define a strict object schema with `additionalProperties: false`, both
fields in `required`, the three-value verdict enum, and explanation
`minLength: 1` and `maxLength: 512`. Include no confidence, ID, range,
diagnostics, or correction field.
5. 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.
6. 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/llm`
- `internal/framework/promptfs`
### Acceptance And Validation
- The package-local embedded prompt and schema prepare offline.
- All schema fields are required and no `uniqueItems` appears.
- 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:
```sh
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
1. 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.
2. Register through a typed builder consuming `request.Dependencies.LLM`.
Retain no unrelated references or mutable request maps.
3. 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.
4. Call `CompleteStructured` once per execution with validator key as stage
name; the selected prompt/version; request profile, session ID, and repair
pointer; cloned chunk source input as `transcript`; and only the scene kind
string as `proposed_kind`. Do not forward references, scene prose,
identifiers, ranges, reason codes, or semantic correction.
5. Interpret output deterministically:
- trim and enforce the 512-code-point explanation bound;
- approve `approved` for either proposed combat status;
- accept `combat_should_be_added` only for a non-combat proposal;
- accept `combat_should_be_removed` only for a combat proposal; and
- treat every unknown or inconsistent result as execution failure.
6. Build bounded `ValidationResult` messages and semantic correction guidance.
Approved results carry no rejection message or quality diagnostic.
7. Implement `Name`, `ExecutionClass`, `Spec`, manifest metadata, checkpoint
fingerprints, and strict option validation. A validator spec does not
declare a producer correction protocol.
8. 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}.go`
- `internal/framework/pipeline/{construction,validator_registry}.go`
- `internal/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:
```sh
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
1. Register the validator builder in
`internal/modules/dnd/register/validators.go` and its assets in
`internal/modules/dnd/register/modules.go`, preserving deterministic
registration and contextual errors.
2. Extend registrar/catalog tests to prove the typed kind, execution class,
production prompt/schema availability, and absence from both scene default
chains.
3. 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`, and `retries` reach the prepared
LLM-backed validator through existing machinery.
4. 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.
5. Do not alter the minimal or complete D&D example configurations.
### Inspect
- `internal/modules/dnd/register/{validators,modules,chains}.go`
- `internal/modules/dnd/register/register_test.go`
- `internal/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:
```sh
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
1. 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.
2. Configure the current deterministic scene extract chain followed by the
semantic validator, with a positive producer retry budget and no unnecessary
validator execution retries.
3. Use a chunk containing clear active combat. Return a valid non-combat scene
first, `combat_should_be_added` from the validator, a corrected combat scene
after feedback, and approval on revalidation.
4. 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: combat` and 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.
5. 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.
6. 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.go`
- `internal/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:
```sh
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
1. 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.
2. 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.
3. 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.
4. 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.
5. Update `docs/internal/dnd.md` to 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.
6. Update `docs/internal/overview.md` or `docs/internal/modules.md` only 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
15, 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:
```sh
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
1. 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.
2. 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.
3. 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.
4. Run the full validation suite. If an environmental limitation blocks a
command, run unaffected checks and record the exact limitation rather than
weakening tests.
5. 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:
```sh
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.