Runbook — Control-plane DB migration drift
Fecha de la auditoría: 2026-07-09
BD auditada: la Postgres de control-plane detrás de .env.local (SUPABASE_DB_URL) — la que golpean canaries y workers.
Herramienta: scripts/dataspaces/audit-migration-drift.ts
(npx dotenv -e .env.local -- tsx scripts/dataspaces/audit-migration-drift.ts)
Por qué existe este runbook
Durante el trabajo de 2d.3 (write nativo) se detectó que record_dataset_schema_version
fallaba en cada write nativo con column reference "version_number" is ambiguous, dejando
el versionado de esquema silenciosamente sin emitir filas (recordSchemaVersion es best-effort,
no hace rollback). El fix ya estaba en el repo (20260634) pero no estaba aplicado en la BD.
La auditoría posterior mostró que no es un caso aislado: esta BD no tiene tracker de
migraciones (supabase_migrations.schema_migrations no existe), las migraciones se aplican
manual/selectivamente, y muchas fueron saltadas — repartidas por todo el rango, no un corte
limpio. Lección clave: un fix CREATE OR REPLACE dentro de una migración no-aplicada queda
invisible hasta que algo lo re-afirma; y re-correr una migración vieja puede pisar un fix
posterior.
Estado tras la auditoría (231 migraciones en repo)
✅ Reconciliado en esta pasada (bugs de columna ambigua — mismo patrón de shadowing)
Aplicado a la BD viva (idempotente, wire-compatible):
| Migración | Objeto | Antes (vivo) | Ahora |
|---|---|---|---|
20261251 (re-asserts 20260634 fn#3) | record_dataset_schema_version | version_number sin calificar | dsv.version_number ✅ |
20260634 fn#2 | rebind_object_type_property | object_type_id sin calificar | otd.object_type_id ✅ |
20260634 fn#1 | rebind_link_type_foreign_key | dataset_id sin calificar | ot.dataset_id ✅ |
Los tres tenían el mismo bug latente: una columna referenciada sin alias que colisiona con
un OUT param de RETURNS TABLE → variable_conflict=error (PG14+) revienta el RPC en runtime.
Las dos rebind_* eran latentes (sólo disparan si se invoca el rebind de ontología); ya no.
⏸️ CERRADO como DIFERIDO (migraciones de FEATURE, no bugfixes) — revisado 2026-07-09
Decisión (dueño): las suites de feature están ausentes a propósito en esta BD; sólo reconciliamos bugfixes. Estas dos NO son bugfixes → se difieren, deben aterrizar CON su contraparte de código de app, no sueltas:
-
20261228_objects_base_properties— feature "decouple ontology from dataset_rows" (Stage 1 · Fase 3 · Track A). Añadeobjects.base_properties(+backfill) y reescribeobjects_resolved+fn_instantiate_links_*para leer debase_propertiesen vez dedr.data. Acoplada al código de app: los sync writers (objects.sync + handleBulkCreate) deben poblarbase_propertiesdesde Iceberg. Aplicar sólo la mitad-BD haría que los objetos nuevos creados por el código actual (que aún lee dr.data) caigan al fallbacko.properties→ divergencia. Aterriza CON el trabajo de decoupling de ontología (parte del lakehouse activo), no ahora. Estado vivo: columna ausente, funciones leendr.data(consistente). -
20260626_property_resolution_events— feature "BindingTimeline" (Phase C): log append-onlyproperty_resolution_events+ RPCsrebind/acknowledgeque insertan eventos. Estado vivo: la tabla existe (0 filas) pero los RPCs NO loguean eventos (mitad aplicada). Suite de ontología dormida → diferido. OJO — conflicto de linaje (bugfix latente, NO aplicar 20260626 tal cual): elrebind_object_type_propertyde 20260626 está SIN aliasear (tiene el bug de columna ambigua) y el de 20260634 (ya aplicado, aliaseado) DROPPEÓ el INSERT de eventos. O sea el cuerpo canónico del repo (20260634, el último) perdió el logging de 'rebound' que 20260626 agregaba. Si algún día se activa BindingTimeline aquí,rebindnecesita un cuerpo FUSIONADO = aliaseado (20260634) + el INSERT de eventos (20260626); ninguna migración lo tiene hoy. Aplicar 20260626 crudo re-introduciría el ambiguous-column que acabamos de cerrar. (Verificado: aplicar 20260634 NO regresó el logging — el vivo estaba en el base 20260625 sin INSERT, 0 filas.)
⏸️ Suites de FEATURE ausentes por completo (dormidas en este entorno)
Tablas + funciones nunca aplicadas. Probablemente porque esas features no se ejercen en esta BD. Aplicar sólo cuando/si la feature corra aquí (varias crean tablas + policies + grants):
- ML (
20260800–20260807): tablasml_models,ml_experiments,ml_runs,ml_model_training_data,ml_evaluations; RPCsml_start_run…ml_record_evaluation,ml_transition_stage, healthz + reapers. (El ml-runner es un servicio Railway aparte; el plano de control ML no vive en esta BD.) - SDK ontología (
20260701/20260702):sdk_upsert_object,sdk_upsert_objects_batch,sdk_archive_object,sdk_create_link,sdk_delete_link,healthz_ontology_invariants. - Objetos RPCs (
20260610/20260611):fn_object_merge_properties_by_id/_by_pk,fn_delete_objects_with_links. - Sueltas:
graph_saved_views(+trigger)20260501;page_shares20260616;notebook_cell_snapshots(+latest_notebook_cell_snapshot)20260710;cleanup_expired_idempotency_keys20260609;reopen_pull_request20260658;warehouse_project_to_schema20261232. - Columnas sueltas:
action_type_executions.execution_kind20260612;object_links.source_idempotency_key20260700.
Cómo re-auditar / re-reconciliar
npx dotenv -e .env.local -- tsx scripts/dataspaces/audit-migration-drift.ts- Secciones: (1) tablas ausentes, (2) funciones ausentes, (3) funciones con cuerpo driftado (el detector de reverts — muestra la primera divergencia normalizada), (4) columnas ausentes.
- Para un bug de columna ambigua: aplicar la migración que lo arregla con
workerDb.query(readFileSync(<migration>))(protocolo simple corre multi-statement). - Verificar con el canary correspondiente (p.ej.
npm run canary:pipeline-output, que ahora asserta que el output recibe su fila v1 endataset_schema_versions).
Riesgo abierto
Sin tracker de migraciones, el drift es la norma, no la excepción. Considerar: (a) adoptar un
tracker (schema_migrations) para que "aplicado" sea verificable, o (b) correr este audit en CI
contra la BD de cada entorno. Hasta entonces, correr el audit antes de confiar en cualquier RPC
recién arreglado.