Baza danych — SQLite Schema

Informacje ogólne

Diagram ER (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 tabele w 24 migracjach. Auth + projekty + dane workspace + warstwa intelligence + billing + beta gating + audit eksportu kontekstu. Wszystkie ograniczenia FK są egzekwowane.

Tabele

users — Użytkownicy

Kolumna Typ Opis
id TEXT PK UUID
email TEXT UNIQUE Email (wymagany)
password_hash TEXT Hash hasła (bcrypt)
role TEXT 'admin' lub 'user'
name TEXT Imię
avatar_url TEXT URL avatara
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 Sekret TOTP (2FA)
last_login TEXT ISO 8601
created_at TEXT ISO 8601

projects — Projekty

Kolumna Typ Opis
id INTEGER PK Auto
technical_name TEXT Nazwa techniczna (unikalna per owner)
owner_id TEXT FK→users Właściciel
display_name TEXT Nazwa wyświetlana
description TEXT Opis
color TEXT Kolor (hex)
icon TEXT Ikona
is_archived INTEGER 0/1
sort_order INTEGER Kolejność (domyślnie 999)
notebook_id TEXT NotebookLM ID
project_protocol TEXT Protokół projektu
created_at, updated_at TEXT Timestamps

UNIQUE(technical_name, owner_id)

account_settings — Ustawienia konta

Kolumna Typ Opis
user_id TEXT PK FK→users
anthropic_key TEXT Klucz Anthropic API
openai_key TEXT Klucz OpenAI API
name TEXT Imię
role TEXT Rola
auto_harvest INTEGER Automatyczne zbieranie skillów 0/1

chat_messages — Historia czatów

Kolumna Typ Opis
id INTEGER PK Auto
project_name TEXT Projekt
worker_id TEXT Worker
role TEXT 'user', 'assistant', 'system'
content TEXT Treść wiadomości (szyfrowana AES-256-GCM gdy encrypted=1)
encrypted INTEGER 0=plaintext, 1=vault-encrypted (Phase 45.3)
attachments TEXT Tablica JSON
timestamp TEXT ISO 8601
metadata TEXT Obiekt JSON

INDEX(project_name, worker_id, timestamp DESC)

recovery_keys — Klucze odzyskiwania (Phase 45.4)

Kolumna Typ Opis
id INTEGER PK Auto
user_id TEXT FK→users Właściciel
encrypted_key TEXT Zaszyfrowany master key
key_hint TEXT Podpowiedź (pierwsze 4 znaki)
created_at TEXT ISO 8601
used_at TEXT Kiedy użyto
revoked INTEGER 0=aktywny, 1=odwołany

INDEX(user_id)

github_links — Powiązania GitHub repo (Phase 49.3)

Kolumna Typ Opis
id INTEGER PK Auto
project_name TEXT Projekt Arc OS
owner TEXT Właściciel GitHub repo
repo TEXT Nazwa GitHub repo
webhook_secret TEXT UNIQUE 32-bajtowy hex do walidacji HMAC-SHA256
created_at TEXT ISO 8601
created_by TEXT FK→users Kto utworzył link

UNIQUE(project_name, owner, repo) — dozwolone multi-repo per projekt. INDEX(project_name), INDEX(webhook_secret)

github_events — Log zdarzeń GitHub (Phase 49.3.1)

Kolumna Typ Opis
id INTEGER PK Auto
link_id INTEGER FK→github_links CASCADE przy usunięciu linka
project_name TEXT Projekt Arc OS (zdenormalizowany dla szybkości zapytań)
event_type TEXT push, pull_request, workflow_run, issues
action TEXT pod-akcja (opened/closed/success/failure)
summary TEXT Sformatowany ciąg wyświetlany
url TEXT Głęboki link GitHub
actor TEXT Nazwa użytkownika GitHub
created_at TEXT ISO 8601

INDEX(project_name, created_at DESC), INDEX(link_id)

skills_global — Globalne skille

Kolumna Typ Opis
id INTEGER PK Auto
name TEXT UNIQUE Nazwa
description TEXT Opis
category TEXT Kategoria (domyślnie 'general')
content TEXT Zawartość skilla (Markdown)
triggers TEXT Tablica JSON wyzwalaczy
keywords TEXT Tablica JSON słów kluczowych
eval_rules TEXT Tablica JSON reguł
tool_code_ts TEXT Implementacja TypeScript
version INTEGER Wersja (auto-increment)
status TEXT 'active', 'draft', 'deprecated', 'archived'

skills_project_forks — Forki skillów

Kolumna Typ Opis
id INTEGER PK Auto
project_name TEXT Projekt
skill_id INTEGER FK→skills_global Nadrzędny skill (CASCADE)
content TEXT Dostosowana zawartość
triggers, keywords, eval_rules, tool_code_ts TEXT Nadpisania
version INTEGER Wersja forka

UNIQUE(project_name, skill_id)

skill_evolution_logs — Historia zmian skillów

Kolumna Typ Opis
id INTEGER PK
skill_id INTEGER FK CASCADE
project_name TEXT Projekt (nullable)
action TEXT 'created', 'modified', 'applied', 'reverted'
diff_summary TEXT Opis zmian
author TEXT Autor (domyślnie 'system')
metadata TEXT JSON

skill_update_requests — PR skillów (Sage)

Kolumna Typ Opis
id INTEGER PK
skill_id INTEGER FK CASCADE
proposed_by TEXT 'sage' (domyślnie)
status TEXT 'pending', 'approved', 'rejected', 'applied'
current_content TEXT Aktualna wersja
proposed_content TEXT Proponowana wersja
reason TEXT Powód zmiany

skill_benchmarks — Testy A/B skillów

Kolumna Typ Opis
id INTEGER PK
request_id INTEGER FK→skill_update_requests CASCADE
test_scenario TEXT Scenariusz testu
old_output, new_output TEXT Wyniki
score_old, score_new REAL Oceny
judgment_reason TEXT Uzasadnienie

pinned_notes — Przypięte notatki

Kolumna Typ Opis
id INTEGER PK
project_name TEXT Projekt
worker_id TEXT Worker
title TEXT Tytuł
body TEXT Treść
source_message_id INTEGER Odniesienie do chat_messages

project_phases — Fazy Roadmap

Kolumna Typ Opis
id INTEGER PK
project_name TEXT Projekt
phase_id INTEGER Numer fazy
phase_title TEXT Nazwa
status TEXT 'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED'
progress REAL 0.0–1.0
deadline TEXT ISO 8601

UNIQUE(project_name, phase_id)

activity_log — Dziennik aktywności

Kolumna Typ Opis
id INTEGER PK
project_name TEXT Projekt
actor TEXT Worker lub użytkownik
event_type TEXT Typ zdarzenia
title TEXT Opis
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 — Konfiguracje systemowe

Kolumna Typ Opis
key TEXT PK Klucz
value TEXT Wartość
description TEXT Opis

marketplace_analysis_cache — Cache marketplace

Cache analizy zgodności skillów z marketplace.

ephemeral_tokens — Tokeny krótkotrwałe (issue #27)

Persistent store dla jednorazowych tokenów OAuth state, resetowania hasła i weryfikacji email. Przed migracją 022 tokeny te żyły w in-memory Map i przepadały przy każdym restarcie mastera, psując logowanie przez Google/GitHub (invalid_state).

Kolumna Typ Opis
token TEXT PK 32-bajtowy hex (randomBytes(32))
type TEXT NOT NULL oauth_state | password_reset | email_verification
payload TEXT NOT NULL JSON: {provider} dla oauth_state, {email} dla pozostałych
expires_at INTEGER NOT NULL ms epoch — TTL: 10 min (oauth) / 30 min (reset) / 24 h (verify)

INDEX(type), INDEX(expires_at). Jednorazowe użycie: consume() w jednej transakcji odczytuje payload i usuwa wiersz.

project_issues — Issues per projekt (issue #53, Phase 53.14)

SQLite SSOT dla issues zamiast per-projekt issues/issues.json. Plik pozostaje na dysku jako derived export (mirror), gitignored — DB jest autorytatywna.

Kolumna Typ Opis
project_name TEXT NOT NULL nazwa z registry; część PK razem z id
id INTEGER NOT NULL sekwencja 1-based per-projekt (max+1 przy insert)
title TEXT NOT NULL tytuł issue
body TEXT NOT NULL DEFAULT '' opis, Markdown
priority TEXT NOT NULL DEFAULT 'P2' P0 | P1 | P2 | P3
labels TEXT NOT NULL DEFAULT '[]' tablica JSON stringów
status TEXT NOT NULL DEFAULT 'open' open | closed
created_at TEXT NOT NULL timestamp ISO
updated_at TEXT NOT NULL timestamp ISO
closed_at TEXT NULL timestamp ISO przy status=closed
activity TEXT NOT NULL DEFAULT '[]' tablica JSON {ts, type, author, text} — append-only audit

PK (project_name, id) — każdy projekt ma własną niezależną przestrzeń id. INDEX (project_name, status) — najczęstszy filtr (tylko open). Operacje żyją w issueQueries (shared/db.ts): list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll — atomowa transakcja; bulkImportINSERT OR IGNORE dla idempotentnego re-seeda.

onboarding_progress — Stan checklisty post-wizard (issue #56, Phase 54.1)

Derived cache per użytkownik nad strumieniem zdarzeń activity_log (SSOT). UI odczytuje jednym zapytaniem zamiast agregować po zdarzeniach. Replayable: dismiss/replay nie zmieniają state, tylko dismissed_at.

Kolumna Typ Opis
chat_id TEXT PRIMARY KEY id użytkownika (chat id z registry)
state TEXT NOT NULL DEFAULT '{}' JSON {workers, cli, skill, bot, issue}completed | skipped (brak klucza = pending)
completed_count INTEGER NOT NULL DEFAULT 0 derived cache — liczba kroków completed (0–5)
started_at TEXT NOT NULL DEFAULT (datetime('now')) utworzenie wiersza (pierwsze zdarzenie/dismiss/replay)
completed_at TEXT NULL timestamp ISO gdy wszystkie 5 kroków completed
dismissed_at TEXT NULL timestamp ISO gdy użytkownik zamknął checklistę
source TEXT NOT NULL DEFAULT 'web' web | cli — do atrybucji lejka (#58 arc tour)
updated_at TEXT NOT NULL DEFAULT (datetime('now')) timestamp mutacji

INDEX na completed_at dla analityki lejka (#61 Phase 54.6). Operacje żyją w onboardingQueries (shared/db.ts): getProgress/recordEvent/dismiss/replay. recordEvent jest idempotentny na (chat_id, step, status) — powtórne identyczne wywołanie zwraca changed=false i nie zapisuje do activity_log. Whitelist: 5 kroków × 2 statusy (completed, skipped). skipped NIE inkrementuje completed_count. Przejście skipped → completed dodaje do count. Wszystkie mutacje emitują zdarzenia do activity_log z event_type LIKE 'onboarding_%' — prawdziwy SSOT dla metryk lejka; kolumny tabeli to derived cache.

platform_audit_log — Ślad rotacji sekretów super-admina (Phase 57, Sentinel #103)

Append-only dziennik mutacji na platform-secrets vault przez UI Platform Settings. SSOT dla forensyki po incydentach ("który admin rotował klucz Anthropic o 03:14 UTC?"). Sam vault.json tego nie zapisuje — tylko wartości.

Kolumna Typ Opis
id INTEGER PRIMARY KEY AUTOINCREMENT porządek czasowy nawet przy clock skew
ts TEXT NOT NULL DEFAULT (datetime('now')) UTC po stronie serwera; atakujący nie może cofnąć daty
user_chat_id TEXT NOT NULL admin wykonujący akcję
user_email TEXT NULL snapshot z tabeli users w momencie akcji
action TEXT NOT NULL list | view | rotate | test | restart
key_name TEXT NOT NULL nazwa wpisu vault (allowlist w platform.ts); * dla list-all
ip TEXT NULL z CF-Connecting-IP / X-Real-IP / XFF tail
result TEXT NOT NULL success lub fail:<reason>
user_agent TEXT NULL ciąg UA, skrócony do 200 znaków

INDEXES: idx_platform_audit_ts (najnowsze wpisy po wszystkich kluczach), idx_platform_audit_user (ślad per admin), idx_platform_audit_key (historia rotacji per klucz). Niezmiennik append-only: brak handlerów UPDATE/DELETE; UI udostępnia read-only recent() + lastRotated() przez platformAuditQueries w shared/db.ts. Każda akcja (success+fail) zapisuje wiersz.

Migracje

# Nazwa Faza Co dodaje
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 na wiadomościach workerów
014 timeline_events Phase 47 tabela timeline_events
015 e2ee_chat Phase 45.3 +kolumna encrypted w chat_messages
016 recovery_keys Phase 45.4 tabela recovery_keys
017 github_links Phase 49.3 tabela github_links (powiązania webhook)
018 github_events Phase 49.3.1 tabela github_events (log zdarzeń dla UI feed)
019 trial_credits Phase 50.1 +users.trial_granted_at, +projects.trial_mode, +projects.trial_tokens_remaining
020 subscriptions Phase 51 tabela subscriptions (plan, stripe_customer_id, status, feature_flags) + log idempotencji stripe_events
021 invites Phase 52.1 tabela invites (zapis do zamkniętej bety) — code, created_by, used_by, parent_invite_code, status, note
022 ephemeral_tokens issue #27 tabela ephemeral_tokens — persistent OAuth state / reset hasła / weryfikacja email, zamiast in-memory Map (fix invalid_state przy restarcie mastera)
023 project_issues Phase 53.14 / issue #53 tabela project_issues (composite PK project_name+id, JSON labels/activity) — przenosi przechowywanie issues z issues/issues.json do SQLite SSOT, JSON staje się derived mirror; eliminuje text-merge drift przy deploy
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) — historia auditu eksportu per projekt dla Project Context Export + przełączniki polityk per projekt; alert przy ≥3 eksportach/24h gdy notify_on_export = true
025 onboarding_progress Phase 54.1 / issue #56 onboarding_progress (chat_id PK, JSON state per 5 kroków, completed_count derived, started_at/completed_at/dismissed_at, source web|cli) — derived cache nad strumieniem zdarzeń activity_log; SSOT = zdarzenia event_type LIKE 'onboarding_%'; recordEvent idempotentny, 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 ślad audytu dla mutacji super-admina przez UI Platform Settings na vault secrets. 3 indeksy: idx_platform_audit_ts/user/key. Bez UPDATE/DELETE — read-only przez 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 log zdarzeń auth + IP. Zdarzenia: signup/login/oauth_login/magic_link/device_code_approve. Wyniki: success/failed/rate_limited/invalid_invite/already_registered/2fa_required/invalid_token. 4 indeksy: ts/user_id/ip/event+result
028 managed_containers Phase 60 #135/#136 managed_containers (id TEXT PK (nazwa kontenera Docker), user_id, status (provisioning|ready|paused|suspended|deleted), server_ip, internal_port INT, claude_authed INT, github_authed INT, created_at, last_active) — Rejestr kontenerów Docker 1-na-użytkownika dla Standard Cloud. managedContainerQueries.findByUser/insert/updateStatus/setAuthFlag/touch/listActive w shared/db.ts. Endpointy: POST /api/crm/cloud/provision, GET /status, POST /deprovision w 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) — Lista oczekujących Early Access dla Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats w shared/db.ts. Endpointy: POST /api/crm/cloud/waitlist (dołącz), GET /waitlist/status (własny status), GET /waitlist (lista admina), POST /waitlist/invite (admin aktywuje + upgraduje plan do 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) — Informacja zwrotna o jakości tłumaczeń per locale. Panel przeglądu admina pod /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())) — Log zużycia tokenów Claude per request. 3 indeksy: owner_id, project_name, created_at. Wypełniany przez child-bot/claude-runner.ts przez POST /api/internal/usage/log (fire-and-forget). Odpytywany przez GET /api/crm/account/usage → zwraca { rows: [...ostatnie 200], totals: { total, input, output } } dla 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)) — Dzienny licznik limitu szybkości dla czatu Pomocy AI w aplikacji (30 wiadomości/dzień). Odpytywany przez GET /api/crm/help/usage; aktualizowany przy POST /api/crm/help/chat.

Powiązania (Foreign Keys)

Wszystkie CASCADE — usunięcie skilla usuwa wszystkie forki, historię, PR i benchmarki.

Diagram ER (tekstowy)

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