Files
notarius/docs/roadmap/implementation.md

610 lines
31 KiB
Markdown

# 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`; and
- the digest of the exact model projection.
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 owns the model
projection digest. 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. Update the occurrence
checkpoint to combine `IdentityDigest()` with the new grounding projection
digest, prompt/schema fingerprint, mapping policy, and its existing inputs;
do not retain dead compatibility aliases.
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.