Datenbank — SQLite Schema
Allgemeine Informationen
- Datei:
data/citadel.db - Modus: WAL (Write-Ahead Logging) — paralleles Lesen
- Foreign Keys: aktiviert
- Busy-Timeout: 5000ms
- Cache: 64MB
- Migrationen:
shared/migrations/001-044
ER-Diagramm (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
TEXT description
TEXT color
TEXT icon
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 Tabellen über 24 Migrationen. Auth + Projekte + Workspace-Daten + Intelligence + Billing + Beta-Gating + Context-Export-Audit. Alle FK-Constraints werden durchgesetzt.
Tabellen
users — Benutzer
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | TEXT PK | UUID |
| TEXT UNIQUE | E-Mail (Pflichtfeld) | |
| password_hash | TEXT | Passwort-Hash (bcrypt) |
| role | TEXT | 'admin' oder 'user' |
| name | TEXT | Name |
| avatar_url | TEXT | Avatar-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-Geheimnis (2FA) |
| last_login | TEXT | ISO 8601 |
| created_at | TEXT | ISO 8601 |
projects — Projekte
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| technical_name | TEXT | Technischer Name (eindeutig pro Eigentümer) |
| owner_id | TEXT FK→users | Eigentümer |
| display_name | TEXT | Anzeigename |
| description | TEXT | Beschreibung |
| color | TEXT | Farbe (hex) |
| icon | TEXT | Symbol |
| is_archived | INTEGER | 0/1 |
| sort_order | INTEGER | Reihenfolge (Standard 999) |
| project_protocol | TEXT | Projektprotokoll |
| created_at, updated_at | TEXT | Timestamps |
UNIQUE(technical_name, owner_id)
account_settings — Konto-Einstellungen
| Spalte | Typ | Beschreibung |
|---|---|---|
| user_id | TEXT PK FK→users | |
| anthropic_key | TEXT | Anthropic API-Schlüssel |
| openai_key | TEXT | OpenAI API-Schlüssel |
| name | TEXT | Name |
| role | TEXT | Rolle |
| auto_harvest | INTEGER | Automatisches Skills-Harvesting 0/1 |
chat_messages — Chat-Verlauf
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| project_name | TEXT | Projekt |
| worker_id | TEXT | Worker |
| role | TEXT | 'user', 'assistant', 'system' |
| content | TEXT | Nachrichtentext (AES-256-GCM verschlüsselt wenn encrypted=1) |
| encrypted | INTEGER | 0=plaintext, 1=vault-encrypted (Phase 45.3) |
| attachments | TEXT | JSON-Array |
| timestamp | TEXT | ISO 8601 |
| metadata | TEXT | JSON-Objekt |
INDEX(project_name, worker_id, timestamp DESC)
recovery_keys — Wiederherstellungsschlüssel (Phase 45.4)
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| user_id | TEXT FK→users | Eigentümer |
| encrypted_key | TEXT | Verschlüsselter Master-Key |
| key_hint | TEXT | Hinweis (erste 4 Zeichen) |
| created_at | TEXT | ISO 8601 |
| used_at | TEXT | Zeitpunkt der Verwendung |
| revoked | INTEGER | 0=aktiv, 1=widerrufen |
INDEX(user_id)
github_links — GitHub-Repo-Verknüpfungen (Phase 49.3)
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| project_name | TEXT | Arc OS Projekt |
| owner | TEXT | GitHub-Repo-Eigentümer |
| repo | TEXT | GitHub-Repo-Name |
| webhook_secret | TEXT UNIQUE | 32-Byte-Hex für HMAC-SHA256-Validierung |
| created_at | TEXT | ISO 8601 |
| created_by | TEXT FK→users | Wer den Link erstellt hat |
UNIQUE(project_name, owner, repo) — mehrere Repos pro Projekt erlaubt. INDEX(project_name), INDEX(webhook_secret)
github_events — Log der GitHub-Ereignisse (Phase 49.3.1)
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| link_id | INTEGER FK→github_links | CASCADE beim Löschen des Links |
| project_name | TEXT | Arc OS Projekt (denormalisiert für Query-Geschwindigkeit) |
| event_type | TEXT | push, pull_request, workflow_run, issues |
| action | TEXT | Sub-Aktion (opened/closed/success/failure) |
| summary | TEXT | Vorformatierter Anzeigestring |
| url | TEXT | GitHub Deep Link |
| actor | TEXT | GitHub-Benutzername |
| created_at | TEXT | ISO 8601 |
INDEX(project_name, created_at DESC), INDEX(link_id)
skills_global — Globale Skills
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| name | TEXT UNIQUE | Name |
| description | TEXT | Beschreibung |
| category | TEXT | Kategorie (Standard 'general') |
| content | TEXT | Skill-Inhalt (Markdown) |
| triggers | TEXT | JSON-Array der Trigger |
| keywords | TEXT | JSON-Array der Schlüsselwörter |
| eval_rules | TEXT | JSON-Array der Regeln |
| tool_code_ts | TEXT | TypeScript-Implementierung |
| version | INTEGER | Version (auto-increment) |
| status | TEXT | 'active', 'draft', 'deprecated', 'archived' |
skills_project_forks — Skill-Forks
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | Auto |
| project_name | TEXT | Projekt |
| skill_id | INTEGER FK→skills_global | Eltern-Skill (CASCADE) |
| content | TEXT | Angepasster Inhalt |
| triggers, keywords, eval_rules, tool_code_ts | TEXT | Überschreibungen |
| version | INTEGER | Fork-Version |
UNIQUE(project_name, skill_id)
skill_evolution_logs — Änderungshistorie der Skills
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| skill_id | INTEGER FK | CASCADE |
| project_name | TEXT | Projekt (nullable) |
| action | TEXT | 'created', 'modified', 'applied', 'reverted' |
| diff_summary | TEXT | Änderungsbeschreibung |
| author | TEXT | Autor (Standard 'system') |
| metadata | TEXT | JSON |
skill_update_requests — Skill-PRs (Sage)
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| skill_id | INTEGER FK | CASCADE |
| proposed_by | TEXT | 'sage' (Standard) |
| status | TEXT | 'pending', 'approved', 'rejected', 'applied' |
| current_content | TEXT | Aktuelle Version |
| proposed_content | TEXT | Vorgeschlagene Version |
| reason | TEXT | Änderungsgrund |
skill_benchmarks — A/B-Tests für Skills
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| request_id | INTEGER FK→skill_update_requests | CASCADE |
| test_scenario | TEXT | Testszenario |
| old_output, new_output | TEXT | Ergebnisse |
| score_old, score_new | REAL | Bewertungen |
| judgment_reason | TEXT | Begründung |
pinned_notes — Angeheftete Notizen
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| project_name | TEXT | Projekt |
| worker_id | TEXT | Worker |
| title | TEXT | Titel |
| body | TEXT | Text |
| source_message_id | INTEGER | Verweis auf chat_messages |
project_phases — Roadmap-Phasen
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| project_name | TEXT | Projekt |
| phase_id | INTEGER | Phasennummer |
| phase_title | TEXT | Bezeichnung |
| status | TEXT | 'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED' |
| progress | REAL | 0.0–1.0 |
| deadline | TEXT | ISO 8601 |
UNIQUE(project_name, phase_id)
activity_log — Aktivitätsjournal
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PK | |
| project_name | TEXT | Projekt |
| actor | TEXT | Worker oder Benutzer |
| event_type | TEXT | Ereignistyp |
| title | TEXT | Beschreibung |
| 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 — System-Einstellungen
| Spalte | Typ | Beschreibung |
|---|---|---|
| key | TEXT PK | Schlüssel |
| value | TEXT | Wert |
| description | TEXT | Beschreibung |
marketplace_analysis_cache — Marketplace-Cache
Cache für die Kompatibilitätsanalyse von Marketplace-Skills.
ephemeral_tokens — Kurzlebige Token (Issue #27)
Persistenter Speicher für One-Time-Token für OAuth-State, Passwort-Reset und E-Mail-Verifizierung. Vor Migration 022 lebten diese Token in einer In-Memory-Map und gingen bei jedem Neustart des Masters verloren, was den Google/GitHub-Login zerstörte (invalid_state).
| Spalte | Typ | Beschreibung |
|---|---|---|
| token | TEXT PK | 32-Byte-Hex (randomBytes(32)) |
| type | TEXT NOT NULL | oauth_state | password_reset | email_verification |
| payload | TEXT NOT NULL | JSON: {provider} für oauth_state, {email} für den Rest |
| expires_at | INTEGER NOT NULL | ms-Epoch — TTL: 10 Min. (oauth) / 30 Min. (reset) / 24 Std. (verify) |
INDEX(type), INDEX(expires_at). Einmalige Verwendung: consume() liest in einer Transaktion den Payload und löscht die Zeile.
project_issues — Issues pro Projekt (Issue #53, Phase 53.14)
SQLite SSOT für Issues anstelle von projektspezifischen issues/issues.json-Dateien. Die Datei verbleibt auf der Festplatte als Derived Export (Mirror), wird per gitignore ausgeschlossen — die DB ist autoritativ.
| Spalte | Typ | Beschreibung |
|---|---|---|
| project_name | TEXT NOT NULL | Name aus Registry; zusammengesetzter PK mit id |
| id | INTEGER NOT NULL | 1-basierte Pro-Projekt-Sequenz (max+1 beim Insert) |
| title | TEXT NOT NULL | Issue-Titel |
| body | TEXT NOT NULL DEFAULT '' | Beschreibung, 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' | draft | open | in_progress | blocked | deferred | closed (SQL-Default 'open' bleibt erhalten — neue Issues werden auf App-Ebene in draft gezwungen) |
| assignee | TEXT NULL | Worker-ID, die das Issue übernommen hat (Migration 036) |
| created_by | TEXT NOT NULL DEFAULT 'legacy' | Worker-ID, die das Issue erstellt hat (Migration 046 / #327) — für Audit (wer, wann, an wen) |
| created_at | TEXT NOT NULL | ISO-Timestamp |
| updated_at | TEXT NOT NULL | ISO-Timestamp |
| closed_at | TEXT NULL | ISO-Timestamp bei status=closed |
| activity | TEXT NOT NULL DEFAULT '[]' | JSON-Array {ts, type, author, text} — append-only Audit |
PK (project_name, id) — jedes Projekt hat seinen eigenen unabhängigen ID-Raum. INDEX (project_name, status) — häufigster Filter (open only). Operationen befinden sich in issueQueries (shared/db.ts): list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll — atomare Transaktion; bulkImport — INSERT OR IGNORE für idempotentes Re-Seeding.
Migration 036 (#187, 2026-05-23): ALTER TABLE project_issues ADD COLUMN assignee TEXT. Neue Statuses (in_progress, blocked, deferred) werden auf App-Ebene validiert (IssueStatus-Union in shared/db.ts), nicht per CHECK-Constraint (SQLite erlaubt kein Hinzufügen von Constraints via ALTER TABLE).
Migration 046 (#327, 2026-06-03): ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy'. Lifecycle um den neuen Status draft erweitert. handleCreateIssue erzwingt status='draft' wenn assignee=null (sonst direkt 'open'), verlangt worker_id (Body oder Auth-Fallback). handleUpdateIssue macht Auto-Promote draft → open bei der ersten Assignee-Zuweisung und lehnt close für never-assigned Issues ab (FR-ISS-101/102/103). Bereits vorhandene Zeilen werden per DEFAULT als 'legacy' backfilled.
onboarding_progress — Post-Wizard-Checklisten-Status (Issue #56, Phase 54.1)
Benutzerspezifischer Derived Cache über den activity_log-Eventstream (SSOT). Die UI liest mit einer einzelnen Abfrage, anstatt über Ereignisse zu aggregieren. Replaybar: Dismiss/Replay ändert nicht den state, nur dismissed_at.
| Spalte | Typ | Beschreibung |
|---|---|---|
| chat_id | TEXT PRIMARY KEY | Benutzer-ID (Registry Chat-ID) |
| state | TEXT NOT NULL DEFAULT '{}' | JSON {workers, cli, skill, bot, issue} → completed | skipped (fehlende Schlüssel = pending) |
| completed_count | INTEGER NOT NULL DEFAULT 0 | Derived-Cache-Zähler der completed-Schritte (0–5) |
| started_at | TEXT NOT NULL DEFAULT (datetime('now')) | Zeilenerstellung (erstes Ereignis/Dismiss/Replay) |
| completed_at | TEXT NULL | ISO-Timestamp wenn alle 5 Schritte completed |
| dismissed_at | TEXT NULL | ISO-Timestamp wenn der Benutzer die Checkliste geschlossen hat |
| source | TEXT NOT NULL DEFAULT 'web' | web | cli — für Funnel-Attribution (#58 arc tour) |
| updated_at | TEXT NOT NULL DEFAULT (datetime('now')) | Mutations-Timestamp |
INDEX auf completed_at für Funnel-Analytik (#61 Phase 54.6). Operationen befinden sich in onboardingQueries (shared/db.ts): getProgress/recordEvent/dismiss/replay. recordEvent ist idempotent auf (chat_id, step, status) — ein wiederholter identischer Aufruf gibt changed=false zurück und schreibt nicht in activity_log. Whitelist: 5 Schritte × 2 Statuses (completed, skipped). skipped inkrementiert completed_count NICHT. Der Übergang skipped → completed erhöht den Zähler. Alle Mutationen emittieren Ereignisse in activity_log mit event_type LIKE 'onboarding_%' — echter SSOT für Funnel-Metriken; Tabellenspalten sind Derived Cache.
platform_audit_log — Super-Admin-Protokoll für Secret-Rotation (Phase 57, Sentinel #103)
Append-only-Journal für Mutationen an Vault-Platform-Secrets über die Platform-Einstellungen-UI. SSOT für Post-Incident-Forensik ("welcher Admin hat den Anthropic-Key um 03:14 UTC rotiert?"). vault.json selbst schreibt dies nicht — nur den Wert.
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PRIMARY KEY AUTOINCREMENT | Zeitliche Reihenfolge auch bei Clock-Skew |
| ts | TEXT NOT NULL DEFAULT (datetime('now')) | Server-seitige UTC; Angreifer können kein Backdating vornehmen |
| user_chat_id | TEXT NOT NULL | Admin, der die Aktion ausgeführt hat |
| user_email | TEXT NULL | Snapshot aus der users-Tabelle zum Zeitpunkt der Aktion |
| action | TEXT NOT NULL | list | view | rotate | test | restart |
| key_name | TEXT NOT NULL | Vault-Eintrag-Name (Allowlist in platform.ts); * für list-all |
| ip | TEXT NULL | aus CF-Connecting-IP / X-Real-IP / XFF-Tail |
| result | TEXT NOT NULL | success oder fail:<reason> |
| user_agent | TEXT NULL | UA-String, gekürzt auf 200 Zeichen |
INDEXES: idx_platform_audit_ts (aktuell über alle Keys), idx_platform_audit_user (Pro-Admin-Trail), idx_platform_audit_key (Rotations-Historie pro Key). Append-only-Invariante: keine UPDATE/DELETE-Handler; UI stellt nur recent() + lastRotated() über platformAuditQueries in shared/db.ts bereit. Jede Aktion (Erfolg + Fehler) schreibt eine Zeile.
auth_events — Authentifizierungs-Ereignislog für Endnutzer (Migration 027, Issue #130)
Append-only-Journal der Auth-Ereignisse für Security-Auditing und Incident-Untersuchungen. Getrennt von platform_audit_log (Admin-only Vault) — diese Tabelle deckt den End-User-Auth-Flow inkl. IP ab.
| Spalte | Typ | Beschreibung |
|---|---|---|
| id | INTEGER PRIMARY KEY AUTOINCREMENT | zeitliche Reihenfolge |
| ts | TEXT NOT NULL DEFAULT (datetime('now')) | server-seitige 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 bei fehlgeschlagenen Versuchen, wenn der User nicht identifiziert wurde |
| TEXT NULL | beim Versuch verwendete E-Mail | |
| ip | TEXT NOT NULL DEFAULT 'unknown' | CF-Connecting-IP → X-Real-IP → letztes XFF-Segment |
| user_agent | TEXT NULL | UA-String |
| result | TEXT NOT NULL | success | failed | rate_limited | invalid_invite | already_registered | 2fa_required | invalid_token |
| meta | TEXT NULL | JSON-Zusatzkontext (z. B. {isNew: true} bei OAuth) |
INDEXES: idx_auth_events_ts (neueste zuerst), idx_auth_events_user (Historie pro User), idx_auth_events_ip (Bedrohungserkennung pro IP), idx_auth_events_event (Filter event+result). authEventQueries.insert/recent in shared/db.ts. Geloggt in master-bot/routes/auth.ts (register/login/oauth/magic-link) und master-bot/routes/cli.ts (device_code_approve).
Migrationen
| # | Name | Phase | Was hinzugefügt wird |
|---|---|---|---|
| 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 auf Worker-Nachrichten |
| 014 | timeline_events | Phase 47 | Tabelle timeline_events |
| 015 | e2ee_chat | Phase 45.3 | +encrypted-Spalte auf chat_messages |
| 016 | recovery_keys | Phase 45.4 | Tabelle recovery_keys |
| 017 | github_links | Phase 49.3 | Tabelle github_links (Webhook-Bindungen) |
| 018 | github_events | Phase 49.3.1 | Tabelle github_events (Ereignis-Log für UI-Feed) |
| 019 | trial_credits | Phase 50.1 | +users.trial_granted_at, +projects.trial_mode, +projects.trial_tokens_remaining |
| 020 | subscriptions | Phase 51 | Tabelle subscriptions (plan, stripe_customer_id, status, feature_flags) + stripe_events-Idempotenz-Log |
| 021 | invites | Phase 52.1 | Tabelle invites (Closed-Beta-Enrollment) — code, created_by, used_by, parent_invite_code, status, note |
| 022 | ephemeral_tokens | Issue #27 | Tabelle ephemeral_tokens — persistenter OAuth-State / Passwort-Reset / E-Mail-Verifizierung, anstelle von In-Memory-Map (Behebung von invalid_state beim Master-Neustart) |
| 023 | project_issues | Phase 53.14 / Issue #53 | Tabelle project_issues (zusammengesetzter PK project_name+id, JSON labels/activity) — verschiebt Issues-Storage von issues/issues.json in SQLite SSOT, JSON wird Derived Mirror; eliminiert Text-Merge-Drift beim 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) — projektweite Audit-Historie für Project Context Export + projektspezifische Policy-Toggles; Alert bei ≥3 Exporten/24h wenn notify_on_export = true |
| 025 | onboarding_progress | Phase 54.1 / Issue #56 | onboarding_progress (chat_id PK, JSON-State pro 5 Schritte, completed_count derived, started_at/completed_at/dismissed_at, source web|cli) — Derived Cache über activity_log-Eventstream; SSOT = Ereignisse event_type LIKE 'onboarding_%'; recordEvent idempotent, dismiss/replay non-destruktiv |
| 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 für Super-Admin-Platform-Einstellungen-UI-Mutationen an Vault-Secrets. 3 Indexes: idx_platform_audit_ts/user/key. Kein UPDATE/DELETE — schreibgeschützt über 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-Ereignisprotokoll. Ereignisse: signup/login/oauth_login/magic_link/device_code_approve. Ergebnisse: success/failed/rate_limited/invalid_invite/already_registered/2fa_required/invalid_token. 4 Indexes: ts/user_id/ip/event+result |
| 028 | managed_containers | Phase 60 #135/#136 | managed_containers (id TEXT PK (Docker-Container-Name), user_id, status (provisioning|ready|paused|suspended|deleted), server_ip, internal_port INT, claude_authed INT, github_authed INT, created_at, last_active) — 1-pro-User Docker-Container-Registry für Standard Cloud. managedContainerQueries.findByUser/insert/updateStatus/setAuthFlag/touch/listActive in shared/db.ts. Endpunkte: POST /api/crm/cloud/provision, GET /status, POST /deprovision in 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-Warteliste für Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats in shared/db.ts. Endpunkte: POST /api/crm/cloud/waitlist (beitreten), GET /waitlist/status (eigener Status), GET /waitlist (Admin-Liste), POST /waitlist/invite (Admin aktiviert + upgraded Plan auf 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 Übersetzungsqualitäts-Feedback. Admin-Review-Dashboard unter /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())) — Per-Request Claude Token-Usage-Log. 3 Indexes: owner_id, project_name, created_at. Befüllt von child-bot/claude-runner.ts via POST /api/internal/usage/log (fire-and-forget). Abgefragt von GET /api/crm/account/usage → gibt { rows: [...letzte 200], totals: { total, input, output } } zurück für 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)) — Täglicher Rate-Limit-Zähler für In-App-KI-Hilfe-Chat (30 Nachrichten/Tag). Abgefragt von GET /api/crm/help/usage; aktualisiert bei POST /api/crm/help/chat. |
| 033 | arc_help_messages | Phase 61 #153 | arc_help_messages (id PK AUTOINCREMENT, user_id, role, text, sources JSON, created_at) — Persistente Arc-Help-Chat-Historie pro User (letzte 60). |
| 034 | skills_owner_project | #157 | ALTER TABLE skills_global ADD COLUMN owner_project TEXT — NULL=global, non-null=gehört dem Projekt. |
| 035 | password_version | Phase 64 #174 | ALTER TABLE users ADD COLUMN password_version INTEGER DEFAULT 0 — wird bei Passwortänderung inkrementiert; der JWT-Claim pv wird von crmAuthMiddleware validiert, um alte Tokens zu invalidieren. |
| 036 | issue_assignee | #187 / 2026-05-23 | ALTER TABLE project_issues ADD COLUMN assignee TEXT — nullable Worker-ID. Ermöglicht arc issue take <id> + erweiterte Statuses in_progress/blocked/deferred (auf App-Ebene validiert). |
| 046 | issue_draft_and_created_by | #327 / 2026-06-03 | ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy' — Audit-Feld „wer hat erstellt". Lifecycle += draft (App-Level-Union). handleCreateIssue erzwingt draft ohne Assignee; handleUpdateIssue Auto-Promote draft→open beim Zuweisen + lehnt close für never-assigned ab (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) — Early-Access-Anfrageformular auf der Login-Seite. |
| 038 | plata_billing | Phase 65 #202 / 2026-05-26 | Fügt Plata-by-mono-Spalten zu subscriptions hinzu: plata_card_token TEXT, plata_wallet_id TEXT, plata_masked_pan TEXT, next_billing_date TEXT, billing_failures INTEGER DEFAULT 0. Erstellt außerdem die Idempotenz-Tabelle 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-Skill-Zugriff über Projekte hinweg. |
| 040 | drop_stripe | #205 / 2026-05-26 | DROP der Spalte stripe_customer_id + DROP TABLE stripe_events. NB: Die Spalte stripe_subscription_id überlebte diese Migration, weil SQLite DROP COLUMN auf UNIQUE-beschränkten Spalten verweigert (per try/catch stillschweigend abgefangen). Siehe Migration 041. |
| 041 | rebuild_subscriptions | #205 / 2026-05-26 | Follow-up zu 040 — baut die Tabelle subscriptions ohne die verwaiste Spalte stripe_subscription_id per CREATE TABLE …-AS-Pattern neu auf. Idempotent (übersprungen, wenn die Spalte bereits fehlt). |
| 042 | totp_backup_codes | — | ALTER TABLE users ADD COLUMN totp_backup_codes TEXT — TOTP-Recovery-Codes. |
| 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)) — Metadaten-Store für hochgeladene Worker-Avatar-Bilder. Dateien liegen in data/worker-avatars/. Ausgeliefert über 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)) — vom User gespeicherte Worker-Templates. Endpunkte: 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), Index auf updated_at) — Live-Runtime-State pro Worker. Geschrieben von handleSetActiveRole (Invariante: eine aktive Rolle pro Projekt). Gelesen von GET /api/crm/projects/:name/workers/:id/runtime mit Staleness-Fallback: status='working' + tmux tot + updated_at > 10 Min → wird zu idle gezwungen (Crash-Erkennung). |
| 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 Virtual Table, embedding FLOAT[1024]). Trennung: vec0 kann Metadaten nicht effizient halten; LIST/DELETE-Workflows laufen als plain SQL. Die sqlite-vec-Extension wird in initDb automatisch geladen, bevor die Migration läuft. |
| 050 | drop_notebook_id | Phase 71.8 #365 / 2026-06-05 | ALTER TABLE projects DROP COLUMN notebook_id. Entfernt das Schema-Feld der NotebookLM-Bridge; die semantische Suche läuft jetzt über self-hosted RAG (Cohere + sqlite-vec). Idempotent per PRAGMA table_info-Check, damit frische DBs mit aktuellem 001-Schema nicht über die fehlende Spalte stolpern. |
| 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)) — täglicher Quota-Zähler für Sprachtranskription, spiegelt die Form von Migration 032 arc_help_usage. Ungefähre Sekunden (geschätzt aus der Upload-Bytegröße bei angenommenen ~32 kbps Voice-Codec); wird vor jedem Transcribe in handleVoiceTranscribe (shared/routes/voice.ts) gegen das weiche Limit von 60 Min/Tag geprüft, damit kein einzelner User den gemeinsamen whisper-server-Worker-Pool auf Contabo monopolisiert. |
| 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) — eine Zeile pro hochgeladener Meeting-Datei. frames_json = JSON-Array aus {ts_ms, description} von Claude Vision (Phase 73.4). summary_json = {tldr, key_points, action_items, decisions, topics, model, generated_at} von Claude Sonnet (Phase 73.5). Die Quelldatei wird von der Festplatte gelöscht, sobald status='done' (CEO-Entscheidung D4). Indexes auf (project, created_at DESC) und (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)) — kumulative Zähler pro Skill pro Projekt. trigger_count wird inkrementiert, wenn der Context Router den Skill injiziert; install_count bei Save/Create; session_count beim ersten Auftreten in einer Session. Genutzt von GET /api/crm/projects/:name/skills/usage. |
| 057 | notes | Phase 78.1 #395 / 2026-06-07 | 4 Tabellen: notes (id PK, project_name, title, description, created_by, created_at, updated_at) — eine Note als Quellen-Container. 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) — eine Quelle pro Zeile, content_text = extrahierter Text für RAG. note_issue_links (note_id FK, issue_id, project_name, linked_at, PK(note_id,issue_id)) — Verknüpfung Note↔Issue. note_chats (id PK, note_id FK→notes, role CHECK(user|assistant), content, created_at) — Chat-Historie der Note. Indexes: 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-Fortschrittstracker für den Background-Worker. Von transcripts isoliert, damit häufige UPDATEs (alle paar Sekunden während ffmpeg/whisper) die Indexes der Haupttabelle nicht belasten. Der SSE-Endpoint streamt diese Tabelle mit 1s-Poll. Status-Lifecycle: queued → extracting_audio → transcribing → (video: extracting_frames → frames_extracted → vision_analyzing → vision_analyzed) → summarizing → summarized → embedding → done (oder failed bei jedem Schritt). Index auf (status, updated_at). |
Beziehungen (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)
Alle CASCADE — das Löschen eines Skills löscht alle Forks, die Historie, PRs und Benchmarks.
ER-Diagramm (Textform)
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