# Plan: social-app 数据库数据模型重设计（支持社交/事项/自动化）

**Date:** 2026-02-26
**Author:** AI Assistant
**Status:** Draft

## 枚举存储约定

**统一使用枚举名称（字符串）存储，不使用整数值。**

- 数据库层：`VARCHAR(20)` + `CHECK` 约束
- 代码层：Python `Enum` 类继承 `str`
- 优势：调试可读、易扩展（新增枚举值无需迁移旧数据）、ORM 友好

```python
class AgentType(str, Enum):
    INTENT_RECOGNITION = "INTENT_RECOGNITION"
    TASK_EXECUTION = "TASK_EXECUTION"
    RESULT_REPORTING = "RESULT_REPORTING"
```

```sql
-- Migration
ALTER TABLE user_agents ADD CONSTRAINT chk_agent_type 
  CHECK (agent_type IN ('INTENT_RECOGNITION', 'TASK_EXECUTION', 'RESULT_REPORTING'));
```

## Overview

本方案面向 `social-app` 的下一阶段功能升级，重设计 PostgreSQL 数据模型，统一支持用户专属 agent、好友/群组协作、待处理消息、设置、可订阅且可授权编辑的日程事项、待办联动与自动化定时任务。目标是在 FastAPI + Flutter 协作场景下提供长期稳定的数据基础，降低后续 API 演进和跨端同步复杂度。

## Requirements

### Functional
- [x] 每个用户有专属 agent，且模型可扩展到未来多 agent 能力
- [x] 用户支持好友关系、群组创建与成员管理
- [x] 用户支持 inbox/pending 待处理消息
- [x] 用户支持个性化设置（偏好/隐私/通知）
- [x] 用户支持“绑定日程的事项”，可多人订阅，且仅特定人可修改
- [x] 用户支持待办事项（可由日程事项提取，也可手动创建）
- [x] 用户支持自动化定时任务（循环触发）

### Non-Functional
- [x] 性能：核心读路径（inbox 列表、待办列表、事项列表）P95 < 150ms（单用户典型数据量）
- [x] 安全：权限以后端业务授权为准；数据库层保留 RLS 防御边界
- [x] 一致性：关键写路径（好友状态、权限变更、任务触发）使用事务保障
- [x] 可演进：当前阶段采用重建库快速迭代；后续稳定后切换为增量迁移与灰度

## Technical Approach

采用“认证域（`auth.users`）+ 业务域（`public.*`）”分层建模。保持 `auth.users` 作为身份主键来源，业务表统一引用 `user_id UUID -> auth.users.id`。领域边界拆分为：Identity/Profile、Social Graph、Collaboration（事项/订阅/权限）、Inbox、Todo、Automation。通过“规范化主模型 + 局部物化/冗余快照”平衡一致性与查询性能。

### Key Decisions

| Decision | Rationale |
|----------|-----------|
| 用户与 agent 采用 1:1 主约束 + 可扩展结构 | 当前满足"每用户专属 agent"，未来允许多 agent 形态演进 |
| 记忆系统采用单表 + memory_type 区分 | user 类型可选 agent_id，work 类型必须绑定 agent_id |
| 好友关系用单表双向规范化表示 | 避免 A-B / B-A 重复，降低去重成本 |
| 事项权限采用 ACL 表而非仅 owner | 满足“仅特定人可修改”的协作场景 |
| 待办采用主表 + 关联表 | `todos` + `todo_sources` 保证来源关系可校验 |
| 自动化采用 Jobs 单表 + Sessions 关联 | `sessions` 通过 `session_type + job_id` 区分普通对话与自动化运行 |
| inbox 采用单表接收者视角 | 发送者 + 消息类型 + 关联业务，一表搞定待处理消息 |

## A. 设计原则与边界

### 1) 核心实体与聚合边界
- 用户聚合：`profiles`（含 settings JSONB）, `user_agents`, `memories`
- 社交聚合：`friendships`, `groups`, `group_members`
- 协作事项聚合：`schedule_items`, `schedule_subscriptions`（当前仅用户主体）
- 消息聚合：`inbox_messages`
- 待办聚合：`todos`
- 自动化聚合：`automation_jobs`

