# Contextual Entity Grounding Implementation Plan

## Objective

Implement [Contextual Entity Grounding](contextual-entity-grounding.md) so D&D
LLM prompts return evidence-grounded contextual selectors while Notarius owns
canonical entity IDs and referential integrity. Preserve every durable D&D
artifact contract and remove opaque IDs only from model-visible inputs and
private model responses.

This plan is written for a gpt-5.6-terra coding agent. Implement the stages in
numeric order. Each stage is intentionally scoped to one implementation prompt
and must leave the repository buildable and its focused tests passing before
the next stage begins.

## Plan-Wide Decisions

Apply these decisions throughout every stage:

- Read `docs/development.md`, all files under `docs/policy/`, the feature
  roadmap, and the focused implementation/tests named by the stage before
  editing.
- Preserve the fixed pipeline, typed artifact boundaries, root `assets`
  content-only rule, module ownership, PromptKit boundary, and evidence rules.
- Do not change the durable NPC-, item-, location-registry, or occurrence Go
  types, JSON schemas, schema IDs, schema versions, media types, reference-slot
  contracts, categories, or generated-reference compatibility.
- Keep the affected prompt and private response-schema identities at `v1`.
  They are private pre-release transport contracts; their changed content
  hashes provide the required compatibility boundary.
- Advance semantic policy identifiers exactly as directed in each stage. Do
  not bump unrelated policy identifiers.
- A contextual name match uses the entity family's existing comparison policy,
  never fuzzy matching. A model selection must resolve to exactly one supplied
  record before a durable ID is attached.
- An invalid NPC, item, or location selection invalidates the complete
  extraction operation. Do not silently drop one response record, accept a
  partial artifact, or defer a known mapping failure to a later validator.
- Registry provenance remains grounding only. Only the current extraction
  chunk's `source_refs` become occurrence evidence.
- Preserve prompt message order and cache controls unless a stage explicitly
  directs otherwise. Edit only the selected module or shared assets; do not
  rewrite unrelated shared prompt bytes.
- Preserve internal opaque IDs where deterministic code needs them. The rule
  applies to material shown to the model or requested from it, not to maps,
  fingerprints, checkpoints, diagnostics, or durable artifacts.
- Follow `docs/policy/testing.md`: test package-level behavior and meaningful
  failure modes, not prompt prose, exact message counts, private helper
  structure, or a repository-wide string-scanning change detector. All tests
  remain deterministic, offline, and credential-free.
- Use `apply_patch` for edits, `gofmt` changed Go files, and preserve unrelated
  worktree changes.

## Final Private Selector Contracts

These shapes are implementation requirements, not public artifact schemas.

### NPC occurrence response

Each response record contains exactly the required fields `name`, `kind`, and
`source_refs`. It does not contain `npc_id`. Notarius resolves `name` through
the normalized NPC registry and writes the matched registry record's `ID` and
canonical `Name` into the durable occurrence.

### Item occurrence response

Each response record contains the existing required `name`, `kind`,
`quantity`, `from`, `to`, and `source_refs` fields. It does not contain
`item_id`. Retain the current nullable representation and kind-specific
semantics. Notarius resolves `name` through the normalized item registry and
adds the matched `ID` and canonical `Name`.

### Location occurrence response

Each response record contains exactly the required fields `name`,
`registry_refs`, `kind`, and `source_refs`. `registry_refs` is always an array
of strict objects containing required integer `start_unit_id` and
`end_unit_id`; it may be empty.

- When `name` has one comparison-identity match in the supplied registry,
  `registry_refs` must be empty and name resolution selects that record.
- When multiple registry records share the comparison identity,
  `registry_refs` must equal one record's complete canonically ordered source
  ranges with `source_id` removed.
- The model-facing registry projection uses the same `name` plus
  `registry_refs` selector and adds a required `context` array. Unique-name
  records project empty `registry_refs` and `context` arrays. The private
  response does not reproduce `context`.
- Same-name records receive bounded identity context consisting of the ordered
  source units covered by their registry ranges. Each context element is a
  strict object with exactly the required fields `unit_id` (integer) and
  `text` (string); do not expose the durable location ID, source ID, digest,
  or a replacement token.
