Base de datos — SQLite Schema

Información general

Diagrama 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 tablas distribuidas en 24 migraciones. Auth + proyectos + datos del workspace + inteligencia + billing + control de acceso beta + auditoría de exportación de contexto. Todas las restricciones FK están aplicadas.

Tablas

users — Usuarios

Columna Tipo Descripción
id TEXT PK UUID
email TEXT UNIQUE Email (obligatorio)
password_hash TEXT Hash de contraseña (bcrypt)
role TEXT 'admin' o 'user'
name TEXT Nombre
avatar_url TEXT URL del avatar
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 Secreto TOTP (2FA)
last_login TEXT ISO 8601
created_at TEXT ISO 8601

projects — Proyectos

Columna Tipo Descripción
id INTEGER PK Auto
technical_name TEXT Nombre técnico (único por owner)
owner_id TEXT FK→users Propietario
display_name TEXT Nombre visible
description TEXT Descripción
color TEXT Color (hex)
icon TEXT Ícono
is_archived INTEGER 0/1
sort_order INTEGER Orden (default 999)
notebook_id TEXT NotebookLM ID
project_protocol TEXT Protocolo del proyecto
created_at, updated_at TEXT Timestamps

UNIQUE(technical_name, owner_id)

account_settings — Ajustes de cuenta

Columna Tipo Descripción
user_id TEXT PK FK→users
anthropic_key TEXT Clave API de Anthropic
openai_key TEXT Clave API de OpenAI
name TEXT Nombre
role TEXT Rol
auto_harvest INTEGER Recolección automática de skills 0/1

chat_messages — Historial de chats

Columna Tipo Descripción
id INTEGER PK Auto
project_name TEXT Proyecto
worker_id TEXT Worker
role TEXT 'user', 'assistant', 'system'
content TEXT Texto del mensaje (cifrado AES-256-GCM si encrypted=1)
encrypted INTEGER 0=plaintext, 1=vault-encrypted (Phase 45.3)
attachments TEXT Array JSON
timestamp TEXT ISO 8601
metadata TEXT Objeto JSON

INDEX(project_name, worker_id, timestamp DESC)

recovery_keys — Claves de recuperación (Phase 45.4)

Columna Tipo Descripción
id INTEGER PK Auto
user_id TEXT FK→users Propietario
encrypted_key TEXT Master key cifrado
key_hint TEXT Pista (primeros 4 caracteres)
created_at TEXT ISO 8601
used_at TEXT Cuándo se usó
revoked INTEGER 0=activo, 1=revocado

INDEX(user_id)

github_links — Vínculos de repositorio GitHub (Phase 49.3)

Columna Tipo Descripción
id INTEGER PK Auto
project_name TEXT Proyecto de Arc OS
owner TEXT Propietario del repo en GitHub
repo TEXT Nombre del repo en GitHub
webhook_secret TEXT UNIQUE Hex de 32 bytes para validación HMAC-SHA256
created_at TEXT ISO 8601
created_by TEXT FK→users Quién creó el link

UNIQUE(project_name, owner, repo) — se permiten múltiples repos por proyecto. INDEX(project_name), INDEX(webhook_secret)

github_events — Log de eventos GitHub (Phase 49.3.1)

Columna Tipo Descripción
id INTEGER PK Auto
link_id INTEGER FK→github_links CASCADE al eliminar el link
project_name TEXT Proyecto de Arc OS (desnormalizado para velocidad de consulta)
event_type TEXT push, pull_request, workflow_run, issues
action TEXT sub-acción (opened/closed/success/failure)
summary TEXT Cadena de visualización pre-formateada
url TEXT Deep link de GitHub
actor TEXT Nombre de usuario de GitHub
created_at TEXT ISO 8601

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

skills_global — Skills globales

Columna Tipo Descripción
id INTEGER PK Auto
name TEXT UNIQUE Nombre
description TEXT Descripción
category TEXT Categoría (default 'general')
content TEXT Contenido del skill (Markdown)
triggers TEXT Array JSON de triggers
keywords TEXT Array JSON de palabras clave
eval_rules TEXT Array JSON de reglas
tool_code_ts TEXT Implementación en TypeScript
version INTEGER Versión (auto-incremento)
status TEXT 'active', 'draft', 'deprecated', 'archived'

