Identity 字段速查
系统里 8 个 identity 字段的身份证 + crosswalk 表。新同事 onboarding 必读。
历史与教学: 字段为什么是这样的、
siteIdgenesis wiring error、franchise 3-way split 故事 →docs/archive/identity-naming-history.md
TL;DR
1 句话: 我们的 identity 字段混乱不是因为改名太多,是因为 genesis commit 就 wire 错了 — siteId 当初 docstring 写的是 "Store ID",但代码实际绑到 RC Account ID,后面所有"rename"讨论都是在清理这个原始错误(2026-04-25 完成清理,canonical 名 = account_id)。
5 个真相:
account_id+providerAccountId+_account_id= 同一个值(RC OAuth Account ID, e.g.,"193365026"),在不同 repo / layer 叫不同名字client_id="{franchise}-{account_id}"派生组合(not independent identity),但是 DDBCallAnalysisConfigurationsPK,仍然活跃使用store_id= 我们自己randomUUID()生成的 UUID,唯一真隔离键franchise_id当前硬编码"orangeTheory"— 不是隔离键,是审计字段userId= Cognito JWTsub,唯一真身份键,串起 DDB 所有身份表
1. 8 个字段身份证
每个字段问 3 个问题: 它是什么 / 它现在叫什么 / 它以前叫过什么。
1.1 userId(Cognito sub)
关键认知: Neon 完全不存 userId — Neon 只存业务数据(按 store_id 隔离),身份解析在 DDB 做。
1.2 store_id / storeId(唯一真隔离键)⭐
关键认知: store_id 是我们控制的 UUID,不依赖任何 RC / Cognito 外部系统。这是为什么它能做终态隔离键。
1.3 account_id / accountId / providerAccountId / _account_id(canonical 名,历史曾叫 site_id / siteId)
别名对照表:
::: details rename 历史
2026-03-14 ~ 2026-04-25 期间存在 site_id + account_id 双列共存的中间态。infra#499 这个 P2 TODO 在 2026-04-23~04-25 通过 common PR #77/#78/#79 + infra PR #668/#669/#671/#676 完成 sweep,公布为 callytics-common 0.22.0。
6 表 rename(migration 0016): contacts / leads / tasks / task_events / staff / contact_timeline 的 site_id 列改名为 account_id。
calls + messages 例外: 这两表保留 legacySiteId: text('site_id') 列(标 legacy 但 schema 仍存在)。新代码不应再写这两个 legacy 列,只写 accountId / account_id。
详见 docs/archive/identity-naming-history.md §2 Genesis story。
:::
1.4 franchise_id / franchiseId / franchise(品牌名)
写入规则: 永远写 parseFranchiseFromClientId(clientId) 拆出来,不要直接写 _client_id(2026-04 踩过坑,见 archive history §3.2)。
1.5 client_id / clientId / _client_id(派生组合,不是独立层)
⚠️ 常见误解:
- ❌ "client = 一个客户" → 错。这里 "client" 是 RC OAuth client 概念,不是终端客户
- ❌ "client_id 是 franchise_id 的别名" → 错。client_id 包含 franchise+site 两部分
- ✅ 正确理解: client_id 是派生的便利字段,用来做 config lookup
1.6 connection_id / connectionId
关键认知: connection_id 是 DDB-only 概念。Neon 根本不存,因为 Neon 只关心"业务数据"(按 store_id 隔离),不关心"哪次 OAuth 连的"。
1.7 Phone 相关字段(4 个 variants,语义不同)
⚠️ 格式约定: 所有 phone 必须 E.164 格式(+1XXXXXXXXXX)。不 normalize 会错 query。详见 phone-normalization-strategy.md。
1.8 extensionId / extensionName(RC 分机号)
关键认知: extensionId 和 account_id 都是纯数字字符串,长得像,但语义完全不同。读代码时看上下文区分。
2. 命名 Crosswalk(同一值在不同地方叫什么)
3. 一次通话穿过所有 identity 字段
4. 遇到 identity 字段的自检清单
不要猜,按这 5 步自查:
-
先 Read schema 文件:
callytics-common/src/db/schema/*.ts(Neon)callytics-infrastructure/lib/stacks/storage-stack.ts(DDB)- CDK 里的
KeySchema+AttributeDefinitions
-
看列是 NOT NULL 还是 nullable — NOT NULL 意味着 Lambda 必须写,nullable 意味着 rollout 中或历史残留
-
grep Lambda 代码 — 这个字段当前谁写、谁读:
-
查 issue tracker — 这个字段有无 ongoing rename / migration
-
查 memory / archive history — 有无相关 pitfall 或 genesis 故事
::: details 命名 DO/DON'T
详见 docs/archive/identity-naming-history.md §8。要点:
- ✅ UUID 优先于数字字符串(避免
account_id/extensionId长得像) - ✅ Provider-agnostic naming(
store_id不叫rcStoreId) - ✅ Docstring 就是合同 — genesis commit 写错就埋雷
- ❌ 不要直接存 composite 值作 identity key(必须 parse)
- ❌ 不要在 2 个 repo 用 2 个不同 name 代表同一字段
:::
5. 快速查询 cheatsheet
Maintenance: 字段改名 / rename 完成时更新 §1 字段身份证 + §2 crosswalk。历史叙事(genesis 故事 / rename timeline / rationale)写入 docs/archive/identity-naming-history.md。