Base de datos — SQLite Schema

Información general

Diagrama 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
    }

    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)
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' draft | open | in_progress | blocked | deferred | closed (el DEFAULT SQL 'open' se conserva — los issues nuevos se fuerzan a draft a nivel de aplicación)
assignee TEXT NULL worker id que tomó el issue (migración 036)
created_by TEXT NOT NULL DEFAULT 'legacy' worker id que creó el issue (migración 046 / #327) — para auditoría (quién, cuándo, a quién)
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.

Migración 036 (#187, 2026-05-23): ALTER TABLE project_issues ADD COLUMN assignee TEXT. Los nuevos statuses (in_progress, blocked, deferred) se validan a nivel de aplicación (union IssueStatus en shared/db.ts), no con CHECK constraint (SQLite no permite añadir constraints vía ALTER TABLE).

Migración 046 (#327, 2026-06-03): ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy'. El lifecycle se amplía con el nuevo status draft. handleCreateIssue fuerza status='draft' cuando assignee=null (en caso contrario, directamente 'open') y requiere worker_id (body o fallback de auth). handleUpdateIssue auto-promociona draft → open al asignar el primer assignee y rechaza close para un issue nunca asignado (FR-ISS-101/102/103). Las filas preexistentes se rellenan vía DEFAULT como 'legacy'.

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.

auth_events — Registro de eventos de autenticación de usuario final (Migración 027, issue #130)

Registro append-only de eventos de auth para auditoría de seguridad e investigación de incidentes. Separado de platform_audit_log (vault solo-admin) — esta tabla cubre el flujo de auth del usuario final con IP.

Columna Tipo Descripción
id INTEGER PRIMARY KEY AUTOINCREMENT orden temporal
ts TEXT NOT NULL DEFAULT (datetime('now')) UTC server-side
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 en intentos fallidos donde el usuario no fue identificado
email TEXT NULL email usado en el intento
ip TEXT NOT NULL DEFAULT 'unknown' CF-Connecting-IP → X-Real-IP → último segmento de XFF
user_agent TEXT NULL cadena UA
result TEXT NOT NULL success | failed | rate_limited | invalid_invite | already_registered | 2fa_required | invalid_token
meta TEXT NULL JSON con contexto extra (p. ej. {isNew: true} para OAuth)

INDEXES: idx_auth_events_ts (más recientes primero), idx_auth_events_user (historial por usuario), idx_auth_events_ip (detección de amenazas por IP), idx_auth_events_event (filtro event+result). authEventQueries.insert/recent en shared/db.ts. Se registra en master-bot/routes/auth.ts (register/login/oauth/magic-link) y master-bot/routes/cli.ts (device_code_approve).

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.
033 arc_help_messages Phase 61 #153 arc_help_messages (id PK AUTOINCREMENT, user_id, role, text, sources JSON, created_at) — Historial persistente del chat de Arc Help por usuario (últimos 60).
034 skills_owner_project #157 ALTER TABLE skills_global ADD COLUMN owner_project TEXT — NULL=global, no-NULL=propiedad de un proyecto.
035 password_version Phase 64 #174 ALTER TABLE users ADD COLUMN password_version INTEGER DEFAULT 0 — se incrementa al cambiar la contraseña; el claim pv del JWT es validado por crmAuthMiddleware para invalidar tokens antiguos.
036 issue_assignee #187 / 2026-05-23 ALTER TABLE project_issues ADD COLUMN assignee TEXT — worker id nullable. Habilita arc issue take <id> + los statuses extendidos in_progress/blocked/deferred (validados a nivel de aplicación).
046 issue_draft_and_created_by #327 / 2026-06-03 ALTER TABLE project_issues ADD COLUMN created_by TEXT NOT NULL DEFAULT 'legacy' — campo de auditoría "quién lo creó". Lifecycle += draft (union a nivel de aplicación). handleCreateIssue fuerza draft sin assignee; handleUpdateIssue auto-promociona draft→open al asignar + rechaza close de issues nunca asignados (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) — formulario de solicitud de early access en la página de login.
038 plata_billing Phase 65 #202 / 2026-05-26 Añade columnas de Plata by mono a subscriptions: plata_card_token TEXT, plata_wallet_id TEXT, plata_masked_pan TEXT, next_billing_date TEXT, billing_failures INTEGER DEFAULT 0. También crea la tabla de idempotencia 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 compuesta) — acceso many-to-many a skills entre proyectos.
040 drop_stripe #205 / 2026-05-26 DROP de la columna stripe_customer_id + DROP TABLE stripe_events. NB: la columna stripe_subscription_id sobrevivió a esta migración porque SQLite rechaza DROP COLUMN sobre columnas con constraint UNIQUE (capturado silenciosamente con try/catch). Ver migración 041.
041 rebuild_subscriptions #205 / 2026-05-26 Follow-up de la 040 — reconstruye la tabla subscriptions sin la columna huérfana stripe_subscription_id mediante el patrón CREATE TABLE … AS. Idempotente (se omite si la columna ya no existe).
042 totp_backup_codes ALTER TABLE users ADD COLUMN totp_backup_codes TEXT — códigos de recuperación 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)) — almacén de metadatos para imágenes de avatar de worker subidas. Archivos guardados en data/worker-avatars/. Se sirven vía 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)) — plantillas de worker guardadas por el usuario. Endpoints: 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 en updated_at) — estado runtime en vivo por worker. Escrito por handleSetActiveRole (invariante: un solo rol activo por proyecto). Leído por GET /api/crm/projects/:name/workers/:id/runtime con fallback de obsolescencia: status='working' + tmux muerto + updated_at > 10 min → forzado a idle (detección de crash).
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 (tabla virtual vec0, embedding FLOAT[1024]). División: vec0 no puede almacenar metadatos eficientemente; los flujos LIST/DELETE corren como SQL plano. La extensión sqlite-vec se auto-carga en initDb antes de ejecutar la migración.
050 drop_notebook_id Phase 71.8 #365 / 2026-06-05 ALTER TABLE projects DROP COLUMN notebook_id. Retira el campo de schema del NotebookLM bridge; la búsqueda semántica ahora corre a través del RAG self-hosted (Cohere + sqlite-vec). Idempotente vía verificación con PRAGMA table_info para que las DBs nuevas con el schema 001 actual no fallen por la columna ausente.
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)) — contador de cuota diaria de transcripción de voz, replica la forma de la migración 032 arc_help_usage. Segundos aproximados (estimados a partir del tamaño en bytes del upload asumiendo un códec de voz de ~32 kbps); comparado contra el tope blando de 60 min/día en handleVoiceTranscribe (shared/routes/voice.ts) antes de cada transcripción para evitar que un usuario monopolice el pool compartido de workers whisper-server en 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) — una fila por archivo de reunión subido. frames_json = array JSON de {ts_ms, description} de Claude vision (Phase 73.4). summary_json = {tldr, key_points, action_items, decisions, topics, model, generated_at} de Claude Sonnet (Phase 73.5). El archivo fuente se elimina del disco cuando status='done' (decisión del CEO D4). Índices en (project, created_at DESC) y (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)) — contadores acumulativos por skill y proyecto. trigger_count se incrementa cada vez que el context router inyecta el skill; install_count al guardar/crear; session_count por aparición única en sesión. Usado por GET /api/crm/projects/:name/skills/usage.
057 notes Phase 78.1 #395 / 2026-06-07 4 tablas: notes (id PK, project_name, title, description, created_by, created_at, updated_at) — la nota como contenedor de fuentes. 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) — una fuente por fila, content_text = texto extraído para RAG. note_issue_links (note_id FK, issue_id, project_name, linked_at, PK(note_id,issue_id)) — vínculo nota↔issue. note_chats (id PK, note_id FK→notes, role CHECK(user|assistant), content, created_at) — historial del chat con la nota. Índices: 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')) — tracker de progreso de alta rotación para el worker en background. Aislado de transcripts para que los UPDATEs frecuentes (cada pocos segundos durante ffmpeg/whisper) no castiguen los índices de la tabla principal. El endpoint SSE transmite esta tabla con poll de 1s. Lifecycle de status: queued → extracting_audio → transcribing → (video: extracting_frames → frames_extracted → vision_analyzing → vision_analyzed) → summarizing → summarized → embedding → done (o failed en cualquier paso). Índice en (status, updated_at).

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