Asset Catalog Ownership and Runtime Strategy
Purpose
Define ownership boundaries and shared mechanics for campaign covers and avatars before expanding image support to users, participants, and characters.
This document separates:
- Entity ownership (which service owns each record).
- Asset mechanics (catalog validation, deterministic default assignment, URL resolution).
Ownership Boundaries
The owning domain service stores the image reference fields for its entities.
| Entity | Owning service | Reason |
|---|---|---|
| Campaign cover | Game service | Campaign is a game-domain aggregate. |
| Participant avatar | Game service | Participant membership and seat state are game-domain concerns. |
| Character avatar | Game service | Character identity and control state are game-domain concerns. |
| User avatar | Auth service | User profile metadata is an auth/account concern. |
Cross-service writes are out of scope. Shared logic must be imported, not called via write APIs across service boundaries.
Shared Catalog Contract
A shared asset-catalog package will be used by both game and auth services. It must provide:
- Set and asset ID definitions.
- ID validation and normalization.
- Alias resolution to canonical IDs.
- Deterministic default assignment.
- URL resolution from canonical IDs plus runtime config.
The shared package does not own domain records or persistence.
Repository Source Files
Checked-in runtime asset metadata lives under internal/platform/assets/catalog/data/: campaign_covers.v1.json and avatars.v1.json define shared selection behavior, cloudinary_assets.high_fantasy.v1.json is the embedded CDN lookup catalog, and daggerheart_assets.v1.json is the published Daggerheart entity-to-asset manifest.
Root-level export files are temporary import inputs only. When a new export is dropped at repo root, promote its relevant data into the checked-in files above and remove the root copy in the same change.
System Content Asset Mapping
For game-system content (for example Daggerheart class/domain/adversary imagery), the shared catalog package also owns:
- A system-scoped manifest (
daggerheart_assets.v1.json) with:- system metadata (
system_id,system_version,locale,theme), - set definitions by asset family,
entity_asset_mapbindings from canonical content IDs to set/asset selectors.
- system metadata (
- Runtime resolution statuses for consumers:
mapped: explicit entity mapping with a deliverable CDN asset,set_default: deterministic set fallback with a deliverable CDN asset,unavailable: no deliverable asset currently available.
- Additive API exposure through a separate content-asset map RPC so content contracts and image contracts evolve independently.
- Daggerheart canonical selectors use catalog IDs themselves (for example
class.bardordomain_card.book-of-ava); provider slugs remain delivery metadata only.
Data Model Contract
Store both set and asset identifiers for all image-bearing entities.
| Entity field pair | Purpose |
|---|---|
cover_set_id, cover_asset_id | Campaign cover identity. |
avatar_set_id, avatar_asset_id | Participant avatar identity. |
avatar_set_id, avatar_asset_id | Character avatar identity. |
avatar_set_id, avatar_asset_id | User account avatar identity. |
Rationale:
- Keeps future set-switching explicit.
- Avoids deriving set membership from asset IDs later.
- Simplifies validation and migration.
Deterministic Default Assignment
Default assignment must be deterministic and replay-safe.
Inputs:
entity_type(for examplecampaign,participant,character,user).entity_id.set_id.- Algorithm version label (for example
asset-default-v1).
Behavior:
- Build a stable input string from these fields.
- Hash the input with a fixed algorithm.
- Select from a stable ordered asset list in the set using modulo.
- Persist selected canonical set/asset IDs.
No runtime non-deterministic random selection is allowed for defaults.
Runtime Asset Hosting
Binary assets should not be stored in repo-embedded static bundles for large catalogs.
Required runtime model:
- Host image binaries in object storage.
- Serve through CDN.
- Keep only compact metadata manifest in repo.
URL contract:
- Key format is immutable and versioned (for example
v1/avatars/{set_id}/{asset_id}.webp). - Services generate URLs from
ASSET_BASE_URLplus manifest key data. - Rebuild is not required when only base URL or CDN origin changes.
Alias and Compatibility Policy
Catalog entries may include aliases for backwards compatibility.
Rules:
- Persist canonical IDs only after normalization.
- Accept aliases at read/update boundaries during migration windows.
- Keep explicit alias mapping until old IDs are removed from stored data.
- Typo IDs are not preserved as aliases; stored values must use canonical IDs only.
Migration and Rollout Order
- Add schema fields and proto fields additively.
- Deploy code that can read both old and new shapes.
- Publish manifest version and object-store keys.
- Enable new writes for set-aware fields.
- Backfill legacy rows where needed.
- Remove temporary fallback paths only after validation period.
Non-Goals for This Phase
- User-driven set switching UI.
- Uploading custom images.
- Cross-service orchestration for profile writes.
Operational Expectations
- Rollback must be possible by manifest version and runtime config.
- Missing asset keys should fail visibly in logs and degrade to known default IDs.
- Coverage/tests must include validation, alias normalization, deterministic assignment, and URL generation.