skills_project_forks — Forks de skills

Columna Tipo Descripción
id INTEGER PK Auto
project_name TEXT Proyecto
skill_id INTEGER FK→skills_global Skill padre (CASCADE)
content TEXT Contenido personalizado
triggers, keywords, eval_rules, tool_code_ts TEXT Sobreescrituras
version INTEGER Versión del fork

UNIQUE(project_name, skill_id)

skill_evolution_logs — Historial de cambios de skills

Columna Tipo Descripción
id INTEGER PK
skill_id INTEGER FK CASCADE
project_name TEXT Proyecto (nullable)
action TEXT 'created', 'modified', 'applied', 'reverted'
diff_summary TEXT Descripción de los cambios
author TEXT Autor (default 'system')
metadata TEXT JSON

skill_update_requests — PRs de skills (Sage)

Columna Tipo Descripción
id INTEGER PK
skill_id INTEGER FK CASCADE
proposed_by TEXT 'sage' (default)
status TEXT 'pending', 'approved', 'rejected', 'applied'
current_content TEXT Versión actual
proposed_content TEXT Versión propuesta
reason TEXT Motivo del cambio

skill_benchmarks — Tests A/B de skills

Columna Tipo Descripción
id INTEGER PK
request_id INTEGER FK→skill_update_requests CASCADE
test_scenario TEXT Escenario de prueba
old_output, new_output TEXT Resultados
score_old, score_new REAL Puntuaciones
judgment_reason TEXT Justificación

pinned_notes — Notas fijadas

Columna Tipo Descripción
id INTEGER PK
project_name TEXT Proyecto
worker_id TEXT Worker
title TEXT Título
body TEXT Texto
source_message_id INTEGER Referencia a chat_messages

project_phases — Fases del Roadmap

Columna Tipo Descripción
id INTEGER PK
project_name TEXT Proyecto
phase_id INTEGER Número de fase
phase_title TEXT Nombre
status TEXT 'PLANNED', 'ACTIVE', 'COMPLETED', 'CANCELLED'
progress REAL 0.0–1.0
deadline TEXT ISO 8601

UNIQUE(project_name, phase_id)

activity_log — Registro de actividad

Columna Tipo Descripción
id INTEGER PK
project_name TEXT Proyecto
actor TEXT Worker o usuario
event_type TEXT Tipo de evento
title TEXT Descripción
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 — Configuración del sistema

Columna Tipo Descripción
key TEXT PK Clave
value TEXT Valor
description TEXT Descripción

marketplace_analysis_cache — Cache del marketplace

Cache de análisis de compatibilidad de skills del marketplace.

