🔌 Registro de Migración de Puertos — al Data Space paradigm
Tracker vivo. El objetivo: cada building block / recurso de Carbon deja de hablar PG crudo y pasa a ser un enchufe del Data Space vĂa la facade
loadItemForConsumption(dataspace-router.md). Approach por fases: source-of-truth.md.Estados:
FACADE= usaloadItemForConsumption(paradigma pleno) ·SEAM= usaresolveDatasetRowSource/Sink(enchufe a nivel seam, correcto pero no facade) ·DORMANT= cableado al seam, executor Iceberg listo, en modo gated ·NO-EXEC= registrado en el seam pero sin executor Iceberg → siempre PG ·RAW-PG= habla PG crudo, bypass del Data Space ·N/A= no aplica (file-backed / control-plane).Actualizado 2026-07-09. 2c CERRADO — todos los readers analytics (incl. los de baja prio y los de live-SQL) rutan por la facade; el camino queda PLANO para el flip global de
pipeline.output(2d). 2d.1/2d.2 HECHOS: el brazo iceberg del Apply-path (transform/join/split) + el deploy replace-puro escriben el tridente nativo; canary VERDE E2E (scripts/dataspaces/pipeline-output-canary.ts,npm run canary:pipeline-output, enmain.test, paridad PG↔Iceberg). SIGUIENTE = 2d.3 / Tier B — ver 2d3-tier-b-approach.md.
Lectura (readers)
| Puerto (readerId) | Consumidor | Estado | Nota / siguiente |
|---|---|---|---|
dataspace.preview | Interfaz Data Space (components/workspace/tabs/dataspace) | ✅ FACADE | item/sample routes → loadItemForConsumption. Nuevo (2026-07-03). |
export.stream | Table export (snapshot read) | âś… FACADE | Migrado 2026-07-03 (run-export.ts streamRows). Delta/ledger sigue RAW-PG (sin equivalente en contract). |
objects.sync | Object sync / bulk-create | ✅ FACADE | Migrado 2026-07-03 → read({ afterRowIndex, limit, includeIdentity }) keyset. El gate de identidad (Iceberg sin __row_id/__row_index → PG) pasó del puerto al ROUTER: la facade cae a PG si se pide includeIdentity sobre un Iceberg no identity-ready. |
datasets.browse | DatasetTab / DataAssetView / ExploreSource (hub de lectura) | ✅ FACADE | Migrado 2026-07-03 (app/api/datasets/route.ts includeRows). Browse sin filtro → loadItemForConsumption(...).read() (keyset en ambos modos cursor/offset, PG↔Iceberg unificado; __created_at añadido al fallback PG de la facade para paridad con Iceberg). El filtro substring (data::text ILIKE) NO tiene equivalente en la contract → se queda en el seam→PG (isla RAW-PG catalogada, como el delta de export.stream). STRICT: hoy resuelve PG salvo flip + caught-up. |
datasets.rows.lookup | Lookup de fila por id | 🟡 DORMANT (STRICT) | Executor icebergRowIdLookup listo. |
graph.explorer.data | Graph explorer (nodos/edges) | ✅ FACADE | Migrado 2026-07-03. Muestreo de nodos (read keyset) + aristas (read acotado) → loadItemForConsumption. Sin filtro → sin delta. Ruta ya no toca dataset_rows (consumidor puro). |
graph.explorer.search | Graph explorer (faceted search) | ✅ FACADE | Migrado 2026-07-03 → read({ filters:[contains] }). El substring por columna gana pushdown a PG (data->>col ILIKE) en el fallback de la facade → sin regresión de latencia; en Iceberg va al row_filter. |
graph.explorer.values | Graph explorer (distinct values) | ✅ FACADE | Migrado 2026-07-03 → aggregate({ contains }). El q tecleado con pushdown a PG; el lado PG converge a la semántica top-N-by-count de Iceberg (antes muestreo de 500). |
graph.kuzu | Kuzu graph hydration | ✅ FACADE | Migrado 2026-07-03 (2c). TODOS los reads (nodos + aristas, ambas hydrate fns) → kuzuFacadeRead (loadItemForConsumption). Los IR filters se mapean a filtros estructurados (pushdown); el regex es post-filtro JS. Ruta ya no toca dataset_rows (salió del ratchet). |
dashboards.aggregate | Dashboards (AGGREGATE + QUERY paths) | âś… FACADE | REFERENCE migrado 2026-07-03 (native → stream() + aggregateInMemory). QUERY multi-dataset CERRADO 2026-07-04 (2c): executeQueryDataset resuelve cada dataset por la facade vĂa hydrate-to-inline-jsonb (lib/lakehouse/sql-source-hydration.ts) — un nativo se hidrata a jsonb_to_recordset (funciĂłn PURA → corre dentro del pool READ ONLY, sin relajar el sandbox); PG queda el default no-op. El literal dataset_rows persiste como el path PG (allowlisted). |
sql-editor.query | SQL Editor internal mode (app/api/sql-editor/execute/route.ts) | ✅ FACADE | Migrado 2026-07-04 (2c). Live-SQL CTE de usuario (READ ONLY) → mismo hydrate-to-inline-jsonb que dashboards QUERY; reader nuevo sql-editor.query en el registry. El literal dataset_rows persiste como el path PG (allowlisted; sandbox a nivel DB-role). |
studio.digest / studio.training_pairs | AI Studio samplers | 🟢 SEAM (EVENTUAL) | flip-ready. |
datasets.export.legacy | CSV export legacy | ✅ FACADE | Migrado 2026-07-04 (2c). stream() (snapshot completo, mismo estado que browse); el filtro por-transacción legacy lo subsume la facade (lee el estado actual — snapshot-replace borra las filas viejas). |
sdk.rows | SDK v1 rows API + notebooks (Jupyter read_table) | âś… FACADE | Migrado 2026-07-03. Los notebooks NO tienen reader propio → leen por este puerto (la capa Jupyter es solo binding). JSON→read({offset,limit}), NDJSON→stream(). CerrĂł un bypass RAW-PG oculto (.from(sourceTable) con nombre en variable → evadĂa el ratchet). El overlay de branch lo resuelve el ROUTER vĂa el contrato v1.1 (ItemRef.branchId → dataset_branch_rows, plano PG), no una isla. |
sdk.files.list | SDK v1 file manifest | ⚪ N/A (control-plane) | Lee el manifiesto de un dataset content_type=file (nombres + signed-URLs), no filas tabulares → nunca va a Iceberg, PG por diseño (como el writer dataset.manifest). |
model.schema.infer | Schema API (app/api/model/schema/route.ts) | ✅ FACADE | Migrado 2026-07-04 (2c). Sample read({limit:25}) + preview paginado read({offset,limit}) (v1.1) + legacy read({limit:500}); el count = descriptor rowCount (datasets.row_count, canónico) → arregla el count=0 latente de un nativo con dataset_rows drenado. Pseudo-dataset (Strategy B6) → handle null → fallback a schema metadata como antes. |
pipeline.expectations | Data-quality checks (post-write) | âś… FACADE | Migrado 2026-07-04 (P0.1). Las 4 checks resuelven la fuente por resolveSqlSource (hydrate-to-inline-jsonb, _shared.resolveExpectationSource); PG hoy vĂa ctx.query (read-your-writes en la tx del materialise), un output nativo hidrata la snapshot → ya no valida en falso sobre dataset_rows drenado. |
mediaset.item | media-set item | 🔴 NO-EXEC | registrado sin executor → PG. |
pipeline.source (sourceCte) | Pipelines compute (input) | ✅ FACADE (read) | LINCHPIN CERRADO 2026-07-03. sourceCte.ts ya no hardcodea FROM dataset_rows — la fuente se resuelve por la facade (source-resolver.ts → loadItemForConsumption) en los 4 call-sites (transform/join/split/runtime). Hoy PG no-op (mode=off); un source Iceberg-nativo se hidrata a una temp (misma tx, Opción A) y el SQL corre idéntico. Karma (Opción B) confirmado diferido. Salió del ratchet (22→21). El WRITE del pipeline (INSERT de salida) sigue PG → paso 6. |
model.objects.grid (objects_resolved) | Objects grid | ✅ DESACOPLADO (seguro) | La vista objects_resolved ya NO hace LEFT JOIN dataset_rows (mig. 20261228) — lee objects.base_properties denormalizado (Track A de objects.sync). SEGURO para datasets nativos sin migración (base_properties vive en PG). Ya NO es el coupling #1. |
| — | ML training | ↪️ externo | ml-runner Python resuelve su tier (snapshot ref). |
Escritura (writers)
| Puerto (writerId) | Productor | Estado | Nota / siguiente |
|---|---|---|---|
sync.full · sync.incremental · sync.append | Data Gateway ingest | 🟢 SEAM (iceberg_native default) | Ya nativo R2 por defecto. |
cdc.append | CDC syncs (4 conectores) | 🟢 SEAM (iceberg_native default) | Ya enchufado (cdc/sink.ts). Append-only. Facade .write() espera paso 6. |
| (native) | EDC landing | 🟢 nativo | writeIcebergNativeFromParquet siempre R2. |
| (facade native) | Facade write() (rows/parquet) | âś… FACADE (solo iceberg) | Verificado E2E. PG-sink LANZA (frontera del paso 6). |
pipeline.output | Pipeline / model outputs (deploy materialiser + model-node) | ✅ SEAM (nativo espectro completo, defaultMode pg) | 2d.1/2d.2/2d.3 CERRADOS. El deploy materialiser + el model-node escriben el tridente nativo en TODO el espectro: primer-deploy (replace) + re-deploy (upsert/append/append_new anti-join) + delta (incremental) + model-node (buffer+single-replace + __source_row_index__). defaultMode=pg → opt-in por-dataset (canary VERDE E2E, npm run canary:pipeline-output). Diferido a Karma/Fase 5: read nativo de la ventana delta, unificación correlación model. Docs: 2d3-tier-b-approach.md (+ b4/b5). |
| — (transform-paths) | transform/join/split/runtime outputs | âś… SEAM (Apply-path nativo, 2d.1b/c) | Los INSERT van por resolveDatasetRowSink('pipeline.output'). El brazo iceberg del Apply-path completo (transform/join/split) escribe el tridente vĂa writeOutputToTrident (replace; split dual-target = 2 writes nativos). Flippable por-dataset; PG default. La procedencia se propaga (Fase D). |
manual.table | Tabla manual | ✅ nativo (default) | Paso 6 · 2a (2026-07-03): defaultMode=iceberg_native. La ruta escribe writeIcebergNativeSnapshot(replace). Hereda el gate system-wide objects_resolved (2c); reversible + pinnable. |
ingest.stream · row.edit | streaming / edición punto | 🔴 RAW-PG (diferido → Karma) | sin primitiva Iceberg (streaming open/commit, point-DELETE) → 2e/Karma. Clamp PG. |
dataset.manifest | Media sets (manifiesto) | ⚪ N/A (PG por diseño) | file-backed; los manifiestos son control-plane, NO van a Iceberg. |
Los dos tracks
- READERS → migran LIMPIO a
loadItemForConsumption(...).read()/.stream()(la facade tiene lectura completa con fallback PG). Ola completa:✅ →datasets.browse✅ →graph.explorer.*notebooks/SDK (✅ →sdk.rows)✅ →objects.sync✅ →graph.kuzu✅ →dashboards.aggregateREFERENCE✅ →datasets.export.legacy✅ →model.schema.infer✅. 2c cerrado. Pendientes menores fuera de 2c:sql-editor+dashboards.aggregateQUERY (live-SQL → hydrate-to-inline-jsonb)datasets.rows.lookup(DORMANT) + dar executor a losNO-EXEC(mediaset.item,pipeline.expectations). Cada migración es reversible (canary-gated).- Los readers de LIVE-SQL (
sql-editor,dashboards.aggregateQUERY) NO usanread()estructurado — corren SQL arbitrario de usuario. Su enchufe = hydrate-to-inline-jsonb (lib/lakehouse/sql-source-hydration.ts): cada dataset referenciado se resuelve por la facade y, si es nativo, sus filas se hidratan a un array JSON servido vĂajsonb_to_recordset($n::jsonb)— una FUNCIĂ“N PURA, asĂ que la CTE corre idĂ©ntica y DENTRO deBEGIN READ ONLY(no hace faltaCREATE TEMP TABLE, prohibido en read-only → sin relajar el sandbox de SQL arbitrario, sin nueva superficie de escritura). Hermano interactivo del linchpinsource-resolver.ts(que hidrata a temp en la tx read-write del pipeline). Inert-but-ready: hoy PG no-op; la rama hydrate se activa por-dataset al flipear (canary). - El contrato es v1 y se ENRIQUECE por demanda (no se fuerza a los building blocks por un router rĂgido). Extensiones ya en la piedra: (a) pushdown de substring por columna en el fallback PG (
data->>col ILIKE, antes JS) → readers interactivos filtrados sin regresión; (b) v1.1ItemRef.branchId(overlay Lazy-COWdataset_branch_rows, plano PG, herencia transparente) +PageQuery.offset(paginación posicional, nativa en PG / emulada en Iceberg) + descriptor auto-descrito (rowCount/currentTransactionId/branchId) — habilitósdk.rowssin islas; (c) gate de identidad en el router:includeIdentitysobre un Iceberg no identity-ready cae a PG (que sà llevaid/row_index) — habilitóobjects.syncmoviendo el gate del puerto a la piedra. - Enriquecimientos PENDIENTES identificados (para migrar el resto sin islas):
sample({stride|reservoir})— muestreo representativo (studio.digest/training_pairs, model.schema.infer);count(filters?)— conteo (model.schema.infer). El muestreo por stride (row_index % n) es nativo en PG; en Iceberg necesita soporte de ml-runner (modulo filter) → decisión de diseño abierta.
- Los readers de LIVE-SQL (
- WRITERS → PASO 6 = encaminar la escritura al tridente. Approach fundado: paso6-write-approach.md (stack de 3 tiers · el reframe "la escritura no adelanta a la lectura" · secuenciación). Estado: Tier-1 (sync/cdc/EDC/ingest.api) nativo ·
manual.tablenativo (2a ✅) · Fase D propagada (2b ✅) ·pipeline.outputgated enobjects_resolved(2c→2d) ·ingest.stream/row.editdiferidos a Karma (2e).
Blockers de fondo (no por-puerto)
✅ CERRADO (2026-07-03) — la fuente del pipeline se resuelve por la facade (sourceCte(linchpin):source-resolver.ts, no-op PG hoy + hydrate-to-temp para nativos). El únicoFROM dataset_rowshardcodeado ya no existe. Queda el WRITE (abajo) = paso 6.- paso 6 — pipeline WRITE (era "fix read desbloquea write", refinado): el pipeline es UNA sentencia atómica
WITH…INSERT INTO dataset_rows; el read ya es facade-routed, pero el output INSERT (materialiserpipeline.outputmaxMode=pg + la fuga directa de transform-paths transform/join/split) sigue PG. Dirigirlo al data space = construir la estrategia de write nativo (compute PG →facade.write()/ml-runner → Iceberg), rompiendo la sentencia atómica. No necesita Karma (ml-runner ya escribe Iceberg); Karma lo subsume después. Investigar el espectro de Fase 2. - transform-paths: cerrar la fuga de
INSERT INTO dataset_rowsdirecto (rutar por el write-seam) — parte del paso 6 arriba. Fase D (gobernanza):✅ CABLEADA (2026-07-03) —mergeUpstreamProvenancepropagado en los chokepoints de derivación (createOutputDatasetdel pipeline output +createTransformOutputDataset+createJoinOutputDataset). El output hereda la procedencia del/los source(s) (counterparty/edc/upstream). Pendiente menor: split branches + object-type + model-node (mismo patrón).
Tracker vivo — actualizar al migrar cada puerto.