### 2) 一致性分级
- 强一致（同事务）：好友关系状态迁移、群组成员角色变更、事项权限写入、定时任务抢占执行
- 最终一致：inbox 衍生、待办同步、提醒派发（允许异步补偿）

### 3) 多租户假设
- 默认假设：单租户产品（同一业务库服务所有用户），以 `user_id` 做数据隔离
- 扩展预留：各核心表可预留 `tenant_id UUID NULL`（需业务确认是否近期引入组织空间）

## B. 领域模型与关系图（文字化）

### 实体与关系
- `auth.users (1) - (1) profiles`（settings 作为 JSONB 内嵌）
- `auth.users (1) - (1) user_agents`
- `auth.users (1) - (N) memories`
- `user_agents (1) - (N) memories`（work 类型）
- `auth.users (N) - (N) auth.users` 通过 `friendships`
- `auth.users (1) - (N) groups`（创建者）
- `groups (1) - (N) group_members`，`auth.users (1) - (N) group_members`
- `auth.users (1) - (N) schedule_items`（创建者）
- `schedule_items (1) - (N) schedule_subscriptions`，`auth.users (1) - (N) schedule_subscriptions`
- `auth.users (1) - (N) inbox_messages`
- `auth.users (1) - (N) todos`
- `auth.users (1) - (N) automation_jobs`
- `automation_jobs (1) - (N) sessions`（通过 `sessions.job_id` 关联）

### 关键约束
- 唯一性：
  - `user_agents.user_id` 唯一
  - `friendships(user_low_id, user_high_id)` 唯一
  - `group_members(group_id, user_id)` 唯一
  - `schedule_subscriptions(item_id, subscriber_id)` 唯一
- CHECK：
  - `friendships`: `user_low_id < user_high_id` 且 `user_low_id <> user_high_id`
  - `schedule_subscriptions`: `permission BETWEEN 0 AND 7`
  - `memories`: `work` 类型必须有 `agent_id`，`user` 类型必须无 `agent_id`
  - `sessions`: `session_type/job_id` 组合一致
- 外键：统一显式 `ON DELETE` 策略（见下）
- 可空性：权限关键字段、状态字段默认 `NOT NULL`
- 删除策略：
  - 用户删除：大部分 `CASCADE`（用户私有数据）；跨用户协作数据优先软删
  - 事项删除：对子表 `CASCADE`；待办保留历史，改 `status = 'archived'`

### 外键删除策略明细（必做）
- `sessions.job_id -> automation_jobs.id`: `ON DELETE RESTRICT`
- `todo_sources.todo_id -> todos.id`: `ON DELETE CASCADE`
- `todo_sources.schedule_item_id -> schedule_items.id`: `ON DELETE CASCADE`
- `inbox_messages.friendship_id -> friendships.id`: `ON DELETE CASCADE`
- `inbox_messages.schedule_item_id -> schedule_items.id`: `ON DELETE CASCADE`
- `inbox_messages.group_id -> groups.id`: `ON DELETE CASCADE`

## C. 数据库表设计（PostgreSQL）

以下为推荐主表（方案 1，规范化优先）。字段示例采用 `UUID + timestamptz + enum/text-check`。

### 1) 用户与 agent

#### `profiles`（已有，建议补齐）
- PK: `id UUID` (`auth.users.id`)
- 关键字段: `username`, `avatar_url`, `bio`
- **新增 JSONB 字段**:
  - `settings JSONB`（用户自定义设置，含 `version`, `preferences`, `privacy`, `notification` 四大块）
- 时间字段: `created_at`, `updated_at`, `deleted_at`
- 索引: 
  - `INDEX(username)`（允许重名，仅用于列表查询）
  - `GIN(settings)`（支持 JSONB 表达式查询）
  - 表达式索引：`(settings->'notification'->>'enabled')`（按需，对高频查询字段单独建）
- 审计: `created_by`, `updated_by`（可等于 id）
- 删除策略: 用户删除时 `CASCADE`

#### `user_agents`
- PK: `id UUID`
- 关键字段: 
  - `user_id UNIQUE`（每用户专属 agent）
  - `llm_id UUID NOT NULL`（关联绑定的 LLM 模型）
  - `agent_type VARCHAR(20) NOT NULL`（枚举限制：`INTENT_RECOGNITION` | `TASK_EXECUTION` | `RESULT_REPORTING`）
  - `config JSONB`（agent 配置参数）
