Files
nanoclaw/docs/answers/06-data-model.md

168 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Q16-Q17: 数据模型
---
## Q16: 中央库 `data/v2.db` 有哪些表?它们之间的外键关系是怎样的?
### 答案
中央库有约 20 张表,横跨多次迁移。以下按逻辑分类:
### 核心实体表(迁移 001
| # | 表 | 用途 | 关键 FK 关系 |
|---|-----|------|-------------|
| 1 | `agent_groups` | Agent 工作区folder、skills、CLAUDE.md | 根实体——无 FK 出 |
| 2 | `messaging_groups` | 一个平台聊天/频道/DM | 根实体;`UNIQUE(channel_type, platform_id)` |
| 3 | `messaging_group_agents` | 多对多 wiringagent↔channel | `→ messaging_groups(id)`, `→ agent_groups(id)``UNIQUE(messaging_group_id, agent_group_id)` |
| 4 | `users` | 平台用户身份(`<channel>:<handle>` | 根实体 |
| 5 | `user_roles` | 特权授予owner/admin | `→ users(id)` (x2: user + granted_by), `→ agent_groups(id)``PK (user_id, role, agent_group_id)` |
| 6 | `agent_group_members` | 非特权访问门 | `→ users(id)` (x2), `→ agent_groups(id)``PK (user_id, agent_group_id)` |
| 7 | `user_dms` | 冷 DM 缓存 | `→ users(id)`, `→ messaging_groups(id)``PK (user_id, channel_type)` |
| 8 | `sessions` | Session 生命周期元数据 | `→ agent_groups(id)`, `→ messaging_groups(id)` |
| 9 | `pending_questions` | 交互式问题状态 | `→ sessions(id)` |
### 后续迁移添加的表
| # | 表 | 迁移 | 说明 |
|---|-----|------|------|
| 10 | `chat_sdk_kv` | 002 | Chat SDK 不透明状态 |
| 11 | `chat_sdk_subscriptions` | 002 | Chat SDK 线程订阅 |
| 12 | `chat_sdk_locks` | 002 | Chat SDK 分布式锁 |
| 13 | `chat_sdk_lists` | 002 | Chat SDK 列表状态 |
| 14 | `pending_approvals` | module-003 | `→ sessions(id)`, `→ agent_groups(id)` |
| 15 | `agent_destinations` | module-004 | `→ agent_groups(id)``PK (agent_group_id, local_name)` |
| 16 | `unregistered_senders` | 008 | 审计跟踪;`PK (channel_type, platform_id)` |
| 17 | `container_configs` | 014 | `→ agent_groups(id) ON DELETE CASCADE` |
| 18 | `pending_sender_approvals` | 011 | `→ messaging_groups(id)`, `→ agent_groups(id)``UNIQUE(messaging_group_id, sender_identity)` |
| 19 | `pending_channel_approvals` | 012 | `→ messaging_groups(id)`, `→ agent_groups(id)` |
| 20 | `schema_version` | 内建 | 迁移分类账——无 FK |
### 已删除的表
- `pending_credentials` — 在迁移 009 中删除(已废弃)
### 列级变更
- `agent_groups.denied_at` — 迁移 012 添加
- `messaging_group_agents` 的 4 列(`engage_mode``engage_pattern``sender_scope``ignored_message_policy`)— 迁移 010 添加,替换旧的 `trigger_rules` + `response_scope`
- `container_configs.cli_scope` — 迁移 015 添加
### ER 关系图
```
agent_groups ──────────────────────────────────────────────────────────────────────────┐
│ │
├── messaging_group_agents ──→ messaging_groups ◄── user_dms │
│ (UNIQUE pair) │
│ │
├── sessions ──→ messaging_groups │
│ └── pending_questions │
│ │
├── user_roles (privilege: owner/admin, scoped or global) ──→ users (user_id, granted_by)
│ │
├── agent_group_members (unprivileged access gate) ──→ users (user_id, added_by) │
│ │
├── agent_destinations (ACL + name→target routing map) │
│ │
├── container_configs (1:1, ON DELETE CASCADE) │
│ │
├── pending_approvals │
├── pending_sender_approvals │
└── pending_channel_approvals │
users ────────────────────────────────────────────────────────────────────────────────┐
├── user_roles (user_id, granted_by 都自引用 users) │
├── agent_group_members │
└── user_dms │
schema_version, chat_sdk_*, unregistered_senders — 无 FK独立叶子表
```
### 关键不变式
- **Owner 必须全局:** `role='owner'` 意味着 `user_roles``agent_group_id IS NULL``grantRole()` 中强制
- **Admin 隐含 membership** agent group A 的 admin 自动是 member——不需要 `agent_group_members`
- **`agent_destinations` 双重角色:** 既是路由表也是 ACL。无行 = 未授权发送。源是中央库spawn 时投影写入容器内 `inbound.db`
- **Chat SDK 表是不透明的**NanoClaw 代码很少直接操作它们——由 `state-sqlite.ts` 持有
- **Session DB 是分离的**`inbound.db` / `outbound.db``data/v2-sessions/<session_id>/` 下,用 `CREATE TABLE IF NOT EXISTS` 做前向兼容
---
## Q17: DB 迁移怎么组织?我要加一张表或一个字段该改哪些文件?
### 答案
### 迁移文件组织
每个迁移是 `src/db/migrations/` 下的一个文件,导出 `Migration` 对象:
```typescript
// src/db/migrations/index.ts:18-22
interface Migration {
version: number; // 在 barrel 数组中的排序提示
name: string; // UNIQUE name — schema_version 中的去重 key
up: (db: Database) => void; // 在事务中运行
}
```
### 迁移注册顺序(`migrations/index.ts:24-38`
```
001-initial.ts → 核心表
002-chat-sdk-state.ts → Chat SDK 表
module-approvals-pending-approvals.ts → pending_approvals
module-agent-to-agent-destinations.ts → agent_destinations
module-approvals-title-options.ts → ALTER pending_approvals
008-dropped-messages.ts → unregistered_senders
009-drop-pending-credentials.ts → DROP pending_credentials
010-engage-modes.ts → ALTER messaging_group_agents
011-pending-sender-approvals.ts
012-channel-registration.ts
013-approval-render-metadata.ts
014-container-configs.ts
015-cli-scope.ts
```
005 和 006 编号空着——早前重编号的。
### 注册机制(`runMigrations``index.ts:40-77`
1. 如果不存在则创建 `schema_version` 表。唯一性在 `name`,不在 `version`
2.`SELECT name FROM schema_version``Set<string>` —— **去重 key 是 `name`**,不是 `version`。这允许模块迁移使用任意版本号
3. `migrations.filter(m => !applied.has(m.name))` 得到待执行列表
4.`db.transaction()` 中运行每个待执行迁移:
-`m.up(db)` 执行 DDL
- 计算 `next = MAX(version) + 1`
- 插入 `schema_version`
### 加一张表
1. 创建 `src/db/migrations/016-your-table.ts`
```typescript
import type Database from 'better-sqlite3';
import type { Migration } from './index.js';
export const migration016: Migration = {
version: 16,
name: 'your-table', // 必须在所有迁移中全局唯一
up(db: Database) {
db.exec(`CREATE TABLE IF NOT EXISTS your_table (...)`);
},
};
```
2. 在 `src/db/migrations/index.ts` 中:
- 添加 `import { migration016 } from './016-your-table.js';`
- 追加到 `migrations` 数组
### 加一个字段
同理,写一个 ALTER TABLE 的迁移。复杂变更(回填)做 JS 行级更新。**幂等守护模式**(迁移 012line 29-32ALTER TABLE ADD COLUMN 前先 `PRAGMA table_info()` 检查列是否已存在。
### 边界情况 / Gotchas
- **DROP TABLE 中的 FK 完整性:** 迁移事务中 DROP TABLE 会触发 SQLite FK 完整性检查。不能 toggle `PRAGMA foreign_keys`。优先 ALTER TABLE ADD COLUMN
- **模块迁移用任意版本:** `module-` 前缀文件的 `name` 与文件名不同——`name` 保持稳定,防止重命名后重新运行
- **Session DB 迁移是独立的:** Session DB schema`INBOUND_SCHEMA`、`OUTBOUND_SCHEMA`)用 `CREATE TABLE IF NOT EXISTS`,新列通过惰性迁移 helper`migrateDeliveredTable()` 等)落地,不跟踪 `schema_version`
- **Container 端也有自己的迁移**`container/agent-runner/src/db/connection.ts:86-110` 有惰性 session DB 迁移(添加 `on_wake` 列、`delivered` 表列等)