Retire the completed asset migration roadmaps

This commit is contained in:
2026-08-05 13:23:00 +00:00
parent 5f207b2ab1
commit 406ad1d362
2 changed files with 0 additions and 1061 deletions

View File

@@ -1,366 +0,0 @@
# Centralized LLM Assets
## Purpose
Centralize Notarius's embedded LLM-facing assets in a top-level `assets/`
package so prompt declarations, prompt fragments, private structured-response
schemas, shared prompt text, and built-in LLM profiles are easy to find,
inspect, compare, and revise.
This is an ownership and repository-layout change, not a prompt redesign or a
change to any durable artifact contract. Modules continue to own all behavior
associated with their assets.
## Motivation
The D&D module family currently distributes its LLM assets across the packages
that consume them. Twelve D&D prompt consumers collectively embed 46 prompt
files. Their private response schemas, shared entity-reconciliation schema,
shared prompt fragments, and built-in `dnd-extraction` profile are likewise
spread across chunk, extract, normalize, shared, and registrar packages.
Co-location made each original module self-contained, but the growing family
now makes prompt inspection and cross-module editing unnecessarily difficult.
Prompt authors commonly need to:
- review all sibling prompts together;
- preserve exact shared bytes and cache-compatible message prefixes;
- compare declarations, instructions, tasks, and response schemas;
- identify duplicated or drifting language; and
- iterate on prompt content without navigating implementation packages.
A centralized, predictable asset tree makes those operations straightforward
while leaving executable behavior with the module packages.
## Decisions
### Top-Level Asset Package
All production LLM-facing assets will live beneath a repository-root
`assets/` directory. That directory will also be a Go package named `assets`
because Go embed patterns cannot embed files outside the embedding package's
directory tree.
The package will contain one Go source file, `assets/package.go`, unless a
future Go toolchain constraint makes that impractical. Its entire production
API will expose the embedded, read-only filesystem. The intended shape is
equivalent to:
```go
package assets
import (
"embed"
"io/fs"
)
//go:embed dnd generic
var embedded embed.FS
func FS() fs.FS { return embedded }
```
The exact embed patterns may reflect the final asset tree, but the package must
retain this minimal character. Adding a new asset domain may require extending
the embed directive; it must not require adding domain behavior to the package.
### Strict Data-Only Boundary
The `assets` package is a content container and dependency leaf. No business,
domain, framework, or PromptKit behavior may live there.
In particular, the package must not own:
- prompt IDs, schema IDs, schema names, or schema versions;
- prompt manifests, message order, cache controls, or input construction;
- module, schema, profile, or PromptKit registration;
- asset selection rules or module-to-path catalogs;
- parsing, validation, normalization, transformation, or hashing;
- checkpoint or fingerprint policy;
- fallback-profile precedence or model defaults; or
- imports of Notarius internal packages, PromptKit, or module packages.
It should expose no path-aware lookup helpers. Consumers obtain the root
read-only filesystem and use the standard library's `fs.Sub` to select the
smallest subtree they own. A module must not depend on an unrelated module's
asset subtree merely because both are available from the same embedded
filesystem.
The package's top-level location and exported accessor are an intentional
tradeoff in favor of human discoverability. They do not establish a supported
public extension API or transfer semantic ownership away from modules.
### Behavioral Ownership Remains With Modules
Each LLM-backed module continues to own:
- its prompt and response-schema identity constants;
- its ordered prompt-asset manifest;
- prompt input preparation and rendered message order;
- its response-schema definition and loading;
- prompt and schema registration with the shared LLM asset registry;
- output decoding and interpretation;
- its component fingerprint composition; and
- tests of its observable prompt and schema behavior.
The D&D shared package continues to own the manifest mechanism and the declared
set of reusable D&D prompt fragments. The D&D registrar continues to own family
composition and registration of the built-in fallback profile. The generic LLM
framework continues to own filesystem flattening, duplicate detection, schema
loading, PromptKit construction, and content hashing.
Physical storage is therefore centralized without creating a central package
that understands or composes every module.
### Asset Scope
The centralized tree includes embedded content whose direct purpose is to
configure or constrain an LLM interaction:
- PromptKit prompt declarations;
- module-owned prompt fragments;
- byte-identical shared prompt fragments;
- private JSON Schemas for structured LLM response envelopes;
- shared private response schemas such as D&D entity reconciliation; and
- built-in fallback LLM profiles.
The migration also includes the generic private LLM response schemas currently
embedded by the LLM framework. The framework may depend on the content-only
asset package for those generic files, but it must refer only to its own scoped
subtree and must not acquire D&D knowledge.
The following embedded files remain with their current owners:
- durable artifact schemas owned by D&D codec packages;
- framework-owned durable schemas such as chunk-map and evidence-context
contracts;
- the D&D spell catalog and other domain data that is not itself a prompt,
private LLM response schema, or built-in LLM profile;
- maintained configuration examples; and
- operator-supplied PromptKit profiles and other runtime files.
Private LLM response schemas must remain clearly distinguishable from durable
artifact schemas. Centralization must not turn a model transport envelope into
an external Notarius contract.
## Target Asset Tree
The target tree groups assets first by domain and then by conceptual module.
Directory names use readable kebab case; Go package names, module keys, prompt
IDs, and schema identities do not change to match the directory spelling.
```text
assets/
package.go
generic/
schemas/
dnd/
shared/
prompts/
profiles/
entity-reconciliation/
schemas/
scenes/
prompts/
schemas/
spells/
prompts/
schemas/
npcs/
extract/
prompts/
schemas/
normalize/
prompts/
combat-turns/
prompts/
schemas/
enemy-events/
prompts/
schemas/
item-events/
prompts/
schemas/
npc-interactions/
prompts/
schemas/
scene-descriptions/
prompts/
schemas/
locations/
extract/
prompts/
schemas/
normalize/
prompts/
location-occurrences/
prompts/
schemas/
```
Each prompt-bearing leaf retains predictable filenames for its declaration,
task, instructions, and any lane-specific fragments. Existing filenames should
be preserved where practical so this migration does not create gratuitous
content churn. Shared content must have one physical source file rather than a
copy in each consuming subtree.
Future LLM-backed domains and modules follow the same taxonomy. A future module
with both extraction and LLM-backed normalization assets uses explicit
`extract/` and `normalize/` children; a module with only one LLM-backed
operation may keep its prompt and schema directories directly beneath the
module directory.
## Filesystem And Registration Model
Consumers use `assets.FS()` plus `fs.Sub` to obtain a domain-, module-, or
asset-class-specific filesystem. Existing manifests continue to enumerate the
exact files used by each prompt. Registration continues through
`llm.AssetRegistry`, which presents PromptKit with its existing flattened
prompt, schema, and fallback-profile filesystems.
The virtual PromptKit layout is stable even though repository source paths
move. In particular:
- prompt IDs and declaration names remain unchanged;
- schema keys, IDs, versions, names, and registered filenames remain unchanged;
- fallback profile IDs, contents, and precedence remain unchanged;
- shared fragments remain mounted at the paths expected by existing prompt
declarations; and
- duplicate-path and missing-file failures continue to occur during
preparation.
No consumer receives a precomposed global prompt catalog from the `assets`
package. Module manifests and the D&D registrar remain the explicit composition
points.
## Prompt Caching And Fingerprints
The migration must preserve every prompt's rendered roles, bytes, input
placement, message order, and cache-control metadata. Merely moving source
files must not reduce backend prompt-cache compatibility or alter model-visible
content.
Each component fingerprint must remain scoped to the exact assets declared by
that component. An edit to one lane must not invalidate checkpoints belonging
to unrelated lanes. The implementation must not replace manifest-scoped
hashing with a digest of the complete root asset filesystem.
Current asset hashes include source paths as well as bytes, so moving the files
may produce a one-time checkpoint fingerprint change for affected components.
That safe invalidation is acceptable. Preserving old internal fingerprints is
not a reason to retain duplicate files, compatibility shims, or legacy asset
paths. After migration, changing an unrelated asset must not affect a
component's fingerprint.
## Architectural Documentation
Central storage refines the accepted domain-packaging decision in ADR-0004,
which currently says domain-specific prompt fragments and schemas live within
the domain implementation tree. Implementation must add a new ADR recording
the content-only root package, module ownership rules, alternatives, and
consequences, and mark only the conflicting asset-co-location portion of
ADR-0004 as superseded. Its remaining domain-first module packaging decision
continues to apply.
Once implemented, the canonical architecture and internal documentation must
describe current behavior:
- architecture documents the data-only asset-package boundary and dependency
direction;
- LLM internals document embedding, scoped filesystem consumption,
registration, and private-schema ownership;
- D&D internals document the domain asset taxonomy, shared-fragment ownership,
manifest-scoped fingerprints, and the convention for new modules; and
- the internal component overview links to the focused owner without
duplicating volatile paths.
The roadmap remains the only documentation of this unimplemented target until
the migration lands.
## Work Scope
Completing this feature requires:
- creating the top-level asset package and target directory structure;
- moving all in-scope files without editing their semantic content;
- replacing package-local `embed.FS` declarations for migrated assets with
scoped views of the central filesystem;
- updating module manifests, response-schema loaders, shared-fragment
resolution, generic schema loading, fallback-profile registration, and asset
hashes to use those scoped views;
- removing obsolete module-local prompt, private-schema, and profile asset
directories and their embedding files;
- preserving module-local behavioral declarations and registration methods;
- updating tests and documentation at their durable ownership boundaries; and
- recording the architectural decision described above.
## Verification Expectations
Verification should protect behavior and architectural boundaries rather than
freeze prompt prose or count files. The completed migration must demonstrate
that:
- all registered prompts and private response schemas prepare successfully;
- all built-in fallback profiles remain available with unchanged effective
configuration;
- rendered prompt messages and cache controls are unchanged for representative
extraction, chunking, and normalization prompts;
- shared prompt fragments come from one physical source and render identically
wherever reused;
- schema loading retains the existing identities and content digests;
- duplicate, missing, or malformed assets still fail at the established
preparation boundary;
- component fingerprints use only their declared assets;
- D&D family and production composition tests pass;
- no package-local migrated copies remain; and
- `assets/` contains only the single minimal Go source file and in-scope data
files, with no business logic or internal/PromptKit dependencies.
Tests must follow the testing policy: do not add exact prompt-prose snapshots,
asset-count assertions, source-path snapshots, or other change-detector tests.
Use existing preparation, rendering, registration, schema, fingerprint, and
composition boundaries to protect meaningful behavior.
Repository-wide validation includes:
```sh
go test ./...
go vet ./...
go build ./cmd/notarius
```
## Non-Goals
This work does not:
- rewrite, consolidate, or otherwise tune prompt content;
- change prompt message order, caching policy, or profile defaults;
- change private or durable schema shapes;
- move durable schemas or the spell catalog into the root package;
- add runtime prompt overrides or live filesystem reloading;
- expose asset paths as configuration or a supported public API;
- replace the existing LLM asset registry or PromptKit filesystem boundary;
- centralize module behavior, registration, or identity constants; or
- preserve reuse of checkpoints created with the pre-migration internal asset
paths.
An optional development-time filesystem overlay may be considered separately
if prompt iteration without rebuilding becomes valuable. It requires distinct
precedence, provenance, fingerprint, reproducibility, and security decisions
and is not implied by this repository-layout change.
## Target End State
Prompt authors can inspect every embedded LLM interaction asset from one
predictable top-level tree. Reusable prompt content has one physical owner, and
private LLM schemas are visibly separated from durable artifact contracts.
The `assets` Go package consists only of `package.go`, embeds the content, and
exposes one read-only filesystem accessor. It contains no application logic and
has no Notarius-internal or PromptKit dependencies. Modules select scoped
subtrees and retain complete ownership of prompt semantics, schema identity,
registration, interpretation, and fingerprints. The framework remains
domain-neutral, the D&D registrar remains the family composition root, and all
observable prompt, schema, profile, and pipeline behavior is unchanged apart
from the accepted one-time invalidation of checkpoints whose fingerprints
include moved source paths.