- Building same-name grounding validates that every registry range belongs to
  and resolves against the current source. If two records still produce the
  same contextual selector, grounding construction fails before the LLM call.
- `registry_refs` never flow into the durable occurrence's `source_refs`.

### Entity-reconciliation response

The shared response remains an object with required `duplicate_groups`.
Every group has required `members` and `canonical`. A member and the canonical
selection are strict contextual objects containing:

```json
{
  "name": "Mira Thorn",
  "source_refs": [
    {"start_unit_id": 12, "end_unit_id": 12}
  ]
}
```

The candidate prompt input uses the same descriptor and contains no `key`.
`source_refs` is required and non-empty for every eligible candidate. The
shared helper may retain its existing `candidate-000001`-style keys strictly
inside Go state to preserve input-position mapping; those keys must never be
serialized into prompt input or accepted in the private response.

If two candidates produce an identical contextual descriptor, neither is
eligible for model-assisted reconciliation because the model cannot identify
them independently. Otherwise the helper converts returned descriptors to its
internal candidate keys before applying all existing unknown-member,
ineligible-member, duplicate-member, canonical-membership, overlap, retry, and
fallback rules.

## Stage 1: Convert NPC Occurrences To Name-Based Resolution

### Goal

Remove durable NPC IDs from the NPC-occurrence prompt and private response,
then resolve the model's contextual name deterministically without weakening
checkpoint identity or downstream validation.

### Work

1. Inspect:
   - `assets/dnd/npc-occurrences/`;
   - `internal/modules/dnd/extract/npcoccurrences/`;
   - `internal/modules/dnd/npcs/registry/`;
   - NPC-occurrence normalizer and validator checkpoint fingerprints; and
   - their focused tests.
2. Change `dnd_npc_occurrences_llm.v1.json` so every occurrence requires only
   `name`, `kind`, and `source_refs`, continues to reject unknown fields, and
   no longer declares `npc_id`.
3. Revise the NPC-occurrence instructions to require a supplied canonical NPC
   name and current-chunk evidence, with no instruction to copy or invent an
   ID. Continue using the existing shared names-only NPC registry fragment and
   preserve manifest order/cache controls.
4. Remove `NPCID` from the private `occurrenceResponse`. After canonicalizing
   response evidence, resolve every response name with the existing
   `npcregistry.Registry.Lookup` comparison-key lookup. On the first unknown
   or non-unique selection, return an extractor-scoped mapping error and no
   value. For a match, construct the durable occurrence with the registry
   record's exact `ID` and canonical `Name`.
5. Change `mappingPolicy` to
   `dnd.npc_occurrences.extract_mapping.v3`.
6. Stop passing `IdentityPromptInput()` to the LLM; use the existing
   names-only `PromptInput()`.
7. Replace the misleading exported model-input API used only for identity
   fingerprints: retain the unexported ordered `{id,name}` projection and its
   digest, expose that value as `IdentityDigest() string`, remove
   `IdentityPromptInput()`, and update NPC-occurrence extractor, normalizer,
   invariant-validator, and registry-validator fingerprints to use
   `IdentityDigest()`. The digest must still distinguish ID/name identity from
   the names-only prompt projection.
8. Rewrite existing focused tests around observable behavior: rendered NPC
   registry input is names-only; the private schema rejects `npc_id`; valid
   names acquire the registry ID; comparison-equivalent names canonicalize;
   unknown names fail the whole extraction; registry identity fingerprints
   remain distinct and defensive; empty registries accept only empty model
   results. Remove tests whose only purpose was requiring the model to return
   exact ID/name pairs.

### Acceptance Criteria

- No NPC-occurrence LLM input or private response contains a durable NPC ID.
- Durable NPC occurrences still contain the exact registry ID/name pair.
- Mapping failures remain extractor failures eligible for the configured
  pipeline retry behavior.
- Deterministic consumers still fingerprint the ordered registry identity,
  while spells, combat turns, enemy events, and NPC occurrences share the
  names-only model projection.

