--- document_type: "design_doc" service: "MOMENTRY_CORE" title: "Identity Update & Alias System" version: "V1.0" date: "2026-05-22" author: "M5" status: "draft" --- # Identity Update & Alias System | Item | Value | |------|-------| | Scope | Identity CRUD expansion, alias system, LLM-enhanced metadata | | Status | Draft | | Key principle | `uuid` is the true identity key; `name` is display label (no longer UNIQUE) | --- ## Overview Currently, identity records have no update endpoint and `name` is constrained to UNIQUE. This design adds: 1. `PATCH /api/v1/identity/:identity_uuid` — partial update for name, metadata, aliases, status, type 2. Remove `name UNIQUE` constraint — allow multiple identities with the same display name 3. TMDb upsert key changes from `name` to `tmdb_id` 4. Alias system with BCP 47 locale tagging stored in `metadata.aliases` 5. LLM background task for metadata structuring and alias generation --- ## Schema Changes ### Migration 030 File: `migrations/030_remove_identity_name_unique.sql` ```sql -- Phase 1: Remove name UNIQUE (keep NOT NULL) ALTER TABLE identities DROP CONSTRAINT IF EXISTS identities_name_key; -- Phase 2: Partial unique index for TMDb-sourced identities CREATE UNIQUE INDEX IF NOT EXISTS idx_identities_tmdb_id ON identities(tmdb_id) WHERE tmdb_id IS NOT NULL; ``` ### Aliases Storage Aliases are stored in `identities.metadata::jsonb` under the key `aliases`: ```json { "aliases": [ {"locale": "en", "name": "John Smith"}, {"locale": "zh-TW", "name": "約翰·史密斯"}, {"locale": "ja", "name": "ジョン・スミス"} ], "summary": "American actor and producer...", "nationality": "American", "birth_date": "1970-01-15", "profession": ["actor", "producer"] } ``` No schema change needed — `metadata JSONB DEFAULT '{}'` already supports this. --- ## API Changes ### PATCH /api/v1/identity/:identity_uuid **Request:** ```json { "name": "John Smith", "aliases": [ {"locale": "en", "name": "John Smith"}, {"locale": "zh-TW", "name": "約翰·史密斯"} ], "metadata": {"summary": "American actor..."}, "status": "confirmed", "identity_type": "people" } ``` All fields are optional. Only provided fields are updated. **Response (200):** ```json { "success": true, "identity_uuid": "abc-...", "updated_fields": ["name", "aliases", "metadata"] } ``` **Processing flow:** 1. Lookup identity by UUID → 404 if not found 2. Dynamic UPDATE SQL (COALESCE for optional fields) 3. If name changed → update `_index.json` 4. If name changed → update Qdrant face point payloads 5. Call `save_identity_file_by_pool()` → sync identity.json to disk 6. Return updated identity detail --- ## Alias System ### Locale Tagging (BCP 47) Standard tags for alias entries: | Locale | Tag | Example | |--------|-----|---------| | English | `en` | John Smith | | Traditional Chinese | `zh-TW` | 約翰·史密斯 | | Simplified Chinese | `zh-CN` | 约翰·史密斯 | | Japanese | `ja` | ジョン・スミス | | Korean | `ko` | 존 스미스 | | Cantonese | `yue` | 約翰·史密夫 | | French | `fr` | Jean Smith | | Spanish | `es` | Juan Smith | ### Frontend Display Logic ```javascript function getDisplayName(identity, preferredLocale) { // 1. Try exact locale match const match = identity.aliases?.find(a => a.locale === preferredLocale); if (match) return match.name; // 2. Try language-only match (zh-TW → zh) const lang = preferredLocale.split('-')[0]; const langMatch = identity.aliases?.find(a => a.locale.startsWith(lang)); if (langMatch) return langMatch.name; // 3. Fallback to identity.name return identity.name; } ``` ### Search Integration Identity search should include aliases: ```sql SELECT * FROM identities WHERE name ILIKE $1 OR metadata->'aliases' @> $2::jsonb ORDER BY name ``` --- ## Upsert Key Changes | Location | Current Key | New Key | Rationale | |----------|------------|---------|-----------| | `tmdb/probe.rs` | `ON CONFLICT (name)` | `ON CONFLICT (tmdb_id) WHERE tmdb_id IS NOT NULL` | TMDb ID is the true identity for TMDb sources | | `tmdb/ingest.rs` | `ON CONFLICT (name)` | `ON CONFLICT (tmdb_id)` | Same as above | | `postgres_db.rs:get_or_create_identity` | `ON CONFLICT (name) DO UPDATE` | Query by name first, then INSERT with uuid | Maintain backward compatibility for name lookup | | `identity_api.rs:upload_identity` | `ON CONFLICT (name)` | `ON CONFLICT (uuid)` | Upload provides uuid; uuid is the true key | --- ## LLM Metadata Enhancer ### Script: `scripts/llm_metadata_enhancer.py` **Trigger:** Called as background task after TMDb registration or manual PATCH. **Input:** ```json { "name": "John Smith", "biography": "John Smith (born January 15, 1970) is an American actor...", "existing_metadata": {"tmdb_id": 123, "tmdb_profile": "..."} } ``` **Output:** ```json { "metadata": { "summary": "American actor and producer known for...", "nationality": "American", "birth_date": "1970-01-15", "profession": ["actor", "producer"], "aliases": [ {"locale": "en", "name": "John Smith"}, {"locale": "zh-TW", "name": "約翰·史密斯"}, {"locale": "ja", "name": "ジョン・スミス"} ] } } ``` ### LLM Prompt Design The prompt defines a fixed output schema for the LLM to follow: 1. Read biography text + identity name 2. Extract structured fields: summary, nationality, birth_date, profession 3. Generate locale-tagged aliases based on known translations 4. Output as JSON only (no extra text) --- ## Affected Files | File | Change | Complexity | |------|--------|------------| | `migrations/030_remove_identity_name_unique.sql` | New | Low | | `src/api/identity_api.rs` | Add PATCH route + handler | Medium | | `src/core/tmdb/probe.rs` | Change upsert key | Low | | `src/core/db/postgres_db.rs` | Change get_or_create_identity | Low | | `src/api/identity_api.rs` (upload) | Change upsert key | Low | | `scripts/llm_metadata_enhancer.py` | New script | Medium | | `src/api/tmdb_api.rs` or background | LLM task integration | Low | --- ## Version History | Version | Date | Author | Description | |---------|------|--------|-------------| | V1.0 | 2026-05-22 | M5 | Initial design |