> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# DynamoDB 表关系图

::: details Audit 历史

- 2026-04-26: 9 个 CDK stack deployed(StorageStack / SqsStack / IamStack / LambdaStack / CognitoStack / ContactsStack / MonitoringStack / EventBridgeStack / MessageSystemStack + 条件性 GitHubOidcStack)
- 2026-04-23: deep audit(AWS CLI 实查 + CDK 代码交叉验证)
- 2026-03-02: 上次全量 audit
- `call-analysis` / `call-events` / `LeadTracking-v2` 仍是 DDB-primary;`calls` / `messages` / `leads` 已切换到 **Neon-primary read**(DDB 表保留作 backward-compat)

:::

| 状态        | 含义                          | 行动                                                                    |
| --------- | --------------------------- | --------------------------------------------------------------------- |
| 🟢 ACTIVE | 有活跃代码引用，正在生产使用              | 正常维护                                                                  |
| 🟡 LEGACY | 功能已被新表替代，但仍有残留代码引用          | 排查引用后迁移                                                               |
| 🔴 DEAD   | 零活跃代码引用，业务读写接近零（≤16 RCU/7d） | 列入 [Archive 候选](#archive)。archive 前必须 CloudTrail 追踪 2-16 RCU/7d 的实际来源 |

## Production (us-east-1) — 26 张表

全部 `PAY_PER_REQUEST`（按需计费）。16 个 CDK-managed + 6 个孤儿表 + 4 个 Legacy 表。

::: info 28 vs 26 — 为什么少了 2 张？
MessageStore 和 Conversations 已部署到 **test** (us-west-2)，尚未部署到 **prod**。部署后 prod 将有 28 张。

:::
**CDK Stack 所有权（5 stacks · 4 repos）**

| CDK Stack                            | Repo                             | 表数 | 表名前缀                          |
| ------------------------------------ | -------------------------------- | -- | ----------------------------- |
| `CallAnalytics-Prod-Storage`         | `callytics-infrastructure`       | 3  | `call-analytics-prod-*`       |
| `OrangeTheoryStack-prod`             | `studio-website-monorepo`        | 10 | `orangetheory-*` / `studio-*` |
| `RCSubscription-prod-Storage`        | `ringcentralSubscriptionService` | 2  | `rc-subscription-prod-*`      |
| `LeadTrackingStack`                  | `lead-tracking`                  | 1  | `LeadTracking-v2-*`           |
| _(已删除)_ `RingCentralOAuthStack-prod` | _(已归档)_ `ringcentralService`     | 6  | `ConnectionService-*-prod`    |

### Call Analytics — 通话分析 (3) <small>CDK: `CallAnalytics-Prod-Storage`</small>

::: info Neon-primary read 已切换 (2026-04-26)
`call-analysis` 表仍接收 ai-analysis-processor 的 dual-write（legacy 兼容），但 **Neon `calls` 已是 primary read source**。studio-api 查询通话数据走 Neon SQL（含 `store_id` filter）。`call-events` 仍 DDB-primary（无 Neon 镜像）。`call-analysis-config` 仍 DDB-only（per-client AI 配置，operational table，不迁 Neon）。

:::

| 表名                                                   | 状态 | PK                 | GSI | 说明                                                               |
| ---------------------------------------------------- | -- | ------------------ | --- | ---------------------------------------------------------------- |
| `call-analytics-prod-call-analysis-us-east-1`        | 🟢 | telephonySessionId | 11  | AI 分析结果（最大表，**Neon `calls` 是 primary read，DDB 仅 dual-write 兼容**） |
| `call-analytics-prod-call-events-us-east-1`          | 🟢 | telephonySessionId | 2   | 原始通话事件（DDB-only，无 Neon 镜像）                                       |
| `call-analytics-prod-call-analysis-config-us-east-1` | 🟢 | client\_id         | 0   | 每客户分析配置（**operational，不迁 Neon**）                                 |

::: details call-analysis 11 个 GSI（全部 `ProjectionType: ALL`）

| GSI 名称                                                     | Partition Key        | Sort Key      | 用途          |
| ---------------------------------------------------------- | -------------------- | ------------- | ----------- |
| `franchise-callStartTime-index`                            | franchise            | callStartTime | 按品牌查通话      |
| `franchise-siteId-index`                                   | franchise            | siteId        | 按品牌+门店查     |
| `siteId-callStartTime-index`                               | siteId               | callStartTime | 按门店查通话      |
| `fromPhoneNumber-callStartTime-index`                      | fromPhoneNumber      | callStartTime | 按来电号码查      |
| `toPhoneNumber-callStartTime-index`                        | toPhoneNumber        | callStartTime | 按被叫号码查      |
| `callDirection-callStartTime-index`                        | callDirection        | callStartTime | 按方向查（呼入/呼出） |
| `staff_performance...name-callStartTime-index`             | staff...name         | callStartTime | 按员工姓名查      |
| `call_categorization...classification-callStartTime-index` | classification       | callStartTime | 按主分类查       |
| `call_categorization.follow_up_type-callStartTime-index`   | follow\_up\_type     | callStartTime | 按跟进类型查      |
| `call_outcome.outcome_category-callStartTime-index`        | outcome\_category    | callStartTime | 按结果类别查      |
| `transcriptionJobName-index`                               | transcriptionJobName | —             | 转录任务关联      |

**call-events** 有 2 个 GSI：`transcriptionJobName-index` 和 `sessionId-index`（RC session 关联）。

⚠ 所有 11 个 GSI 均 `ALL` projection，写入成本 12 倍。其中 3 个 GSI 记录数为 0，建议排查后删除或改为 `KEYS_ONLY`。**TODO**：2026-03-02 审计未记录具体是哪 3 个 GSI。候选（按 feature status 推断，未 verify）：`call_outcome.outcome_category-callStartTime-index` / `call_categorization.follow_up_type-callStartTime-index` / `staff_performance...name-callStartTime-index`。下次 audit 需 per-GSI `scan --projection-expression` 确认。

:::

### ConnectionService — 孤儿表 (6) <small>⚠ 全部 KMS + Deletion Protection</small>

原 `RingCentralOAuthStack-prod` CDK stack 于 2025-12-29 删除，表因 `RETAIN` + DP 保留。功能已被 `orangetheory-*` 的 user+store-centric 模型替代。主应用**无运行时依赖**（`USE_V2_CONFIG_LOADER=false`，code path short-circuit），但 `lib/stacks/lambda-stack.ts:440-447, 471-479` 仍 hardcode `CONFIG_TABLE_V2` / `ORG_TABLE_V2` / `CONN_TABLE_V2` 3 个 env vars 到 `AnalyticsGenerator` + `ForceRefresh` 2 个 Lambda 的 env（值含 `-test` 硬后缀，指向在 us-west-2 不存在的 phantom 表）——**未启用但未移除**。追踪见 [infra#658](https://github.com/retaintive/callytics-infrastructure/issues/658)。

::: details ConnectionService → orangetheory-\* 新旧表映射

| ConnectionService (老)              | orangetheory-\* (新)                     | 说明                |
| ---------------------------------- | --------------------------------------- | ----------------- |
| `Organizations` (orgId)            | `StoresV2` (storeId)                    | 数据围墙从 org → store |
| `Connections` (orgId+provider)     | `UserConnections` (userId+connectionId) | RC 连接改为按用户        |
| `UserOrganizations` (userId+orgId) | `UserStore` (userId+storeId)            | 成员权限改为按门店         |
| `OrgConfigurations` (orgId)        | `StoresV2.businessConfig`               | 配置合并到门店表          |
| `SubAccountLinks`                  | `SubAccounts`                           | 同样功能，不同 schema    |
| `CallAnalysisConfigurations`       | `call-analysis-config`                  | 直接替代（CDK 管理）      |

:::

| 表名                                                  | 状态 | PK           | SK          | GSI | Streams         | 说明                                         |
| --------------------------------------------------- | -- | ------------ | ----------- | --- | --------------- | ------------------------------------------ |
| `ConnectionService-CallAnalysisConfigurations-prod` | 🟢 | client\_id   | —           | 0   | ❌               | Legacy Lambda 仍在读（RCU \~450/7d）            |
| `ConnectionService-Organizations-prod`              | 🟡 | orgId        | —           | 0   | ✅ NEW\_AND\_OLD | V2 loader 存在但 `USE_V2_CONFIG_LOADER=false` |
| `ConnectionService-Connections-prod`                | 🟡 | orgId        | provider    | 1   | ✅ NEW\_AND\_OLD | V2 loader 存在但未启用                           |
| `ConnectionService-OrgConfigurations-prod`          | 🟡 | orgId        | —           | 0   | ✅ NEW\_AND\_OLD | V2 loader 存在但未启用                           |
| `ConnectionService-UserOrganizations-prod`          | 🔴 | userId       | orgId       | 1   | ✅ NEW\_AND\_OLD | 零代码引用                                      |
| `ConnectionService-SubAccountLinks-prod`            | 🔴 | parentUserId | childUserId | 1   | ✅ NEW\_AND\_OLD | 零代码引用                                      |

### Phone Management — 电话管理 (3) <small>CDK: `OrangeTheoryStack-prod`</small>

| 表名                                        | 状态 | PK                | SK           | GSI | 说明                  |
| ----------------------------------------- | -- | ----------------- | ------------ | --- | ------------------- |
| `orangetheory-UserConnections-prod`       | 🟢 | userId            | connectionId | 4   | RC OAuth 连接（KMS 加密） |
| `orangetheory-PhoneNumbers-prod`          | 🟢 | providerAccountId | phoneNumber  | 2   | RC 电话线路与分机          |
| `orangetheory-PhoneStoreAssignments-prod` | 🟢 | storeId           | phoneNumber  | 1   | 电话→门店映射             |

### Store & Operations — 门店运营 (7) <small>CDK: `OrangeTheoryStack-prod`</small>

| 表名                              | 状态 | PK          | SK         | GSI | 说明                                             |
| ------------------------------- | -- | ----------- | ---------- | --- | ---------------------------------------------- |
| `orangetheory-StoresV2-prod`    | 🟢 | storeId     | —          | 1   | 门店信息（GSI: userId-index）                        |
| `orangetheory-UserStore-prod`   | 🟢 | userId      | storeId    | 1   | 用户-门店权限 OWNER/EDITOR/VIEWER                    |
| `orangetheory-SubAccounts-prod` | 🟢 | childUserId | —          | 1   | 子账号关系                                          |
| `orangetheory-OAuthStates-prod` | 🟢 | stateId     | —          | 0   | OAuth 瞬态（TTL: `ttl`）                           |
| `studio-Cache-prod`             | 🟢 | cacheKey    | —          | 0   | 通用缓存（TTL 30d: `ttl`）                           |
| `studio-BlackoutPeriods-prod`   | 🟢 | storeId     | blackoutId | 1   | 禁呼时段                                           |
| `studio-OperatingHours-prod`    | 🟢 | storeId     | sk         | 0   | 营业时间（SK: `SCHEDULE#date#ts` / `OVERRIDE#date`） |

### Lead Tracking — 线索追踪 (1) <small>CDK: `LeadTrackingStack`</small>

::: info Neon-primary read 已切换 (2026-04-26)
`LeadTracking-v2` 仍接收 lead-tracking Lambda 的 dual-write（legacy 兼容），但 **Neon `leads` 已是 primary read source**，并已带 `store_id`（lead-tracking #88）。studio-api 查询线索数据走 Neon SQL。

:::

| 表名                          | 状态 | PK | GSI | 说明                                                        |
| --------------------------- | -- | -- | --- | --------------------------------------------------------- |
| `LeadTracking-v2-us-east-1` | 🟢 | id | 4   | 线索记录（**Neon `leads` 是 primary read，DDB 仅 dual-write 兼容**） |

::: details LeadTracking 4 个 GSI

| GSI 名称                                   | Partition Key         | Sort Key   | 用途            |
| ---------------------------------------- | --------------------- | ---------- | ------------- |
| `email-receivedAt-index`                 | leadEmail             | receivedAt | 按客户邮箱查        |
| `emailFrom-receivedAt-index`             | emailFrom             | receivedAt | 按来源邮箱查        |
| `phone-receivedAt-index`                 | phone                 | receivedAt | 按电话号码查（E.164） |
| `forwardedOriginalFrom-receivedAt-index` | forwardedOriginalFrom | receivedAt | 按转发源查         |

:::

### RC Subscription — Webhook 订阅 (2) <small>CDK: `RCSubscription-prod-Storage`</small>

| 表名                                           | 状态 | PK        | GSI | 说明                                         |
| -------------------------------------------- | -- | --------- | --- | ------------------------------------------ |
| `rc-subscription-prod-webhook-subscriptions` | 🟢 | accountId | 1   | Webhook 订阅状态                               |
| `rc-subscription-prod-idempotency`           | 🟢 | id        | 0   | Lambda Powertools 幂等性去重（TTL: `expiration`） |

### Legacy 遗留表 (4) <small>Pre-CDK，仅 prod</small>

| 表名                      | 状态 | 说明                                        |
| ----------------------- | -- | ----------------------------------------- |
| `call-analysis`         | 🟡 | CDK 前旧表，`production-resources.ts` 仍引用 ARN |
| `call-events`           | 🟡 | CDK 前旧表，`production-resources.ts` 仍引用 ARN |
| `CallAnalysisMetadata`  | 🔴 | 更早期格式，零代码引用                               |
| `client-configurations` | 🔴 | 旧客户配置，零代码引用                               |

***

## Test (us-west-2) — 19 张表

Prod 的镜像子集。无 ConnectionService 孤儿表、无 Legacy 表、无 LeadTracking。另有 2 张 prod 尚未部署的新表。

| #  | 表名                                                   | CDK Stack                   | 备注               |
| -- | ---------------------------------------------------- | --------------------------- | ---------------- |
| 1  | `call-analytics-test-call-analysis-us-west-2`        | CallAnalytics-Test-Storage  |                  |
| 2  | `call-analytics-test-call-events-us-west-2`          | CallAnalytics-Test-Storage  |                  |
| 3  | `call-analytics-test-call-analysis-config-us-west-2` | CallAnalytics-Test-Storage  |                  |
| 4  | `call-analytics-test-message-store-us-west-2`        | MessageSystemStack          | **Prod 未部署**     |
| 5  | `call-analytics-test-conversations-us-west-2`        | MessageSystemStack          | **Prod 未部署**     |
| 6  | `orangetheory-UserConnections-test`                  | OrangeTheoryStack-test      |                  |
| 7  | `orangetheory-PhoneNumbers-test`                     | OrangeTheoryStack-test      |                  |
| 8  | `orangetheory-PhoneStoreAssignments-test`            | OrangeTheoryStack-test      |                  |
| 9  | `orangetheory-StoresV2-test`                         | OrangeTheoryStack-test      |                  |
| 10 | `orangetheory-UserStore-test`                        | OrangeTheoryStack-test      |                  |
| 11 | `orangetheory-SubAccounts-test`                      | OrangeTheoryStack-test      |                  |
| 12 | `orangetheory-OAuthStates-test`                      | OrangeTheoryStack-test      |                  |
| 13 | `orangetheory-BlackoutPeriods-test`                  | OrangeTheoryStack-test      | ⚠ 与 `studio-` 重复 |
| 14 | `orangetheory-OperatingHours-test`                   | OrangeTheoryStack-test      | ⚠ 与 `studio-` 重复 |
| 15 | `studio-Cache-test`                                  | OrangeTheoryStack-test      |                  |
| 16 | `studio-BlackoutPeriods-test`                        | OrangeTheoryStack-test      |                  |
| 17 | `studio-OperatingHours-test`                         | OrangeTheoryStack-test      |                  |
| 18 | `rc-subscription-test-webhook-subscriptions`         | RCSubscription-test-Storage |                  |
| 19 | `rc-subscription-test-idempotency`                   | RCSubscription-test-Storage |                  |

::: details Test 环境差异

- **无 Deletion Protection、无 PITR、全部 `removalPolicy: DESTROY`** — test 环境允许随时重建
- BlackoutPeriods / OperatingHours 同时存在 `orangetheory-*` 和 `studio-*` 两个版本。Prod 已清理仅保留 `studio-*`，test 的 `orangetheory-*` 版本应在下次部署时清理。**⚠️ 2026-04-23 live 发现**：`orangetheory-BlackoutPeriods-test` 有 **29 items**（2026-01-10 创建），`studio-BlackoutPeriods-test` 只有 **1 item**——legacy 表的数据量反而更多。下次部署清理前必须先把 29 items 迁移到 `studio-*`，否则丢数据。
- **缺少**: LeadTracking（CDK 未部署到 test）、ConnectionService（孤儿表，从未创建）、Legacy 4 张（Pre-CDK 手动创建）

## :::

## 实体关系

### 全局关系图

> 完整的 DynamoDB 全量表关系图（21 张表 · 36 GSIs · 色彩分组 + 关系箭头），源文件: [`diagrams/dynamodb.drawio`](/diagrams/dynamodb.drawio)

![DynamoDB 全量表关系图](/images/dynamodb.svg)

### 数据隔离模型

**`storeId` 是数据围墙** — 每个门店只能看到自己电话号码关联的数据：

```
老板 (userId)
  ├── 门店 A (storeId-A)
  │     ├── +1914265xxxx ──→ 门店 A 的 SMS / 通话 / 线索
  │     └── +1914265xxxx ──→ 门店 A 的 SMS / 通话 / 线索
  │
  └── 门店 B (storeId-B)
        ├── +1978489xxxx ──→ 门店 B 的 SMS / 通话 / 线索
        └── +1978489xxxx ──→ 门店 B 的 SMS / 通话 / 线索

❌ 门店 A 看不到门店 B 的数据
❌ 同一个客户在两家店 = 两条独立记录
✅ 老板可以切换门店分别查看
```

### Phone Management 数据流

核心问题：**哪个电话号码属于哪个门店？**

```mermaid
graph LR
    UC[UserConnections] -->|providerAccountId| PN[PhoneNumbers]
    PN -->|phoneNumber| PSA[PhoneStoreAssignments]
    PSA -->|storeId| ST[StoresV2]
```

***

## 注意事项

::: danger siteId ≠ storeId — 两个完全不同的字段！

- `call-analysis.siteId` = `providerAccountId`（**numeric**，RingCentral 账号 ID，如 `277275028`）
- `StoresV2.storeId` = **UUID**（如 `18a16bde-8f20-4442-9b95-35f9d144b0e7`）
- **两者不可互换！** 要从通话记录关联到门店：`phoneNumber → PhoneStoreAssignments → storeId`
- `call-analysis.clientId` = `franchise-providerAccountId`（如 `orangeTheory-277275028`）
- `call-analysis.franchise` = 品牌名（如 `orangeTheory`）—— GSI `franchise-callStartTime-index` / `franchise-siteId-index` 用此字段

:::
::: info ⚠️ `siteId` / `providerAccountId` 即将 rename → `accountId`
`siteId` 已统一 rename 为 `account_id`(callytics-common 0.22.0,migration 0016,2026-04-25 完成)。GSI **索引名** `franchise-siteId-index` 等不会变(DDB 索引名不能改名),但 **item 内字段名**已切换。详见 [`identity-fields.md`](/system-design/identity-fields.md) §1.3 + [`docs/archive/identity-naming-history.md`](../archive/identity-naming-history.md) 演变史。

:::
**表名前缀**

| 前缀                         | Repo                             | CDK Stack                                      |
| -------------------------- | -------------------------------- | ---------------------------------------------- |
| `call-analytics-prod-*`    | `callytics-infrastructure`       | `CallAnalytics-Prod-Storage`                   |
| `ConnectionService-*-prod` | _(已归档)_ `ringcentralService`     | _(已删除)_ ⚠ 孤儿表                                  |
| `orangetheory-*-prod`      | `studio-website-monorepo`        | `OrangeTheoryStack-prod`（旧品牌前缀）                |
| `studio-*-prod`            | `studio-website-monorepo`        | `OrangeTheoryStack-prod`（新前缀，勿用 orangetheory-） |
| `LeadTracking-v2-*`        | `lead-tracking`                  | `LeadTrackingStack`                            |
| `rc-subscription-prod-*`   | `ringcentralSubscriptionService` | `RCSubscription-prod-Storage`                  |

`PhoneNumbers` 表的 `extensionId` 字段类型不一致：有分机的号码为 Number 类型，无分机的为空 String。代码需兼容两种类型。

***

## Archive 候选清单 \{ #archive }

> **审计日期**: 2026-04-23 · AWS CLI 实查 + CDK 代码引用 + 全仓库 grep 交叉验证
> 2026-03-02 首次 audit；2026-04-23 deep re-audit 发现 RCU 数据与前次结论的 drift（见各节 live 提示）。

### 🔴 DEAD — archive 前必须 CloudTrail 追踪读源

::: warning 2026-04-23 live 发现：DEAD 表并非完全零读
`aws cloudwatch get-metric-statistics --metric-name ConsumedReadCapacityUnits --period 604800` 显示所有 4 张 DEAD 表在过去 7 天都有 2-16 RCU 的读取。读量极低，但 **"零业务读写"** 的旧结论已被证伪。archive 前必须用 CloudTrail 定位读源，否则冒 "删掉仍在 low-volume 被读的表" 风险。

另：`client-configurations` 经 `aws lambda get-function-configuration RingCentralTokenRefresher-Multi-Tenant` 验证，运行时 env var `CLIENT_CONFIG_TABLE_V1=client-configurations` 被设置（而非 doc 之前推断的空串）——这是 legacy Python Lambda，属于 `ConnectionService-CallAnalysisConfigurations-prod` 的同批退役范围。

:::

| 表名                                         | 数据量           | DP | 7d RCU  | 前置步骤                                                         | 说明                                       |
| ------------------------------------------ | ------------- | -- | ------- | ------------------------------------------------------------ | ---------------------------------------- |
| `CallAnalysisMetadata`                     | 401 条 / 117KB | ❌  | **16**  | CloudTrail 追源 → 确认后删除                                        | 更早期格式，零活跃引用                              |
| `client-configurations`                    | 2 条 / 1.5KB   | ❌  | **2**   | 退役 `RingCentralTokenRefresher-Multi-Tenant`（Python，非 CDK）后删除 | 运行时被 `CLIENT_CONFIG_TABLE_V1` env var 引用 |
| `ConnectionService-UserOrganizations-prod` | 62 条          | ✅  | **2.5** | CloudTrail 追源 → 关闭 DP → 删除                                   | 零活跃代码引用，新模型用 `UserStore`                 |
| `ConnectionService-SubAccountLinks-prod`   | 13 条          | ✅  | **2**   | CloudTrail 追源 → 关闭 DP → 删除                                   | 零活跃代码引用，新模型用 `SubAccounts`               |

### 🟡 LEGACY — 需完成前置步骤后 archive

**ConnectionService 低活跃表（3 张）** — 功能已被 orangetheory-\* 替代。`callytics-infrastructure` 中有 V2 config loader 引用，但 `USE_V2_CONFIG_LOADER=false`（未启用）。

| 表名                                         | DP | 替代表                                 | 前置步骤                        |
| ------------------------------------------ | -- | ----------------------------------- | --------------------------- |
| `ConnectionService-Organizations-prod`     | ✅  | `orangetheory-StoresV2-prod`        | ① 确认 V2 loader 不再需要 ② 关闭 DP |
| `ConnectionService-Connections-prod`       | ✅  | `orangetheory-UserConnections-prod` | ① 同上 ② 关闭 DP                |
| `ConnectionService-OrgConfigurations-prod` | ✅  | `StoresV2.businessConfig`           | ① 同上 ② 关闭 DP                |

**ConnectionService-CallAnalysisConfigurations-prod（🟢 仍活跃）**

| 属性               | 值                                                                                                                                              |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **DP**           | ✅                                                                                                                                              |
| **7d RCU**       | **\~450** ← 仍有大量读取（2026-04-23 确认）                                                                                                              |
| **替代表**          | `call-analytics-prod-call-analysis-config-us-east-1`（8 条，CDK 管理）                                                                               |
| **仍在读取的 Lambda** | `RingCentralTokenRefresher-Multi-Tenant`（Legacy Python，非 CDK 管理），env var `CONFIG_TABLE_NAME=ConnectionService-CallAnalysisConfigurations-prod` |
| **关联 DEAD 读源**   | 同一 Legacy Lambda env var `CLIENT_CONFIG_TABLE_V1=client-configurations` —— 同批退役                                                                |
| **前置步骤**         | ① 退役 Legacy Lambda（CDK 替代: `orangetheory-token-refresh-prod`，已在运行） ② 切换 `USE_V2_CONFIG_LOADER` 到新表 ③ 关闭 DP                                     |

**Legacy 通话表（2 张）** — `callytics-infrastructure/lib/config/production-resources.ts` 中硬编码了 ARN（`fromTableArn` 引用）。

::: warning 2026-04-23 live 发现：Legacy 表仍有显著读取流量
这两张表不是 quiet legacy —— 7 天 RCU Sum 分别为 `call-analysis=3,482` 和 `call-events=8,819`。"前置步骤 ②（确认零业务读写）" 目前**不满足**，archive 前必须 CloudTrail 追源并迁移 caller。

:::

| 表名              | 数据量             | DP | 7d RCU    | 前置步骤                                                                                               |
| --------------- | --------------- | -- | --------- | -------------------------------------------------------------------------------------------------- |
| `call-analysis` | 20,487 条 / 27MB | ✅  | **3,482** | ① 移除 `production-resources.ts` 中的 ARN 引用 ② CloudTrail 追源，迁移 caller 到 `-prod-` 新表 ③ 确认零业务读写 ④ 关闭 DP |
| `call-events`   | 24,550 条 / 69MB | ✅  | **8,819** | ① 同上                                                                                               |

**Test 环境重复表（2 张）**

| 表名                                  | 环境        | 说明                                 |
| ----------------------------------- | --------- | ---------------------------------- |
| `orangetheory-BlackoutPeriods-test` | us-west-2 | 与 `studio-BlackoutPeriods-test` 重复 |
| `orangetheory-OperatingHours-test`  | us-west-2 | 与 `studio-OperatingHours-test` 重复  |

### Archive 优先级

```
Phase 1 (立即): CallAnalysisMetadata, client-configurations,
                ConnectionService-UserOrganizations, ConnectionService-SubAccountLinks
              → 零引用零读写，风险最低

Phase 2 (退役 Legacy Lambda 后): ConnectionService-CallAnalysisConfigurations-prod
              → 需先退役 RingCentralTokenRefresher-Multi-Tenant

Phase 3 (清理 V2 loader 引用后): ConnectionService-Organizations/Connections/OrgConfigurations
              → 需确认 USE_V2_CONFIG_LOADER 代码路径可移除

Phase 4 (清理 production-resources.ts 后): call-analysis(legacy), call-events(legacy)
              → 需移除 callytics-infrastructure 中的 hardcoded ARN 引用

Phase 5 (Test 清理): orangetheory-BlackoutPeriods-test, orangetheory-OperatingHours-test
              → 下次 CDK deploy 时清理
```

### 删除操作步骤

```bash
# Step 1: 关闭 Deletion Protection（需要的表）
aws dynamodb update-table \
  --table-name <TABLE_NAME> \
  --no-deletion-protection \
  --region us-east-1

# Step 2: 删除表
aws dynamodb delete-table \
  --table-name <TABLE_NAME> \
  --region us-east-1
```

::: warning 删除前 checklist

- [ ] 确认 7 天 CloudWatch RCU/WCU = 0（或已确认来源并切换）
- [ ] grep 所有活跃代码仓库确认 0 引用
- [ ] 确认数据不需要归档（如需要，先导出到 S3）
- [ ] 在 test 环境先执行删除，观察 7 天无报错
- [ ] 最后在 prod 执行

## :::

## 安全现状

### 保护状态总览

每张 prod 持久表都有三层保护：**DP**（Deletion Protection，防 API/Console 误删）、**RETAIN**（`removalPolicy`，防 `cdk destroy` 误删）、**PITR**（Point-in-Time Recovery，防数据损坏，可恢复 35 天内任意时间点）。

::: danger ⚠️ 2026-04-23 live 审计发现：Call Analytics 3 张 prod 表 DP + PITR **未启用**
CDK code 中 `lib/stacks/storage-stack.ts:131-138, 161-168, 279-286` intent 正确（`deletionProtection: envConfig.environmentName === 'prod'` + `pointInTimeRecoverySpecification`），但 **`CallAnalytics-Prod-Storage` stack `LastUpdatedTime = 2026-02-04`，CDK 保护代码 2026-03-02 才 commit**（SHA `c129c1d`）——从未 deploy 到 prod。52 天 risk window。

```
$ aws dynamodb describe-table --table-name call-analytics-prod-call-analysis-us-east-1 \
    --region us-east-1 --query 'Table.DeletionProtectionEnabled'
false

$ aws dynamodb describe-continuous-backups --table-name call-analytics-prod-call-analysis-us-east-1 \
    --region us-east-1 \
    --query 'ContinuousBackupsDescription.PointInTimeRecoveryDescription.PointInTimeRecoveryStatus'
"DISABLED"
```

同样问题影响 `call-analytics-prod-call-events-us-east-1` + `call-analytics-prod-call-analysis-config-us-east-1`。另：`rc-subscription-prod-webhook-subscriptions` DP 也是 False（PITR ✅，单项漂移）。

**Ops 追踪见 [infra#656](https://github.com/retaintive/callytics-infrastructure/issues/656)** — 需要 out-of-band `aws dynamodb update-table --deletion-protection-enabled` + `update-continuous-backups`，然后 `deploy-prod.yml` 让 CF 模板对齐。Deploy cadence meta 追踪见 [infra#659](https://github.com/retaintive/callytics-infrastructure/issues/659)。

:::

| 分组（表数）                                   | Prod DP                  | Prod RETAIN | Prod PITR                | Test DP | Test RETAIN | Test PITR |
| ---------------------------------------- | ------------------------ | ----------- | ------------------------ | ------- | ----------- | --------- |
| Call Analytics (3)                       | ⚠️ **intent=✅ / live=❌** | ✅           | ⚠️ **intent=✅ / live=❌** | ❌       | ❌           | ❌         |
| Studio 持久表 (8)                           | ✅                        | ✅           | ✅                        | ❌       | ❌           | ❌         |
| LeadTracking (1)                         | ✅                        | ✅           | ✅                        | —       | —           | —         |
| RC webhook-subscriptions (1)             | ⚠️ **intent=✅ / live=❌** | ✅           | ✅                        | ❌       | ❌           | ❌         |
| Message System (2)                       | —                        | —           | —                        | ❌       | ❌           | ❌         |
| 瞬态表 (3: OAuthStates, Cache, idempotency) | ❌                        | ❌           | ❌                        | ❌       | ❌           | ❌         |

**Prod**: ⚠️ 4 张应 ✅✅✅ 的持久表实际 DP=False（3 张 Call Analytics + 1 张 webhook-subscriptions），pending ops 修复。Studio 持久表 + LeadTracking 验证 ✅✅✅。瞬态表（TTL 自动过期）无需保护。
**Test**: 全部 ❌❌❌，允许随时重建。`—` = 该环境无此表。
ConnectionService 孤儿表和 Legacy 表的保护状态见 [Archive 候选](#archive)。

### 其他配置

| 配置项              | 当前状态                                                               | 备注                                           |
| ---------------- | ------------------------------------------------------------------ | -------------------------------------------- |
| 计费模式             | 全部 PAY\_PER\_REQUEST                                               | 当前规模合适                                       |
| KMS 加密           | 仅 ConnectionService（6 张，共用 1 个 KMS key）                            | 含 PII 的表应启用                                  |
| DynamoDB Streams | ConnectionService 5 张表（`NEW_AND_OLD_IMAGES`）                       | call-analysis 表待启用 `NEW_IMAGE`               |
| TTL              | OAuthStates (`ttl`)、Cache (`ttl`)、idempotency (`expiration`) = 3 张 | 瞬态数据表                                        |
| CDK Tags         | callytics 3 + ConnectionService 6 + RC Subscription 2 = 11 张       | orangetheory-_、studio-_、LeadTracking 缺少 tags |
