Files
JobRadar/.craftkit/designs/engineering-baseline/database-design.md
T

241 lines
11 KiB
Markdown
Raw 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.
---
reviewStatus: pending
reviewedAt: null
replacedBy: null
---
# 第一阶段工程基线数据库设计
## 1. 设计边界
本文档定义第一阶段用户资料和 Agent 运行审计数据的逻辑结构、约束、索引、迁移与回滚策略。开发和自动化测试使用 SQLite,集成与生产使用 PostgreSQL。
项目尚未确认 PostgreSQL 精确版本,因此本文档不采用版本专属字段、索引方法、表达式索引或方言 SQL。实现以 Django 6.0.8 ORM 和迁移能力表达;版本专属优化须在 PostgreSQL 版本确认后另行评审。
关联设计:
- [后端落地设计](backend-design.md)
- [前端落地设计](frontend-design.md)
- [接口与页面契约设计](interface-design.md)
## 2. 设计原则
- 用户私有数据必须具有明确的用户归属。
- 状态转换由服务层执行,数据库约束负责守住唯一性和基础合法性。
- 运行事件采用追加模式,保留审计轨迹。
- JSON 字段只保存经过脱敏和大小限制的摘要。
- 时间统一存储为支持时区的 Django 时间值,显示时转换到用户时区。
- 金额、token 和耗时使用确定类型,避免浮点误差。
- 表名、约束名和索引名在迁移中显式稳定,避免不同数据库自动命名差异影响排查。
## 3. 实体关系
```mermaid
erDiagram
AUTH_USER ||--o| USER_PROFILE : has
AUTH_USER ||--o{ AGENT_RUN : owns
AUTH_USER ||--o{ MODEL_PROVIDER_CONFIG : owns
AGENT_RUN ||--o{ AGENT_RUN_EVENT : contains
AGENT_RUN ||--o{ TOOL_CALL : contains
AGENT_RUN ||--o{ HUMAN_APPROVAL : requests
AUTH_USER ||--o{ HUMAN_APPROVAL : resolves
```
## 4. 表结构草案
### 4.1 `accounts_user_profile`
| 字段 | Django 类型建议 | 空值 | 约束与说明 |
| --- | --- | --- | --- |
| `id` | `BigAutoField` | 否 | 主键 |
| `user_id` | `OneToOneField` | 否 | 唯一,关联 Django 用户 |
| `display_name` | `CharField` | 是 | 空字符串表示未设置,长度上限实现时确认 |
| `timezone` | `CharField` | 否 | 默认 `Asia/Shanghai`,必须来自受控候选 |
| `created_at` | `DateTimeField` | 否 | 创建时写入 |
| `updated_at` | `DateTimeField` | 否 | 修改时更新 |
删除策略:用户尚无审计数据时资料可随用户删除;用户存在 Agent Run 时由运行记录的保护关系阻止用户物理删除。
### 4.2 `agent_runtime_agent_run`
| 字段组 | 字段 | 类型建议 | 说明 |
| --- | --- | --- | --- |
| 标识 | `id` | `UUIDField` | 主键,避免在页面暴露连续编号 |
| 归属 | `owner_id` | `ForeignKey(PROTECT)` | 用户私有归属 |
| 展示 | `title` | `CharField` | 简短标题 |
| 状态 | `status` | `CharField` | 使用代码枚举和数据库检查约束 |
| 并发 | `lock_version` | `PositiveBigIntegerField` | 每次状态写入递增 |
| 摘要 | `input_summary` | `JSONField` | 脱敏、限长输入摘要 |
| 摘要 | `output_summary` | `JSONField` | 可空,脱敏输出摘要 |
| 版本 | `agent_name` | `CharField` | Agent 稳定标识 |
| 版本 | `agent_version` | `CharField` | 可空 |
| 版本 | `instruction_version` | `CharField` | 可空 |
| 版本 | `toolset_version` | `CharField` | 可空 |
| 模型 | `model_name` | `CharField` | 第一阶段允许为空 |
| 追踪 | `trace_id` | `CharField` | 可空,非秘密标识 |
| 时间 | `started_at` | `DateTimeField` | 可空 |
| 时间 | `finished_at` | `DateTimeField` | 可空 |
| 耗时 | `duration_ms` | `PositiveBigIntegerField` | 可空 |
| 用量 | `input_tokens` | `PositiveBigIntegerField` | 可空 |
| 用量 | `output_tokens` | `PositiveBigIntegerField` | 可空 |
| 费用 | `estimated_cost` | `DecimalField` | 可空,精度实现前确认 |
| 失败 | `error_code` | `CharField` | 可空、稳定机器码 |
| 失败 | `error_summary` | `TextField` | 可空、脱敏且限长 |
| 审计 | `created_at` | `DateTimeField` | 否 |
| 审计 | `updated_at` | `DateTimeField` | 否 |
检查约束至少保证:
- `status` 属于已定义枚举。
- `lock_version >= 0`。
- token、耗时和费用非负。
### 4.2.1 `agent_runtime_model_provider_config`
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `owner_id` | `ForeignKey(PROTECT)` | 用户私有归属 |
| `provider_code` | `CharField` | 对应代码中的服务商稳定编码 |
| `encrypted_api_key` | `TextField` | Fernet 密文,禁止通过页面或 Admin 展示 |
| `key_hint` | `CharField` | 仅保存末四位提示 |
| `status`、`enabled`、`is_default` | 状态字段 | 连接状态和当前默认选择 |
| `default_model_id` | `CharField` | 用户选择或预设推荐模型 |
| `available_models` | `JSONField` | 最近一次验证取得的模型标识快照 |
| `last_verified_at`、`last_error_code` | 审计字段 | 验证时间和稳定错误码 |
唯一约束为 `(owner_id, provider_code)`。API Key 加密主密钥只从部署环境读取,不进入数据库;主密钥轮换必须先完成密文重加密,不能直接替换后重启。
跨字段状态不变量仍由服务层验证,因为 SQLite 与 PostgreSQL 对复杂检查约束和时间语义的行为需要保持一致。
### 4.3 `agent_runtime_agent_run_event`
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | `BigAutoField` | 主键 |
| `run_id` | `ForeignKey(PROTECT)` | 关联 Run,审计事件不可级联丢失 |
| `sequence` | `PositiveBigIntegerField` | Run 内递增序号 |
| `event_type` | `CharField` | 事件枚举 |
| `summary` | `CharField` | 短摘要 |
| `payload_summary` | `JSONField` | 脱敏事件数据 |
| `occurred_at` | `DateTimeField` | 业务发生时间 |
| `created_at` | `DateTimeField` | 数据库记录时间 |
唯一约束:`(run_id, sequence)`。
索引:`(run_id, sequence)` 唯一索引已覆盖详情页时间线读取,无需重复创建同前缀普通索引。
### 4.4 `agent_runtime_tool_call`
| 字段组 | 字段 | 类型建议 | 说明 |
| --- | --- | --- | --- |
| 标识 | `id` | `BigAutoField` | 主键 |
| 归属 | `run_id` | `ForeignKey(PROTECT)` | 关联 Run |
| 调用 | `call_id` | `CharField` | Run 内调用标识 |
| 工具 | `tool_name` | `CharField` | 工具稳定名称 |
| 工具 | `tool_version` | `CharField` | 可空 |
| 幂等 | `idempotency_key` | `CharField` | 可空,作用域见约束 |
| 状态 | `status` | `CharField` | `started/succeeded/failed` |
| 摘要 | `arguments_summary` | `JSONField` | 脱敏、限长 |
| 摘要 | `result_summary` | `JSONField` | 可空、脱敏、限长 |
| 失败 | `error_code` | `CharField` | 可空 |
| 失败 | `error_summary` | `TextField` | 可空、限长 |
| 时间 | `started_at` | `DateTimeField` | 否 |
| 时间 | `finished_at` | `DateTimeField` | 可空 |
| 耗时 | `duration_ms` | `PositiveBigIntegerField` | 可空 |
| 审计 | `created_at`、`updated_at` | `DateTimeField` | 否 |
唯一约束:
- `(run_id, call_id)` 唯一。
- 非空幂等键的作用域在真实工具模型确定前采用 `(run_id, idempotency_key)`,后续如需跨 Run 幂等必须重新设计业务作用域。
### 4.5 `agent_runtime_human_approval`
| 字段 | 类型建议 | 说明 |
| --- | --- | --- |
| `id` | `UUIDField` | 主键 |
| `run_id` | `ForeignKey(PROTECT)` | 关联 Run |
| `request_key` | `CharField` | Run 内稳定请求键 |
| `approval_type` | `CharField` | 确认类型枚举 |
| `status` | `CharField` | 确认状态枚举 |
| `request_summary` | `JSONField` | 脱敏请求摘要 |
| `decision_summary` | `JSONField` | 可空、脱敏处理摘要 |
| `requested_at` | `DateTimeField` | 请求时间 |
| `resolved_at` | `DateTimeField` | 可空 |
| `resolved_by_id` | `ForeignKey(PROTECT)` | 可空,处理人 |
| `created_at`、`updated_at` | `DateTimeField` | 审计时间 |
唯一约束:`(run_id, request_key)`。
索引:`(status, requested_at)`,支持 Admin 查询待处理确认。
## 5. 主要访问模式与索引
| 访问模式 | 过滤与排序 | 索引建议 |
| --- | --- | --- |
| 当前用户 Run 列表 | `owner_id`,`created_at DESC` | `(owner_id, created_at)` |
| 当前用户按状态查询 | `owner_id`、`status`、`created_at DESC` | 第一阶段无筛选需求,暂不新增 |
| Run 事件时间线 | `run_id`、`sequence ASC` | 唯一索引 `(run_id, sequence)` |
| Run 工具调用列表 | `run_id`、`started_at ASC` | `(run_id, started_at)` |
| 待处理人工确认 | `status`、`requested_at ASC` | `(status, requested_at)` |
| trace 定位运行 | `trace_id` | 有真实查询需求后再决定唯一性和索引 |
索引以第一阶段真实页面和 Admin 查询为依据,不为未来假设查询提前增加索引。
## 6. 事务与并发
- Run 状态变更、状态事件和关联确认记录在同一事务内完成。
- 更新 Run 前锁定目标记录,并校验 `lock_version`。
- 事件序号在锁定 Run 后读取最大值并递增。
- 唯一约束捕获遗漏的并发冲突,并转换为领域冲突错误。
- 工具外部调用不持有数据库事务;开始和结束分别使用短事务。
- SQLite 不提供等价的行锁验证,PostgreSQL 集成测试必须覆盖双并发更新。
## 7. 数据生命周期
- 用户资料可修改,保留最新值即可。
- Agent Run、事件、工具调用和人工确认属于审计数据,第一阶段不提供删除入口。
- 用户存在运行记录时不得物理删除,改为停用账户。
- 第一阶段不自动清理日志或审计记录。
- 后续建立保留周期时必须区分运行摘要、完整原始证据和应用日志,不能使用同一删除策略。
## 8. 迁移顺序
1. 创建 `common` 基础抽象代码,不生成抽象表。
2. 创建 `accounts` 与 `UserProfile` 迁移。
3. 创建 `agent_runtime` 核心表和基础约束。
4. 单独迁移补充经验证的组合索引和检查约束。
5. 在空 SQLite 数据库执行完整迁移。
6. 确认 PostgreSQL 精确版本和隔离环境。
7. 在空 PostgreSQL 数据库执行完整迁移和核心约束测试。
迁移发布后不得直接修改历史迁移;修正通过新迁移完成。
## 9. 回滚策略
- 第一阶段没有存量业务数据转换,回滚以撤销未发布迁移为主。
- 已发布环境回滚前必须确认表内是否已产生审计数据。
- 存在数据时禁止直接反向删除表,应先停止写入、导出验证数据并制定显式迁移。
- 应用代码回滚必须与数据库迁移兼容,不能先删除旧代码仍会读取的字段。
## 10. 验证清单
- Django 迁移无遗漏。
- 空 SQLite 数据库全量迁移和反向迁移测试。
- 空 PostgreSQL 数据库全量迁移。
- 用户一对一资料唯一约束。
- Run 状态、非负数值和用户保护约束。
- 事件序号唯一约束。
- 工具调用编号和幂等键冲突。
- 人工确认请求键唯一约束。
- 两个并发状态更新只有一个成功。
- 主要列表查询使用预期索引;数据量不足时不声称完成性能验收。
## 11. 待确认项
- PostgreSQL 精确版本、部署方式和连接池策略。
- 摘要字段的最大序列化字节数。
- 字符字段长度、费用精度和稳定约束名称。
- trace 标识是否需要全局唯一。
- PostgreSQL 版本确认后是否需要部分索引或其他专属优化。