View File

@@ -1,695 +0,0 @@
# Centralized LLM Assets Implementation Plan
## Objective
Implement the target state defined in [Centralized LLM Assets](assets.md): move
embedded LLM-facing content into a minimal top-level `assets` package while
leaving prompt semantics, schema identities, registration, interpretation, and
fingerprint composition with their current framework and module owners.
This plan is ordered. Implement each stage in sequence and complete its focused
validation before beginning the next stage. Do not retire either roadmap when
the stages are complete; roadmap cleanup is a separate maintainer decision.
## Plan-Wide Constraints
These constraints apply to every stage:
- Treat [the feature roadmap](assets.md) as the canonical source for scope,
ownership, target layout, and non-goals.
- Follow the architecture, documentation, and testing policies under
`docs/policy/`.
- Move asset files without changing their bytes. Use Git rename detection or
content hashes to investigate any move reported as a content edit.
- Move a coherent asset set and update every consumer in the same stage. Never
leave a second authoritative copy in the old package.
- Keep `assets/package.go` as the only Go source file under `assets/`. Do not add
tests, generated Go files, path constants, module descriptors, lookup
helpers, registration, validation, hashing, or other behavior to that
package.
- The root package may import only `embed` and `io/fs`. It exposes one
read-only `FS() fs.FS` accessor and contains no Notarius-internal or PromptKit
dependency.
- A consuming package must import the root package consistently as
`rootassets`, call `fs.Sub(rootassets.FS(), "<owned-subtree>")`, and operate
only on its own subtree. The alias distinguishes content from local registry
parameters commonly named `assets`. Do not pass the global filesystem
through the application or introduce a global asset catalog.
- Retain prompt IDs, schema keys, IDs, versions, names, registered filenames,
profile IDs and contents, prompt message order, rendered bytes, and cache
controls.
- Keep `promptAssetRoot = "assets/prompts"` and each manifest's `ModuleDir`
unchanged where they define the synthesized PromptKit filesystem. Only the
source paths inside the manifest change.
- Register a scoped module filesystem's `schemas` directory as the schema root
so the flattened PromptKit schema filenames remain unchanged.
- Keep prompt fingerprints manifest-scoped. Accept the feature roadmap's
one-time fingerprint change caused by new source paths; do not preserve old
paths through compatibility files or hash the complete root filesystem.
- Do not move codec schemas, chunk-map or evidence-context durable schemas, the
spell catalog, examples, or operator-provided profiles.
- Do not rewrite prompts or schemas during this migration.
- Update existing tests only where their setup refers to the old filesystem or
path. Add a test only for a meaningful behavior not already protected. Do not
add prompt-prose snapshots, asset counts, source-path snapshots, exact shared
prefix lengths, or other change-detector tests.
For each moved set, inspect `git diff --summary` and the ordinary diff before
finishing the stage. Asset files should appear as exact renames unless their
parent directory changes prevent Git from displaying the move immediately;
their content must nevertheless be identical.
## Stage 1: Establish The Data-Only Package And Move Generic LLM Schemas
### Goal
Create the minimal root package and use it for the two generic private response
schemas currently embedded by the LLM framework. This proves the dependency
direction and scoped-filesystem pattern before any D&D assets move.
### Changes
1. Create `assets/package.go` with:
- a package comment explaining that it exposes embedded LLM-facing content;
- imports of only `embed` and `io/fs`;
- an unexported `embed.FS` populated initially with `//go:embed generic`;
- one exported `FS() fs.FS` accessor returning the embedded filesystem; and
- no other declarations or behavior.
2. Move these files without editing their contents:
- `internal/framework/llm/assets/schemas/test_artifact.v1.json` to
`assets/generic/schemas/test_artifact.v1.json`;
- `internal/framework/llm/assets/schemas/test_validator_decision.v1.json` to
`assets/generic/schemas/test_validator_decision.v1.json`.
3. Update `internal/framework/llm/schema_registry.go`:
- remove its `embed` import, embed directive, and package-local embedded
filesystem;
- import the root `assets` package;
- create its existing package-private schema filesystem from
`fs.Sub(rootassets.FS(), "generic")`;
- keep the existing fail-fast initialization behavior for an invalid
compile-time subtree, using a small private helper in the LLM package if
needed; and
- change only the two source paths from `assets/schemas/...` to
`schemas/...`. Preserve every schema definition identity and the existing
defensive loading behavior.
4. Remove the now-empty `internal/framework/llm/assets/` tree.
Do not introduce a reusable root-package subdirectory helper. The private
framework helper, if used, is an initialization mechanism for the existing
framework registry and does not belong in `assets`.
### Acceptance Criteria
- `assets/package.go` is the sole Go file beneath `assets/` and has the exact
minimal responsibility above.
- The two generic schemas exist only in `assets/generic/schemas/`.
- `RegisteredResponseSchemas`, lookup, schema identities, and schema content
digests behave exactly as before.
- The generic framework imports no D&D package or path.
- No unrelated D&D asset has moved.
### Validation
```sh
gofmt -w assets/package.go internal/framework/llm/schema_registry.go
go test ./assets ./internal/framework/llm
go vet ./assets ./internal/framework/llm
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 2: Centralize Shared D&D Prompts, Reconciliation Schema, And Profile
### Goal
Create the shared D&D asset foundation required by every later D&D migration,
including the single physical copy of shared prompt wording, the shared private
entity-reconciliation schema, and the built-in fallback profile.
### Changes
1. Extend the embed directive in `assets/package.go` to embed both `generic` and
`dnd`. Make no other root-package API change.
2. Move the seven files under
`internal/modules/dnd/shared/assets/prompts/` unchanged to
`assets/dnd/shared/prompts/`.
3. Update `internal/modules/dnd/shared/assets.go`:
- remove the local embed declaration and `embed` import;
- import the root `assets` package;
- obtain a scoped filesystem with
`fs.Sub(rootassets.FS(), "dnd/shared")` inside the shared package;
- update `sharedPromptPaths` from `assets/prompts/<name>` to
`prompts/<name>`;
- continue resolving only the names explicitly declared by a
`PromptAssetManifest`; and
- retain the existing validation, virtual `sharedassets` mount names,
defensive copying, ordering, and manifest-scoped hashing.
Propagate subtree errors through the existing `PromptFS` and `Hash` error
paths. Do not panic for D&D module asset preparation and do not move the
manifest implementation into the root package.
4. Update the shared asset tests to obtain the scoped shared filesystem through
the shared package's private mechanism instead of referring to the removed
`embeddedAssets`. Retain the behavioral coverage for missing files, unknown
names, exact declared parts, and deterministic hashing without asserting the
repository tree or prompt prose.
5. Move
`internal/modules/dnd/shared/entityreconcile/assets/schemas/dnd_entity_reconcile_llm.v1.json`
unchanged to
`assets/dnd/entity-reconciliation/schemas/dnd_entity_reconcile_llm.v1.json`.
In `entityreconcile/schema.go`, scope the root filesystem to
`dnd/entity-reconciliation`, change `SchemaAssetPath` to
`schemas/dnd_entity_reconcile_llm.v1.json`, and use the same scoped
filesystem for loading and registration. Register the `schemas` root so the
flattened filename remains `dnd_entity_reconcile_llm.v1.json`. Delete the
obsolete `entityreconcile/assets.go`.
6. Move
`internal/modules/dnd/register/assets/profiles/dnd-extraction.yaml`
unchanged to `assets/dnd/profiles/dnd-extraction.yaml`. Update
`register/profiles.go` to scope `rootassets.FS()` to `dnd/profiles` and
register that filesystem at `.`. Preserve the profile's virtual filename,
ID, contents, fallback precedence, and digest behavior.
7. Remove the now-empty local shared/profile asset directories. Do not touch
module-local prompt or schema assets in this stage.
### Acceptance Criteria
- Every shared D&D prompt fragment has one physical source under
`assets/dnd/shared/prompts/`.
- Existing D&D manifests still select shared files explicitly and mount them
at the same virtual names.
- Entity reconciliation loads and registers the same private schema identity
and bytes.
- The `dnd-extraction` fallback profile resolves with unchanged effective
content and precedence.
- Full D&D registration succeeds while the remaining modules still consume
their package-local assets.
- `assets/package.go` remains the only Go file in the root package.
### Validation
```sh
gofmt -w assets/package.go internal/modules/dnd/shared/assets.go internal/modules/dnd/shared/entityreconcile/schema.go internal/modules/dnd/register/profiles.go
go test ./assets ./internal/framework/llm ./internal/modules/dnd/shared/... ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 3: Migrate Scene Planning And Scene Descriptions
### Goal
Move the two scene-oriented prompt/schema sets and establish the repeatable
module-consumer pattern used in subsequent stages.
### Changes
Move these directories without editing asset contents:
| Current source | Target |
| --- | --- |
| `internal/modules/dnd/chunk/scenes/assets/prompts/` | `assets/dnd/scenes/prompts/` |
| `internal/modules/dnd/chunk/scenes/assets/schemas/` | `assets/dnd/scenes/schemas/` |
| `internal/modules/dnd/extract/scenedescriptions/assets/prompts/` | `assets/dnd/scene-descriptions/prompts/` |
| `internal/modules/dnd/extract/scenedescriptions/assets/schemas/` | `assets/dnd/scene-descriptions/schemas/` |
For both consuming packages:
1. Delete the package-local `assets.go` embed file.
2. In `prompt_assets.go`, add a private `moduleAssetFS() (fs.FS, error)` that
scopes the root filesystem to the package's exact target subtree.
3. Change manifest source paths from `assets/prompts/<file>` to
`prompts/<file>`. Do not change `ModuleDir`, the declared file order, shared
file order, or `promptAssetRoot`.
4. Resolve the scoped filesystem in `RegisterPromptAssets`, pass it to the
existing manifest, and register its `schemas` root.
5. Resolve the same scoped filesystem in the synchronized prompt fingerprint
initializer and hash only the manifest's declared assets.
6. Update the response-schema loader to use the scoped filesystem and
`schemas/<filename>`, preserving all identity constants.
7. Update focused tests that directly used `embeddedAssets` or old source paths
to use `moduleAssetFS`. Preserve tests of prepared inputs, message placement,
cache control, diagnostics redaction, schema compatibility, and
registration.
Keep scene planning in the chunk module and scene-description semantics in the
extractor; the common asset location must not merge their behavior.
### Acceptance Criteria
- Both scene asset sets exist only in their target root directories.
- Both packages use scoped filesystems and retain their own manifests, schema
definitions, registration, and fingerprints.
- Prepared prompt IDs, rendered messages, schema identities, and PromptKit
virtual filenames are unchanged.
- Scene chunking and scene-description extraction tests pass, including D&D
family registration.
### Validation
```sh
gofmt -w internal/modules/dnd/chunk/scenes/*.go internal/modules/dnd/extract/scenedescriptions/*.go
go test ./internal/modules/dnd/chunk/scenes ./internal/modules/dnd/extract/scenedescriptions ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 4: Migrate The NPC Prompt Family
### Goal
Centralize NPC extraction, NPC normalization, and NPC-interaction assets while
preserving their distinct module behavior and shared-prefix contracts.
### Changes
Move these directories unchanged:
| Current source | Target |
| --- | --- |
| `internal/modules/dnd/extract/npcs/assets/prompts/` | `assets/dnd/npcs/extract/prompts/` |
| `internal/modules/dnd/extract/npcs/assets/schemas/` | `assets/dnd/npcs/extract/schemas/` |
| `internal/modules/dnd/normalize/npcs/assets/prompts/` | `assets/dnd/npcs/normalize/prompts/` |
| `internal/modules/dnd/extract/npcinteractions/assets/prompts/` | `assets/dnd/npc-interactions/prompts/` |
| `internal/modules/dnd/extract/npcinteractions/assets/schemas/` | `assets/dnd/npc-interactions/schemas/` |
Apply the module-consumer pattern established in Stage 3 to all three packages:
- delete each local embed-only `assets.go`;
- add a private scoped filesystem accessor in the owning `prompt_assets.go`;
- make manifest and response-schema paths relative to the scoped subtree;
- retain virtual prompt roots, module directories, IDs, manifest ordering, and
shared file selection;
- use the scoped filesystem for registration, schema loading, and
manifest-scoped fingerprinting; and
- update focused tests only for the filesystem/path move.
NPC normalization has no module-private response schema; it must continue using
the shared entity-reconciliation schema migrated in Stage 2. NPC extraction and
NPC interactions retain their own private response schemas. Do not combine the
three manifests or add NPC behavior to the root package.
### Acceptance Criteria
- NPC extraction, normalization, and interaction assets exist only in the
target directories.
- The modules retain three independent manifests and their existing semantic
responsibilities.
- NPC normalization still registers and loads the shared reconciliation
schema rather than a copied schema.
- Extraction prompt prefix order, normalization ordering, generated-reference
grounding, cache controls, schema identities, and diagnostics behavior are
unchanged.
- Focused and family registration tests pass.
### Validation
```sh
gofmt -w internal/modules/dnd/extract/npcs/*.go internal/modules/dnd/normalize/npcs/*.go internal/modules/dnd/extract/npcinteractions/*.go
go test ./internal/modules/dnd/extract/npcs ./internal/modules/dnd/normalize/npcs ./internal/modules/dnd/extract/npcinteractions ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 5: Migrate The Location Prompt Family
### Goal
Centralize location extraction, location normalization, and
location-occurrence assets while preserving registry grounding and shared
entity-reconciliation behavior.
### Changes
Move these directories unchanged:
| Current source | Target |
| --- | --- |
| `internal/modules/dnd/extract/locations/assets/prompts/` | `assets/dnd/locations/extract/prompts/` |
| `internal/modules/dnd/extract/locations/assets/schemas/` | `assets/dnd/locations/extract/schemas/` |
| `internal/modules/dnd/normalize/locations/assets/prompts/` | `assets/dnd/locations/normalize/prompts/` |
| `internal/modules/dnd/extract/locationoccurrences/assets/prompts/` | `assets/dnd/location-occurrences/prompts/` |
| `internal/modules/dnd/extract/locationoccurrences/assets/schemas/` | `assets/dnd/location-occurrences/schemas/` |
Apply the Stage 3 module-consumer pattern to all three packages. In particular:
- delete local embed-only files;
- scope each package to only its target subtree;
- update manifest and schema source paths without altering identities or
virtual PromptKit paths;
- keep fingerprints limited to each manifest's module and selected shared
assets; and
- update focused tests that refer to the old filesystem.
Location normalization continues to use the shared entity-reconciliation
schema. Location occurrence extraction retains its own private response schema
and its existing required location-registry input. Do not conflate the
locations registry artifact with occurrence events.
### Acceptance Criteria
- All three location-oriented asset sets exist only under their target root
directories.
- Extraction, normalization, and occurrence modules retain separate manifests,
schema ownership, prompt inputs, and fingerprints.
- Reconciliation and location-registry grounding behavior is unchanged.
- Prompt preparation, schema compatibility, normalization, reference handoff,
and family registration tests pass.
### Validation
```sh
gofmt -w internal/modules/dnd/extract/locations/*.go internal/modules/dnd/normalize/locations/*.go internal/modules/dnd/extract/locationoccurrences/*.go
go test ./internal/modules/dnd/extract/locations ./internal/modules/dnd/normalize/locations ./internal/modules/dnd/extract/locationoccurrences ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 6: Migrate Spell And Combat-Turn Assets
### Goal
Move the spell and combat-turn LLM assets while preserving their distinct
catalog, NPC-grounding, and combat-scene eligibility behavior.
### Changes
Move these directories unchanged:
| Current source | Target |
| --- | --- |
| `internal/modules/dnd/extract/spells/assets/prompts/` | `assets/dnd/spells/prompts/` |
| `internal/modules/dnd/extract/spells/assets/schemas/` | `assets/dnd/spells/schemas/` |
| `internal/modules/dnd/extract/combatturns/assets/prompts/` | `assets/dnd/combat-turns/prompts/` |
| `internal/modules/dnd/extract/combatturns/assets/schemas/` | `assets/dnd/combat-turns/schemas/` |
Apply the Stage 3 module-consumer pattern to both extractors. Preserve the
spell manifest's catalog fragment and optional NPC registry placement. Preserve
combat-turn routing so the LLM runs only for exact combat scene
classifications; this refactor must not move eligibility logic into the assets
package or prompt declaration.
Do not move or modify
`internal/modules/dnd/spells/catalog/assets/dnd_5e_2014_srd_spells.json`; it is
domain reference data, not an LLM interaction asset.
### Acceptance Criteria
- Spell and combat-turn prompts and private response schemas exist only in the
new root subtrees.
- Both packages retain independent manifests, response-schema definitions,
registrations, and component fingerprints.
- Spell catalog overlay identity, NPC grounding, combat eligibility, prompt
message order, cache controls, schema identities, and diagnostics remain
unchanged.
- The spell catalog stays with its existing package.
- Focused and family registration tests pass.
### Validation
```sh
gofmt -w internal/modules/dnd/extract/spells/*.go internal/modules/dnd/extract/combatturns/*.go
go test ./internal/modules/dnd/extract/spells ./internal/modules/dnd/extract/combatturns ./internal/modules/dnd/spells/catalog ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 7: Migrate Item-Event And Enemy-Event Assets
### Goal
Complete the module asset migration with item-event and enemy-event extraction,
leaving every D&D LLM prompt and private response schema under the root asset
tree.
### Changes
Move these directories unchanged:
| Current source | Target |
| --- | --- |
| `internal/modules/dnd/extract/itemevents/assets/prompts/` | `assets/dnd/item-events/prompts/` |
| `internal/modules/dnd/extract/itemevents/assets/schemas/` | `assets/dnd/item-events/schemas/` |
| `internal/modules/dnd/extract/enemyevents/assets/prompts/` | `assets/dnd/enemy-events/prompts/` |
| `internal/modules/dnd/extract/enemyevents/assets/schemas/` | `assets/dnd/enemy-events/schemas/` |
Apply the Stage 3 module-consumer pattern to both packages. Preserve the
enemy-event grounding fragment, required generated references, combat-scene
eligibility, and observation semantics. Preserve item-event categories and
prompt structure. Do not change deterministic normalizers or durable codecs.
### Acceptance Criteria
- Item-event and enemy-event LLM assets exist only in their target directories.
- Both extractors retain their module-owned manifests, schema definitions,
registrations, response interpretation, and fingerprints.
- Prompt preparation, reference grounding, scene gating, schema compatibility,
normalization, and family registration behavior is unchanged.
- No D&D prompt, private LLM schema, shared fragment, or built-in profile
remains beneath an `internal/modules/dnd/**/assets/` directory.
### Validation
```sh
gofmt -w internal/modules/dnd/extract/itemevents/*.go internal/modules/dnd/extract/enemyevents/*.go
go test ./internal/modules/dnd/extract/itemevents ./internal/modules/dnd/extract/enemyevents ./internal/modules/dnd/register
git diff --check
git diff --summary
```
This stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 8: Audit Asset Boundaries And Consolidate Behavioral Verification
### Goal
Review the completed physical migration as one system, remove obsolete
embedding surfaces, and ensure existing behavior-level tests protect the
meaningful asset contracts without introducing structural change detectors.
### Changes
1. Inventory every remaining `//go:embed` directive and every directory named
`assets` under `internal/`. Classify each remaining file against the feature
roadmap. Expected retained local embeds include durable codec/framework
schemas and the spell catalog; no migrated LLM interaction asset may remain.
2. Confirm all prompt-bearing consumers import the root package, scope it with
`fs.Sub`, and expose no root filesystem beyond their package.
3. Confirm every old embed-only module `assets.go` has been deleted. Keep files
named `assets.go` that still own actual manifest behavior, such as the D&D
shared implementation; file names alone are not grounds for deletion.
4. Inspect all `PromptAssetManifest` values and response-schema definitions:
- source paths are relative to the correct scoped filesystem;
- virtual `ModuleDir`, prompt IDs, schema identities, and registration names
are unchanged;
- shared fragments remain explicitly enumerated in order; and
- hashes consume only the manifest's selected files.
5. Run and review the existing family-wide prompt-prefix, schema-compatibility,
profile, registration, and composition tests. Repair coverage only if a
meaningful behavior lost protection during the move. Prefer existing public
or package-level preparation boundaries; do not test the literal root tree
shape, exact prose, file counts, or private call sequences.
6. Verify that only `assets/package.go` is Go code in the root asset tree and
that its imports and declarations still satisfy the strict data-only rule.
Useful non-test inspection commands include:
```sh
rg -n '//go:embed' --glob '*.go' .
find internal -type f -path '*/assets/*' | sort
find assets -type f | sort
find assets -name '*.go' -print
go list -f '{{join .Imports "\n"}}' ./assets
```
These are manual audit checks, not assertions to encode in permanent tests.
### Acceptance Criteria
- The root tree contains every in-scope LLM asset and no out-of-scope durable
contract or domain-data asset.
- Old module-local LLM assets and embed declarations are gone.
- There is one physical copy of each shared prompt fragment.
- Modules and framework consumers access only scoped subtrees.
- PromptKit preparation, response schemas, fallback profiles, family
registration, and production composition remain operational.
- No low-value structural or prose change-detector test has been added.
### Validation
```sh
go test ./assets ./internal/framework/llm/... ./internal/modules/dnd/... ./internal/cli/...
go vet ./assets ./internal/framework/llm/... ./internal/modules/dnd/...
git diff --check
git diff --summary
```
This audit and focused remediation stage is sized for one gpt-5.6-terra
implementation prompt.
## Stage 9: Record The Decision And Update Canonical Documentation
### Goal
Document the implemented ownership boundary in its canonical homes without
duplicating volatile details or presenting the root package as a public
extension contract.
### Changes
1. Add `docs/adr/0011-centralize-llm-assets.md` in the policy-required Nygard
format. Record:
- why prompt-author discoverability now outweighs package-local physical
co-location;
- the data-only top-level package and single-filesystem API;
- continued module ownership of semantics and registration;
- scoped consumer access and dependency direction;
- exclusion of durable schemas and non-LLM domain data;
- the accepted public import path and one-time checkpoint invalidation;
- alternatives of package-local assets, `internal/llmassets`, a behavioral
central registry, and runtime filesystem overlays; and
- the exact portion of ADR-0004 that this decision supersedes.
2. Change only ADR-0004's status metadata to state that its asset-co-location
rule is superseded by ADR-0011 while its domain-first module packaging
decision remains accepted. Do not rewrite its accepted decision text.
3. Update `docs/policy/architecture.md` at the layer/domain and LLM boundaries:
- define the root package as a content-only dependency leaf;
- prohibit business logic and internal/PromptKit dependencies there;
- state that physical centralization does not transfer semantic ownership
from modules; and
- keep generic framework code domain-neutral even when it reads its own
scoped generic assets from the shared container.
4. Update `docs/internal/llm.md` to describe the implemented root filesystem,
scoped consumers, asset registry flattening, private schema distinction, and
manifest-scoped fingerprints. Link to architecture and the ADR instead of
repeating their rationale.
5. Update `docs/internal/dnd.md` to document the convention for locating new
D&D LLM assets, shared-fragment ownership, extract/normalize subtrees, and
module-owned manifests. Preserve its existing canonical prompt-order and
caching guidance.
6. Update `docs/internal/overview.md` with a concise current component entry for
`assets/` and links to the focused LLM/D&D documents. Do not duplicate the
full directory tree.
7. Search current documentation for claims that LLM assets are physically
package-local and revise only the canonical owners. Do not change user,
operator, configuration, or integration contracts unless inspection finds a
directly false statement caused by this internal-only move.
### Acceptance Criteria
- ADR-0011 records the durable decision and ADR-0004's status identifies the
limited supersession without altering its historical decision.
- Architecture contains the normative data-only boundary.
- Internal documentation accurately describes the implemented mechanics and
convention for future modules.
- No document outside `docs/roadmap/` describes unimplemented behavior.
- Documentation does not duplicate prompt contents, schema definitions,
module inventories, or volatile asset path lists unnecessarily.
- Relative links resolve to existing files.
### Validation
```sh
rg -n 'package-specific assets|module-owned assets|internal/modules/dnd/.*/assets|internal/framework/llm/assets' README.md docs
git diff --check
go test ./internal/framework/llm/... ./internal/modules/dnd/...
```
Manually verify changed Markdown links because the repository currently has no
dedicated documentation link checker.
This documentation stage is sized for one gpt-5.6-terra implementation prompt.
## Stage 10: Final Repository Verification
### Goal
Perform a clean final review of the complete migration, correct any remaining
in-scope defect, and demonstrate that the repository builds and tests as a
coherent whole.
### Changes
1. Re-read the feature roadmap and inspect the complete diff from the base
commit, not only the most recent stage.
2. Confirm every target-tree entry is present and every excluded asset remains
with its original owner.
3. Confirm moved prompt, schema, and profile files have unchanged content.
4. Confirm `assets/package.go` is still the sole Go file under `assets`, exposes
only `FS() fs.FS`, and imports only `embed` and `io/fs`.
5. Confirm no consumer uses an unrelated subtree and no generic package names a
D&D asset path.
6. Confirm all old local LLM embed declarations, files, and directories are
gone, without deleting retained durable schemas or the spell catalog.
7. Review test changes for compliance with the testing policy and remove any
redundant structural or exact-content assertion introduced during the
migration.
8. Review documentation changes for canonical ownership, current-state
wording, correct links, and absence of duplicated volatile content.
9. Run formatting and repository-wide validation. Fix only defects within this
roadmap's scope; report unrelated pre-existing failures separately.
### Acceptance Criteria
- Every target-end-state and verification expectation in `assets.md` is met.
- The complete diff contains no prompt, schema, profile, module key, profile
default, durable contract, or pipeline behavior change beyond source
relocation and the accepted internal fingerprint invalidation.
- The working tree contains no accidental generated files or obsolete empty
asset directories.
- All repository-wide checks pass.
### Validation
```sh
go fmt ./...
go test ./...
go vet ./...
go build ./cmd/notarius
git diff --check
git status --short
```
Review `git diff --stat`, `git diff --summary`, and the full diff before
reporting completion. Do not delete `docs/roadmap/assets.md` or
`docs/roadmap/implementation.md` in this stage.
This final verification stage is sized for one gpt-5.6-terra implementation
prompt.
## Open Questions
None. The feature roadmap and this plan resolve the package API, target tree,
asset scope, consumer pattern, fingerprint policy, documentation ownership,
and accepted compatibility consequences needed for implementation.