From 20397ef7103171095c76102cd0e0561b9004dcaf Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 8 Aug 2026 15:08:57 +0000 Subject: [PATCH] Document deterministic entity identity resolution --- ...ue-entity-identifiers-deterministically.md | 64 +++++++++++++++++++ .../dnd-item-occurrence-artifacts.md | 9 +-- .../dnd-item-registry-artifacts.md | 7 +- .../dnd-location-occurrence-artifacts.md | 10 +-- .../dnd-location-registry-artifacts.md | 8 ++- .../dnd-npc-occurrence-artifacts.md | 10 +-- .../dnd-npc-registry-artifacts.md | 7 +- docs/internal/dnd.md | 39 +++++++---- docs/internal/llm.md | 3 + docs/policy/architecture.md | 6 ++ docs/roadmap/future.md | 14 ++-- 11 files changed, 136 insertions(+), 41 deletions(-) create mode 100644 docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md diff --git a/docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md b/docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md new file mode 100644 index 0000000..82e2ca3 --- /dev/null +++ b/docs/adr/0012-resolve-opaque-entity-identifiers-deterministically.md @@ -0,0 +1,64 @@ +# ADR-0012: Resolve opaque entity identifiers deterministically + +**Status:** Accepted +**Date:** 2026-08-08 + +## Context + +Entity IDs in durable Notarius artifacts are application-owned, deterministic +identifiers. They are useful to artifact consumers, but their hash-based form +does not help a model distinguish entities and would make the model reproduce +an opaque implementation detail. A plain name is likewise insufficient where +multiple supplied records share that name. + +The LLM boundary must preserve the typed artifact and durable-schema ownership +of [ADR-0003](0003-strongly-typed-stage-interfaces.md) and the distinction +between disambiguating references and source evidence in +[ADR-0009](0009-prefer-minimal-evidence-grounded-extraction-artifacts.md). + +## Decision + +Callers present a model with semantic selections: a canonical name when it is +unique in the request, or a contextual descriptor containing the name and +source coordinates when that context is needed to distinguish supplied +records. The model returns only those supplied selections. The caller resolves +each accepted selection against the request-local supplied records and attaches +the opaque application ID deterministically. + +Source coordinates are permitted in a selection solely as identity context. +They neither establish an occurrence fact nor replace that occurrence's +current-transcript evidence. A selector must resolve exactly; unknown, +ambiguous, partial, reordered, or otherwise unsafe selections are not mapped. +Where an operation requires a complete grounded artifact, that failure rejects +the complete artifact rather than accepting a partially mapped result. + +An explicitly scoped request-local short label is permitted only when a +contextual descriptor would be impractical and the caller can deterministically +map the label within that one request. Such a label is not a durable ID, must +not escape the request boundary, and requires a concrete justification in its +own module contract. + +## Alternatives considered + +- Ask the model to return durable IDs. This exposes opaque implementation + state, does not improve semantic disambiguation, and makes model output + depend on hash formatting. +- Select by name alone. This cannot safely distinguish same-name records. +- Make request-local labels durable identifiers. This would turn prompt + presentation into a public identity contract and create avoidable migration + pressure. +- Let the model invent identifiers or resolve ambiguity. This makes identity + assignment non-deterministic and weakens validation. + +## Consequences + +Durable integration contracts retain their exact ID/name pairs while models +operate on readable contextual selections. Calling modules must own selector +construction, exact resolution, ambiguity handling, and conversion into their +durable artifact type; PromptKit and its adapter remain transport-only. + +Some ambiguous or invalid proposals are deliberately omitted, retried, or +rejected according to the caller's existing failure policy. Internal candidate +keys may support deterministic request-local mapping, but they are not +model-visible selectors or durable data. This adds local validation work while +keeping identity assignment auditable and stable. diff --git a/docs/integrations/dnd-item-occurrence-artifacts.md b/docs/integrations/dnd-item-occurrence-artifacts.md index 471ec2c..18883e2 100644 --- a/docs/integrations/dnd-item-occurrence-artifacts.md +++ b/docs/integrations/dnd-item-occurrence-artifacts.md @@ -24,12 +24,13 @@ An incompatible shape change requires a new schema version. Both extraction and normalization require an `item_registry` reference bound to an earlier normalized `dnd/item-registry` artifact. The registry is immutable -for an operation and contributes only its ordered `{id,name}` projection after -the shared evidence message. It is never occurrence evidence. +for an operation and contributes names-only grounding after the shared evidence +message. Notarius resolves the model's selected name into the unchanged exact +durable ID/name pair. It is never occurrence evidence. Each occurrence must use one exact registry ID/name pair. An extraction response -with an unknown ID or mismatched name is rejected as invalid model output; the -configured pipeline may retry it and never accepts a partial artifact. +with an unknown or ambiguous selected name is rejected as invalid model output; +the configured pipeline may retry it and never accepts a partial artifact. Normalization and validation remain defense in depth for artifacts entering through other boundaries: normalization canonicalizes a recognized name by ID, preserves unknown values for the registry validator, and the registry validator diff --git a/docs/integrations/dnd-item-registry-artifacts.md b/docs/integrations/dnd-item-registry-artifacts.md index c8b756b..92a64bf 100644 --- a/docs/integrations/dnd-item-registry-artifacts.md +++ b/docs/integrations/dnd-item-registry-artifacts.md @@ -85,9 +85,10 @@ for later artifacts. `dnd/item-occurrences` requires one approved item registry through its `item_registry` reference slot for both extraction and normalization. Its -consumer receives only an ordered, source-free `{id,name}` projection; the -registry’s source references are never occurrence evidence. Unknown IDs and -mismatched pairs are rejected by the occurrence contract. See the +consumer receives names-only grounding; Notarius resolves the selected name +into the unchanged exact durable ID/name pair. The registry’s source references +are never occurrence evidence. Unknown or ambiguous selections are rejected by +the occurrence contract. See the [item-occurrence artifact](dnd-item-occurrence-artifacts.md) for that strict wire contract, [Configuration](../config.md#d-d-reference-slots) for binding rules and validator selection, and the [JSON output contract](json-output.md) diff --git a/docs/integrations/dnd-location-occurrence-artifacts.md b/docs/integrations/dnd-location-occurrence-artifacts.md index 9ac6fad..94db5ac 100644 --- a/docs/integrations/dnd-location-occurrence-artifacts.md +++ b/docs/integrations/dnd-location-occurrence-artifacts.md @@ -73,10 +73,12 @@ complete canonical evidence sequence. Both extraction and normalization require exactly one `location_registry` reference of kind `dnd/location-registry`, media type `application/json`, and at most 1 MiB. The -registry provides identity grounding only: unknown IDs and mismatched ID/name -pairs are rejected rather than guessed or reassigned. The current transcript is -the only evidence source for an occurrence; registry evidence and provenance -never become occurrence evidence. +registry provides identity grounding only. The model selects a supplied +contextual name-and-registry-reference descriptor, and Notarius resolves it +into the exact durable ID/name pair. Unknown, partial, or ambiguous selections +are rejected rather than guessed or reassigned. The current transcript is the +only evidence source for an occurrence; registry evidence and provenance never +become occurrence evidence. See [Configuration](../config.md#d-d-reference-slots) for the selectable slot and generated-handoff compatibility, [D&D module internals](../internal/dnd.md) diff --git a/docs/integrations/dnd-location-registry-artifacts.md b/docs/integrations/dnd-location-registry-artifacts.md index a53cb8a..687502c 100644 --- a/docs/integrations/dnd-location-registry-artifacts.md +++ b/docs/integrations/dnd-location-registry-artifacts.md @@ -83,9 +83,11 @@ not evidence for later artifacts. ## Consumers and publication `dnd/location-occurrences` requires one approved location registry through its -`location_registry` reference slot. Its prompt receives an ordered source-free `{id, -name}` projection and must not treat registry references as occurrence -evidence. See the [location-occurrence artifact](dnd-location-occurrence-artifacts.md) +`location_registry` reference slot. Its prompt receives contextual selectors +containing a canonical name and registry references; Notarius resolves a +selection into the unchanged exact durable ID/name pair. Registry references +must not be treated as occurrence evidence. See the +[location-occurrence artifact](dnd-location-occurrence-artifacts.md) for that contract, [Configuration](../config.md#references-and-ordered-handoffs) for binding rules, and the [JSON output contract](json-output.md) for publication. diff --git a/docs/integrations/dnd-npc-occurrence-artifacts.md b/docs/integrations/dnd-npc-occurrence-artifacts.md index 4abf947..b86ff8f 100644 --- a/docs/integrations/dnd-npc-occurrence-artifacts.md +++ b/docs/integrations/dnd-npc-occurrence-artifacts.md @@ -67,10 +67,12 @@ for uncertain classification. ## Identity, evidence, and order -The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) resolves -the exact `{npc_id, name}` pair. Unknown IDs and names that do not match their -ID are rejected; normalization does not repair names by similarity. Registry -references are provenance only and never replace an occurrence's own evidence. +The required normalized [NPC registry artifact](dnd-npc-registry-artifacts.md) +supplies names-only contextual grounding to the model. Notarius resolves the +selected name and writes the exact `{npc_id, name}` pair. An unknown or +ambiguous selection rejects the complete model result; normalization does not +repair names by similarity. Registry references are provenance only and never +replace an occurrence's own evidence. The registry may include an identity established by a factual third-party mention; that provenance alone does not create a `mentioned` occurrence. Each occurrence remains a separately cited fact in the current transcript. diff --git a/docs/integrations/dnd-npc-registry-artifacts.md b/docs/integrations/dnd-npc-registry-artifacts.md index eb5da55..d98d02e 100644 --- a/docs/integrations/dnd-npc-registry-artifacts.md +++ b/docs/integrations/dnd-npc-registry-artifacts.md @@ -82,9 +82,10 @@ with its own cited evidence and category. This registry can ground actor or caster names in the [spell](dnd-spell-artifacts.md) and [combat-turn](dnd-combat-turn-artifacts.md) artifacts. It is required to resolve the canonical `name` in an [NPC occurrence](dnd-npc-occurrence-artifacts.md). -Occurrence consumers receive an ordered source-free `{id,name}` projection; -spells, combat turns, and the [enemy-event artifact](dnd-enemy-event-artifacts.md) -receive names-only grounding for actor or subject display. None of these +Occurrence consumers receive names-only grounding; Notarius resolves the +selected canonical name and writes the unchanged exact durable ID/name pair. +Spells, combat turns, and the [enemy-event artifact](dnd-enemy-event-artifacts.md) +also receive names-only grounding for actor or subject display. None of these projections supply later-artifact evidence. [Configuration](../config.md#d-d-reference-slots) owns the `npc_registry` binding rules. The [JSON output contract](json-output.md) defines publication, and diff --git a/docs/internal/dnd.md b/docs/internal/dnd.md index e4b024f..76aa279 100644 --- a/docs/internal/dnd.md +++ b/docs/internal/dnd.md @@ -127,14 +127,17 @@ combine results from distinct scenes, so it intentionally does not apply that rule. Configuration owns the exact validator key and chain position. Normalizers are deterministic for spells, combat turns, item occurrences, NPC -occurrences, scene descriptions, enemy events, and location occurrences. They canonicalize display -values and evidence, use source-document order for stable output, and issue -bounded warnings for changes or collapsed duplicates. The NPC and location -normalizers are intentional exceptions: each first produces a deterministic -candidate set, then may use a bounded structured-LLM proposal to reconcile -identity groups. Invalid or unusable proposals retain the deterministic result -and surface retry or fallback diagnostics; the model does not directly replace -durable records. +occurrences, scene descriptions, enemy events, and location occurrences. They +canonicalize display values and evidence, use source-document order for stable +output, and issue bounded warnings for changes or collapsed duplicates. NPC, +item, and location registry normalizers are intentional exceptions: each first +produces a deterministic candidate set, then may use a bounded structured-LLM +proposal to reconcile identity groups. The proposal selects supplied +descriptors—names with their candidate source references—not durable IDs. +Request-local candidate keys may support resolution internally, but are never +included in model input or output. Colliding descriptors are ineligible, and +invalid or unusable proposals retain the deterministic result with retry or +fallback diagnostics; the model does not directly replace durable records. ## Generated References And Grounding @@ -144,11 +147,19 @@ producer provenance; consumers resolve the handed-off artifact into an immutable, validated projection for each operation. External files are checked during preparation, while generated artifacts are resolved at the handoff. -NPC, location, and item registries project ordered, source-free `{id, name}` -pairs to their respective occurrence extractors and normalizers. Exact ID/name -matching preserves every identity the registry recognizes, including same-name -locations with distinct source anchors. The NPC registry additionally supplies -names-only actor grounding to spells, combat turns, and enemy events. +NPC and item registry consumers receive names-only grounding. Location +consumers receive a contextual selector containing the canonical name and the +registry references needed to distinguish same-name places. The calling module +resolves those supplied selections locally and maps them into the unchanged +durable ID/name pair; an unknown or ambiguous selection rejects the complete +occurrence result rather than accepting a partial mapping. The NPC registry +additionally supplies names-only actor grounding to spells, combat turns, and +enemy events. + +Registry references establish a registry identity and may disambiguate a +selection, but never become occurrence evidence. Each occurrence keeps its own +current-transcript source references, even when it was grounded through the +same registry record. Scene descriptions are eligibility-only projections: they retain current-chunk classification data, not scene prose or evidence, and exist to route combat extraction. Enemy-event extraction also projects combat turns to `actor` and @@ -170,7 +181,7 @@ checkpoint fingerprint. | Spells | May use a spell-catalog overlay and optional NPC grounding; the catalog validator supplies domain-specific semantic checks. | | NPC registry | Establishes transcript-grounded NPC identities, including factual third-party mentions, without assigning occurrence categories. It does not consume an NPC registry, and its normalizer is the LLM-assisted reconciliation exception described above. | | Combat turns | Requires a scene-description artifact. It calls the LLM only for an exact `combat` classification; exact non-combat classifications return an accepted empty result, while missing or mismatched classifications return an empty result with a bounded warning. Optional NPC grounding never becomes evidence. | -| Item occurrences | Requires the normalized item registry for exact ID/name grounding at extraction and normalization. Campaign context may disambiguate, but the registry never becomes occurrence evidence. | +| Item occurrences | Requires the normalized item registry for exact deterministic grounding at extraction and normalization. Campaign context may disambiguate, but the registry never becomes occurrence evidence. | | Item registry | Produces source-grounded item types and unique designations. Its LLM-assisted reconciliation is proposal-only, preserves distinct currency denominations and item types, and does not create per-instance identities. | | NPC occurrences | Requires the normalized NPC registry at extraction and normalization, using it for canonical actor grounding only. It separately emits cited current-transcript occurrence facts, including `mentioned`, rather than deriving them from registry provenance. | | Scene descriptions | Produces the classifications consumed by combat routing; it does not consume an NPC registry or provide evidence for combat artifacts. | diff --git a/docs/internal/llm.md b/docs/internal/llm.md index 156adba..763dd29 100644 --- a/docs/internal/llm.md +++ b/docs/internal/llm.md @@ -24,6 +24,9 @@ adapter does not own source evidence, artifact conversion, normalization, or durable schemas. Those responsibilities remain with the module and its [integration contract](../integrations/). +The calling module also resolves contextual entity selections and attaches any +application identity; PromptKit and this adapter do not own entity identity. + `PromptKitClient` validates the request target and prompt identity, maps each named material to a PromptKit inline artifact while preserving its origin URI, passes the supplied request session through to PromptKit's direct per-run diff --git a/docs/policy/architecture.md b/docs/policy/architecture.md index fc1c7fe..b7bd5e1 100644 --- a/docs/policy/architecture.md +++ b/docs/policy/architecture.md @@ -183,6 +183,12 @@ The caller of the LLM owns prompt selection, prompt inputs, response schema, and interpretation of structured output. Provider adapters do not own source- or domain-specific prompt logic. +When a model selects an application entity, callers must supply a contextual +selection and deterministically attach the opaque application identity whenever +the selection resolves exactly. Models do not receive or reproduce opaque +application identifiers; [ADR-0012](../adr/0012-resolve-opaque-entity-identifiers-deterministically.md) +records the rationale and limited request-local-label exception. + LLM calls and other external operations accept cancellation and respect timeouts. Concurrency control belongs in shared runtime plumbing rather than in individual modules. diff --git a/docs/roadmap/future.md b/docs/roadmap/future.md index c830d35..92b3ea1 100644 --- a/docs/roadmap/future.md +++ b/docs/roadmap/future.md @@ -29,13 +29,15 @@ not as committed release dates. - Add a reusable normalizer that asks an LLM to identify duplicate sets in a list and propose one replacement element for each set. - Define the minimum domain-neutral input contract, initially an ordered list - whose elements have stable unique IDs. Artifact-kind registrations or - adapters may expose that structure without moving domain rules into the - generic package. + whose elements retain stable unique IDs as internal deterministic state. + Model proposals use contextual descriptors, or a specifically justified + request-local short label, rather than durable IDs. Artifact-kind + registrations or adapters may expose that structure without moving domain + rules into the generic package. - Keep mutation deterministic: parse and validate the model's duplicate groups, - require every referenced ID to exist, reject overlapping or malformed groups, - prevent unrelated insertion or deletion, and apply only approved replacement - operations in code. + resolve every supplied descriptor or local label exactly, reject overlapping + or malformed groups, prevent unrelated insertion or deletion, and apply only + approved replacement operations in code. - Preserve provenance needed for audit and downstream validation, and emit warnings describing every collapsed group. - Evaluate batching and context-window limits before applying the normalizer to