8.3 KiB
8.3 KiB
DATA_MODEL.md 的核心目标是:让 AI 在触碰任何数据相关代码时,都能理解业务语义,而不只是看到一堆字段名。
必须包含的内容
1. 领域概览(Domain Overview)
用业务语言(不是技术语言)描述核心概念和它们的关系。这是 AI 理解"为什么"的基础。
md
## Domain Overview
This is a **multi-tenant** project management tool.
Core concepts:
- **Workspace**: The top-level container, maps to a paying customer (company)
- **Project**: Lives inside a Workspace, has a lifecycle (draft → active → archived)
- **Task**: The atomic unit of work, always belongs to a Project
- **Member**: A User's role within a specific Workspace (not global)
Key business rules:
- A User can belong to multiple Workspaces with different roles
- Deleting a Project soft-deletes all its Tasks (never hard delete)
- Only Workspace `owner` or `admin` can invite new Members
2. 实体关系图(Entity Relationship)
用文字或 ASCII 图表达清楚关系,不要依赖读者去脑补。
md
## Entity Relationships
Workspace (1) ──── (N) Project
Workspace (1) ──── (N) Member
Member (N) ──── (1) User ← same User, different roles per Workspace
Project (1) ──── (N) Task
Task (N) ──── (1) Member ← assignee
Task (1) ──── (N) Comment
Task (N) ──── (N) Tag ← via task_tags join table
3. 完整 Schema 定义(Schema Definition)
每个表都需要:字段 + 类型 + 约束 + 业务注释。注释是给 AI 的关键上下文。
md
## Schema
### workspaces
| Column | Type | Constraints | Notes |
|--------------|-------------|-------------------|--------------------------------|
| id | uuid | PK, default gen | |
| slug | text | UNIQUE, NOT NULL | URL identifier, immutable after creation |
| name | text | NOT NULL | |
| plan | text | NOT NULL | enum: 'free' | 'pro' | 'enterprise' |
| created_at | timestamptz | NOT NULL, default now() | |
| deleted_at | timestamptz | nullable | soft delete, filter WHERE deleted_at IS NULL |
### members
| Column | Type | Constraints | Notes |
|--------------|-------------|-------------------|--------------------------------|
| id | uuid | PK | |
| workspace_id | uuid | FK → workspaces.id, CASCADE DELETE | |
| user_id | uuid | FK → users.id | |
| role | text | NOT NULL | enum: 'owner' \| 'admin' \| 'member' \| 'viewer' |
| invited_by | uuid | FK → members.id, nullable | null = founding owner |
| joined_at | timestamptz | nullable | null = invitation pending |
-- UNIQUE(workspace_id, user_id) — one membership per workspace per user
4. 枚举与常量(Enums & Constants)
把所有枚举值集中在一处,AI 就不会自己发明状态值。
md
## Enums & Constants
### Task Status (ordered, represents workflow progression)
pending → in_progress → in_review → done → cancelled
Rules:
- Only forward transitions are allowed (no reopening cancelled tasks)
- `done` and `cancelled` are terminal states
- Changing status logs an entry in task_activity
### Member Role Permissions
| Action | owner | admin | member | viewer |
|---------------------|-------|-------|--------|--------|
| Invite members | ✅ | ✅ | ❌ | ❌ |
| Delete project | ✅ | ❌ | ❌ | ❌ |
| Create tasks | ✅ | ✅ | ✅ | ❌ |
| Comment on tasks | ✅ | ✅ | ✅ | ✅ |
5. 索引策略(Indexes)
告诉 AI 哪些查询是热路径,避免它写出全表扫描的代码。
md
## Indexes
-- tasks is the most queried table, optimize for these patterns:
CREATE INDEX idx_tasks_project_status ON tasks(project_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_assignee ON tasks(assignee_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_members_user ON members(user_id); -- "get all workspaces for a user"
-- Avoid: never query tasks without a project_id filter
-- Avoid: never do SELECT * on tasks, always select specific columns
6. 软删除约定(Soft Delete Convention)
如果使用软删除,必须明确说明规则,否则 AI 会写出漏掉过滤条件的查询。
md
## Soft Delete Convention
Tables with soft delete: workspaces, projects, tasks
Rules:
- ALL queries MUST include `WHERE deleted_at IS NULL` unless explicitly retrieving deleted records
- Use `deletedAt: new Date()` to soft delete, never use DELETE SQL
- Cascading: deleting a Project sets deleted_at on all its Tasks in the same transaction
- Deleted records are purged by a cron job after 30 days (hard delete)
⚠️ AI Note: Never generate a query on these tables without the soft delete filter.
7. 多租户隔离规则(Multi-tenancy Rules)
这是安全的核心,必须让 AI 理解每次查询都要带租户过滤。
md
## Multi-tenancy & Data Isolation
This is a **workspace-scoped** multi-tenant system.
Rules:
- EVERY query involving projects, tasks, members MUST be scoped by workspace_id
- Never query tasks directly by id alone — always join through project → workspace
- The workspace_id must come from the authenticated session, never from user input
- Row Level Security (RLS) is enabled on Supabase for all tenant tables
Correct pattern:
SELECT * FROM tasks
JOIN projects ON tasks.project_id = projects.id
WHERE projects.workspace_id = :workspaceId ← always present
AND tasks.id = :taskId
Wrong pattern (security hole):
SELECT * FROM tasks WHERE id = :taskId ← missing tenant scope
8. 核心查询模式(Common Query Patterns)
把最常用的查询写出来,AI 会直接复用,而不是重新发明。
md
## Common Query Patterns
### Get tasks for a project (with assignee info)
```sql
SELECT t.*, m.user_id, u.name as assignee_name
FROM tasks t
LEFT JOIN members m ON t.assignee_id = m.id
LEFT JOIN users u ON m.user_id = u.id
WHERE t.project_id = :projectId
AND t.deleted_at IS NULL
ORDER BY t.created_at DESC;
```
### Check if user has permission in workspace
```sql
SELECT role FROM members
WHERE workspace_id = :workspaceId
AND user_id = :userId
AND joined_at IS NOT NULL; -- pending invitations don't count
```
9. 明确禁止的操作(Forbidden Operations)
md
## Forbidden Operations
- ❌ Never hard DELETE on workspaces, projects, or tasks — use soft delete
- ❌ Never UPDATE a task's status by skipping intermediate states
- ❌ Never expose internal UUIDs in URLs — use slugs for workspaces/projects
- ❌ Never store user PII (email, name) in tasks or comments — always join from users table
- ❌ Never write a migration that drops a column without a deprecation period
10. 数据迁移约定(Migration Convention)
md
## Migration Convention
- Migration tool: Drizzle Kit
- File naming: `0001_create_workspaces.ts`, sequential, never rename
- Every migration must be reversible (include `down` function)
- Never edit a migration that has been merged to main
- Backfill scripts go in /scripts, not in migrations
内容优先级总结
| 优先级 | 内容 | 为什么关键 |
|---|---|---|
| 🔴 必须 | 领域概览 + 业务规则 | AI 需要理解"为什么"才能做正确取舍 |
| 🔴 必须 | 完整 Schema + 字段注释 | 防止字段命名歧义和类型错误 |
| 🔴 必须 | 软删除 + 多租户规则 | 安全漏洞和数据污染的高发区 |
| 🟡 重要 | 枚举常量 + 状态机 | 防止 AI 自己发明状态值 |
| 🟡 重要 | 常用查询模式 | 复用 > 重新发明 |
| 🟢 加分 | 索引策略 | 引导 AI 写出性能友好的查询 |
| 🟢 加分 | 迁移约定 | 保持 schema 演进的可控性 |
核心原则:DATA_MODEL.md 写的不是给数据库看的 DDL,而是给 AI 看的业务契约。每一个"显而易见"的业务规则,在 AI 眼里都是需要被明确告知的约束。没写的,它都会自己猜。