9.1 KiB
9.1 KiB
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 |
多对多 wiring:agent↔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_scopecontainer_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 对象:
// 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)
- 如果不存在则创建
schema_version表。唯一性在name,不在version - 读
SELECT name FROM schema_version到Set<string>—— 去重 key 是name,不是version。这允许模块迁移使用任意版本号 migrations.filter(m => !applied.has(m.name))得到待执行列表- 在
db.transaction()中运行每个待执行迁移:- 调
m.up(db)执行 DDL - 计算
next = MAX(version) + 1 - 插入
schema_version
- 调
加一张表
-
创建
src/db/migrations/016-your-table.ts: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 (...)`); }, }; -
在
src/db/migrations/index.ts中:- 添加
import { migration016 } from './016-your-table.js'; - 追加到
migrations数组
- 添加
加一个字段
同理,写一个 ALTER TABLE 的迁移。复杂变更(回填)做 JS 行级更新。幂等守护模式(迁移 012,line 29-32):ALTER 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表列等)