docs(rules): db migrations obligatorias retroactivas y siempre
- db_migrations.md (nuevo): doctrina archivos numerados, aditivo, idempotente, embed.FS pattern, branch-by-abstraction para destructivos, anti-patrones, inventario retroactivo del ecosistema - INDEX: entrada 25 - CLAUDE.md: nota en cabecera Aplicado retroactivamente en commit paralelo: kanban (003-005), deploy_server (001-002), agents_and_robots/memory (001). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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_<accion>.sql`. Numeracion zero-padded de 3 digitos. Nombre descriptivo.
|
||||
|
||||
```
|
||||
apps/<app>/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_<accion>.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/<app>/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_<accion>.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.
|
||||
Reference in New Issue
Block a user