### Validation

```sh
go fmt ./internal/modules/dnd/npcs/registry ./internal/modules/dnd/extract/npcoccurrences ./internal/modules/dnd/normalize/npcoccurrences ./internal/modules/dnd/validate/npcoccurrences/...
go test ./internal/modules/dnd/npcs/registry ./internal/modules/dnd/extract/npcoccurrences ./internal/modules/dnd/normalize/npcoccurrences ./internal/modules/dnd/validate/npcoccurrences/...
```

This stage is suitable for one gpt-5.6-terra prompt.

## Stage 2: Convert Item Occurrences To Name-Based Resolution

### Goal

Give item occurrences the same contextual-name/deterministic-ID boundary while
preserving item-specific nullable fields and kind rules.

### Work

1. Inspect `assets/dnd/item-occurrences/`, the item occurrence extractor, the
   item registry, the item occurrence normalizer and registry validator, and
   their focused tests.
2. Change `dnd_item_occurrences_llm.v1.json` to remove `item_id` from required
   fields and properties. Preserve required `name`, `kind`, `quantity`, `from`,
   `to`, and `source_refs`, all current enums/nullability, and strict unknown
   field rejection.
3. Rewrite the item registry fragment and module instructions to require the
   supplied canonical name and current-chunk evidence without mentioning an
   ID. Preserve prompt order and cache controls.
4. Change the item registry's model projection from ordered `{id,name}` pairs
   to ordered names-only objects, add a comparison-key index, and expose a
   defensive `Lookup(name) (dnd.Item, bool)` analogous to the NPC registry.
   Retain exact `LookupID` for durable normalizers and validators. Because item
   IDs are derived from the item comparison identity, the names-only
   `ProjectionDigest` remains sufficient for model input and existing
   checkpoint consumers.
5. Remove `ItemID` from the private response. During response canonicalization,
   resolve every contextual name, replace it with the registry record's
   canonical name, and attach its durable ID when constructing the final
   `dnd.ItemOccurrence`. Unknown selections fail the complete extraction; do
   not alter evidence or nullable-field validation ownership.
6. Change `mappingPolicy` to
   `dnd.item_occurrences.extract_mapping.v2`.
7. Update focused tests to cover names-only projection, defensive comparison
   lookup, schema rejection of `item_id`, deterministic durable mapping,
   unknown-name failure after an otherwise valid record, empty registry/result
   behavior, and preservation of nullable/kind-specific fields.

### Acceptance Criteria

- Model-visible item registry and response content contain no item hash.
- Every accepted durable occurrence has the matched registry ID and canonical
  name.
- Invalid selection remains all-or-nothing, and existing normalizer/validator
  defense in depth remains unchanged.

### Validation

```sh
go fmt ./internal/modules/dnd/items/registry ./internal/modules/dnd/extract/itemoccurrences
go test ./internal/modules/dnd/items/registry ./internal/modules/dnd/extract/itemoccurrences ./internal/modules/dnd/normalize/itemoccurrences ./internal/modules/dnd/validate/itemoccurrences/...
```

This stage is suitable for one gpt-5.6-terra prompt.

## Stage 3: Add Contextual Location Grounding

### Goal

Replace the location registry's ID/name prompt projection with an immutable,
source-aware grounding object that can represent same-name locations safely.
Introduce the new path alongside the old occurrence input so this stage remains
buildable; Stage 4 performs the atomic extractor cutover and removes the old
path.

### Work

1. Inspect the location registry, location identity and source-reference
   helpers, the generic source document index, occurrence checkpoint consumers,
   and their focused tests.
2. In `internal/modules/dnd/locations/registry`, define the private-model
   types needed by both grounding and the location occurrence extractor:
   - a returned selector with exactly `name` and `registry_refs`;
   - a registry projection entry with exactly `name`, `registry_refs`, and
     `context`;
   - a source-free range with exactly `start_unit_id` and `end_unit_id`; and
   - a context unit with exactly `unit_id` and `text`.
   All fields are required in their private JSON shapes, and constructors and
   accessors must make defensive copies.
