База даних — схема SQLite

Загальна інформація

ER-діаграма (Phase 65)

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
        INTEGER is_archived
        TEXT model_mode "strict|optimize (#520, migration 060)"
    }

    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
    }

30+ таблиць у 35 міграціях. Auth + проєкти + дані робочого простору + intelligence + білінг + beta-гейтинг + аудит context-export. Усі 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
password_version INTEGER Лічильник змін пароля (migration 035). Включений у JWT як pv; crmAuthMiddleware відхиляє застарілі токени після зміни пароля.

projects — Проєкти

Колонка Тип Опис
id INTEGER PK Auto
technical_name TEXT Технічна назва (унікальна для власника)
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)
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 key
openai_key TEXT OpenAI API key
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=active, 1=revoked

INDEX(user_id)

github_links — Прив'язки GitHub-репозиторіїв (Phase 49.3)

Колонка Тип Опис
id INTEGER PK Auto
project_name TEXT Проєкт Arc OS
owner TEXT Власник GitHub-репозиторію
repo TEXT Назва GitHub-репозиторію
webhook_secret TEXT UNIQUE 32-байтовий hex для валідації HMAC-SHA256
created_at TEXT ISO 8601
created_by TEXT FK→users Хто створив прив'язку

UNIQUE(project_name, owner, repo) — дозволено кілька репозиторіїв на проєкт. INDEX(project_name), INDEX(webhook_secret)

github_events — Лог подій GitHub (Phase 49.3.1)

Колонка Тип Опис
id INTEGER PK Auto
link_id INTEGER FK→github_links CASCADE при видаленні прив'язки
project_name TEXT Проєкт Arc OS (денормалізовано для швидкості запитів)
event_type TEXT push, pull_request, workflow_run, issues
action TEXT під-дія (opened/closed/success/failure)
summary TEXT Заздалегідь відформатований рядок для відображення
url TEXT Deep link на GitHub
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'
owner_project TEXT NULL=global/marketplace; назва проєкту=видимий лише власнику (migration 034)

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 — Фази роадмапу

Колонка Тип Опис
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)

