База данных — SQLite Schema
Общая информация
- Файл:
data/citadel.db
- Режим: WAL (Write-Ahead Logging) — параллельное чтение
- Foreign keys: включены
- Busy timeout: 5000ms
- Cache: 64MB
- Миграции:
shared/migrations/001-035
ER Diagram (Phase 52.1)
erDiagram
users ||--o{ projects : owns
users ||--|| account_settings : has
users ||--o{ recovery_keys : holds
users ||--o{ subscriptions : pays
users ||--o{ invites : creates
users ||--o{ chat_messages : sends
users ||--o{ trial_credits : claims
users ||--o{ activity_log : generates
projects ||--o{ project_phases : tracks
projects ||--o{ pinned_notes : pins
projects ||--o{ chat_messages : contains
projects ||--o{ timeline_events : records
projects ||--o{ github_links : links
projects ||--o{ skill_benchmarks : runs
projects ||--o{ marketplace_cache : caches
github_links ||--o{ github_events : receives
invites }o--|| users : "consumed by"
subscriptions ||--o{ stripe_events : "logs idempotency"
users {
TEXT id PK
TEXT email UK
TEXT password_hash
TEXT role "admin · user"
TEXT google_id UK
TEXT github_id UK
INTEGER email_verified
INTEGER two_factor_enabled
TEXT last_login
}
projects {
INTEGER id PK
TEXT technical_name
TEXT owner_id FK
TEXT display_name
TEXT notebook_id "NotebookLM"
INTEGER is_archived
}
chat_messages {
INTEGER id PK
TEXT project_name
TEXT worker_id
TEXT role "user · assistant · system"
TEXT content "AES-256-GCM if encrypted=1"
INTEGER encrypted
TEXT timestamp
}
subscriptions {
TEXT user_id PK
TEXT plan "free · min · max · beta"
TEXT stripe_customer_id
TEXT status
}
invites {
TEXT code PK "arc-XXXX-XXXX"
TEXT created_by FK
TEXT used_by FK
TEXT parent_invite_code
TEXT status "active · used · revoked"
}
recovery_keys {
INTEGER id PK
TEXT user_id FK
TEXT encrypted_key
TEXT key_hint "first 4 chars"
}
trial_credits {
TEXT email PK
INTEGER credits_used
TEXT first_used_at
}
23 таблицы в 24 миграциях. Auth + проекты + данные workspace + интеллект-слой + billing + бета-gating + аудит контекстного экспорта. Все FK-ограничения соблюдены.
Таблицы
users — Пользователи
| Колонка |
Тип |
Описание |
| id |
TEXT PK |
UUID |
| email |
TEXT UNIQUE |
Email (обязательный) |
| password_hash |
TEXT |
Хеш пароля (bcrypt) |
| role |
TEXT |
'admin' или 'user' |
| name |
TEXT |
Имя |
| avatar_url |
TEXT |
URL аватара |
| google_id |
TEXT UNIQUE |
Google OAuth ID |
| github_id |
TEXT UNIQUE |
GitHub OAuth ID |
| email_verified |
INTEGER |
0/1 |
| two_factor_enabled |
INTEGER |
0/1 |
| totp_secret |
TEXT |
TOTP-секрет (2FA) |
| last_login |
TEXT |
ISO 8601 |
| created_at |
TEXT |
ISO 8601 |
projects — Проекты
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| technical_name |
TEXT |
Техническое имя (уникальное per owner) |
| owner_id |
TEXT FK→users |
Владелец |
| display_name |
TEXT |
Отображаемое имя |
| description |
TEXT |
Описание |
| color |
TEXT |
Цвет (hex) |
| icon |
TEXT |
Иконка |
| is_archived |
INTEGER |
0/1 |
| sort_order |
INTEGER |
Порядок (default 999) |
| notebook_id |
TEXT |
NotebookLM ID |
| project_protocol |
TEXT |
Протокол проекта |
| created_at, updated_at |
TEXT |
Timestamps |
UNIQUE(technical_name, owner_id)
account_settings — Настройки аккаунта
| Колонка |
Тип |
Описание |
| user_id |
TEXT PK FK→users |
|
| anthropic_key |
TEXT |
Anthropic API ключ |
| openai_key |
TEXT |
OpenAI API ключ |
| name |
TEXT |
Имя |
| role |
TEXT |
Роль |
| auto_harvest |
INTEGER |
Автосбор навыков 0/1 |
chat_messages — История чатов
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| project_name |
TEXT |
Проект |
| worker_id |
TEXT |
Воркер |
| role |
TEXT |
'user', 'assistant', 'system' |
| content |
TEXT |
Текст сообщения (зашифровано AES-256-GCM если encrypted=1) |
| encrypted |
INTEGER |
0=plaintext, 1=vault-encrypted (Phase 45.3) |
| attachments |
TEXT |
JSON-массив |
| timestamp |
TEXT |
ISO 8601 |
| metadata |
TEXT |
JSON-объект |
INDEX(project_name, worker_id, timestamp DESC)
recovery_keys — Ключи восстановления (Phase 45.4)
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| user_id |
TEXT FK→users |
Владелец |
| encrypted_key |
TEXT |
Зашифрованный master key |
| key_hint |
TEXT |
Подсказка (первые 4 символа) |
| created_at |
TEXT |
ISO 8601 |
| used_at |
TEXT |
Когда использован |
| revoked |
INTEGER |
0=активный, 1=отозван |
INDEX(user_id)
github_links — Привязки GitHub repo (Phase 49.3)
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| project_name |
TEXT |
Arc OS project |
| owner |
TEXT |
GitHub repo owner |
| repo |
TEXT |
GitHub repo name |
| webhook_secret |
TEXT UNIQUE |
32-байтный hex для HMAC-SHA256 validation |
| created_at |
TEXT |
ISO 8601 |
| created_by |
TEXT FK→users |
Кто создал link |
UNIQUE(project_name, owner, repo) — multi-repo per project allowed.
INDEX(project_name), INDEX(webhook_secret)
github_events — Лог GitHub-событий (Phase 49.3.1)
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| link_id |
INTEGER FK→github_links |
CASCADE on link delete |
| project_name |
TEXT |
Arc OS project (denormalized для query speed) |
| event_type |
TEXT |
push, pull_request, workflow_run, issues |
| action |
TEXT |
sub-action (opened/closed/success/failure) |
| summary |
TEXT |
Pre-formatted display string |
| url |
TEXT |
GitHub deep link |
| actor |
TEXT |
GitHub username |
| created_at |
TEXT |
ISO 8601 |
INDEX(project_name, created_at DESC), INDEX(link_id)
skills_global — Глобальные скилы
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| name |
TEXT UNIQUE |
Название |
| description |
TEXT |
Описание |
| category |
TEXT |
Категория (default 'general') |
| content |
TEXT |
Содержимое скила (markdown) |
| triggers |
TEXT |
JSON-массив триггеров |
| keywords |
TEXT |
JSON-массив ключевых слов |
| eval_rules |
TEXT |
JSON-массив правил |
| tool_code_ts |
TEXT |
TypeScript-реализация |
| version |
INTEGER |
Версия (auto-increment) |
| status |
TEXT |
'active', 'draft', 'deprecated', 'archived' |
skills_project_forks — Форки скилов
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
Auto |
| project_name |
TEXT |
Проект |
| skill_id |
INTEGER FK→skills_global |
Родительский скил (CASCADE) |
| content |
TEXT |
Кастомизированное содержимое |
| triggers, keywords, eval_rules, tool_code_ts |
TEXT |
Переопределения |
| version |
INTEGER |
Версия форка |
UNIQUE(project_name, skill_id)
skill_evolution_logs — История изменений скилов
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| skill_id |
INTEGER FK |
CASCADE |
| project_name |
TEXT |
Проект (nullable) |
| action |
TEXT |
'created', 'modified', 'applied', 'reverted' |
| diff_summary |
TEXT |
Описание изменений |
| author |
TEXT |
Автор (default 'system') |
| metadata |
TEXT |
JSON |
skill_update_requests — PR скилов (Sage)
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| skill_id |
INTEGER FK |
CASCADE |
| proposed_by |
TEXT |
'sage' (default) |
| status |
TEXT |
'pending', 'approved', 'rejected', 'applied' |
| current_content |
TEXT |
Текущая версия |
| proposed_content |
TEXT |
Предложенная версия |
| reason |
TEXT |
Причина изменения |
skill_benchmarks — A/B тесты скилов
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| request_id |
INTEGER FK→skill_update_requests |
CASCADE |
| test_scenario |
TEXT |
Сценарий теста |
| old_output, new_output |
TEXT |
Результаты |
| score_old, score_new |
REAL |
Оценки |
| judgment_reason |
TEXT |
Обоснование |
pinned_notes — Закреплённые заметки
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| project_name |
TEXT |
Проект |
| worker_id |
TEXT |
Воркер |
| title |
TEXT |
Заголовок |
| body |
TEXT |
Текст |
| source_message_id |
INTEGER |
Ссылка на chat_messages |
project_phases — Фазы Roadmap
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| project_name |
TEXT |
Проект |
| phase_id |
INTEGER |
Номер фазы |
| phase_title |
TEXT |
Название |
| status |
TEXT |
'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED' |
| progress |
REAL |
0.0–1.0 |
| deadline |
TEXT |
ISO 8601 |
UNIQUE(project_name, phase_id)
activity_log — Журнал активности
| Колонка |
Тип |
Описание |
| id |
INTEGER PK |
|
| project_name |
TEXT |
Проект |
| actor |
TEXT |
Воркер или пользователь |
| event_type |
TEXT |
Тип события |
| title |
TEXT |
Описание |
| metadata |
TEXT |
JSON |
| created_at |
TEXT |
ISO 8601 |
INDEX(created_at DESC), INDEX(project_name, created_at DESC), INDEX(event_type, created_at DESC), INDEX(actor, created_at DESC)
system_configs — Системные настройки
| Колонка |
Тип |
Описание |
| key |
TEXT PK |
Ключ |
| value |
TEXT |
Значение |
| description |
TEXT |
Описание |
marketplace_analysis_cache — Кеш marketplace
Кеш анализа совместимости скилов из marketplace.
ephemeral_tokens — Короткоживущие токены (issue #27)
Persistent-хранилище для одноразовых токенов: OAuth state, password reset, email verification. До миграции 022 эти токены жили в in-memory Map и терялись при каждом рестарте master, ломая Google/GitHub login (invalid_state).
| Колонка |
Тип |
Описание |
| token |
TEXT PK |
32-байтный hex (randomBytes(32)) |
| type |
TEXT NOT NULL |
oauth_state | password_reset | email_verification |
| payload |
TEXT NOT NULL |
JSON: {provider} для oauth_state, {email} для остальных |
| expires_at |
INTEGER NOT NULL |
ms epoch — TTL: 10 мин (oauth) / 30 мин (reset) / 24 ч (verify) |
INDEX(type), INDEX(expires_at). Одноразовое использование: consume() в одной транзакции читает payload и удаляет строку.
project_issues — Задачи per project (issue #53, Phase 53.14)
SQLite SSOT для задач вместо per-project issues/issues.json. Файл остаётся на диске как derived export (mirror), gitignored — DB авторитативна.
| Колонка |
Тип |
Описание |
| project_name |
TEXT NOT NULL |
name из registry; составная PK вместе с id |
| id |
INTEGER NOT NULL |
1-based per-project sequence (max+1 при insert) |
| title |
TEXT NOT NULL |
заголовок задачи |
| body |
TEXT NOT NULL DEFAULT '' |
описание, markdown |
| priority |
TEXT NOT NULL DEFAULT 'P2' |
P0 | P1 | P2 | P3 |
| labels |
TEXT NOT NULL DEFAULT '[]' |
JSON array of strings |
| status |
TEXT NOT NULL DEFAULT 'open' |
open | closed |
| created_at |
TEXT NOT NULL |
ISO timestamp |
| updated_at |
TEXT NOT NULL |
ISO timestamp |
| closed_at |
TEXT NULL |
ISO timestamp при status=closed |
| activity |
TEXT NOT NULL DEFAULT '[]' |
JSON array {ts, type, author, text} — append-only audit |
PK (project_name, id) — у каждого проекта свой независимый id-пространство. INDEX (project_name, status) — самый частый фильтр (open only). Operations live in issueQueries (shared/db.ts): list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll — атомарная транзакция; bulkImport — INSERT OR IGNORE для idempotent re-seed.
onboarding_progress — Состояние post-wizard чеклиста (issue #56, Phase 54.1)
Per-user derived cache над event stream из activity_log (SSOT). UI читает одним запросом вместо агрегации по событиям. Replayable: dismiss/replay не меняют state, только dismissed_at.
| Колонка |
Тип |
Описание |
| chat_id |
TEXT PRIMARY KEY |
user id (registry chat id) |
| state |
TEXT NOT NULL DEFAULT '{}' |
JSON {workers, cli, skill, bot, issue} → completed | skipped (отсутствующие ключи = pending) |
| completed_count |
INTEGER NOT NULL DEFAULT 0 |
derived cache count of completed steps (0–5) |
| started_at |
TEXT NOT NULL DEFAULT (datetime('now')) |
создание строки (первое событие/dismiss/replay) |
| completed_at |
TEXT NULL |
ISO timestamp когда все 5 steps completed |
| dismissed_at |
TEXT NULL |
ISO timestamp когда пользователь закрыл чеклист |
| source |
TEXT NOT NULL DEFAULT 'web' |
web | cli — для funnel attribution (#58 arc tour) |
| updated_at |
TEXT NOT NULL DEFAULT (datetime('now')) |
mutation timestamp |
INDEX на completed_at для funnel-аналитики (#61 Phase 54.6). Operations live in onboardingQueries (shared/db.ts): getProgress/recordEvent/dismiss/replay. recordEvent идемпотентен на (chat_id, step, status) — повторный идентичный вызов возвращает changed=false и не пишет в activity_log. Whitelist: 5 steps × 2 statuses (completed, skipped). skipped НЕ инкрементирует completed_count. Переход skipped → completed добавляет к count. Все мутации эмитируют события в activity_log с event_type LIKE 'onboarding_%' — настоящий SSOT для funnel-метрик; колонки таблицы — derived cache.
platform_audit_log — Super-admin trail ротации секретов (Phase 57, Sentinel #103)
Append-only журнал мутаций platform-secrets через vault в Platform Settings UI. SSOT для post-incident forensics («какой admin ротировал Anthropic key в 03:14 UTC?»). Vault.json сам этого не пишет — только значения.
| Колонка |
Тип |
Описание |
| id |
INTEGER PRIMARY KEY AUTOINCREMENT |
временной порядок даже при clock skew |
| ts |
TEXT NOT NULL DEFAULT (datetime('now')) |
server-side UTC; attacker не может backdating |
| user_chat_id |
TEXT NOT NULL |
admin, выполнивший действие |
| user_email |
TEXT NULL |
snapshot из таблицы users на момент действия |
| action |
TEXT NOT NULL |
list | view | rotate | test | restart |
| key_name |
TEXT NOT NULL |
имя vault-записи (allowlist в platform.ts); * для list-all |
| ip |
TEXT NULL |
из CF-Connecting-IP / X-Real-IP / XFF tail |
| result |
TEXT NOT NULL |
success или fail:<reason> |
| user_agent |
TEXT NULL |
UA-строка, truncated 200 chars |
INDEXES: idx_platform_audit_ts (recent across keys), idx_platform_audit_user (per-admin trail), idx_platform_audit_key (история ротаций per key). Инвариант append-only: нет UPDATE/DELETE-обработчиков; UI предоставляет read-only recent() + lastRotated() через platformAuditQueries в shared/db.ts. Каждое действие (success+fail) записывает строку.
Миграции
| # |
Название |
Фаза |
Что добавляет |
| 001 |
initial_schema |
Phase 40 |
users, projects, account_settings |
| 002 |
project_protocol |
Phase 40.7 |
projects.project_protocol |
| 003 |
chat_messages |
Phase 40.10 |
chat_messages |
| 004 |
skill_system |
Phase 41 |
skills_global, skills_project_forks, skill_evolution_logs, skill_update_requests |
| 005 |
skill_benchmarks |
Phase 40.11d |
skill_benchmarks |
| 006 |
system_configs |
— |
system_configs + FIGMA_CACHE_ROOT |
| 007 |
marketplace_cache |
Phase 40.12.1 |
marketplace_analysis_cache |
| 008 |
harvester |
Phase 40.13 |
+is_suggestion, +source_query, +auto_harvest |
| 009 |
pinned_notes |
Phase 41.8 |
pinned_notes |
| 010 |
project_phases |
Phase 44 |
project_phases |
| 011 |
activity_log |
Phase 44.1 |
activity_log |
| 012 |
performance_indexes |
Perf Audit |
+idx_activity_log_actor, +idx_chat_msg_role_ts |
| 013 |
timeline_timestamps |
Phase 47 |
+timestamp на сообщениях воркеров |
| 014 |
timeline_events |
Phase 47 |
таблица timeline_events |
| 015 |
e2ee_chat |
Phase 45.3 |
+encrypted колонка в chat_messages |
| 016 |
recovery_keys |
Phase 45.4 |
таблица recovery_keys |
| 017 |
github_links |
Phase 49.3 |
таблица github_links (webhook bindings) |
| 018 |
github_events |
Phase 49.3.1 |
таблица github_events (лог событий для UI feed) |
| 019 |
trial_credits |
Phase 50.1 |
+users.trial_granted_at, +projects.trial_mode, +projects.trial_tokens_remaining |
| 020 |
subscriptions |
Phase 51 |
таблица subscriptions (plan, stripe_customer_id, status, feature_flags) + stripe_events idempotency log |
| 021 |
invites |
Phase 52.1 |
таблица invites (closed beta enrollment) — code, created_by, used_by, parent_invite_code, status, note |
| 022 |
ephemeral_tokens |
issue #27 |
таблица ephemeral_tokens — persistent OAuth state / password reset / email verification, вместо in-memory Map (фикс invalid_state при рестарте master) |
| 023 |
project_issues |
Phase 53.14 / issue #53 |
таблица project_issues (composite PK project_name+id, JSON labels/activity) — переносит хранение задач из issues/issues.json в SQLite SSOT, JSON становится derived mirror; устраняет text-merge drift при деплое |
| 024 |
context_exports |
Phase 56.4 + 56.5 / issues #100 + #101 |
export_audit_log (id PK, project_name, owner_id, exported_at, sections JSON, findings_critical/high/medium/low INT, bytes) + export_preferences (project_name PK, always_include_emails BOOL, auto_redact_critical BOOL DEFAULT 1, notify_on_export BOOL DEFAULT 0, updated_at) — per-project история аудита для Project Context Export + per-project policy toggles; алерт при ≥3 exports/24h когда notify_on_export = true |
| 025 |
onboarding_progress |
Phase 54.1 / issue #56 |
onboarding_progress (chat_id PK, JSON state per 5 steps, completed_count derived, started_at/completed_at/dismissed_at, source web|cli) — derived cache над event stream из activity_log; SSOT = события event_type LIKE 'onboarding_%'; recordEvent идемпотентен, dismiss/replay non-destructive |
| 026 |
platform_audit_log |
Phase 57 / Sentinel #103 |
platform_audit_log (id PK AUTOINCREMENT, ts, user_chat_id, user_email, action, key_name, ip, result, user_agent) — append-only audit trail для super-admin Platform Settings UI мутаций vault secrets. 3 indexes: idx_platform_audit_ts/user/key. Нет UPDATE/DELETE — read-only через platformAuditQueries.recent + lastRotated |
| 027 |
auth_events |
Issue #130 |
auth_events (id PK AUTOINCREMENT, ts, event, provider, user_id, email, ip, user_agent, result, meta JSON) — Append-only лог событий аутентификации + IP. События: signup/login/oauth_login/magic_link/device_code_approve. Результаты: success/failed/rate_limited/invalid_invite/already_registered/2fa_required/invalid_token. 4 индекса: ts/user_id/ip/event+result |
| 028 |
managed_containers |
Phase 60 #135/#136 |
managed_containers (id TEXT PK (имя Docker-контейнера), user_id, status (provisioning|ready|paused|suspended|deleted), server_ip, internal_port INT, claude_authed INT, github_authed INT, created_at, last_active) — Реестр Docker-контейнеров 1-на-пользователя для Standard Cloud. managedContainerQueries.findByUser/insert/updateStatus/setAuthFlag/touch/listActive в shared/db.ts. Endpoints: POST /api/crm/cloud/provision, GET /status, POST /deprovision в shared/routes/cloud.ts |
| 029 |
cloud_waitlist |
Phase 60 #134 |
cloud_waitlist (id INT PK AUTOINCREMENT, user_id TEXT UNIQUE, email, position INT, status (waiting|invited|activated|declined), notes, joined_at, invited_at, activated_at) — Список ожидания Early Access для Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats в shared/db.ts. Endpoints: POST /api/crm/cloud/waitlist (вступить), GET /waitlist/status (собственный статус), GET /waitlist (список для admins), POST /waitlist/invite (admin активирует + апгрейд плана до cloud) |
| 030 |
translation_feedback |
Phase 59.4 #122 |
translation_feedback (id INT PK AUTOINCREMENT, locale TEXT, message_id TEXT, original TEXT, current_translation TEXT, suggestion TEXT, user_id TEXT, status (pending|accepted|rejected), created_at, reviewed_at) — Обратная связь о качестве переводов по locale. Дашборд проверки для admin на /api/crm/translations/feedback (list/stats/accept/reject). |
| 031 |
token_usage_log |
Phase 63 #148 |
token_usage_log (id INT PK AUTOINCREMENT, project_name TEXT NOT NULL, owner_id TEXT NOT NULL, worker_id TEXT, input_tokens INT DEFAULT 0, output_tokens INT DEFAULT 0, cache_tokens INT DEFAULT 0, total_tokens INT DEFAULT 0, created_at INT DEFAULT (unixepoch())) — Лог использования токенов Claude на каждый запрос. 3 индекса: owner_id, project_name, created_at. Заполняется из child-bot/claude-runner.ts через POST /api/internal/usage/log (fire-and-forget). Запрашивается через GET /api/crm/account/usage → возвращает { rows: [...последние 200], totals: { total, input, output } } для BillingPage + UserDropdown UsageCard. |
| 032 |
arc_help_usage |
Phase 61 #147 |
arc_help_usage (id INT PK AUTOINCREMENT, user_id TEXT NOT NULL, date TEXT NOT NULL (YYYY-MM-DD), count INT NOT NULL DEFAULT 0, PRIMARY KEY (user_id, date)) — Ежедневный счётчик rate-limit для внутреннего чата AI-помощника (30 сообщений/день). Запрашивается через GET /api/crm/help/usage; обновляется при POST /api/crm/help/chat. |
Связи (Foreign Keys)
- skills_project_forks.skill_id → skills_global.id (CASCADE DELETE)
- skill_evolution_logs.skill_id → skills_global.id (CASCADE DELETE)
- skill_update_requests.skill_id → skills_global.id (CASCADE DELETE)
- skill_benchmarks.request_id → skill_update_requests.id (CASCADE DELETE)
Все CASCADE — удаление скила удаляет все форки, историю, PR и бенчмарки.
ER-диаграмма (текстовая)
users ─1:N──> projects
├── chat_messages
├── pinned_notes
├── project_phases
└── skills_project_forks ──> skills_global
├── skill_evolution_logs
├── skill_update_requests
│ └── skill_benchmarks
└── (standalone tables)
├── activity_log
├── system_configs
└── marketplace_analysis_cache