3. Add an operation-scoped immutable grounding type constructed from a resolved
   registry and the current `*source.SourceDocument`. Its API must provide:
   - a cloned `contracts.LLMInputMaterial` for the `location_registry` slot;
   - deterministic resolution of a returned selector to one cloned
     `dnd.Location`.
   The prompt material's existing `Digest` field owns the digest of the exact
   model projection; do not expose a second grounding-specific digest API.
4. Construct the projection in registry order. Group entries by the existing
   location comparison key:
   - every projection entry has exactly the required fields `name`,
     `registry_refs`, and `context`;
   - comparison-unique entries use empty `registry_refs` and `context` arrays;
   - every same-name entry uses its complete canonical source ranges stripped
     of `source_id` and includes ordered context units covered by those ranges;
   - each context unit contains exactly required integer `unit_id` and string
     `text` fields, and units are deduplicated in source order; and
   - same-name ranges must have `SourceID == doc.ID` and pass
     `source.DocumentIndex.ValidateRef`.
5. Fail grounding construction with a bounded, content-safe error if the
   source is nil, a same-name range is invalid or belongs to another source,
   a comparison key is empty, or two records produce the same selector. Do not
   expose transcript text in the error.
6. Resolution uses the existing comparison key. A unique-name selector is
   accepted only with empty `registry_refs`; a same-name selector is accepted
   only on an exact canonical range match. Reject unknown names, a non-empty
   range list for a unique name, an empty/partial/reordered range list for an
   ambiguous name, or any selector not present in the grounding.
7. Separate deterministic identity fingerprinting from LLM material. Add
   `IdentityDigest()` over the registry's ordered `{id,name}` identity
   projection, and update the location normalizer and registry-validator
   checkpoint consumers to use it. The new operation grounding carries the
   model projection digest in its `LLMInputMaterial`. Retain the old ID-bearing
   prompt accessor only as a documented transitional dependency of the
   still-unchanged location occurrence extractor; do not add new callers.
8. Add focused tests for unique names, same-name context and selectors,
   canonical range order, deterministic projection/digest, defensive copies,
   exact selector resolution, nil/foreign/invalid references, selector
   collisions, empty registries, and identity fingerprint stability. Do not
   assert large rendered prompt strings; decode the JSON projection and assert
   its semantic shape.

### Acceptance Criteria

- The location package can build and resolve contextual selectors without
  exposing `location_id`, `source_id`, digests, or replacement labels.
- Same-name locations remain distinct and receive meaningful bounded context.
- Deterministic checkpoint consumers retain an ID-sensitive fingerprint.
- Only the existing occurrence extractor remains wired to the legacy
  ID-bearing prompt path until Stage 4; the repository compiles and tests pass.

### Validation

```sh
go fmt ./internal/modules/dnd/locations/registry ./internal/modules/dnd/normalize/locationoccurrences ./internal/modules/dnd/validate/locationoccurrences/...
go test ./internal/modules/dnd/locations/registry ./internal/modules/dnd/normalize/locationoccurrences ./internal/modules/dnd/validate/locationoccurrences/...
```

This stage is suitable for one gpt-5.6-terra prompt.

## Stage 4: Convert Location Occurrences To Contextual Resolution

### Goal

Wire the Stage 3 grounding object into location occurrence extraction and
remove durable location IDs from the prompt and private response.

### Work

1. Inspect `assets/dnd/location-occurrences/`, the location occurrence model,
   schema loader, extractor, canonicalization, prompt tests, and Stage 3
   grounding tests.
2. Change `dnd_location_occurrences_llm.v1.json` so each occurrence requires
   exactly `name`, `registry_refs`, `kind`, and `source_refs`; remove
   `location_id`. Keep all four occurrence kinds. Make `registry_refs` a
   required array, including an empty array, of strict required positive
   integer ranges. Keep occurrence `source_refs` separate and unchanged.
3. Rewrite `location-registry.md` and module instructions to explain the two
   selector cases, require exact supplied contextual selectors, prohibit
   invented locations, and state that registry ranges/context are identity
   grounding rather than occurrence evidence. Preserve manifest order and
   cache controls.
