🏛️ Warehouse — Reorganización a modelo Unity-Catalog (Visión + Approach)
Documento vivo. Se irá modificando conforme madure el concepto. Fija el norte y el approach de fases; el detalle de cada fase se refina cuando se aborde.
La tesis: separar la app en dos planos — Project & Files (autoría) y Warehouse (datos + gobernanza estilo Unity Catalog) — y darle al Warehouse una jerarquía real (
Catalog › Schema › Dataset/Volume) en vez de re-proyectar el árbol de carpetas de project_files.Complementa a dataspace-router.md (la facade de acceso — capa 4) y port-migration.md (el tracker de puertos). Este doc es la capa de organización y gobernanza que se monta ENCIMA del tridente ya construido. Grounded en el substrato verificado (2026-07-04).
1. La visión en una frase
El dato vive en el Warehouse; el documento vive en Project & Files.
Todo lo tabular (datasets) y no-estructurado gobernado (media → volumes), más credentials, connections, permisos, linaje y gobernanza, se organiza en un catálogo real tipo Unity Catalog. Las definiciones y documentos que autoran las building blocks (notebooks, notepads, dashboards, recetas de pipeline/workflow, ontología, definición de modelo) se quedan en Project & Files.
2. Los dos planos
| Plano | Qué contiene | Sharing / gobernanza |
|---|---|---|
| Project & Files — autoría | Notebooks (.ipynb), notepads, dashboards, definiciones de pipeline/workflow, object-types/ontología, definición de modelo | file_access_grants (20261121), page_shares (20260616), workspace groups |
| Warehouse — datos + gobernanza | Dataset (tabular), Volume (media), Credentials, Connections, Permisos, Lineage, Governance, Shares | Modelo GRANT (a construir), cascada Catalog→Schema→Dataset |
El criterio del split: DATO vs. DOCUMENTO — no "quién lo produjo"
Trampa a evitar: "todos los outputs de las building blocks van a Files" es incorrecto. El output tabular de un pipeline / notebook / modelo es un dato → va al Warehouse como Dataset. Su receta/definición → Files.
Casi toda building block tiene dos artefactos: la definición (→ Files) y el output (→ Warehouse). Un pipeline = receta (file) + dataset resultante (tabla). No es "la app X vive aquí"; es "la receta a Files, el dato al Warehouse".
3. La jerarquía (DECIDIDA)
Workspace = Metastore (frontera de tenant — ya existe)
└─ Catalog CREABLE, varios por workspace (equipo / dominio / entorno) ◄─ modelo Multi-Catalog
└─ Schema ← un Project actual mapea aquí (= namespace Iceberg)
├─ Dataset ← tabla / datos (reutiliza el vocabulario actual)
└─ Volume ← media sets / items de media (no-estructurado gobernado)
Governance / Credentials / Connections / Permisos / Lineage ─ anclados a nivel de Catalog (cascada ↓)
Shares received (EDC / Delta-sharing-like) ─ hermano del árbol
Por qué "Catalog" y no un nombre inventado: no es un préstamo de Databricks — es la verdad técnica del substrato. Lakekeeper ES un Iceberg REST catalog, y el modelo Iceberg ya es Catalog → Namespace → Table. Nombrar el contenedor top "Catalog" le pone a la capa de producto el nombre que la capa de almacenamiento ya tiene. Mapea 1:1:
| Nivel Carbon | Databricks UC | Apache Iceberg | Hoy en Carbon |
|---|---|---|---|
| Workspace | Metastore | (account) | workspaces (existe) |
| Catalog | Catalog | Catalog (Lakekeeper) | ○ nuevo (hoy 1 namespace fijo datasets) |
| Schema | Schema | Namespace | ○ nuevo (hoy = Project/carpeta) |
| Dataset | Table | Table | ✅ storage listo (tridente Iceberg) |
| Volume | Volume | — | ○ nuevo (hoy media_set en project_files) |
4. Estado actual — la "chapuza" (grounded)
Hoy un dataset ES un file, y el "catálogo" del Warehouse es el árbol de carpetas re-proyectado:
- Derivación del árbol:
app/api/dataspace/catalog/route.ts:44→GROUP BY project_id. Los "proyectos" y "datasets" del Warehouse son literalmente la estructura deproject_files, agrupada por la FKdatasets.project_id. La posición la dicta la carpeta, no un schema. - Namespace: hardcodeado a
'datasets'(WarehouseTab.tsx:210default;checksum-client.ts:49; contrato enconsumption-contract.ts:154). No hay jerarquía de namespace. - Landing de dataset synceado: el usuario elige un output folder en Data Connection (
dataset-writer.ts:746leeoutput_folderde la metadata de credencial), y el ingest crea dos filas atómicas (app/api/datasets/ingest/route.ts:115-150): unproject_files type='dataset'bajo esa carpeta + una filadatasetscolgada porfile_id. - Media sets: patrón idéntico (
lib/projects/fileBackedDatasetUpload.ts:121-172):project_files type='media_set'+ filadatasets service='media_set'+ los ficheros como manifest endataset_rows.
Veredicto: un dataset = artefacto project_files en una carpeta + fila de metadata. Su lugar en el catálogo es folder-driven, no schema-driven. Eso es lo contrario del modelo UC de las capturas de Databricks (Catalog → Schema → Table/Volume, con Credentials/Connections/Permissions de primera clase).
5. Gap vs. Unity Catalog (~29% del modelo hoy)
| Primitivo UC | Estado | Dónde / nota |
|---|---|---|
| Metastore | ○ ausente | workspaces es app-level, no storage-level |
| Catalog | ○ ausente | todo plano bajo datasets |
| Schema (first-class) | ○ ausente | hoy = Project/carpeta |
| Namespace Iceberg | ◐ parcial | fijo a datasets, no jerárquico |
| Table (governada) | ✅ existe | tridente: iceberg_sync_log, identity cols, read/write routers |
| View / Function | ○ ausente | sin registro de objetos |
| Model (ML) | ✅ existe | ml_model_registry (20260607) — subsistema aparte |
| Volume | ○ ausente | storage.buckets no catalogado |
| External Location | ○ ausente | paths en env vars, sin registro |
| Storage Credentials | ○ ausente | secretos inline / config |
| Connections | ◐ parcial | integrations por-workspace, sin registro central |
| GRANT (objeto-nivel) | ○ ausente | hoy es RLS (20260416, 20260417): user_id + workspace_id, acoplado a tenancy |
| File Access Grants | ✅ existe | 20261121 — solo project_files (Files plane), no datos |
| Governance metadata (tags/clasif./lineage) | ◐ parcial | provenance estampada por el tridente; falta tags/clasificación/lineage |
| Shares (Delta Sharing) | ○ ausente | existe EDC (dataspace_assets/dataspace_transfers, 20261230), orthogonal a UC |
Lo bueno: el plano de datos ya está catalog-ready — el tridente le dio a cada dataset identidad Iceberg real (namespace + snapshot) independiente del file. Lo que falta es el control-plane de organización y gobernanza encima, que vive en PG (PG = plano de control; R2+Lakekeeper = plano de datos).
6. Modelo de datos objetivo (BORRADOR — se refina por fase)
Tablas nuevas en PG (control plane). Nombres/columnas provisionales.
-- Contenedor top, creable, múltiple por workspace
catalogs (
id, workspace_id, name, comment, owner_id,
created_at, updated_at,
UNIQUE (workspace_id, name)
)
-- Agrupación; un Project actual migra a un Schema
schemas (
id, catalog_id → catalogs.id, name, comment, owner_id,
legacy_project_id, -- puente al project_files actual
created_at, updated_at,
UNIQUE (catalog_id, name)
)
-- El dataset gana su posición de catálogo (bridge: nullable al principio)
datasets.schema_id → schemas.id -- backfill desde project_id
-- project_files/file_id se mantiene como PUNTERO LEGACY detrás de flag
-- Media governado
volumes (
id, schema_id → schemas.id, name, kind ('media'|'file'),
storage_ref, comment, created_at,
UNIQUE (schema_id, name)
)
-- Registro central (sustituye secretos dispersos / env vars)
storage_credentials (id, workspace_id, name, provider, secret_ref, ...)
external_locations (id, credential_id → storage_credentials.id, url_prefix, ...)
connections (id, workspace_id, name, type, credential_id, config, ...)
-- El modelo GRANT (fase tardía — reemplaza el enforcement RLS-tenancy)
grants (id, principal_type, principal_id, object_type, object_id, privilege, ...)
Namespace Iceberg objetivo: de datasets.ds_{id} (fijo) → {catalog}.{schema}.ds_{id} (Lakekeeper soporta namespaces multi-nivel). El namespace pasa a ser derivado de la jerarquía, no una constante.
7. El linchpin: desacoplar el Dataset del project_files
Es LA pieza. Hoy el dato es un file; el Warehouse debe volverse autoritativo: el catálogo (catalog.schema.dataset) posiciona el dato, y la carpeta deja de ser su identidad.
Estrategia = seam / bridge detrás de flags (mismo método que el tridente, source-of-truth.md):
- Se crean
catalogs/schemasy se siembra 1 catalog default (main) por workspace. - Cada
projectexistente se migra a unschemadentro demain(schemas.legacy_project_id). datasets.schema_idse backfillea desdeproject_id.- El árbol del Warehouse gana el nivel Catalog, leyendo del nuevo modelo (derivado del legacy al principio).
- El
project_filesqueda como puntero legacy detrás de flag; se drena cuando el catálogo es fuente única. - El picker "output folder" de Data Connection se sustituye por "catalog.schema".
Multi-Catalog no implica big-bang: se empieza con el main sembrado y se crean más catalogs a futuro.
8. Approach por fases (BORRADOR)
| Fase | Objetivo | Rompe / riesgo |
|---|---|---|
| W0 — Modelo + siembra ✅ (migración escrita) | Tablas catalogs/schemas; seed main por workspace; projects→schemas; backfill datasets.schema_id. Inert (árbol sigue leyendo legacy). → supabase/migrations/20261231_warehouse_catalog_hierarchy.sql (aditiva, idempotente, type-agnostic, sin aplicar). | Bajo (aditivo) |
| W1 — Árbol re-enraizado + Catalog creable ✅ (implementada) 📄 approach | /api/…/catalog devuelve catalogs→schemas→datasets/volumes del nuevo modelo; UI "Create catalog"/"Create schema"; invariantes por trigger (20261232). Re-enraizado LÓGICO; el namespace Iceberg FÍSICO (datasets.*→catalog.schema.*) se difiere a W1.5. (pendiente: aplicar 20261232 + verificar) | Medio (UI) |
| W1.5 — Namespace físico Iceberg 📄 approach · W1.5-a ✅ · W1.5-b ✅ · W1.5-c 📐 | Born-in-place (write nativo nace en catalog.schema) + barrido metadata-only del datasets.* existente. Persiste datasets.iceberg_namespace (cutover por-dataset). | Medio (metadata-only; el riesgo es coordinación, no datos) |
| W2 — Volumes | Media sets → volumes gobernados; separar del data-path de project_files. | Medio |
| W3 — Credentials & Connections | Registro central; Data Connection lee/escribe de ahí; picker catalog.schema. | Medio |
| W4 — Governance metadata | Tags, clasificación, lineage (aprovechar la provenance del tridente). | Bajo-medio |
| W5 — RLS → GRANT ⚠️ | Introducir grants; migrar enforcement de RLS-tenancy a principal→objeto→rol con herencia catalog→schema→dataset. | Alto — toca auth. El hueso. Aislada a propósito. |
| W6 — Shares | Formalizar "Shares received" (EDC) hacia share/recipient (Delta-sharing-like). | Medio |
Cada fase: seam-first, detrás de flags, project_files como puntero legacy hasta el drenaje.
Coordinación: hay una migración del tridente en curso (pipeline.output nativo, fase 2d — ver port-migration.md). W0-W1 son ortogonales (organización, no I/O), pero el cambio de namespace de W1 debe coordinarse con el write-path nativo.
9. Decisiones abiertas (a madurar)
- Frontera exacta Workspace = Metastore: ¿un metastore por org o por workspace? ¿catalogs compartibles entre workspaces?
- RLS → GRANT: cómo migrar el enforcement sin downtime; convivencia temporal RLS+GRANT.
- Views / Functions como objetos de catálogo (UC los tiene; Carbon no) — ¿en scope?
- Delta Sharing propio vs. seguir en EDC para el share cross-org.
- Credential vending para motores externos (del dataspace-router.md) — ¿converge aquí?
- Zero-copy EDC (Fase 5 del track dataspace) vs. copia actual.
- Modelos: en UC son objeto de catálogo (
catalog.schema.model). ¿Se unificaml_model_registrybajo el Warehouse o se queda en Files como "definición"?
10. Registro de decisiones
| Fecha | Decisión |
|---|---|
| 2026-07-04 | Rename "Data Space" → "Warehouse" (UI + identificadores: WarehouseTab, view-type/app-id 'warehouse', icono cloud-server). Capa B (rutas /api/dataspace/*, lib/dataspaces, edc-service, DB, término Gaia-X/EDC) intacta. |
| 2026-07-04 | Split por dato vs. documento (no por productor). |
| 2026-07-04 | Unidad contenedora top = Catalog (1:1 con Lakekeeper/Iceberg). |
| 2026-07-04 | Mapeo Multi-Catalog (fiel a Databricks): Workspace=metastore, Catalogs creables, Project→Schema. |
| 2026-07-04 | Naming hoja: Dataset (tabular, reutiliza vocabulario actual) · Volume (media). Nivel medio = Schema. |
| 2026-07-04 | W0 APLICADA en Supabase (migración 20261231): catalogs/schemas/datasets.schema_id + siembra. |
| 2026-07-04 | Approach W1 redactado (warehouse-w1-approach.md): re-enraizado lógico (invariantes por trigger); el namespace Iceberg físico se difiere a W1.5. |
| 2026-07-04 | W1 implementada: migración 20261232 (triggers T1/T2/T3), read-path v2 (app/api/dataspace/catalog), CRUD (catalogs/schemas), WarehouseTab árbol 3 niveles + create inline. Pendiente: aplicar 20261232 + verificar en preview. |
| 2026-07-04 | W0+W1 aplicadas en Supabase (20261231+20261232). W1.5 recon + approach (warehouse-w1_5-approach.md): move metadata-only; estrategia born-in-place + barrido; persistir datasets.iceberg_namespace; namespace 2-nivel. W1.5-a escrita (20261233 slugs + columna). |