Постійне сховище одноразових токенів: OAuth state, password reset, email verification. До migration 022 ці токени жили в in-memory Map і губилися на кожному рестарті master, ламаючи логін Google/GitHub (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 — Задачі на проєкт (issue #53, Phase 53.14)

SQLite SSOT для задач, що замінює issues/issues.json на проєкт. Файл залишається на диску як похідний експорт (дзеркало), gitignored — DB є авторитетною.

Колонка Тип Опис
project_name TEXT NOT NULL назва з реєстру; частина композитного PK з id
id INTEGER NOT NULL 1-based послідовність на проєкт (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-масив рядків
status TEXT NOT NULL DEFAULT 'open' draft | open | in_progress | blocked | deferred | closed (SQL-дефолт 'open' збережено — нові задачі примусово переводяться в draft на рівні застосунку)
assignee TEXT NULL id воркера, що взяв задачу (migration 036)
created_by TEXT NOT NULL DEFAULT 'legacy' id воркера, який створив задачу (migration 046 / #327) — для аудиту (хто, коли, кому призначено)
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-масив {ts, type, author, text} — append-only аудит

PK (project_name, id) — кожен проєкт має власний незалежний простір id. INDEX (project_name, status) — найчастіший фільтр (лише open). Операції живуть у issueQueries (shared/db.ts): list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll — атомарна транзакція; bulkImportINSERT OR IGNORE для ідемпотентного re-seed.

Migration 036 (#187, 2026-05-23): ALTER TABLE project_issues ADD COLUMN assignee TEXT. Нові статуси (in_progress, blocked, deferred) валідуються на рівні застосунку (об'єднання IssueStatus у shared/db.ts), а не через CHECK-обмеження (SQLite не дозволяє додавати обмеження через ALTER TABLE).

Migration 046 (#327, 2026-06-03): ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy'. Життєвий цикл розширено новим статусом draft. handleCreateIssue примусово встановлює status='draft', коли assignee=null (інакше одразу 'open'), і вимагає worker_id (body або auth fallback). handleUpdateIssue авто-промоутить draft → open на першому призначенні assignee й відхиляє close для ніколи не призначеної задачі (FR-ISS-101/102/103). Наявні рядки заповнюються через DEFAULT як 'legacy'.

onboarding_progress — Стан post-wizard чекліста (issue #56, Phase 54.1)

Похідний кеш на користувача над потоком подій activity_log (SSOT). UI читає його одним запитом замість агрегування подій. Повторюваний: dismiss/replay не змінюють state, лише dismissed_at.

Колонка Тип Опис
chat_id TEXT PRIMARY KEY 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 похідний кеш-лічильник кроків completed (0–5)
started_at TEXT NOT NULL DEFAULT (datetime('now')) створення рядка (перша подія/dismiss/replay)
completed_at TEXT NULL ISO timestamp, коли всі 5 кроків completed
dismissed_at TEXT NULL ISO timestamp, коли користувач закрив чекліст
source TEXT NOT NULL DEFAULT 'web' web | cli — для атрибуції воронки (#58 arc tour)
updated_at TEXT NOT NULL DEFAULT (datetime('now')) timestamp мутації

INDEX на completed_at для аналітики воронки (#61 Phase 54.6). Операції живуть в onboardingQueries (shared/db.ts): getProgress/recordEvent/dismiss/replay. recordEvent ідемпотентний на (chat_id, step, status) — повторний ідентичний виклик повертає changed=false і не пише в activity_log. Whitelist: 5 кроків × 2 статуси (completed, skipped). skipped НЕ інкрементує completed_count. Перехід skipped → completed додає до лічильника. Усі мутації емітять події в activity_log з event_type LIKE 'onboarding_%' — справжнє SSOT для метрик воронки; колонки таблиці — похідний кеш.

platform_audit_log — Слід ротації секретів супер-адміном (Phase 57, Sentinel #103)

Append-only лог мутацій vault platform-секретів через UI Platform Settings. SSOT для post-incident форензики ("який адмін ротував ключ Anthropic о 03:14 UTC?"). Сам vault.json цього не записує — лише значення.

Колонка Тип Опис
id INTEGER PRIMARY KEY AUTOINCREMENT темпоральний порядок навіть при зсуві годинника
ts TEXT NOT NULL DEFAULT (datetime('now')) server-side UTC; атакувальник не може датувати заднім числом
user_chat_id TEXT NOT NULL адмін, що діяв
user_email TEXT NULL знімок із таблиці 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, обрізаний до 200 символів

INDEXES: idx_platform_audit_ts (недавні по ключах), idx_platform_audit_user (слід по адміну), idx_platform_audit_key (історія ротацій по ключу). Append-only інваріант: немає хендлерів UPDATE/DELETE; UI надає read-only recent() + lastRotated() через platformAuditQueries у shared/db.ts. Кожна дія (success+fail) пише рядок.

auth_events — Лог подій автентифікації кінцевого користувача (Migration 027, issue #130)

Append-only лог подій auth для аудиту безпеки та розслідування інцидентів. Окремий від platform_audit_log (лише admin vault) — ця таблиця покриває потік auth кінцевого користувача з IP.

Колонка Тип Опис
id INTEGER PRIMARY KEY AUTOINCREMENT темпоральний порядок
ts TEXT NOT NULL DEFAULT (datetime('now')) server-side UTC
event TEXT NOT NULL signup | login | oauth_login | magic_link | device_code_approve
provider TEXT NOT NULL DEFAULT 'password' password | google | github | magic_link | device_code
user_id TEXT NULL NULL для невдалих спроб, де користувача не ідентифіковано
email TEXT NULL email, використаний у спробі
ip TEXT NOT NULL DEFAULT 'unknown' CF-Connecting-IP → X-Real-IP → останній сегмент XFF
user_agent TEXT NULL рядок UA
result TEXT NOT NULL success | failed | rate_limited | invalid_invite | already_registered | 2fa_required | invalid_token
meta TEXT NULL JSON додатковий контекст (наприклад {isNew: true} для OAuth)

INDEXES: idx_auth_events_ts (найновіші першими), idx_auth_events_user (історія по користувачу), idx_auth_events_ip (виявлення загроз по IP), idx_auth_events_event (фільтр event+result). authEventQueries.insert/recent у shared/db.ts. Логується в master-bot/routes/auth.ts (register/login/oauth/magic-link) та master-bot/routes/cli.ts (device_code_approve).

Міграції

# Назва Phase Що додає
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 (прив'язки вебхуків)
018 github_events Phase 49.3.1 таблиця github_events (лог подій для стрічки UI)
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
021 invites Phase 52.1 таблиця invites (enrollment у closed beta) — code, created_by, used_by, parent_invite_code, status, note
022 ephemeral_tokens issue #27 таблиця ephemeral_tokens — постійний OAuth state / password reset / email verification, що замінює in-memory Map (виправляє invalid_state при рестарті master)
023 project_issues Phase 53.14 / issue #53 таблиця project_issues (композитний PK project_name+id, JSON labels/activity) — переносить зберігання задач з issues/issues.json у SQLite SSOT, JSON стає похідним дзеркалом; усуває 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 перемикачі політик; alert при ≥3 експортах/24г, коли notify_on_export = true
025 onboarding_progress Phase 54.1 / issue #56 onboarding_progress (chat_id PK, JSON state на 5 кроків, completed_count похідний, started_at/completed_at/dismissed_at, source web|cli) — похідний кеш над потоком подій activity_log; SSOT = події з event_type LIKE 'onboarding_%'; recordEvent ідемпотентний, dismiss/replay неруйнівні
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 слід аудиту для мутацій UI супер-адмін Platform Settings над vault-секретами. 3 індекси: 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 + подій auth. Події: 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. Ендпоінти: 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 waitlist для Standard Cloud.
030 translation_feedback Phase 59.4 #122 translation_feedback (id INT PK AUTOINCREMENT, locale, message_id, original, current_translation, suggestion, user_id, status (pending|accepted|rejected), created_at, reviewed_at).
031 token_usage_log Phase 63 #148 token_usage_log (id INT PK AUTOINCREMENT, project_name, owner_id, worker_id, input_tokens, output_tokens, cache_tokens, total_tokens, created_at INT) — Token usage Claude на запит. Заповнюється claude-runner.ts через /api/internal/usage/log.
032 arc_help_usage Phase 61 #147 arc_help_usage (user_id, date PK composite) — Щоденний лічильник rate-limit для Arc Help (30 повідомлень/день).
033 arc_help_messages Phase 61 #153 arc_help_messages (id PK AUTOINCREMENT, user_id, role, text, sources JSON, created_at) — Постійна історія чату Arc Help на користувача (останні 60).
034 skills_owner_project #157 ALTER TABLE skills_global ADD COLUMN owner_project TEXT — NULL=global, non-null=owned by project.
035 password_version Phase 64 #174 ALTER TABLE users ADD COLUMN password_version INTEGER DEFAULT 0 — інкрементується при зміні пароля; JWT claim pv валідується crmAuthMiddleware для інвалідації старих токенів.
036 issue_assignee #187 / 2026-05-23 ALTER TABLE project_issues ADD COLUMN assignee TEXT — nullable id воркера. Вмикає arc issue take <id> + розширені статуси in_progress/blocked/deferred (валідуються на рівні застосунку).
046 issue_draft_and_created_by #327 / 2026-06-03 ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy' — поле аудиту "хто створив". Життєвий цикл += draft (об'єднання на рівні застосунку). handleCreateIssue примусово draft без assignee; handleUpdateIssue авто-промоутить draft→open при призначенні + відхиляє close для ніколи не призначеної (FR-ISS-101/102/103).
037 waitlist #197 / 2026-05-25 waitlist (id INT PK AUTOINCREMENT, email TEXT UNIQUE, message, status DEFAULT 'pending', created_at, updated_at) — форма запиту раннього доступу на сторінці логіну.
038 plata_billing Phase 65 #202 / 2026-05-26 Додає колонки Plata by mono до subscriptions: plata_card_token TEXT, plata_wallet_id TEXT, plata_masked_pan TEXT, next_billing_date TEXT, billing_failures INTEGER DEFAULT 0. Також створює таблицю ідемпотентності plata_events (invoice_id PK, status, processed_at).
039 skill_project_grants #201 / 2026-05-26 skill_project_grants (skill_id INT FK→skills_global.id CASCADE, project_name TEXT, granted_at, PK composite) — many-to-many доступ до скілів між проєктами.
040 drop_stripe #205 / 2026-05-26 DROP колонки stripe_customer_id + DROP TABLE stripe_events. NB: колонка stripe_subscription_id пережила цю міграцію, бо SQLite відмовляється від DROP COLUMN на колонках із UNIQUE-обмеженням (тихо перехоплено try/catch). Див. migration 041.
041 rebuild_subscriptions #205 / 2026-05-26 Наступник 040 — перебудовує таблицю subscriptions без осиротілої колонки stripe_subscription_id через патерн CREATE TABLE … AS. Ідемпотентна (пропускається, коли колонка вже відсутня).
042 totp_backup_codes ALTER TABLE users ADD COLUMN totp_backup_codes TEXT — коди відновлення TOTP.
043 worker_avatars Phase D #304 / 2026-05-30 worker_avatars (id PK AUTOINCREMENT, project_name, worker_id, file_path, mime_type, created_at; UNIQUE(project_name, worker_id)) — сховище метаданих для завантажених зображень аватарів воркерів. Файли зберігаються в data/worker-avatars/. Віддаються через GET /api/crm/projects/:name/workers/:id/avatar.
044 worker_templates Phase I #304 / 2026-05-30 worker_templates (id PK AUTOINCREMENT, owner_id, name, description, config JSON, is_public, created_at; UNIQUE(owner_id, name)) — збережені користувачем шаблони воркерів. Ендпоінти: GET/POST /api/crm/workers/templates, DELETE /api/crm/workers/templates/:id.
045 workers_runtime_state Phase F #306 / 2026-05-31 workers_runtime_state (project_name, worker_id, status, status_started_at, current_skill, current_task_id, updated_at; PK(project_name, worker_id), idx на updated_at) — живий runtime-стан на воркера. Пишеться handleSetActiveRole (інваріант однієї активної ролі на проєкт). Читається GET /api/crm/projects/:name/workers/:id/runtime зі staleness fallback: status='working' + tmux dead + updated_at > 10 хв → примусово idle (виявлення крашів).
049 embeddings Phase 71.2 #359 / 2026-06-04 embeddings (id PK AUTOINCREMENT, project, doc_type wiki/issue/skill, doc_id, chunk_ix, text, created_at) + embeddings_vec (vec0 віртуальна таблиця, embedding FLOAT[1024]). Розділення: vec0 не може ефективно тримати метадані; LIST/DELETE-процеси виконуються як звичайний SQL. Розширення sqlite-vec авто-завантажується в initDb перед запуском міграції.
050 drop_notebook_id Phase 71.8 #365 / 2026-06-05 ALTER TABLE projects DROP COLUMN notebook_id. Виводить з експлуатації поле схеми NotebookLM bridge; семантичний пошук тепер працює через самохостингований RAG (Cohere + sqlite-vec). Ідемпотентна через перевірку PRAGMA table_info, тож свіжі DB з поточною схемою 001 не спотикаються на відсутній колонці.
051 voice_usage Phase 62.4 #373 / 2026-06-05 voice_usage_log (user_id TEXT, date TEXT YYYY-MM-DD, seconds_used INTEGER DEFAULT 0, PRIMARY KEY(user_id, date)) — щоденний лічильник квоти голосової транскрипції, дзеркалить форму migration 032 arc_help_usage. Приблизні секунди (оцінені з розміру байтів завантаження за припущення voice-кодека ~32 kbps); порівнюється з м'яким капом 60 хв/день у handleVoiceTranscribe (shared/routes/voice.ts) перед кожною транскрипцією, щоб один користувач не монополізував спільний воркер-пул whisper-server на Contabo.
052 transcripts Phase 73.1 #377 / 2026-06-05 transcripts (id PK AUTOINCREMENT, project TEXT, owner_id TEXT, source_filename TEXT, source_bytes INTEGER, source_kind TEXT CHECK(audio|video), duration_seconds INTEGER, transcript_text TEXT, frames_json TEXT, summary_json TEXT, embed_to_rag INTEGER DEFAULT 1, status TEXT DEFAULT 'queued', error TEXT, created_at TEXT DEFAULT datetime('now'), completed_at TEXT) — один рядок на завантажений файл зустрічі. frames_json = JSON-масив {ts_ms, description} з Claude vision (Phase 73.4). summary_json = {tldr, key_points, action_items, decisions, topics, model, generated_at} з Claude Sonnet (Phase 73.5). Вихідний файл видаляється з диска, щойно status='done' (CEO decision D4). Індекси на (project, created_at DESC) та (owner_id, created_at DESC).
056 skill_usage #319 / 2026-06-07 skill_usage (skill_name TEXT, project_name TEXT DEFAULT '_global_', trigger_count INTEGER DEFAULT 0, install_count INTEGER DEFAULT 0, session_count INTEGER DEFAULT 0, last_triggered_at TEXT, updated_at TEXT, PRIMARY KEY(skill_name, project_name)) — кумулятивні лічильники на скіл на проєкт. trigger_count інкрементується щоразу, коли context router інжектує скіл; install_count на save/create; session_count на появу в унікальній сесії. Використовується GET /api/crm/projects/:name/skills/usage.
057 notes Phase 78.1 #395 / 2026-06-07 4 таблиці: notes (id PK, project_name, title, description, created_by, created_at, updated_at) — нотатка як контейнер джерел. note_sources (id PK, note_id FK→notes, source_type CHECK(video|audio|youtube|web|pdf|docx|txt|image), filename, file_path, file_size, url, title, content_text, duration_seconds, status CHECK(queued|processing|done|error), error, processed_at, created_at) — одне джерело на рядок, content_text = витягнутий текст для RAG. note_issue_links (note_id FK, issue_id, project_name, linked_at, PK(note_id,issue_id)) — зв'язок нотатка↔задача. note_chats (id PK, note_id FK→notes, role CHECK(user|assistant), content, created_at) — історія чату з нотаткою. Індекси: notes(project_name,created_at DESC), note_sources(note_id), note_sources(status), note_issue_links(project_name,issue_id), note_chats(note_id,created_at).
053 transcript_jobs Phase 73.1 #377 / 2026-06-05 transcript_jobs (job_id TEXT PK, transcript_id INTEGER, status TEXT DEFAULT 'queued', progress_pct INTEGER DEFAULT 0, step_label TEXT, error TEXT, started_at TEXT DEFAULT datetime('now'), updated_at TEXT DEFAULT datetime('now')) — high-churn трекер прогресу для фонового воркера. Ізольований від transcripts, щоб часті UPDATE (кожні кілька секунд під час ffmpeg/whisper) не churn-или індекси основної таблиці. SSE-ендпоінт стрімить цю таблицю з опитуванням 1с. Життєвий цикл статусів: queued → extracting_audio → transcribing → (video: extracting_frames → frames_extracted → vision_analyzing → vision_analyzed) → summarizing → summarized → embedding → done (або failed на будь-якому кроці). Індекс на (status, updated_at).

Зв'язки (Foreign Keys)

Усі 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

| 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 waitlist для Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats у shared/db.ts. Ендпоінти: POST /api/crm/cloud/waitlist (join), GET /waitlist/status (own користувача), GET /waitlist (admin list), POST /waitlist/invite (admin activate + апгрейд плану до 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) — Per-locale фідбек якості перекладу. Дашборд адмін-рев'ю на /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())) — Лог token usage 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: [...last 200], totals: { total, input, output } } для BillingPage + usage-картки UserDropdown. | | 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 для in-app AI Help chat (30 повідомлень/день). Запитується GET /api/crm/help/usage; оновлюється на POST /api/crm/help/chat. | | 033 | arc_help_messages | Phase 61 #153 | arc_help_messages (id INT PK AUTOINCREMENT, user_id TEXT NOT NULL, role TEXT NOT NULL CHECK(user|assistant), text TEXT NOT NULL, sources TEXT (JSON array, nullable), created_at TEXT DEFAULT datetime('now')) — Постійна історія чату Arc Help на користувача. INDEX на (user_id, created_at DESC). arcHelpQueries.save/history(limit=60)/clearHistory у shared/db.ts. GET /api/crm/help/history повертає останні 60 повідомлень (найстаріші першими). DELETE /api/crm/help/history очищає все для користувача. Фронтенд завантажує на mount. | | 034 | skills_owner_project | #157 | ALTER TABLE skills_global ADD COLUMN owner_project TEXT DEFAULT NULL — NULL=global/marketplace (видимий усім), non-null=owned by that project (фільтрується в listForProject). Backfill: скіли з не-універсальною категорією (odoo, python тощо) отримують owner_project=category. Універсальні категорії: general/frontend/backend/devops/security/testing/database/api/mobile. Скіли з DB тепер інжектуються в промти Claude через routeContextFromDb() у child-bot (#158). |

Migration 064 — token_usage_log.model (#562 S5)

Nullable-колонка model TEXT на token_usage_log: яка модель Claude обслуговувала рядок usage (проброшена з claude-runner через POST /api/internal/usage/log). Живить розбивку токенів по tier'ах у GET /api/crm/analytics/cascade, тож ефект вартості spec-gated каскаду вимірюваний. Застарілі рядки залишаються NULL (unattributed).

Migration 067 — cli_devices (#627, Phase B of #617)

cli_devices (id INT PK AUTOINCREMENT, chat_id TEXT NOT NULL, device_id TEXT NOT NULL, hostname, platform, arc_version, last_project, first_seen, last_seen; UNIQUE(chat_id, device_id); INDEX idx_cli_devices_chat (chat_id, last_seen DESC)) — серверний реєстр прив'язаних інсталяцій arc CLI, один рядок на (користувач, пристрій). device_id — випадковий 12-байтовий hex, згенерований на боці клієнта (arc ≥1.0.14) і збережений у ~/.arc/config.json. Пишеться POST /api/cli/heartbeat (кожен виклик arc + одразу після arc login) і опціонально через device/poll, коли клієнт надсилає інфо client{}. ПЕРШИЙ рядок пристрою користувача також логує activity_log(cli_invocation, event=first_device_linked), тож onboarding cli-status probe і воронка активації реєструють прив'язку. Читається GET /api/crm/cli/devices (cliDeviceQueries.upsert/listForUser у shared/db.ts) → список "Connected devices" у CLI-модалці сайдбара (у стилі Tailscale: hostname · platform · version · last seen).

Migration 068 — account_settings.hide_browser_chat (#630, Phase E of #617)

Nullable-default hide_browser_chat INTEGER DEFAULT 0 на account_settings — per-user feature flag, що ховає поверхні browser-chat (nav-елемент робочого простору + стандартна сторінка проєкту стає read-only стрічкою Sessions). Default 0 = чат видимий, нічого не змінюється; перемикання назад відновлює старий UI дослівно (жоден код чату не видалено). Виводиться як hideBrowserChat у GET/PUT /api/crm/account/settings; accountQueries.getHideBrowserChat/setHideBrowserChat у shared/db.ts.

Migration 070 — account_settings.calm_mode + quiet hours (#644, T8)

Додає calm_mode INTEGER DEFAULT 0 + quiet_start / quiet_end (INTEGER години 0–23, nullable) до account_settings. Calm mode = придушення success/info-тостів із центру (помилки завжди спливають); значимі події залишаються в стрічці Home + дзвіночку хедера. Quiet hours = вікно [start,end) (огортає північ), протягом якого не-error тости приглушуються, навіть якщо calm mode вимкнено. Гейтинг на боці клієнта (ToastViewport читає дзеркало localStorage['arc-calm'], яке App пише з цих налаштувань); колонки лише зберігають преференцію. Виводиться як calmMode/quietStart/quietEnd у GET/PUT /api/crm/account/settings; accountQueries.getCalm/setCalm.

Migration 069 — cli_transcripts + account_settings.upload_transcripts (#632)

cli_transcripts (id INT PK AUTOINCREMENT, chat_id TEXT NOT NULL, project_name TEXT NOT NULL, session_id TEXT NOT NULL, worker_id, device_id, message_count INT, first_prompt TEXT, size_bytes INT, content BLOB NOT NULL, created_at, updated_at; UNIQUE(chat_id, session_id); INDEX idx_cli_transcripts_lookup (chat_id, project_name, updated_at DESC)) — opt-in серверне зберігання повних транскриптів CLI-сесій для cross-device continuity. contentgzip-стиснутий сирий jsonl (Bun.gzipSync); size_bytes — нестиснутий розмір. Пишеться POST /api/cli/transcript/:project/:mode (arc ≥1.0.16) лише коли власник увімкнув прапорець; upsert на (chat_id, session_id), тож зростаюча сесія перезаписує. Читається owner-gated через GET /api/crm/projects/:name/cli-transcript?session=… (cliTranscriptQueries у shared/db.ts). Також додає upload_transcripts INTEGER DEFAULT 0 до account_settings — opt-in прапорець (default OFF; транскрипти можуть містити код/секрети), виводиться як uploadTranscripts у GET/PUT /api/crm/account/settings і повертається в CLI у блоці settings ендпоінта /api/cli/init. GDPR Art.17: cli_transcripts + cli_devices видаляються за chat_id у каскаді erasure.