4. Change the private response type to `Name`, `RegistryRefs`, `Kind`, and
   `SourceRefs`. Do not reuse `source.SourceRef` for the source-free registry
   range type.
5. In `Extract`, construct operation grounding from the resolved registry and
   `req.Source` before calling the LLM, put its projection in the
   `location_registry` input, and resolve every returned selector after
   completion. Attach the selected registry record's exact ID and canonical
   name to the durable occurrence while retaining only the response's
   current-source `source_refs` as evidence.
6. Fail the whole extraction on grounding-construction failure or the first
   unknown, malformed, mismatched, or ambiguous selector. This replaces the
   current behavior that can preserve unknown ID/name pairs for later
   validators. Keep later normalizer and validator checks as defense in depth
   for artifacts entering other boundaries.
7. Change `mappingPolicy` to
   `dnd.location_occurrences.extract_mapping.v2`.
8. Remove the legacy ID-bearing registry `PromptInput` and its model-projection
   digest once the extractor uses operation grounding. Keep the occurrence's
   static module fingerprint based on `IdentityDigest()`, prompt/schema
   fingerprints, and mapping policy. The operation-scoped projection is already
   covered by source/chunk identity and configured or generated reference
   dependencies, while its `LLMInputMaterial.Digest` identifies the exact model
   input; do not add a second digest API or operation-aware static fingerprint.
9. Update focused schema, prompt, extractor, canonicalization, checkpoint, and
   generated-reference tests. Cover unique-name empty selectors, successful
   same-name selection, failure for an unsupported ambiguous mention,
   partial/reordered ranges, no registry-to-occurrence evidence leakage,
   all-or-nothing failure, empty registry/result behavior, and unchanged
   durable ordering/deduplication.

### Acceptance Criteria

- The location prompt and private response contain no durable location ID.
- Unique and same-name records resolve according to the final selector
  contract.
- Accepted durable output is unchanged in shape and still contains an exact
  location ID/name pair.
- Registry context cannot become durable occurrence evidence.

### Validation

```sh
go fmt ./internal/modules/dnd/extract/locationoccurrences ./internal/modules/dnd/locations/registry
go test ./internal/modules/dnd/locations/registry ./internal/modules/dnd/extract/locationoccurrences ./internal/modules/dnd/normalize/locationoccurrences ./internal/modules/dnd/validate/locationoccurrences/...
```

This stage is suitable for one gpt-5.6-terra prompt.

## Stage 5: Replace Reconciliation Keys With Contextual Descriptors

### Goal

Change the shared NPC/item/location registry-normalization proposal contract so
opaque candidate keys remain internal and the model sees and returns only
names plus evidence coordinates.

### Work

1. Inspect:
   - `internal/modules/dnd/shared/entityreconcile/`;
   - `assets/dnd/shared/prompts/common-dnd-entity-reconciliation.md`;
   - `assets/dnd/entity-reconciliation/schemas/`;
   - all three registry normalization manifests and prompt tests; and
   - the NPC, item, and location registry normalizers and reconciliation tests.
2. Introduce one exported, defensively copied contextual selector type in
   `entityreconcile` with JSON `name` and `source_refs`, plus a strict
   source-free range type. Use it for candidate input views and for
   `DuplicateGroup.Members` and `.Canonical`.
3. Keep deterministic candidate keys only inside `Materials`. During
   `BuildContext`, validate and canonicalize candidate references as today,
   serialize candidate views without `key`, derive a stable internal lookup
   from canonical selector JSON to the corresponding internal candidate key,
   and detect descriptor collisions before eligibility is established.
   Colliding candidates must not appear in the prompt input or become
   eligible; their records remain in deterministic normalization output.
4. Update `Materials.Assess` to resolve every returned selector through that
   internal lookup before running the existing group assessment. Preserve
   existing issue categories where their meaning still applies. Treat an
   unknown or collided descriptor as an unknown/ineligible selection, discard
   only the affected group, and retain existing overlap handling. `SafeGroup`
   may continue returning internal candidate keys so the three domain
   normalizers retain their position mapping; those keys are not model-facing.