ephemeral_tokens — Tokens de vida corta (issue #27)

Almacén persistente para tokens OAuth state, restablecimiento de contraseña y verificación de email de un solo uso. Antes de la migración 022, estos tokens vivían en un Map en memoria y se perdían en cada reinicio del master, rompiendo el login con Google/GitHub (invalid_state).

Columna Tipo Descripción
token TEXT PK Hex de 32 bytes (randomBytes(32))
type TEXT NOT NULL oauth_state | password_reset | email_verification
payload TEXT NOT NULL JSON: {provider} para oauth_state, {email} para el resto
expires_at INTEGER NOT NULL ms epoch — TTL: 10 min (oauth) / 30 min (reset) / 24 h (verify)

INDEX(type), INDEX(expires_at). Uso único: consume() lee el payload y elimina la fila en una sola transacción.

project_issues — Issues por proyecto (issue #53, Phase 53.14)

SQLite SSOT para issues en lugar de issues/issues.json por proyecto. El archivo permanece en disco como export derivado (mirror), ignorado por git — la DB es la fuente autoritativa.

Columna Tipo Descripción
project_name TEXT NOT NULL nombre del registry; parte de la PK compuesta con id
id INTEGER NOT NULL secuencia 1-based por proyecto (max+1 en insert)
title TEXT NOT NULL título del issue
body TEXT NOT NULL DEFAULT '' descripción, Markdown
priority TEXT NOT NULL DEFAULT 'P2' P0 | P1 | P2 | P3
labels TEXT NOT NULL DEFAULT '[]' Array JSON de strings
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 cuando status=closed
activity TEXT NOT NULL DEFAULT '[]' Array JSON {ts, type, author, text} — auditoría append-only

PK (project_name, id) — cada proyecto tiene su propio espacio de IDs independiente. INDEX (project_name, status) — filtro más frecuente (solo open). Las operaciones viven en issueQueries (shared/db.ts): list/get/nextId/insert/upsert/replaceAll/bulkImport. replaceAll es una transacción atómica; bulkImport usa INSERT OR IGNORE para re-seed idempotente.

onboarding_progress — Estado del checklist post-wizard (issue #56, Phase 54.1)

Cache derivado por usuario sobre el event stream de activity_log (SSOT). La UI lee con una sola consulta en lugar de agregar eventos. Reproducible: dismiss/replay no modifican state, solo dismissed_at.

Columna Tipo Descripción
chat_id TEXT PRIMARY KEY id de usuario (chat id del registry)
state TEXT NOT NULL DEFAULT '{}' JSON {workers, cli, skill, bot, issue}completed | skipped (claves ausentes = pending)
completed_count INTEGER NOT NULL DEFAULT 0 conteo derivado en cache de pasos completed (0–5)
started_at TEXT NOT NULL DEFAULT (datetime('now')) creación del registro (primer evento/dismiss/replay)
completed_at TEXT NULL Timestamp ISO cuando todos los 5 pasos están completed
dismissed_at TEXT NULL Timestamp ISO cuando el usuario cerró el checklist
source TEXT NOT NULL DEFAULT 'web' web | cli — para atribución de funnel (#58 arc tour)
updated_at TEXT NOT NULL DEFAULT (datetime('now')) timestamp de mutación

INDEX en completed_at para análisis de funnel (#61 Phase 54.6). Las operaciones viven en onboardingQueries (shared/db.ts): getProgress/recordEvent/dismiss/replay. recordEvent es idempotente en (chat_id, step, status) — una llamada idéntica repetida devuelve changed=false y no escribe en activity_log. Whitelist: 5 pasos × 2 statuses (completed, skipped). skipped NO incrementa completed_count. La transición skipped → completed suma al conteo. Todas las mutaciones emiten eventos en activity_log con event_type LIKE 'onboarding_%' — verdadero SSOT para métricas de funnel; las columnas de la tabla son cache derivado.

platform_audit_log — Registro de rotación de secretos super-admin (Phase 57, Sentinel #103)

Registro append-only de mutaciones sobre los platform-secrets del vault a través de la UI de Platform Settings. SSOT para forense post-incidente ("¿qué admin rotó la clave de Anthropic a las 03:14 UTC?"). El propio vault.json no escribe esto — solo el valor.

Columna Tipo Descripción
id INTEGER PRIMARY KEY AUTOINCREMENT orden temporal incluso ante clock skew
ts TEXT NOT NULL DEFAULT (datetime('now')) UTC server-side; un atacante no puede backdatear
user_chat_id TEXT NOT NULL admin que realizó la acción
user_email TEXT NULL snapshot de la tabla users en el momento de la acción
action TEXT NOT NULL list | view | rotate | test | restart
key_name TEXT NOT NULL nombre de entrada del vault (allowlist en platform.ts); * para list-all
ip TEXT NULL de CF-Connecting-IP / X-Real-IP / XFF tail
result TEXT NOT NULL success o fail:<reason>
user_agent TEXT NULL cadena UA, truncada a 200 caracteres

INDEXES: idx_platform_audit_ts (recientes entre claves), idx_platform_audit_user (trail por admin), idx_platform_audit_key (historial de rotación por clave). Invariante append-only: sin handlers UPDATE/DELETE; la UI expone solo lectura recent() + lastRotated() a través de platformAuditQueries en shared/db.ts. Cada acción (success+fail) escribe una fila.

Migraciones

# Nombre Fase Qué agrega
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 en mensajes de worker
014 timeline_events Phase 47 tabla timeline_events
015 e2ee_chat Phase 45.3 +columna encrypted en chat_messages
016 recovery_keys Phase 45.4 tabla recovery_keys
017 github_links Phase 49.3 tabla github_links (webhook bindings)
018 github_events Phase 49.3.1 tabla github_events (log de eventos para UI feed)
019 trial_credits Phase 50.1 +users.trial_granted_at, +projects.trial_mode, +projects.trial_tokens_remaining
020 subscriptions Phase 51 tabla subscriptions (plan, stripe_customer_id, status, feature_flags) + log de idempotencia stripe_events
021 invites Phase 52.1 tabla invites (inscripción a beta cerrada) — code, created_by, used_by, parent_invite_code, status, note
022 ephemeral_tokens issue #27 tabla ephemeral_tokens — OAuth state / restablecimiento de contraseña / verificación de email persistentes, en lugar de Map en memoria (fix de invalid_state al reiniciar el master)
023 project_issues Phase 53.14 / issue #53 tabla project_issues (PK compuesta project_name+id, JSON labels/activity) — migra el almacenamiento de issues de issues/issues.json a SQLite SSOT, el JSON se convierte en mirror derivado; elimina text-merge drift en 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) — historial de auditoría por proyecto para Project Context Export + toggles de política por proyecto; alerta cuando ≥3 exports/24h si notify_on_export = true
025 onboarding_progress Phase 54.1 / issue #56 onboarding_progress (chat_id PK, JSON state por 5 pasos, completed_count derivado, started_at/completed_at/dismissed_at, source web|cli) — cache derivado sobre el event stream de activity_log; SSOT = eventos event_type LIKE 'onboarding_%'; recordEvent idempotente, dismiss/replay no destructivos
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) — audit trail append-only para mutaciones de la UI super-admin Platform Settings sobre vault secrets. 3 indexes: idx_platform_audit_ts/user/key. Sin UPDATE/DELETE — solo lectura a través de 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) — Registro de eventos de autenticación + IP append-only. Eventos: signup/login/oauth_login/magic_link/device_code_approve. Resultados: success/failed/rate_limited/invalid_invite/already_registered/2fa_required/invalid_token. 4 índices: ts/user_id/ip/event+result
028 managed_containers Phase 60 #135/#136 managed_containers (id TEXT PK (nombre del contenedor Docker), user_id, status (provisioning|ready|paused|suspended|deleted), server_ip, internal_port INT, claude_authed INT, github_authed INT, created_at, last_active) — Registro de contenedores Docker 1-por-usuario para Standard Cloud. managedContainerQueries.findByUser/insert/updateStatus/setAuthFlag/touch/listActive en shared/db.ts. Endpoints: POST /api/crm/cloud/provision, GET /status, POST /deprovision en 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 de espera Early Access para Standard Cloud. cloudWaitlistQueries.join/findByUser/list/invite/activate/decline/stats en shared/db.ts. Endpoints: POST /api/crm/cloud/waitlist (unirse), GET /waitlist/status (propio), GET /waitlist (lista admin), POST /waitlist/invite (admin activa + actualiza plan a 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) — Feedback de calidad de traducción por locale. Panel de revisión admin en /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())) — Registro de uso de tokens Claude por request. 3 índices: owner_id, project_name, created_at. Poblado por child-bot/claude-runner.ts vía POST /api/internal/usage/log (fire-and-forget). Consultado por GET /api/crm/account/usage → devuelve { rows: [...últimas 200], totals: { total, input, output } } para 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)) — Contador de rate-limit diario para el chat de Ayuda IA en la app (30 mensajes/día). Consultado por GET /api/crm/help/usage; actualizado en POST /api/crm/help/chat.

Relaciones (Foreign Keys)

Todos los CASCADE — eliminar un skill elimina todos sus forks, historial, PRs y benchmarks.

Diagrama ER (textual)

users ─1:N──> projects
            ├── chat_messages
            ├── pinned_notes
            ├── project_phases
            └── skills_project_forks ──> skills_global
                                          ├── skill_evolution_logs
                                          ├── skill_update_requests
                                          │    └── skill_benchmarks
                                          └── (tablas independientes)
                                               ├── activity_log
                                               ├── system_configs
                                               └── marketplace_analysis_cache