diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4b2cae2f..dc4e7f40 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -14,6 +14,8 @@ Registry personal de codigo reutilizable con busqueda FTS. Diseñado para compos **Reglas y convenciones:** ver `.claude/rules/INDEX.md` +**Migraciones SQLite obligatorias:** todo cambio de schema en cualquier `.db` (apps, operations.db, registry.db) va en `migrations/NNN_*.sql` numerado. Aditivo, idempotente, aplicado al arrancar via `embed.FS`. Nunca borrar `.db` ni modificar migraciones existentes. Aplica retroactivamente. Ver `.claude/rules/db_migrations.md`. + --- ## Explorar el registry (OBLIGATORIO) diff --git a/.claude/rules/INDEX.md b/.claude/rules/INDEX.md index c9ad9258..8822f3a8 100644 --- a/.claude/rules/INDEX.md +++ b/.claude/rules/INDEX.md @@ -28,3 +28,4 @@ Reglas operativas del proyecto. Cada archivo es una regla independiente. | 22 | [registry_first.md](registry_first.md) | Antes de escribir codigo en un artefacto: buscar en el registry, reutilizar si existe, delegar a `fn-constructor` si falta | | 23 | [fn_doctor.md](fn_doctor.md) | `fn doctor`: diagnostico read-only de artefactos, services, sync drift, uses_functions, unused — wrappers de funciones del registry | | 24 | [feature_flags.md](feature_flags.md) | TBD: feature flags para mergear codigo incompleto sin romper master. Patrones por stack (Go/TS/Bash/Py), branch-by-abstraction, anti-patrones | +| 25 | [db_migrations.md](db_migrations.md) | Migraciones SQLite obligatorias para cualquier cambio de schema. Aditivas, idempotentes, archivos numerados. Nunca borrar .db ni modificar migraciones existentes | diff --git a/.claude/rules/db_migrations.md b/.claude/rules/db_migrations.md new file mode 100644 index 00000000..dd14b4b8 --- /dev/null +++ b/.claude/rules/db_migrations.md @@ -0,0 +1,165 @@ +## Migraciones de BBDD: nunca perder datos + +**Regla absoluta:** todo cambio de schema en SQLite (apps con `kanban.db`, `operations.db` propia, registry.db, etc.) DEBE ir en un archivo de migración versionado. Nunca borrar/recrear tablas, nunca cambiar tipos sin proceso seguro, nunca confiar en "borra el .db y vuelve a empezar". + +### Por que + +- Las apps almacenan **datos vivos** (cards, entities, executions, assertions, columns, sessions). +- Borrar = perder horas/dias/semanas de trabajo del usuario. +- Lo que es trivial en dev (`rm operations.db`) es destructivo en produccion (deploys + sync entre PCs). +- Sync entre PCs (`fn sync`, `/full-git-pull`) trae bases de datos de otros equipos: si tu schema asume tabla recreada, los datos del otro PC desaparecen. + +### Patrones obligatorios + +#### 1. Archivos numerados en `migrations/` + +Cada cambio de schema = un archivo nuevo `migrations/NNN_.sql`. Numeracion zero-padded de 3 digitos. Nombre descriptivo. + +``` +apps//migrations/ + 001_init.sql # CREATE TABLE inicial (no se modifica nunca) + 002_add_stickers.sql # ALTER TABLE cards ADD COLUMN stickers + 003_add_assignees.sql # ALTER TABLE cards ADD COLUMN assignee_id + 004_create_lock_history.sql # CREATE TABLE card_lock_history + ... +``` + +#### 2. Solo operaciones aditivas seguras + +| Operacion | Seguro | Notas | +|---|---|---| +| `CREATE TABLE IF NOT EXISTS` | si | idempotente | +| `CREATE INDEX IF NOT EXISTS` | si | idempotente | +| `ALTER TABLE ... ADD COLUMN` | si | aditivo, default obligatorio | +| `INSERT INTO ... ON CONFLICT IGNORE` | si | seed data idempotente | +| `DROP TABLE` | NO | destructivo | +| `DROP COLUMN` | NO | destructivo (SQLite < 3.35 ni siquiera lo soporta) | +| `ALTER TABLE ... RENAME COLUMN` | precaucion | rompe codigo viejo si rollback | +| `ALTER TABLE ... DROP/ALTER constraint` | NO sin backup | requiere recreate-and-copy | + +Si necesitas cambiar tipo, eliminar columna, o cambiar PK: hacer **migracion en pasos** (Branch by Abstraction): +1. Crear nueva columna/tabla con la forma deseada (migration N). +2. App escribe en ambas (migration N+1, codigo). +3. Backfill de datos viejos (migration N+2, script). +4. App lee solo de la nueva (migration N+3, codigo). +5. Eliminar la vieja (migration N+4, despues de tener backups verificados). + +Cada paso = una rama TBD corta + commit + verificacion. Nunca un solo PR que rompa lectores. + +#### 3. Aplicacion idempotente al arrancar + +La app aplica todas las migraciones en orden al iniciar. Patron canonico (Go): + +```go +//go:embed migrations/*.sql +var migrationsFS embed.FS + +func applyMigrations(conn *sql.DB) error { + files, err := fs.Glob(migrationsFS, "migrations/*.sql") + if err != nil { return err } + sort.Strings(files) + for _, f := range files { + b, err := migrationsFS.ReadFile(f) + if err != nil { return err } + if _, err := conn.Exec(string(b)); err != nil { + // SQLite ALTER TABLE ADD COLUMN no es idempotente nativamente. + // Si ya existe, ignorar el error de "duplicate column". + if !strings.Contains(err.Error(), "duplicate column") && + !strings.Contains(err.Error(), "already exists") { + return fmt.Errorf("%s: %w", f, err) + } + } + } + return nil +} +``` + +Alternativa: tabla `_migrations` con las versiones aplicadas (mas robusta para schemas grandes). Para apps pequeñas (kanban, operations.db), bastan los archivos numerados + `IF NOT EXISTS` / catch de "duplicate column". + +#### 4. Migracion + cambios en codigo en el mismo commit + +Cuando añades una columna: +- `migrations/NNN_.sql` (nueva) +- `db.go` (lee/escribe la columna) +- `types.ts` (frontend type) +- Tests + +Todo en el mismo commit/rama. Si solo mergeas la migracion pero no el codigo, otros PCs aplican la migracion al sync y luego el codigo viejo no la usa. OK. Si mergeas el codigo sin la migracion, la app peta al arrancar en otros PCs. Mal. **Migracion antes que codigo en el orden de archivos** (no de tiempo). + +#### 5. Tests sobre la migracion + +Cada migracion debe tener test que: +- Arranca con DB vacia → aplica todas → verifica schema. +- Arranca con DB en estado N-1 (datos previos) → aplica migracion N → verifica que los datos se conservan. + +Esto detecta migraciones destructivas antes de mergear. + +### Que NO hacer + +| Anti-patron | Consecuencia | +|---|---| +| Borrar `*.db` durante dev y commitear "schema actualizado" | Otros PCs pierden datos al sync. | +| Modificar `001_init.sql` para añadir columnas | Las DBs ya creadas no se actualizan. Datos divergentes. | +| `DROP TABLE x; CREATE TABLE x ...` | Borra todo lo que el usuario tenga. | +| Usar `ensureColumns` sin archivo SQL paralelo | El cambio de schema vive solo en codigo Go, no auditable, no migrable manualmente. | +| Cambiar tipo de columna in-place | SQLite necesita recreate-and-copy. Asume que pierde datos si no se hace bien. | +| "fn index" como solucion para regenerar registry.db | OK para `registry.db` (regenerable). NUNCA para `operations.db`, `kanban.db`, etc. | + +### Casos especiales + +#### registry.db (raiz del fn_registry) + +`registry.db` SE PUEDE regenerar con `fn index` desde los `.go` y `.md`. Para cambios de schema del registry: actualizar `registry/migrations.go` o el codigo de creacion + `fn index`. NO hace falta archivo de migracion porque la fuente de verdad son los `.md`/`.go`. Excepcion: tablas con datos vivos (`proposals`, `pc_locations`) — esas SI requieren migracion preservando datos. + +#### operations.db (por app) + +Cada app tiene su `operations.db` con entities/relations/executions. Schema definido en `fn_operations/`. Cambios al schema → archivo de migracion en `fn_operations/migrations/` aplicado al abrir la BD. Idempotente. + +#### apps con BD propia (kanban, etc.) + +Mismo patron: `apps//migrations/NNN_*.sql`, embebido y aplicado al arrancar. + +### Comandos utiles + +```bash +# Ver schema actual +sqlite3 apps/kanban/operations.db ".schema" + +# Ver columnas de una tabla +sqlite3 apps/kanban/operations.db "PRAGMA table_info(cards);" + +# Backup antes de migracion arriesgada +sqlite3 apps/kanban/operations.db ".backup apps/kanban/operations.db.bak.$(date +%Y%m%d)" + +# Aplicar una migracion manual (si la app no esta corriendo) +sqlite3 apps/kanban/operations.db < apps/kanban/migrations/00X_.sql + +# Listar archivos de migracion en orden +ls apps/kanban/migrations/*.sql | sort +``` + +### Resumen + +- Cada cambio de schema = archivo numerado nuevo en `migrations/`. +- Aditivo siempre que se pueda. Destructivo solo en pasos verificados con backup. +- App aplica migraciones al arrancar, idempotente. +- Migracion + codigo + tests en el mismo commit. +- Nunca borrar `.db` para "arreglar" schema. Nunca modificar migraciones existentes. + +### Estado retroactivo (2026-05-09) + +Inventario de BDs del ecosistema y conformidad con la regla: + +| Repo / App | BD | `migrations/` | Estado | +|---|---|---|---| +| `registry/` | `registry.db` | si (11 archivos) | ✓ | +| `fn_operations/` | `operations.db` por app | si (4 archivos) | ✓ | +| `apps/kanban/` | `operations.db` (kanban) | si (5 archivos: 001 init, 002 stickers, 003 columns_extras, 004 cards_extras, 005 history_actor) | ✓ | +| `apps/deploy_server/` | `operations.db` (deploys) | si (2 archivos: 001 init, 002 target_extras) | ✓ | +| `apps/dag_engine/store/` | DB del dag_engine | si (001_init) | ✓ | +| `projects/element_agents/.../shell/memory/` | memoria del agente | si (001_init) | ✓ | +| `projects/osint_graph/apps/graph_explorer/` | DBs C++ inline (project_manager, layout_store, jobs, node_groups) | NO | **pendiente** — refactor C++ multi-archivo, mover schema inline a `migrations/*.sql` aplicado al abrir cada DB. | + +Las apps marcadas ✓ usan el patron canonico `embed.FS + applyMigrations()` (Go) o equivalente. La C++ pendiente requiere ronda dedicada — tracker via issue cuando se aborde. + +`apps/kanban/db.go::ensureColumns` se mantiene como **backstop idempotente** para DBs muy antiguas creadas antes del refactor de migraciones. NO añadir columnas nuevas alli — siempre via archivo SQL.