5. Rewrite `dnd_entity_reconcile_llm.v1.json` so members and canonical are
   strict selector objects. Require non-empty `name` structurally where the
   current schemas do so, require `source_refs`, and make each range strict
   with required positive integer endpoints. Preserve `duplicate_groups` and
   the existing semantic assessment of minimum group size, membership,
   duplicates, eligibility, and overlap rather than moving every semantic
   failure into JSON Schema.
6. Rewrite the shared reconciliation fragment to tell the model to return
   supplied contextual descriptors and never invent names or ranges. Remove
   every instruction about opaque keys. Preserve all three manifests' message
   ordering and cache controls.
7. Change registry normalization policy identifiers to:
   - `dnd.npc_registry.normalize.v4`;
   - `dnd.item_registry.normalize.v2`; and
   - `dnd.location_registry.normalize.v2`.
8. Update shared and domain tests to cover candidate JSON without keys,
   contextual proposal decoding, valid selector-to-internal-key mapping,
   equal names with different evidence, descriptor collision exclusion,
   unknown/partial/reordered descriptors, overlapping groups, canonical
   membership, invalid structured-output fallback, currency safety, and
   preservation of every non-applied deterministic candidate. Update prompt
   asset fixtures to the new selector schema; do not snapshot prompt prose.

### Acceptance Criteria

- No registry normalization prompt input or private response contains a
  `candidate-*` key.
- Internal keys remain inaccessible to the model but may still support safe
  deterministic position mapping.
- All existing normalizer safety, retry, fallback, warning, currency, and
  same-name-location policies remain intact.
- Identical contextual descriptors cannot be arbitrarily reconciled.

### Validation

```sh
go fmt ./internal/modules/dnd/shared/entityreconcile ./internal/modules/dnd/normalize/npcregistry ./internal/modules/dnd/normalize/itemregistry ./internal/modules/dnd/normalize/locationregistry
go test ./internal/modules/dnd/shared/entityreconcile ./internal/modules/dnd/normalize/npcregistry ./internal/modules/dnd/normalize/itemregistry ./internal/modules/dnd/normalize/locationregistry
```

This is the largest stage, but it is one cohesive shared-contract migration
and is suitable for one gpt-5.6-terra prompt when implemented exactly within
the listed packages. Do not combine it with occurrence or documentation work.

## Stage 6: Record The Decision And Update Canonical Documentation

### Goal

Document the implemented policy in its durable architectural, internal, and
integration homes without duplicating volatile details or presenting roadmap
work as current behavior prematurely.

### Work

1. Re-read `docs/policy/documentation.md`, ADR-0003, ADR-0009, ADR-0011,
   `docs/internal/dnd.md`, `docs/internal/llm.md`, and the six affected registry
   and occurrence integration documents. Verify the code before describing it.
2. Add
   `docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md` in
   the repository's Nygard ADR format with status `Accepted` and the actual
   implementation date. Record the model-semantic/deterministic-identity
   boundary, source-coordinate allowance, request-local-label exception,
   alternatives, ambiguity behavior, and consequences. Link ADR-0003 and
   ADR-0009 rather than repeating their complete decisions.
3. Add a concise normative invariant under the LLM boundary in
   `docs/policy/architecture.md`: callers use contextual model selections and
   attach opaque application identities deterministically when possible. Link
   ADR-0012 for rationale.
4. Update `docs/internal/dnd.md` to replace exact model-facing `{id,name}`
   claims with the implemented NPC/item names-only and location contextual
   selector behavior. Document reconciliation descriptors, internal-only keys,
   all-or-nothing occurrence mapping failures, normalization fallback, and the
   separation between registry and occurrence evidence. Do not duplicate the
   private JSON schemas.
5. Add only a short ownership clarification to `docs/internal/llm.md`: the
   calling module resolves contextual selections; PromptKit and its adapter do
   not own entity identity.