- 时间字段: `created_at`, `updated_at`, `deleted_at`
- 状态字段: `status`（`active|paused|migrating`）
- 索引:
  - `UNIQUE(user_id) WHERE deleted_at IS NULL`
  - `INDEX(status)`
  - `INDEX(agent_type)`
  - `GIN(config)`（按需）
- 审计: `created_by`, `updated_by`

#### `memories`
- PK: `id UUID`
- 关键字段:
  - `owner_id`（用户，NOT NULL）
  - `agent_id`（work 类型时必需）
  - `memory_type`（枚举：`user | work`）
  - `title`
  - `content`（JSONB，存储具体记忆结构）
  - `source`（`manual | agent | imported`）
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status`（`active | disabled`）
- 索引:
  - `INDEX(owner_id, memory_type, status)`
  - `INDEX(agent_id, memory_type, status)`
  - `GIN(content)`（支持 JSONB 内容查询）
- 约束: `CHECK ((memory_type = 'work' AND agent_id IS NOT NULL) OR (memory_type = 'user' AND agent_id IS NULL))`

**memory_type 说明**：
| 类型 | agent_id | 说明 |
|------|----------|------|
| `user` | 可空 | 用户记忆：偏好、背景信息、实体等 |
| `work` | 必需 | 工作记忆：长期运行后对工作流程的经验整理，避免重复错误 |

**content JSONB 示例**：
```json
// 用户记忆
{"type": "preference", "data": {"style": "concise", "language": "zh-CN"}}

// 工作记忆
{"type": "workflow_summary", "data": {"task": "代码审查", "learnings": ["优先检查安全漏洞", "关注性能热点"], "improvements": []}}
```

### 2) 社交关系

#### `friendships`
- PK: `id UUID`
- 关键字段: 
  - `user_low_id`（两者中较小的 UUID）
  - `user_high_id`（两者中较大的 UUID）
  - `initiator_id`（发起请求方的 user_id，用于追溯谁主动）
  - `status`, `requested_at`, `accepted_at`, `blocked_by`
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status`（`pending|accepted|blocked|declined|canceled`）
- 约束:
  - `CHECK(user_low_id < user_high_id)`（强制小值放 low，大值放 high，确保 A→B 和 B→A 是同一行）
  - `CHECK(initiator_id IN (user_low_id, user_high_id))`
  - `UNIQUE(user_low_id, user_high_id)`
- 索引:
  - `INDEX(user_low_id, status)`
  - `INDEX(user_high_id, status)`
  - 部分索引 `INDEX(status) WHERE status='pending'`
- 审计: `created_by`, `updated_by`

**查询示例**：
- 查询用户 A 的所有好友：`SELECT * FROM friendships WHERE user_low_id = A OR user_high_id = A`

#### `groups`
- PK: `id UUID`
- 关键字段: `name`, `description`, `owner_id`
- 时间字段: `created_at`, `updated_at`, `deleted_at`
- 状态字段: `status`（`active|archived`）
- 索引: `INDEX(owner_id, status)`
- 审计: `created_by`, `updated_by`

#### `group_members`
- PK: `id UUID`
- 关键字段: 
  - `group_id`, `user_id`
  - `role`（枚举：`owner` | `admin` | `member`）
  - `join_source`（`invited|joined`）
  - `invited_by`, `joined_at`
- 时间字段: `created_at`, `updated_at`, `removed_at`
- 状态字段: `status`（`active|muted|removed`）
- 约束: `UNIQUE(group_id, user_id)`
- 索引:
  - `INDEX(group_id, role, status)`
  - `INDEX(user_id, status)`
- 审计: `created_by`, `updated_by`

**role 说明**：
| role | 含义 |
|------|------|
| `owner` | 群主/创建者 |
| `admin` | 管理员 |
| `member` | 普通成员 |

- 角色可升降：服务层变更 role 字段即可

### 3) 用户设置（已合并至 profiles 表）

用户设置采用 JSONB 内嵌方式，渐进式扩展无需改表结构：

```json
{
  "version": 1,
  "preferences": {
    "interface_language": "zh-CN",
    "ai_language": "zh-CN",
    "timezone": "Asia/Shanghai"
  },
  "privacy": {},
  "notification": {}
}
```
- 索引策略：对高频查询字段使用表达式索引
- 更新方式：服务层使用 JSONB merge 或字段级 UPDATE，避免读-改-写并发问题（建议用 `jsonb_set` 原子操作）

