Restore NPC canonical name selection

This commit is contained in:
2026-08-04 13:08:00 +00:00
parent d52387c1f7
commit d1f43df88e
3 changed files with 366 additions and 570 deletions

View File

@@ -55,6 +55,14 @@ with the same canonical identity, retains their earliest position, and merges
their canonicalized evidence; it does not add aliases, roles, descriptions, or their canonicalized evidence; it does not add aliases, roles, descriptions, or
relationship fields. relationship fields.
When evidence supports a semantically duplicate group, the canonical display
name is one of that group's supplied candidates. A complete, stable proper name
is preferred over an abbreviation. An unadorned proper name is preferred over
the same name plus a contextual class, role, title, or relationship descriptor
unless the transcript establishes that descriptor as part of the person's
name. A longer candidate is not preferred solely because it includes such a
descriptor.
## Scope and consumers ## Scope and consumers
Only individually identifiable NPC names with transcript evidence belong in Only individually identifiable NPC names with transcript evidence belong in

View File

@@ -2,648 +2,431 @@
## Objective ## Objective
Implement the target state in [D&D Location Tracking](location.md): an Complete the target state in [D&D Location Tracking](location.md) by correcting
evidence-grounded `dnd/locations` registry lane and a dependent the behavioral and maintainability issues found in the post-implementation
`dnd/location-occurrences` lane, including conservative location identity, audit. The location registry and occurrence lanes are already implemented and
shared D&D entity-reconciliation infrastructure, production validation, production-selectable; the remaining work restores one NPC normalization
generated-reference wiring, maintained examples, and current documentation. contract, aligns location-occurrence classification with the roadmap, narrows a
module's declared references, and factors demonstrated registry-resolution
mechanics into a D&D-shared helper.
This is an ordered implementation plan for a `gpt-5.6-terra` coding agent. This is an ordered implementation plan for a `gpt-5.6-terra` coding agent.
Implement one stage per prompt, in order. Finish each stage's tests and leave Stages 1 through 14 are complete and must not be reimplemented. Implement one
the repository coherent before proceeding. Do not implement later-stage new stage per prompt, beginning with Stage 15, and complete each stage's focused
production registrations early merely to make an incomplete feature selectable. validation before proceeding.
All stages must follow: All remaining stages must follow:
- [Architecture Policy](../policy/architecture.md) - [Architecture Policy](../policy/architecture.md);
- [Testing Policy](../policy/testing.md) - [Testing Policy](../policy/testing.md);
- [Documentation Policy](../policy/documentation.md) - [Documentation Policy](../policy/documentation.md);
- the D&D conventions in [D&D Module Internals](../internal/dnd.md) - [D&D Module Internals](../internal/dnd.md); and
- the durable policy decisions in [the feature roadmap](location.md) - the durable policy decisions in [the feature roadmap](location.md).
Use behavior-level tests. Do not add tests that merely freeze source layout, Use behavior-level tests. Do not add tests that freeze exact prompt wording,
exact prompt wording, message counts, shared-prefix length, or other incidental message counts, source layout, shared-prefix length, or other incidental
implementation details. Keep tests deterministic, offline, and owned by the implementation details. Prompts and canonical documentation must agree, but
component whose behavior they exercise. that agreement is a review responsibility rather than a reason to add a
change-detector test.
## Stage 1: Define Location Domain Types And Identity ## Completed Work Summary
Stages 1 through 14 delivered the following production state:
| Completed stage | Result |
| --- | --- |
| Stage 1 | Added location and location-occurrence domain types plus evidence-anchored location identity. |
| Stage 2 | Added strict `v1` durable codecs and JSON Schemas for both artifact kinds. |
| Stage 3 | Added shared, opaque-key D&D entity-reconciliation context and proposal safety infrastructure. |
| Stage 4 | Migrated NPC normalization to the shared reconciliation machinery. |
| Stage 5 | Added evidence-grounded `dnd/locations` extraction. |
| Stage 6 | Added conservative LLM-assisted location normalization. |
| Stage 7 | Added immutable location-registry resolution and source-free `{id, name}` projection. |
| Stage 8 | Added registry-grounded `dnd/location-occurrences` extraction. |
| Stage 9 | Added deterministic location-occurrence normalization. |
| Stage 10 | Added location shape, identity, evidence, and advisory validators. |
| Stage 11 | Added location-occurrence shape, registry, invariant, evidence, and advisory validators. |
| Stage 12 | Registered both lanes, their assets, evidence projectors, default profiles, and validator chains. |
| Stage 13 | Added the ordered generated-reference handoff to the complete D&D example and integration coverage. |
| Stage 14 | Published the current integration, configuration, internal, output, and top-level documentation and ran final verification. |
The post-implementation audit confirmed that the feature's durable types,
identity derivation, codecs, registration, validators, generated handoff,
evidence publication, maintained example, and integration coverage are in
place. At the audit baseline, `go test ./...`, focused race tests, `go vet
./...`, `go build ./cmd/notarius`, and `git diff --check` all passed.
## Stage 15: Restore NPC Canonical-Name Selection Semantics
### Goal ### Goal
Establish the in-process contracts and deterministic identity policy on which Restore the NPC-specific canonical display-name policy that was weakened during
both lanes depend. the shared reconciliation migration, without changing the shared proposal
contract or any durable artifact shape.
### Work ### Work
- Extend `internal/modules/dnd/types.go` with: - Update the NPC normalization task asset under
- `LocationListKind` = `dnd/location-list`; `internal/modules/dnd/normalize/npcs/assets/prompts/` so canonical selection
- `LocationOccurrenceListKind` = `dnd/location-occurrence-list`; again applies all of these rules:
- `LocationList`, `Location`, `LocationOccurrenceList`, - choose only a supplied candidate from an evidence-supported duplicate
`LocationOccurrence`, and `LocationOccurrenceKind`; group;
- exact JSON members and the four occurrence constants specified in - prefer a complete, stable proper name over an abbreviation; and
`location.md`. - prefer an unadorned proper name over that name plus a contextual class,
- Add `internal/modules/dnd/locations/identity`. role, title, or relationship descriptor unless the transcript establishes
- Implement display normalization and comparison normalization consistently the descriptor as part of the person's name.
with the existing NPC identity policy. Share a lower-level comparison helper - Preserve the opaque-key shared reconciliation instructions, prompt-message
only if doing so preserves NPC behavior exactly; otherwise keep the small ordering, response schema, retry/fallback behavior, and deterministic group
policy-specific function explicit. application. Do not move NPC-specific naming policy into
- Implement the versioned compact-JSON-array ID derivation contract from `shared/entityreconcile`.
`location.md`, including ID syntax checks and immutable list validation. - Update the canonical NPC artifact contract in
- Identity validation must require correctly derived, unique IDs while allowing `docs/integrations/dnd-npc-artifacts.md` with the observable canonical-name
two records to have the same comparison name when their evidence anchors selection rule. Keep `docs/internal/dnd.md` focused on normalization
differ. mechanics and link to the integration contract rather than maintaining a
- Add focused tests for Unicode normalization, whitespace, apostrophes, competing definition; add or adjust a link only if needed for clarity.
deterministic encoding, evidence ordering, same-name/different-anchor IDs, - Allow the prompt fingerprint and any derived manifest/checkpoint metadata to
malformed IDs, missing evidence, and non-mutation. change through the existing asset-fingerprint mechanism. Do not manually
pin a digest.
- Retain existing behavior tests for opaque candidate selection, evidence
union, ID derivation, retry/fallback, and non-mutation. Add a test only if it
exercises deterministic code behavior; do not assert prompt prose.
### Acceptance Criteria ### Acceptance Criteria
- The types compile without production registration. - The rendered NPC normalization prompt contains one unambiguous NPC-owned
- ID derivation exactly follows the documented five-element compact JSON input. policy that distinguishes proper names from contextual descriptors.
- Same normalized name plus different earliest evidence yields different IDs. - `Captain Aria` is not preferred over `Aria` solely because it is longer when
- Validation does not reject same-name records solely because their names `Captain` is only a contextual title, while an established full name may
match, and it reports duplicate or mismatched IDs deterministically. still outrank an abbreviation.
- `go test ./internal/modules/dnd/locations/... ./internal/modules/dnd/...` passes - No module key, schema, identity policy, response schema, validator chain, or
for the packages available at this stage. shared reconciliation asset changes.
- Canonical current documentation, rather than this implementation plan, owns
### Prompt Size the restored externally observable rule.
- `go test ./internal/modules/dnd/normalize/npcs/... ./internal/modules/dnd/shared/entityreconcile/... ./internal/modules/dnd/register/...`
Small enough for one implementation prompt.
## Stage 2: Add Durable Codecs And Schemas
### Goal
Create strict durable JSON ownership for both artifact kinds without exposing
either lane as a selectable pipeline yet.
### Work
- Add `internal/modules/dnd/codec/locations` and
`internal/modules/dnd/codec/locationoccurrences`, following the existing D&D
candidate/approved codec pattern.
- Add embedded Draft 2020-12 schemas with the IDs, names, root members, required
fields, enums, source-reference shape, and `additionalProperties: false`
contracts in `location.md`.
- Keep both schemas at `v1`.
- Support strict candidate decoding before semantic approval and strict durable
encoding/decoding after approval.
- Add representative valid fixtures and tests for schema metadata, defensive
schema bytes, empty arrays, unknown fields, missing fields, invalid types,
invalid enum values, malformed source references, invalid ID syntax, and
round trips.
### Acceptance Criteria
- Each codec advertises the correct artifact kind and metadata count.
- Candidate decoding preserves semantic mistakes for validators while
rejecting structurally invalid JSON.
- Approved encoding and decoding enforce the durable shape.
- `go test ./internal/modules/dnd/codec/locations/... ./internal/modules/dnd/codec/locationoccurrences/...`
passes offline. passes offline.
- `git diff --check` reports no errors.
### Prompt Size ### Prompt Size
Small enough for one implementation prompt. Small and suitable for one implementation prompt.
## Stage 3: Extract Shared Entity-Reconciliation Infrastructure ## Stage 16: Correct Hypothetical Location-Occurrence Classification
### Goal ### Goal
Create the D&D-shared, domain-safe proposal machinery needed by both NPC and Make the location-occurrence prompt and durable documentation implement the
location normalization, without changing NPC production behavior yet. roadmap's distinction between an explicit speculative mention and an inferred
or unsupported occurrence.
### Work ### Work
- Add `internal/modules/dnd/shared/entityreconcile`. - Update the location-occurrence task asset under
- Move or generalize the reusable behavior currently owned by `internal/modules/dnd/extract/locationoccurrences/assets/prompts/` to encode
`internal/modules/dnd/normalize/npcs/context_material.go` and `proposal.go`: these exact semantic outcomes:
- assign deterministic opaque candidate keys such as `candidate-000001` in - explicit non-actionable speculation or a mere hypothetical reference to a
input order; registry location is `mentioned`;
- clone candidate names and source references; - an explicit proposal, intention, or agreement to travel is `planned`;
- build bounded transcript windows in source-document order; - a mere hypothetical or speculation is not `planned`; and
- omit candidates whose references cannot safely produce context; - an inferred, unstated, uncertain, or unsupported place or occurrence is
- coalesce overlapping or adjacent windows without mutating the source; omitted.
- define the private `duplicate_groups` proposal with `members` and - Preserve the documented precedence `visited`, `planned`, `recalled`, then
`canonical` candidate keys; `mentioned`, including the ability to return multiple independently
- reject blank, unknown, repeated, ineligible, overlapping, too-small, or evidenced facts from one passage.
canonical-not-a-member groups; and - Update the `mentioned` definition in
- return defensive, immutable assessment data identifying only safe groups. `docs/integrations/dnd-location-occurrence-artifacts.md` to explicitly include
- Keep LLM calls, retry decisions, artifact mutation, canonical-name policy, non-actionable speculation. Keep durable category semantics in that
durable ID derivation, and warning wording out of this package. integration contract and link from internal documentation rather than
- Add a shared prompt instruction asset that states the key-copying and duplicating the taxonomy elsewhere.
proposal-safety contract without NPC- or location-specific identity rules. - Do not change the occurrence enum, durable schema, normalizer, validators, or
- Add a shared private structured-response schema and loader/registration generated-reference contract.
support with a stable `v1` key, ID, name, and fingerprint. Registering the - Allow existing prompt fingerprinting to record the asset change. Do not add
schema more than once must not be required. an exact-text or exact-digest test.
- Add table-driven tests for context bounds, ordering, invalid references,
coalescing, every unsafe proposal category, non-overlapping safe groups,
deterministic keys, defensive copies, and non-mutation.
### Acceptance Criteria ### Acceptance Criteria
- The package has no dependency on `dnd.NPC`, `dnd.Location`, either - The prompt no longer instructs the model to omit every hypothetical
normalizer, or a concrete LLM client. reference.
- Proposal values can identify duplicate candidates even when display names - An explicit statement such as “What if we went to Moon Gate?” is eligible as
are equal. `mentioned` when it is supported by a narrow transcript range, but is not a
- The shared response contract cannot directly supply replacement records or `planned` occurrence without an actual proposal or intention.
evidence. - An unstated location inferred only from surrounding events remains omitted.
- Existing NPC packages still compile before their migration. - The feature roadmap, prompt, and canonical integration contract agree on all
- `go test ./internal/modules/dnd/shared/...` passes offline. four categories and their precedence.
- `go test ./internal/modules/dnd/extract/locationoccurrences/... ./internal/modules/dnd/register/...`
### Prompt Size
Medium, but coherent and suitable for one implementation prompt. Do not combine
it with the NPC migration.
## Stage 4: Migrate NPC Normalization To The Shared Helper
### Goal
Make the existing NPC normalizer the first production consumer of the shared
entity-reconciliation contract while retaining its durable behavior.
### Work
- Refactor `internal/modules/dnd/normalize/npcs` to use opaque candidate keys,
shared context construction, shared proposal assessment, the shared response
schema, and the shared generic reconciliation instruction asset.
- Retain NPC-owned responsibilities:
- comparison-name preparation and deterministic duplicate handling;
- the NPC-specific task and canonical display-name rules;
- LLM invocation, bounded retry, fallback, warnings, and diagnostics;
- application of safe groups, evidence union, NPC ID derivation, and output
ordering.
- Remove superseded NPC-private context/proposal code and private schema assets
once no longer referenced.
- Update NPC prompt metadata and checkpoint fingerprints for the intentional
prompt/private-schema contract change.
- Preserve public module keys, durable NPC schema, identity policy, validator
chains, warning bounds, and fallback semantics.
- Test equal display names as distinct keyed candidates, alias consolidation,
rejected unsafe groups, retry exhaustion, private input ownership, redacted
errors, deterministic fallback, and non-mutation.
### Acceptance Criteria
- No durable NPC artifact field or module key changes.
- NPC normalization cannot confuse two candidates merely because their display
names match.
- Unsafe proposals leave a valid deterministic result and follow existing
retry/fallback policy.
- Obsolete NPC-only reconciliation helpers and schema are removed.
- `go test ./internal/modules/dnd/normalize/npcs/... ./internal/modules/dnd/shared/...`
passes offline. passes offline.
- `git diff --check` reports no errors.
### Prompt Size ### Prompt Size
Medium-to-large but bounded to one existing module. Suitable for one Small and suitable for one implementation prompt.
implementation prompt; do not add location normalization in this stage.
## Stage 5: Implement Location Extraction ## Stage 17: Narrow Location-Occurrence Normalizer References
### Goal ### Goal
Add the LLM-backed extractor that produces evidence-grounded location Make the deterministic normalizer advertise exactly the reference material it
candidates. uses.
### Work ### Work
- Add `internal/modules/dnd/extract/locations` following current D&D extractor - Change `internal/modules/dnd/normalize/locationoccurrences` so its
conventions: strict empty options, typed builder and registration function, `ReferenceSlots` and `ModuleSpec` expose only the required `locations` slot.
`llm_backed` execution metadata, immutable inputs, redacted errors, prompt - Keep that slot's existing contract unchanged: exactly one
and response-schema fingerprints, and bounded diagnostics. `dnd/location-list` artifact, `application/json`, at most 1,048,576 bytes,
- Add a private response schema containing only `name` and source ranges; the required at extraction and normalization.
model must not produce durable IDs or prose. - Remove the normalizer's unused `party`, `roster`, `players`, and `glossary`
- Compose the prompt from existing shared D&D system, identity, campaign declarations and any imports or description tables made dead by that change.
reference, transcript, and evidence assets plus module-owned task and Do not remove those optional slots from the LLM-backed occurrence extractor,
instructions. Preserve the documented extraction-message ordering and cache which actually presents campaign material to the model.
controls. - Add or revise focused module-contract tests to compare the normalizer's
- Define physical-place inclusion and conservative omission exactly as in complete declared slot set, including requiredness, media type, artifact kind,
`location.md`, including generic labels, aliases, and nested places. and size. This is a stable module contract test, not a source-layout test.
- Map source ranges to the current source ID, canonicalize exact duplicate - Verify that pipeline-level campaign reference defaults continue to bind only
ranges, derive candidate location IDs in code, preserve semantically invalid to eligible targets and that the complete example still resolves. Current
candidates for validators where safe, and return deterministic ordering. configuration documentation already states that `locations` is required by
- Add prompt-asset tests that verify shared asset reuse and rendered inputs by both occurrence stages; update it only if repository inspection finds a claim
behavior, without asserting exact shared-prefix length or prompt wording. that optional campaign references are consumed by the normalizer.
- Add extractor tests for empty output, mapping, evidence ownership, generic - Do not change normalization output, registry fingerprints, warnings, or
same-name locations with different anchors, invalid candidate preservation, checkpoint handling for the required generated registry.
client failures, registration metadata, fingerprints, and non-mutation.
### Acceptance Criteria ### Acceptance Criteria
- The extractor cannot manufacture source identities or accept campaign - Catalog and `ModuleSpec` inspection report exactly one normalizer reference
references as evidence. slot: required `locations`.
- The private model response does not contain a durable ID. - The extractor continues to accept its existing optional campaign context and
- Same-name candidates with different evidence survive extraction as distinct required location registry.
candidates. - Binding optional campaign references no longer implies that deterministic
- The package is testable through its local registration but is not yet added occurrence normalization consumes them.
to the production D&D registrar. - The complete configuration and generated location handoff still resolve and
- `go test ./internal/modules/dnd/extract/locations/...` passes offline. run through existing tests.
- `go test ./internal/modules/dnd/normalize/locationoccurrences/... ./internal/modules/dnd/register/... ./internal/modules/integration/... ./internal/cli/...`
passes offline.
- `git diff --check` reports no errors.
### Prompt Size ### Prompt Size
Medium and suitable for one implementation prompt. Small and suitable for one implementation prompt.
## Stage 6: Implement Semantic Location Normalization ## Stage 18: Add Shared Registry-Resolution Infrastructure
### Goal ### Goal
Add conservative alias and repeated-place reconciliation without collapsing Introduce a focused D&D-shared primitive for the demonstrated immutable
same-named or nested locations by default. registry-resolution mechanics, without changing any production consumer in the
same prompt.
### Work ### Work
- Add `internal/modules/dnd/normalize/locations` as an `llm_backed` normalizer - Add `internal/modules/dnd/shared/registryresolver` as a domain-shared package;
using the shared entity-reconciliation package and private response schema. do not put this helper in the generic framework.
- Deterministically clone and prepare the merged candidates first: - Provide an idiomatic generic resolver driven by callbacks or a small adapter
- normalize display whitespace; so it can operate on immutable domain registry views without importing NPC,
- canonicalize and deduplicate source references; location, scene-description, or codec packages.
- remove only exact duplicates with the same comparison name and exact - The resolver core must own the behavior currently duplicated by the NPC and
canonical evidence; location resolvers:
- assign opaque reconciliation keys; and - validate and retain one construction-time seeded view;
- retain same-name records with different evidence. - treat a declared generated-reference placeholder with zero construction-
- Use bounded transcript windows and a module-owned location task that permits time items as absent only during construction;
grouping only when evidence clearly identifies one physical place. Explicitly - use the seeded view when an operation does not supply the target slot;
prohibit grouping solely by equal names, proximity, nesting, or generic - resolve an operation-time override when the target slot is present;
labels. - memoize valid immutable views by a raw key and by semantic identity;
- Validate proposals through the shared package. Apply only safe groups in - reuse the seeded view when bound state and semantic identity match;
deterministic code, choose the canonical name from the selected existing - synchronize concurrent resolution; and
candidate, union evidence, and derive the final evidence-anchored ID. - never retain caller-owned reference maps, item content, or mutable values.
- Retain the deterministic candidate set on unusable proposals and follow the - Raw cache identity must normalize the media-type spelling and hash the actual
existing NPC retry/fallback and bounded-warning conventions. item content. Do not trust a caller-supplied digest as proof that different
- Publish prompt, response-schema, identity-policy, normalization-policy, and bytes are equal.
semantic-context fingerprints. - Provide a narrow helper for resolving an optional single reference item that
- Test aliases, repeated appearances, same-name distinct places, parent/child validates the configured slot name, exact item count when bound, parsed media
locations, invalid and overlapping proposals, proposal retries, fallback, type, and maximum size. Return owned bytes or require immediate decode so the
ordering, ID recomputation, warning bounds, idempotent deterministic helper cannot retain caller-owned content.
application, and non-mutation. - Keep artifact decoding, semantic identity validation, canonical encoding,
prompt projection, lookup indexes, clone policy, and domain-specific error
context in the owning registry packages.
- Keep errors bounded and content-free. Do not include raw reference bytes,
origin paths, or campaign data in diagnostics.
- Add focused tests for absent construction state, zero-item generated
placeholders, seeded fallback, operation overrides, malformed cardinality,
media types, size limits, raw reuse, semantic reuse, caller-supplied digest
mismatch, concurrent access, loader failure, and non-retention/non-mutation.
### Acceptance Criteria ### Acceptance Criteria
- The model proposes groups but cannot directly replace durable locations. - The shared package depends only on generic source/framework contracts and Go
- A failed or ambiguous proposal cannot lose a valid candidate. standard-library packages, not concrete D&D artifact or codec packages.
- Same-name locations remain distinct unless an approved evidence-backed group - The helper owns only demonstrated resolution, validation, caching, and
joins them. ownership mechanics; it does not become a universal artifact registry.
- Final IDs are derived only after group evidence is unioned. - Invalid values and loader errors are never cached.
- `go test ./internal/modules/dnd/normalize/locations/...` passes offline. - Concurrent callers resolving identical bytes receive the same prepared
immutable view without races.
### Prompt Size - Existing production registry packages remain unchanged in this stage.
- `go test -race ./internal/modules/dnd/shared/registryresolver/...` passes
Medium-to-large but scoped to one normalizer and suitable for one implementation
prompt.
## Stage 7: Add The Immutable Location Registry
### Goal
Provide safe generated-reference resolution and an unambiguous prompt
projection for downstream occurrence extraction.
### Work
- Add `internal/modules/dnd/locations/registry`, modeled on the immutable NPC
registry and its operation-time resolver.
- Define `ReferenceSlot = "locations"`, a 1,048,576-byte limit, and exactly one
accepted `application/json` location-list item when bound.
- Validate durable decoding and location identity before constructing a
registry.
- Store canonical durable bytes and semantic digests without retaining mutable
caller-owned content. Return defensive copies from all accessors.
- Produce a compact, source-free prompt projection containing ordered
`{id, name}` pairs. Do not include source references or generated-reference
provenance.
- Support exact lookup by ID and verify the matching canonical name; do not
provide an ambiguous name-only lookup as the occurrence linkage mechanism.
- Preserve the established seeded/operation resolver behavior and concurrency-
safe semantic caching.
- Test absent, empty, malformed, oversized, wrong-media-type, invalid-identity,
and valid registries; projections; ID lookup; defensive copies; raw and
semantic cache reuse; and concurrent resolution.
### Acceptance Criteria
- Distinct same-name records are both representable and addressable by ID.
- Registry evidence cannot appear in the prompt projection.
- Malformed static references fail during construction and malformed generated
references fail at operation resolution through existing boundaries.
- `go test ./internal/modules/dnd/locations/registry/...` passes offline.
### Prompt Size
Medium and suitable for one implementation prompt.
## Stage 8: Implement Location-Occurrence Extraction
### Goal
Add the dependent LLM-backed lane that classifies source-grounded location
occurrences.
### Work
- Add `internal/modules/dnd/extract/locationoccurrences` with module key
`dnd/location-occurrences`, `llm_backed` execution metadata, strict empty
options, typed construction, and a required `locations` reference slot.
- Resolve the immutable registry at construction and for each operation using
the established generated-reference pattern.
- Add a private response schema requiring `location_id`, `name`, `kind`, and
source ranges. Restrict kinds to `visited`, `planned`, `recalled`, and
`mentioned`.
- Reuse the shared D&D extraction prompt assets and ordering. Place the compact
location registry after the shared transcript/evidence material and before
module task/instructions, consistent with current generated grounding.
- Encode the exact classification rules, precedence, multi-fact behavior, and
conservative omission policy from `location.md`.
- Map evidence only to the current source. Copy candidate IDs and names without
silently repairing unknown or mismatched values so deterministic validators
retain ownership of those diagnostics.
- Canonically order output and exact duplicates without dropping distinct
kinds or independent evidence.
- Test every kind, precedence, multiple supported facts, no-location and no-
occurrence outputs, required registry failures, same-name ID selection,
source-free prompt projection, current-transcript evidence, prompt/profile
metadata, client failures, non-mutation, and local registration.
### Acceptance Criteria
- Construction and operation specs declare `locations` as required and accept
only `dnd/location-list` JSON.
- The model sees IDs and names but no registry evidence.
- Registry context never becomes occurrence evidence.
- The extractor remains locally testable but is not production-selectable yet.
- `go test ./internal/modules/dnd/extract/locationoccurrences/...` passes
offline. offline.
- `git diff --check` reports no errors.
### Prompt Size ### Prompt Size
Medium and suitable for one implementation prompt. Medium and suitable for one implementation prompt. Keep production migrations
out of this stage so the generic ownership and concurrency contract can be
reviewed independently.
## Stage 9: Implement Deterministic Occurrence Normalization ## Stage 19: Migrate The NPC Registry To The Shared Resolver
### Goal ### Goal
Canonicalize occurrence records against the exact location registry without a Make the established NPC registry the first production consumer of the shared
second LLM call. resolver while preserving all public and operational behavior.
### Work ### Work
- Add `internal/modules/dnd/normalize/locationoccurrences` as a deterministic - Refactor `internal/modules/dnd/npcs/registry` to compose the Stage 18 helper
normalizer with the same required `locations` slot. for construction/operation selection, single-item validation, raw caching,
- Clone all inputs. Normalize source ranges, names, ordering, and exact semantic caching, and synchronization.
duplicates. - Preserve the existing package API used by extractors, normalizers,
- For a known `location_id`, replace display-name variation with the registry's validators, manifests, and tests, including `NewResolver`, `Resolve`,
exact canonical name. Do not perform a name-only guess. `Seeded`, `Bound`, `NPCs`, `List`, `CanonicalBytes`, `Digest`,
- Preserve an unknown ID or otherwise invalid record for validator diagnostics `ProjectionDigest`, `Count`, `PromptInput`, `CanonicalName`, and `Matches`.
and emit bounded warnings where current D&D normalizer conventions require - Keep NPC-owned behavior in the NPC package:
them. - strict approved NPC codec use;
- Sort using the complete order defined in `location.md`. - NPC identity validation;
- Publish normalization and registry-projection fingerprints consistent with - canonical durable JSON and names-only prompt projection;
the other registry-backed normalizers. - comparison-key lookup;
- Test all kind values, canonical name replacement, same-name distinct IDs, - defensive NPC/source-reference cloning; and
exact-duplicate removal, distinct evidence retention, stable ordering, - NPC-specific redacted error context.
unknown IDs, malformed registry resolution, warnings, idempotence, and - Preserve the exact empty projection, semantic and projection digest behavior,
non-mutation. seeded/generated resolution semantics, manifest metadata, checkpoint
fingerprints, and source-free prompt input.
- Remove superseded NPC-private resolver/cache/media/digest code only after the
shared helper supplies the same behavior. Do not migrate locations or scene
descriptions in this stage.
- Update focused tests as needed to verify behavior through the NPC package,
including malformed static and operation-time references, raw/semantic cache
reuse, concurrent resolution, defensive copies, prompt projection, and
downstream grounding.
### Acceptance Criteria ### Acceptance Criteria
- No LLM client or prompt assets are required. - Existing NPC consumers compile without API changes.
- Canonicalization is exclusively ID-based. - NPC registry output bytes, prompt projection, lookup behavior, digests,
- Invalid records are not silently redirected to a different location. reference limits, errors, manifests, and checkpoints retain their established
- `go test ./internal/modules/dnd/normalize/locationoccurrences/...` passes semantics.
offline. - The NPC package no longer contains a second implementation of resolver
selection, caching, or synchronization.
### Prompt Size - `go test -race ./internal/modules/dnd/npcs/registry/... ./internal/modules/dnd/extract/npcinteractions/... ./internal/modules/dnd/normalize/npcinteractions/... ./internal/modules/dnd/validate/npcinteractions/...`
passes offline.
Small enough for one implementation prompt. - `go test ./internal/modules/dnd/register/...` passes offline.
## Stage 10: Add Location Registry Validators
### Goal
Give `dnd/location-list` the complete validator ownership expected of a
production D&D artifact.
### Work
- Add location validator packages under
`internal/modules/dnd/validate/locations` for:
- extraction shape;
- source-reference bounds/current-chunk ownership;
- advisory source relatedness; and
- normalized identity derivation and ID uniqueness.
- Use shared D&D citation, unit-reference, diagnostic, and matching helpers
where their contracts apply.
- The identity validator must allow repeated comparison names and validate the
evidence-anchored derivation policy instead of importing NPC uniqueness
assumptions.
- Keep diagnostics indexed, aggregated, bounded, stable, and free of raw prompt
or reference content.
- Add tests for accepted values, every owned failure, same-name distinct
locations, malformed/unreadable citations, advisory relatedness, bounds,
registration metadata, fingerprints, nil safety where applicable, and
non-mutation.
### Acceptance Criteria
- Validator responsibilities do not overlap merely to increase test coverage.
- Relatedness remains advisory and uses only cited current-transcript text.
- Validators do not repair or mutate artifacts.
- `go test ./internal/modules/dnd/validate/locations/...` passes offline.
### Prompt Size
Medium and suitable for one implementation prompt.
## Stage 11: Add Location-Occurrence Validators
### Goal
Give `dnd/location-occurrence-list` complete structural, registry, ordering,
evidence, and advisory validation.
### Work
- Add validator packages under
`internal/modules/dnd/validate/locationoccurrences` for:
- extraction shape and supported kinds;
- required registry membership and exact `location_id`/`name` pairing;
- normalized ordering and exact-duplicate invariants;
- source-reference bounds/current-chunk ownership; and
- advisory source relatedness.
- Reuse the immutable location resolver rather than decoding caller-owned
references independently in each validator.
- Ensure same-name registry records remain distinguishable by ID.
- Keep registry context out of evidence checks.
- Add focused tests for each kind, unknown IDs, mismatched names, same-name
locations, ordering, duplicates, malformed required references, invalid
evidence, advisory diagnostics, registration metadata, fingerprints,
diagnostic bounds, and non-mutation.
### Acceptance Criteria
- An ID/name mismatch is rejected even when another registry record has the
supplied name.
- Missing or malformed required registry references fail at the established
boundary.
- Validators remain deterministic and do not alter occurrence records or the
registry.
- `go test ./internal/modules/dnd/validate/locationoccurrences/...` passes
offline.
### Prompt Size
Medium and suitable for one implementation prompt.
## Stage 12: Compose The Production D&D Family
### Goal
Make both lanes selectable as one coherent production addition after all
component contracts are present.
### Work
- Extend `internal/modules/dnd/register` to register, in dependency-safe order:
- both codecs;
- both extractors;
- typed append-order mergers;
- the LLM-backed location normalizer;
- the deterministic occurrence normalizer;
- all validators;
- shared reconciliation schema assets and both new prompt manifests;
- evidence projectors; and
- extract and normalize default validator chains.
- Ensure both LLM-backed modules select the maintained `dnd-extraction`
fallback profile and inherit the existing profile policy.
- Define chain order consistently with existing D&D artifacts: generic JSON,
shape, registry/identity or normalized invariants at the appropriate stage,
source references, durable JSON Schema, then advisory relatedness.
- Update registrar tests for artifact kinds, keys, execution classes, reference
slot requirements, builder construction, assets, profile use, evidence
projection, chain contents/order, duplicate registration, and failure
propagation.
- Update any integration-level artifact-kind allowlists or typed registries
required by the framework; do not add module-specific orchestration logic.
### Acceptance Criteria
- One D&D registration call exposes both complete lanes and no partial
registration succeeds silently.
- Catalog inspection reports correct artifact kinds, stages, execution classes,
reference slots, profiles, and fingerprints.
- Both artifact kinds support evidence projection without registry evidence
leakage.
- `go test ./internal/modules/dnd/register/... ./internal/modules/dnd/...` passes
offline.
### Prompt Size
Medium-to-large but limited to composition and suitable for one implementation
prompt.
## Stage 13: Add Maintained Pipeline And Handoff Coverage
### Goal
Exercise the feature through real configuration, ordered generated references,
chunk operations, acceptance gates, and durable output.
### Work
- Update `examples/dnd-complete.config.yml`:
- add `locations` to the first descriptive step with extract, append-order
merge, and LLM-backed normalize bindings;
- add a generated `locations` reference to the next step;
- add `location-occurrences` to that step with extract, append-order merge,
and deterministic normalize bindings; and
- add both lanes to evidence-context output where appropriate.
- Keep the minimal example minimal unless its stated purpose requires a
location lane; do not turn it into a second complete example.
- Extend maintained example-loading/config-validation tests.
- Add integration tests that prove:
- the normalized accepted registry is handed off in memory;
- the occurrence lane cannot run before its producer;
- missing, cyclic, wrong-kind, wrong-media-type, rejected, or unaccepted
producers are rejected at the existing boundaries;
- same-name locations remain distinguishable by ID through the handoff;
- registry evidence never becomes occurrence evidence;
- retries and checkpoints honor prompt, schema, identity, and generated-
reference fingerprints; and
- output contains both durable artifact envelopes and evidence context.
- Use recording/fake structured clients only; no network-dependent tests.
### Acceptance Criteria
- The complete example loads through the real config path and exercises all
registered D&D lanes.
- Ordered handoff failure semantics match the framework's existing fail-whole-
run policy.
- Integration tests cover behavior rather than duplicating package internals.
- `go test ./internal/modules/integration/... ./internal/config/...` and any
example-specific test targets pass offline.
### Prompt Size
Medium-to-large but coherent as one end-to-end integration prompt.
## Stage 14: Publish Current Documentation And Perform Final Verification
### Goal
Make the implemented feature discoverable and retire fulfilled future-work
language without leaving development-history documentation behind.
### Work
- Create canonical integration contracts:
- `docs/integrations/dnd-location-artifacts.md`;
- `docs/integrations/dnd-location-occurrence-artifacts.md`.
- Update `docs/config.md` with both selectable keys, the `locations` reference
slot and compatibility, execution classes, validators, default chains, and
the complete-example link.
- Update `docs/internal/dnd.md` with nine-lane composition, shared entity
reconciliation, evidence-anchored identity, occurrence grounding, prompt
asset reuse, and intentional lane differences. Link to integration contracts
instead of duplicating their JSON shapes.
- Update `docs/integrations/json-output.md`, `README.md`, and other current
canonical inventories only where repository inspection shows that the new
artifact kinds or maintained example must be listed.
- Remove the fulfilled `Location Extraction` section from
`docs/roadmap/future.md`. Keep the generic LLM-assisted deduplication item and
clarify only if needed that the new D&D helper does not fulfill that broader
feature.
- Verify all relative links and search current documentation for stale
seven-lane counts, missing keys, obsolete location-planning claims, and
accidental claims that references are evidence.
- Run formatting, focused tests, the full Go test suite, static analysis, and
repository-provided config/example checks. Inspect `git diff --check` and
confirm no unrelated files changed.
- After implementation and verification are complete, leave `location.md` and
this plan in place for the user's separate roadmap-retirement step; do not
delete them unless explicitly asked.
### Acceptance Criteria
- Current documentation describes the implemented contracts and only
implemented behavior outside `docs/roadmap/`.
- The roadmap no longer presents completed location tracking as future work.
- Links, examples, module inventories, and default-chain tables agree with
production registration.
- `go test ./internal/modules/dnd/...` passes.
- `go test ./internal/modules/integration/...` passes.
- `go test ./...` passes.
- `go vet ./...` passes.
- Repository-provided configuration/example validation passes.
- `git diff --check` reports no errors. - `git diff --check` reports no errors.
### Prompt Size ### Prompt Size
Medium and suitable for one implementation prompt. Medium and suitable for one implementation prompt.
## Stage 20: Migrate The Location Registry To The Shared Resolver
### Goal
Remove the newly introduced NPC/location resolver duplication while preserving
the location identity and generated-handoff contracts.
### Work
- Refactor `internal/modules/dnd/locations/registry` to compose the Stage 18
helper for the same resolver, reference-item validation, cache, and
synchronization responsibilities migrated in Stage 19.
- Preserve the existing package API, including `NewResolver`, `Resolve`,
`Seeded`, `Bound`, `Locations`, `List`, `CanonicalBytes`, `Digest`,
`ProjectionDigest`, `Count`, `PromptInput`, `Lookup`, and `Matches`.
- Keep location-owned behavior in the location package:
- strict approved location codec use;
- evidence-anchored identity validation;
- canonical durable JSON and ordered source-free `{id, name}` projection;
- exact ID lookup and exact ID/name matching;
- defensive location/source-reference cloning; and
- location-specific redacted error context.
- Preserve absent/empty/bound distinctions, same-name locations with distinct
IDs, seeded/generated resolution, semantic and projection digests, manifest
metadata, checkpoint fingerprints, and the 1 MiB limit.
- Remove superseded location-private resolver/cache/media/digest code only
after parity is established.
- Do not migrate the scene-description eligibility registry in this stage. It
has a different projection and matching contract; including it is not needed
to eliminate the exact NPC/location duplication identified by the audit.
- Update focused and integration tests as needed for static and generated
references, malformed inputs, raw/semantic reuse, concurrency, defensive
copies, source-free projections, same-name ID lookup, normalizer grounding,
validator grounding, and handoff checkpoint invalidation.
### Acceptance Criteria
- Existing location consumers compile without API changes.
- Location registry output bytes, projection, identity validation, lookup,
digests, reference errors, manifests, and checkpoints retain their
established semantics.
- NPC and location registries share one implementation of construction/
operation resolution, raw and semantic caching, and synchronization.
- No registry evidence or provenance appears in the occurrence prompt
projection.
- `go test -race ./internal/modules/dnd/locations/registry/... ./internal/modules/dnd/extract/locationoccurrences/... ./internal/modules/dnd/normalize/locationoccurrences/... ./internal/modules/dnd/validate/locationoccurrences/... ./internal/modules/integration/...`
passes offline.
- `git diff --check` reports no errors.
### Prompt Size
Medium and suitable for one implementation prompt.
## Stage 21: Final Regression And Documentation Verification
### Goal
Verify the remediations as one coherent production state without expanding the
feature or prematurely retiring its roadmap documents.
### Work
- Run `gofmt` on all changed Go files and inspect the final diff for unrelated
changes, stale private resolver implementations, unused imports, and
accidental artifact-contract changes.
- Re-read the affected prompt assets and canonical integration documents to
verify that:
- NPC canonical-name selection retains the proper-name/title distinction;
- explicit non-actionable speculation maps to `mentioned`;
- inferred, uncertain, and unsupported occurrences remain omitted; and
- prompt changes did not reorder or duplicate shared cacheable prompt assets.
- Inspect module catalog output or registration tests to confirm the
location-occurrence normalizer declares only `locations`, while its extractor
retains the intended optional campaign slots.
- Confirm the shared resolver has exactly the NPC and location consumers
planned here and that domain-specific codecs, identity, projections, lookups,
and error context remain in their owning packages.
- Run the maintained complete example/configuration loading path and all
repository-wide validation.
- Leave `docs/roadmap/location.md` and this implementation plan in place for the
user's separate roadmap-retirement step. Do not delete them in this stage.
### Acceptance Criteria
- `go test ./internal/modules/dnd/...` passes.
- `go test ./internal/modules/integration/... ./internal/cli/...` passes.
- `go test -race ./internal/modules/dnd/shared/registryresolver/... ./internal/modules/dnd/npcs/registry/... ./internal/modules/dnd/locations/registry/...` passes.
- `go test ./...` passes.
- `go vet ./...` passes.
- `go build ./cmd/notarius` passes.
- Maintained configuration/example validation passes through its repository
test target.
- `git diff --check` reports no errors.
- Current documentation contains no stale contradiction about hypothetical
location mentions or NPC canonical-name selection.
- The worktree contains only the intended remediation changes and the roadmap
documents remain available for later retirement.
### Prompt Size
Small-to-medium and suitable for one implementation prompt.
## Open Questions ## Open Questions
None. The feature roadmap fixes the artifact shapes, identity scope and None. The feature roadmap and audit findings determine the required behavioral
derivation, occurrence categories, classification precedence, reference semantics, module reference contract, abstraction boundary, migration order,
dependency, reconciliation safety boundary, pipeline placement, and non-goals and validation expectations. The scene-description eligibility registry is
needed to implement every stage without an additional product decision. explicitly outside this remediation because its projection and matching
semantics differ from the duplicated NPC/location grounding registries.

View File

@@ -3,6 +3,11 @@ that refer to the same individual. Propose only groups supported by the
transcript, and preserve distinct individuals even when their names are transcript, and preserve distinct individuals even when their names are
similar. similar.
For every accepted group, choose as canonical the supplied candidate whose For every accepted group, choose as canonical only a supplied candidate from
display name is the most complete and clear NPC name. Do not invent, edit, or that evidence-supported duplicate group. Prefer a complete, stable proper name
combine display names. over an abbreviation. Prefer an unadorned proper name over that name plus a
contextual class, role, title, or relationship descriptor unless the transcript
establishes the descriptor as part of the person's name. A longer display name
is not inherently more canonical; for example, do not prefer `Captain Aria`
over `Aria` solely because it includes the contextual title `Captain`. Do not
invent, edit, or combine display names.