6. Update these durable integration contracts while preserving their public
   ID-bearing wire examples and schema statements:
   - `docs/integrations/dnd-npc-registry-artifacts.md`;
   - `docs/integrations/dnd-npc-occurrence-artifacts.md`;
   - `docs/integrations/dnd-item-registry-artifacts.md`;
   - `docs/integrations/dnd-item-occurrence-artifacts.md`;
   - `docs/integrations/dnd-location-registry-artifacts.md`; and
   - `docs/integrations/dnd-location-occurrence-artifacts.md`.
   Remove claims that LLM consumers receive `{id,name}` or that raw model
   output supplies an ID. State that Notarius maps contextual output into the
   unchanged exact durable pair.
7. Revise the generic LLM-assisted deduplication entry in
   `docs/roadmap/future.md`: stable unique IDs remain internal deterministic
   state, while a future model proposal uses contextual descriptors or a
   specifically justified request-local short label.
8. Do not change README, CLI, configuration, operations, examples, or public
   schema files; this feature has no user-selectable surface or public wire
   change.

### Acceptance Criteria

- ADR-0012 owns rationale; architecture owns the normative boundary; internal
  docs own mechanics; integration docs own unchanged durable contracts; and
  the future roadmap no longer proposes durable IDs as the default model
  selector.
- No current-behavior document claims that a model copies hash-based entity
  IDs or opaque reconciliation keys.
- Documentation does not duplicate private schemas or implementation history.

### Validation

```sh
git diff --check
rg -n '\{id,name\}|ID/name grounding|Candidate keys are opaque|candidate-[0-9]' docs assets/dnd
```

Review every search result semantically; durable wire-contract ID/name
requirements and internal test fixtures are not automatically errors.

This stage is suitable for one gpt-5.6-terra prompt.

## Stage 7: Integration Audit And Final Verification

### Goal

Verify the assembled D&D family, remove obsolete identity-copy paths, and
finish with a clean, policy-compliant implementation.

### Work

1. Audit every maintained D&D prompt manifest, selected fragment, private
   schema, and constructed prompt projection. Confirm that no model is asked to
   reproduce `npc:sha256:...`, `item:sha256:...`,
   `location:sha256:...`, `candidate-*`, a UUID, a digest, or another opaque
   entity handle. Do not confuse runtime metadata or durable output contracts
   with model-visible material.
2. Trace all former APIs and fields, including `IdentityPromptInput`,
   ID-bearing item/location prompt projections, private `NPCID`/`ItemID`/
   `LocationID` response fields, and model-visible candidate keys. Remove dead
   code, obsolete comments, stale test names, and unused assets. Retain
   identity-only digests and exact durable lookup APIs used by deterministic
   consumers.
3. Review prompt fingerprint registration and checkpoint fingerprints. Confirm
   that each affected prompt/schema/policy/projection change invalidates the
   relevant operation and that unrelated D&D lanes retain their existing
   fingerprints.
4. Run representative production registration and multi-step pipeline tests
   using existing fakes. Update only tests whose stable behavior changed.
   Confirm generated NPC/item/location registry handoffs still prepare and
   that final durable occurrences encode and validate under their existing
   `v1` codecs.
5. Run formatting, focused suites, full tests, vet, build, and documentation
   whitespace checks. Fix only failures caused by this feature. Report any
   unrelated pre-existing failure without broadening scope.
6. Review the feature roadmap acceptance criteria one by one. Do not delete
   `contextual-entity-grounding.md` or this implementation plan in this stage;
   roadmap retirement is a separate maintainer action after review.

### Acceptance Criteria

- All feature-roadmap acceptance criteria are met.
- The repository contains no obsolete model-facing opaque-identity path.
- Public artifacts and generated handoffs remain compatible.
- Tests are focused on behavior rather than prose or implementation shape.
- The worktree contains only intentional feature and documentation changes.

### Validation

```sh
go fmt ./internal/modules/dnd/...
go test ./internal/modules/dnd/...
go test ./internal/modules/integration/...
go test ./...
go vet ./...
go build ./cmd/notarius
git diff --check
git status --short
```

This stage is suitable for one gpt-5.6-terra prompt.
