docs(issues): add 0071a chat, 0071b jobs, 0071g app_db_init extraction plans
Sub-issues activables de 0071 con plan concreto: API, dependencias, migracion, tests, anti-patrones. - 0071a (alta): claude_chat_panel — 2 consumidores reales (graph_explorer + navegator_dashboard, ~2500 LoC dup). Depende de 0071f. - 0071b (media): jobs_queue_panel — absorbe issue 0065. Depende de 0071f. Pre-requisito: auditar dup vs odr_console. - 0071g (media): app_db_init Tier 4 — 4+ duplicaciones en graph_explorer. Bajo riesgo. README actualizado. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
---
|
||||
id: 0071g
|
||||
title: Extraer `app_db_init` a cpp/functions/core/ (sub-issue de 0071, Tier 4)
|
||||
status: pending
|
||||
priority: media
|
||||
created: 2026-05-10
|
||||
parent: 0071
|
||||
related_apps: [graph_explorer, navegator_dashboard, kanban, deploy_server, registry_dashboard]
|
||||
---
|
||||
|
||||
## Contexto
|
||||
|
||||
Sub-issue derivado de 0071. **Primitiva Tier 4** — sirve a TODOS los paneles de cualquier app que use SQLite. Bajo riesgo, alta reusabilidad.
|
||||
|
||||
Cada app C++ del registry repite el mismo patron al abrir su SQLite local:
|
||||
|
||||
1. Resolver path: `<exe_dir>/local_files/<app>.db` via `fn::local_path()`.
|
||||
2. Abrir con `sqlite3_open_v2(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)`.
|
||||
3. `PRAGMA journal_mode=WAL`.
|
||||
4. `PRAGMA foreign_keys=ON`.
|
||||
5. `PRAGMA busy_timeout=5000`.
|
||||
6. Aplicar migrations desde `embed.FS` o array de strings (`migrations/001_*.sql`, `002_*.sql`, ...).
|
||||
7. Catch "duplicate column" / "already exists" para idempotencia.
|
||||
8. Verificar version con tabla `_migrations` o assumir orden archivos.
|
||||
|
||||
Sitios actuales con esta logica embebida (~300 LoC duplicadas):
|
||||
|
||||
- `projects/osint_graph/apps/graph_explorer/project_manager.cpp:37-100` (graph_explorer.db)
|
||||
- `projects/osint_graph/apps/graph_explorer/jobs.cpp` (jobs persistence)
|
||||
- `projects/osint_graph/apps/graph_explorer/layout_store.cpp` (layout positions)
|
||||
- `projects/osint_graph/apps/graph_explorer/node_groups.cpp` (groups)
|
||||
- `apps/kanban/db.go::ensureColumns` (Go, fuera de scope C++ pero patron equivalente)
|
||||
- Proxima `navegator_dashboard/local_api.cpp` (history, tabs)
|
||||
|
||||
5+ duplicaciones C++ confirmadas. Rule of three superada con creces.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Funcion `app_db_init` en `cpp/functions/core/` que recibe un path + lista de migraciones SQL y devuelve un `sqlite3*` listo, con todos los pragmas + migraciones aplicadas.
|
||||
|
||||
## API propuesta
|
||||
|
||||
```cpp
|
||||
namespace fn_core {
|
||||
|
||||
struct AppDbConfig {
|
||||
std::string path; // path absoluto al .db
|
||||
std::vector<std::pair<std::string, std::string>> migrations;
|
||||
// ^ pares (nombre_archivo, sql). Aplicados en orden de aparicion.
|
||||
// Ej: {"001_init.sql", "CREATE TABLE ..."}, {"002_add_x.sql", "ALTER ..."}
|
||||
bool wal = true;
|
||||
bool foreign_keys = true;
|
||||
int busy_timeout_ms = 5000;
|
||||
};
|
||||
|
||||
struct AppDbResult {
|
||||
sqlite3* conn; // null si error
|
||||
std::string error; // descripcion del error si conn==null
|
||||
int migrations_applied; // cuantas se ejecutaron sin error de duplicado
|
||||
int migrations_skipped; // cuantas eran idempotentes ya aplicadas
|
||||
};
|
||||
|
||||
AppDbResult app_db_init(const AppDbConfig& cfg);
|
||||
|
||||
// Helper para apps que tienen migrations en disco (no embebidas):
|
||||
std::vector<std::pair<std::string, std::string>>
|
||||
app_db_load_migrations_from_dir(const std::string& dir_path);
|
||||
|
||||
} // namespace fn_core
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
- Crea el directorio del path si no existe.
|
||||
- Aplica cada migracion en transaccion individual.
|
||||
- Captura errores de tipo "duplicate column" / "table already exists" → no fail, suma a `skipped`.
|
||||
- Cualquier otro error de SQL → cierra conn, devuelve null + mensaje.
|
||||
- Logging via `fn_log::log_debug` con nombre del archivo aplicado.
|
||||
|
||||
## Migracion
|
||||
|
||||
1. **Crear** `cpp/functions/core/app_db_init.{h,cpp,md}` + tests.
|
||||
2. **Tests** en primitives_gallery:
|
||||
- DB nueva con 1 migracion → tabla creada, 1 applied.
|
||||
- DB existente con la misma migracion → 0 applied, 1 skipped.
|
||||
- DB con 2 migraciones, segunda falla por SQL invalido → conn null, error claro.
|
||||
- DB con migracion que añade columna existente → catch "duplicate column", continua.
|
||||
3. **Migrar consumidores uno a uno**:
|
||||
- graph_explorer: project_manager.cpp primero (mas codigo).
|
||||
- graph_explorer: jobs, layout_store, node_groups.
|
||||
- navegator_dashboard cuando empiece a tocar SQLite serio.
|
||||
4. Cada migracion en commit propio.
|
||||
|
||||
## Definicion de hecho
|
||||
|
||||
- `app_db_init.{h,cpp,md}` registrado.
|
||||
- Tests pasan (>= 4 casos).
|
||||
- 4 consumidores migrados en graph_explorer.
|
||||
- LoC eliminadas: ~300 (los 4 sitios reducidos a llamadas de ~5 LoC).
|
||||
- `app.md` de cada app actualizado.
|
||||
|
||||
## Riesgos
|
||||
|
||||
- **Bajo**. La operacion es read+exec SQL. No hay concurrencia compleja. Falla detectable inmediato.
|
||||
- Riesgo unico: si la app espera un schema custom no via migrations (ej. `CREATE TABLE IF NOT EXISTS` inline), hay que mover ese SQL a un string `migrations/001_init.sql` antes.
|
||||
|
||||
## Anti-patrones
|
||||
|
||||
- Auto-detectar formato (YAML/JSON) de migrations — recibir `vector<pair<name, sql>>` directo. KISS.
|
||||
- Versioning con tabla `_migrations` — para apps pequeñas basta el archivo numerado + idempotencia. Si una app crece, ella misma migra al patron tabla.
|
||||
- Embedded `embed.FS` baked-in — la funcion recibe pares ya cargados; cada app decide si los embebe con `#embed` (C++23) o los carga del filesystem.
|
||||
- Wrapper RAII sobre `sqlite3*` — fuera de scope. Devuelve raw pointer, la app lo maneja con su propia clase.
|
||||
Reference in New Issue
Block a user