- Add PATCH /api/v1/identity/:identity_uuid endpoint - Migration 030: remove name UNIQUE, add tmdb_id index - TMDb upsert: ON CONFLICT (name) -> ON CONFLICT (tmdb_id) - get_or_create_identity: pre-check by name - upload_identity: ON CONFLICT (name) -> ON CONFLICT (uuid) - Search: include aliases in identity text search - Add scripts/llm_metadata_enhancer.py - Add DESIGN/IdentityUpdateAndAliasSystem.md
6.1 KiB
6.1 KiB
document_type, service, title, version, date, author, status
| document_type | service | title | version | date | author | status |
|---|---|---|---|---|---|---|
| design_doc | MOMENTRY_CORE | Identity Update & Alias System | V1.0 | 2026-05-22 | M5 | 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:
PATCH /api/v1/identity/:identity_uuid— partial update for name, metadata, aliases, status, type- Remove
name UNIQUEconstraint — allow multiple identities with the same display name - TMDb upsert key changes from
nametotmdb_id - Alias system with BCP 47 locale tagging stored in
metadata.aliases - LLM background task for metadata structuring and alias generation
Schema Changes
Migration 030
File: migrations/030_remove_identity_name_unique.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:
{
"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:
{
"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):
{
"success": true,
"identity_uuid": "abc-...",
"updated_fields": ["name", "aliases", "metadata"]
}
Processing flow:
- Lookup identity by UUID → 404 if not found
- Dynamic UPDATE SQL (COALESCE for optional fields)
- If name changed → update
_index.json - If name changed → update Qdrant face point payloads
- Call
save_identity_file_by_pool()→ sync identity.json to disk - 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
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:
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:
{
"name": "John Smith",
"biography": "John Smith (born January 15, 1970) is an American actor...",
"existing_metadata": {"tmdb_id": 123, "tmdb_profile": "..."}
}
Output:
{
"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:
- Read biography text + identity name
- Extract structured fields: summary, nationality, birth_date, profession
- Generate locale-tagged aliases based on known translations
- 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 |