### 4) 事项与订阅/权限

#### `schedule_items`
- PK: `id UUID`
- 关键字段:
  - `owner_id`
  - `title`
  - `description`
  - `start_at`
  - `end_at`
  - `timezone`（用于将日程时间转换为用户本地时间显示）
  - `metadata`（JSONB，扩展字段）
  - `recurrence_rule`（可选，支持循环日程）
  - `source_type`（`manual | imported | agent_generated`）
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status`（`active | completed | canceled | archived`）
- 索引: 
  - `INDEX(owner_id, start_at)`
  - `INDEX(status, start_at)`
- 审计: `created_by`

**metadata JSONB 示例**：
```json
{
  "color": "#FF6B6B",
  "location": "会议室A",
  "notes": "记得提前准备投影仪",
  "attachments": [
    {
      "name": "会议纪要.pdf",
      "url": "https://...",
      "visible_to": [],
      "type": "document"
    },
    {
      "name": "投影仪提醒",
      "visible_to": ["uuid1"],
      "type": "reminder",
      "content": "记得带投影仪"
    },
    {
      "name": "技术方案.docx",
      "url": "https://...",
      "visible_to": ["uuid2"],
      "type": "document",
      "note": "需要他确认预算"
    }
  ],
  "version": 1
}
```

| type | 说明 | 特殊字段 |
|------|------|----------|
| document | 文档/文件 | url, note |
| reminder | 提醒 | content |

#### `schedule_subscriptions`
- PK: `id UUID`
- 关键字段: 
  - `item_id`
  - `subscriber_id`
  - `permission`（INTEGER，用位运算存储权限组合，`NOT NULL DEFAULT 1`）
  - `notify_level`（`all | mentions | none`，`NOT NULL DEFAULT 'all'`）
- 时间字段: `created_at`
- 状态字段: `status`（`active | paused | unsubscribed`，`NOT NULL DEFAULT 'active'`）
- 约束: `UNIQUE(item_id, subscriber_id)`
- 约束补充: `CHECK(permission BETWEEN 0 AND 7)`（`view=1, invite=2, edit=4`，`0` 表示无权限）
- 索引: `INDEX(subscriber_id, status)`, `INDEX(item_id, status)`
- 审计: `created_by`

**权柄说明（位运算）**：
| 权柄 | 值 | 二进制 | 说明 |
|------|-----|--------|------|
| view | 1 | 001 | 查看事项详情 |
| invite | 2 | 010 | 邀请其他人订阅此事项 |
| edit | 4 | 100 | 修改事项内容、管理订阅 |

- 权限检查：`permission & 2 = 2` 检查是否有 invite 权限
- 权限添加：`permission | 2` 添加 invite 权限
- 事项 owner 默认拥有全部权柄：`7`（111）
- owner 权柄由服务层恒等判定为 `7`，不依赖 owner 是否在 `schedule_subscriptions` 中存在记录

**当前版本边界**：
- `schedule_subscriptions` 仅支持用户订阅（`subscriber_id -> auth.users.id`）
- 事项协作暂不引入群主体授权

### 5) 待处理消息（Inbox）

#### `inbox_messages`
- PK: `id UUID`
- 关键字段: 
  - `recipient_id`（接收者）
  - `sender_id`（发送者，系统消息可为 NULL）
  - `message_type`（枚举：`friend_request | calendar | system | group`）
  - `friendship_id`（可空，`friend_request` 时必填）
  - `schedule_item_id`（可空，`calendar` 时必填）
  - `group_id`（可空，`group` 时必填）
  - `content`（TEXT，消息内容，系统消息用）
- 时间字段: `created_at`
- 状态字段: 
  - `is_read`（BOOLEAN，是否已读）
  - `status`（`pending | accepted | rejected | dismissed`）
- 索引:
  - `INDEX(recipient_id, status, created_at DESC)`
  - 部分索引 `INDEX(recipient_id, created_at DESC) WHERE status='pending'`
- 审计: `created_by`

**message_type 与业务字段对应关系**：
| message_type | 对应业务字段 |
|--------------|-----------------|
| friend_request | friendship_id -> friendships.id |
| calendar | schedule_item_id -> schedule_items.id |
| system | 三个业务字段均为 NULL（内容直接在 content） |
| group | group_id -> groups.id |

**说明**：一张表搞定，接收者视角，通过 `message_type + 对应业务字段` 直接定位要处理的业务，避免单列多态外键带来的引用不一致问题。

**一致性约束（必做）**：
- 使用 `CHECK` 保证不同 `message_type` 下仅允许对应业务字段非空（`system` 时业务字段全空）
- 使用 `CHECK` 保证 `message_type='system'` 时 `sender_id IS NULL`，否则 `sender_id IS NOT NULL`
- `friendship_id`、`schedule_item_id`、`group_id` 分别建立 FK，并显式声明 `ON DELETE` 策略

### 6) 待办

#### `todos`
- PK: `id UUID`
- 关键字段: 
  - `owner_id`
  - `title`
  - `description`
  - `due_at`
  - `priority`（INTEGER，用于四象限：1=重要且紧急, 2=重要不紧急, 3=紧急不重要, 4=不重要不紧急）
- 时间字段: `created_at`, `completed_at`
- 状态字段: `status`（`pending | done | canceled`）
- 索引:
  - `INDEX(owner_id, status, due_at)`
  - `INDEX(owner_id, created_at DESC)`
  - 部分索引 `INDEX(owner_id, due_at) WHERE status='pending'`
- 审计: `created_by`

#### `todo_sources`
- PK: `id UUID`
- 关键字段:
  - `todo_id`（FK -> todos.id）
  - `schedule_item_id`（FK -> schedule_items.id）
- 时间字段: `created_at`
- 约束: `UNIQUE(todo_id, schedule_item_id)`
- 索引: `INDEX(todo_id)`, `INDEX(schedule_item_id)`

**说明**：
- 手动创建待办：不写 `todo_sources`
- 从事项提取待办：写入 `todo_sources`，替代 JSONB 数组，保证来源关系可校验

### 7) 自动化定时任务

#### `automation_jobs`
- PK: `id UUID`
- 关键字段: 
  - `owner_id`
  - `title`（任务标题）
  - `prompt`（AI 执行的 prompt）
  - `schedule_type`（枚举：`daily | weekly`）
  - `run_at`（首次运行时间）
  - `next_run_at`（下次运行时间，调度器扫描主字段）
  - `timezone`（时区，如 `Asia/Shanghai`）
  - `last_run_at`（最近运行时间，可空）
- 时间字段: `created_at`, `updated_at`
- 状态字段: `status`（`active | disabled`）
- 索引: `INDEX(owner_id, status)`, `INDEX(status, next_run_at)`
- 约束补充: `UNIQUE(id, owner_id)`（用于 `sessions(job_id, user_id)` 归属一致性外键）
- 审计: `created_by`

**说明**：定时任务执行时，在 sessions 表创建记录存储 AI 对话内容。

### 8) 会话表扩展（已有 sessions）

#### `sessions`（更新）
- 新增字段:
  - `session_type`（`chat | automation`）
  - `job_id`（可空，FK -> automation_jobs.id）
- 一致性约束:
  - `CHECK((session_type = 'chat' AND job_id IS NULL) OR (session_type = 'automation' AND job_id IS NOT NULL))`
  - 通过复合 FK 约束归属一致性：`FOREIGN KEY(job_id, user_id) -> automation_jobs(id, owner_id)`
- 索引:
  - `INDEX(user_id, session_type, last_activity_at DESC)`
  - `INDEX(job_id)`

## D. 权限与协作模型

### 1) 事项权限落表
  - 权限直接存储在 `schedule_subscriptions.permission` 整数中（位运算）
  - owner 不写入 `schedule_subscriptions`，owner 权限仅由 `schedule_items.owner_id` 推导
- 权限决策顺序：
  1. `schedule_items.owner_id` → 服务层恒等全部权柄 `["view", "invite", "edit"]`（7）
  2. `schedule_subscriptions` 中该用户的 `permission` 位图
  3. 非 owner 且非 subscriber 默认无权限（0）

### 2) 当前版本边界
- 事项权限仅处理用户主体（owner + subscriber）
- 群组与事项权限继承关系不在本期范围

## E. 消息与待办联动

### 1) inbox 关联业务对象
- `inbox_messages.message_type` 枚举：
  - `friend_request`（好友请求）→ `friendship_id` 指向 friendships
  - `calendar`（日程邀请）→ `schedule_item_id` 指向 schedule_items
  - `system`（系统消息）→ 业务字段均为 NULL
  - `group`（群组邀请）→ `group_id` 指向 groups
- 通过 `message_type + 对应业务字段` 直接定位业务对象，并用 `CHECK` 约束保证字段一致性

### 2) 待办来源提取
- 从事项提取待办时，写入 `todo_sources(todo_id, schedule_item_id)`
- 手动创建的待办不写 `todo_sources`
- 支持多来源：同一待办可关联多个日程事项（多行 `todo_sources`）
- 待办完成时无需反向更新来源事项状态（简化设计）

## F. 定时任务模型

### 1) 调度规则
- `schedule_type` 枚举：`daily`（每日） | `weekly`（每周）
- `run_at` 用于首次执行时间，`next_run_at` 用于后续调度
- 调度器扫描 `status='active' AND next_run_at <= now()` 的任务，执行后回写下一次 `next_run_at`
- `timezone` 参与下一次执行时间计算，避免时区偏差

### 2) 执行记录
- 每次执行在 sessions 表创建记录，通过 `sessions.job_id` 关联 job
- `sessions` 通过 `session_type` 区分 `chat` 与 `automation`
- 执行失败时记录在 `automation_jobs`（如 `last_error`，可后续细化）

## G. 数据库迁移思路

### 策略：重建数据库 + Alembic ORM 迁移

由于是全新设计的数据模型，且当前处于开发初期（可清除旧数据），采用**重建数据库**策略：

**执行门禁（强制）**：
- 仅允许在本地开发环境执行
- 禁止在生产/共享环境执行 `rm backend/alembic/versions/*.py`
- 执行前必须备份数据库或创建 git tag

1. **删除所有旧 migration 脚本**（保留 `env.py`）
2. **创建 ORM 模型文件**
3. **生成 Alembic migration**
4. **重建数据库并执行迁移**

### 执行步骤

1. 删除旧 migration 文件
   ```bash
   rm backend/alembic/versions/*.py
   ```

2. 重建空数据库（确保以空库基线生成 initial migration）
   ```bash
   docker compose --env-file .env -f infra/docker/docker-compose.yml down -v
   docker compose --env-file .env -f infra/docker/docker-compose.yml up -d db
   ```

3. 创建 ORM 模型文件（参考 `models/` 目录结构）
   - 新增：`user_agents.py`, `memories.py`, `friendships.py`, `groups.py`, `group_members.py`, `schedule_items.py`, `schedule_subscriptions.py`, `inbox_messages.py`, `todos.py`, `todo_sources.py`, `automation_jobs.py`
   - 更新：`profile.py` - 添加 `settings` 字段
   - 更新：`agent_chat_session.py` - 添加 `session_type`、`job_id` 字段
   - 重写：`create_profile_for_new_user` 触发器，确保 `profiles.settings` 有默认值

4. 更新 `models/__init__.py` 导出所有模型

5. 更新 `alembic/env.py` 添加模型导入

6. 生成 initial migration（以空库为对比基线）
   ```bash
   cd backend && uv run alembic revision --autogenerate -m "initial schema"
   ```

7. 为所有新建 `public` 业务表补齐 RLS（`SELECT/INSERT/UPDATE/DELETE` policy）
   - 每张表都执行 `ENABLE ROW LEVEL SECURITY`
   - 每张表都显式创建 `SELECT/INSERT/UPDATE/DELETE` policy
   - downgrade 必须可逆，不得弱化既定安全边界
   - `anon/authenticated` 默认全部 deny

   RLS 最小策略矩阵（本期统一模板）：
   - `anon`：`SELECT/INSERT/UPDATE/DELETE` 全部 deny
   - `authenticated`：`SELECT/INSERT/UPDATE/DELETE` 全部 deny
   - `service_role`：由后端服务连接使用，不依赖 RLS 放行

8. 执行迁移
   ```bash
   cd backend && uv run alembic upgrade head
   ```

9. 验证表结构

## H. 交付物

### ORM 模型文件清单

| 文件 | 说明 |
|------|------|
| `models/user_agents.py` | 用户专属 agent |
| `models/memories.py` | 用户/工作记忆 |
| `models/friendships.py` | 好友关系 |
| `models/groups.py` | 群组 |
| `models/group_members.py` | 群组成员 |
| `models/schedule_items.py` | 日程事项 |
| `models/schedule_subscriptions.py` | 日程订阅与权限 |
| `models/inbox_messages.py` | 待处理消息 |
| `models/todos.py` | 待办 |
| `models/todo_sources.py` | 待办与事项来源关联 |
| `models/automation_jobs.py` | 定时任务 |
| `models/profile.py` | 更新：添加 `settings` 字段 |
| `models/agent_chat_session.py` | 更新：添加 `session_type`、`job_id` 字段 |

### 执行步骤

1. 删除旧 migration 文件
   ```bash
   rm backend/alembic/versions/*.py
   ```

2. 重建空数据库（确保以空库基线生成 initial migration）
   ```bash
   docker compose --env-file .env -f infra/docker/docker-compose.yml down -v
   docker compose --env-file .env -f infra/docker/docker-compose.yml up -d db
   ```

3. 创建/更新 ORM 模型文件

4. 更新 `models/__init__.py` 导出所有模型

5. 更新 `alembic/env.py` 添加模型导入

6. 生成 initial migration（以空库为对比基线）
   ```bash
   cd backend && uv run alembic revision --autogenerate -m "initial schema"
   ```

7. 为所有新建 `public` 业务表补齐 RLS（`SELECT/INSERT/UPDATE/DELETE` policy）
   - 每张表都执行 `ENABLE ROW LEVEL SECURITY`
   - 每张表都显式创建 `SELECT/INSERT/UPDATE/DELETE` policy
   - downgrade 必须可逆，不得弱化既定安全边界

8. 执行迁移
   ```bash
   cd backend && uv run alembic upgrade head
   ```

9. 更新测试文件适配新表结构

## I. 数据库表名规范与审计

### 1) 命名规范（统一执行）
- 使用 `snake_case`
- 业务表统一使用复数名词（如 `profiles`, `friendships`, `automation_jobs`）
- 关联表使用 `<主实体复数>_<从实体复数>` 或约定俗成复数短语（如 `group_members`, `todo_sources`）
- 禁止过于泛化的表名（如 `messages`, `sessions`），必须带业务前缀
- 存量历史表允许短期例外，但必须在审计表中登记并给出迁移计划
- 缩写保持一致：LLM 统一使用 `llm` 前缀，不混用 `model`/`llm` 两套命名

### 2) 表名审计结果

| 当前表名 | 审计结论 | 建议表名 | 说明 |
|----------|----------|----------|------|
| `profiles` | 通过 | - | 符合复数名词规范 |
| `user_agents` | 通过 | - | 语义清晰 |
| `memories` | 通过 | - | 语义清晰 |
| `friendships` | 通过 | - | 关系表命名清晰 |
| `groups` | 通过 | - | 符合规范 |
| `group_members` | 通过 | - | 关联表命名清晰 |
| `schedule_items` | 通过 | - | 语义清晰 |
| `schedule_subscriptions` | 通过 | - | 语义清晰 |
| `inbox_messages` | 通过 | - | 带业务前缀，避免歧义 |
| `todos` | 通过 | - | 简洁且清晰 |
| `todo_sources` | 通过 | - | 关联关系明确 |
| `automation_jobs` | 通过 | - | 语义清晰 |
| `llms` | 通过 | - | 与 LLM 语义一致 |
| `llm_factory` | 建议调整 | `llm_factories` | 当前为单数，建议改复数以统一规范 |
| `sessions` | 建议调整 | `agent_chat_sessions` | 过于泛化，建议加业务前缀 |
| `messages` | 建议调整 | `agent_chat_messages` | 过于泛化，建议加业务前缀 |

### 3) 落地建议
- 本期命名边界：不重命名 `llm_factory/sessions/messages`，仅在新表严格执行命名规范
- 本期最小可行：先保持现有表名可运行，新增表全部遵循规范
- 下期统一治理：通过一次性迁移将 `llm_factory/sessions/messages` 重命名到规范名
- 若本期直接重命名，需同步 ORM 模型、外键、索引、RLS policy 名称与运行文档
