Fase 2 — Data plane sobre R2 (el conector toca nuestra capa de datos)
Documento padre: README.md (charter/roadmap) · Base: fase-1-findings.md
Estado: approach / plan de ejecución.
Naturaleza: en Fase 1 dos conectores movieron un dato ajeno (HttpData de jsonplaceholder). En Fase 2 el conector empieza a mover datos sobre nuestra propia capa de almacenamiento: S3 → R2, el mismo bucket donde vive el lakehouse Iceberg. Aquí "soberanía sobre el dato" deja de ser diapositiva y se vuelve un objeto que aterriza en R2 bajo contrato.
1. Filosofía — qué es y qué no
Qué es: graduar el PoC de conectores-de-sample a nuestro propio conector (services/edc-service/), añadirle el data plane S3 de EDC, y demostrar una transferencia de un objeto (un Parquet, para prefigurar el lakehouse) de S3 a R2 entre dos conectores — primero contra MinIO local (stand-in de R2), y luego contra R2 de verdad (un prefijo de scratch en el bucket del lakehouse). Ese último paso es el money shot: probar que el data plane de EDC escribe en el mismo R2 que ya usa Carbon.
Qué NO es (fuera de alcance, a propósito):
- ❌ Integración con Carbon/Node (executor,
edc-client.ts, UI) → Fase 3. - ❌ Semántica de tabla Iceberg / zero-copy vía Lakekeeper → Fase 5. Aquí movemos el Parquet como objeto (Estrategia A, §3.1 del research), no como tabla.
- ❌ IdentityHub / DID / VC → Fase 6 (seguimos con
iam.mock). - ❌ Persistencia Postgres + Vault dedicados → Fase 8 (seguimos in-memory).
Por qué este recorte: el objetivo es despejar el riesgo técnico #1 del proyecto — "¿el data plane de EDC habla con R2?" — con el mínimo de piezas alrededor. Todo lo demás se apila después.
2. Objetivos medibles (lo que Fase 2 debe dejar)
- Nuestro launcher en
services/edc-service/compila y arranca (build propio, no el del sample). - Transfer S3→S3 verificado entre dos conectores contra MinIO: un objeto aterriza en el bucket destino (comprobado con
mc ls/listado). - Transfer contra R2 real verificado: el mismo flujo escribe un objeto en un prefijo del bucket del lakehouse en R2 (
dataspace-inbox/), conregion=auto+ path-style. - Superficie de API recapturada en la versión vigente (revisar v4alpha; documentar deltas vs v3, sobre todo en transfer).
- Config de R2 probada y documentada (endpoint, jurisdicción EU, credenciales vía vault/env) — semilla para el despliegue.
fase-2-findings.mdcon resultados y sondas respondidas.
3. Decisiones de arranque
| Decisión | Elección | Por qué |
|---|---|---|
| De dónde parte el conector | Launcher propio en services/edc-service/ (nuestro build.gradle.kts), tomando poc/ como referencia | Fase 2 es donde "poseemos" el conector. Se acabó depender del árbol de samples. |
| Módulos del launcher | control-plane base BOM + data-plane-aws-s3 (Technology-Aws) + iam.mock + management-api | Añadir S3 al set que ya validamos en Fase 1. |
| Alineación de versiones | data-plane-aws-s3 pineado a la misma release de EDC que el core | ⚠️ Technology-Aws y el core deben coincidir en versión o el build/runtime rompe. |
| Stand-in de R2 | MinIO (S3-compatible, OSS, self-hostable) | Iterar sin gastar/ensuciar R2; además on-brand (soberano, self-hostable). |
| Destino real | Un prefijo scratch en el bucket del lakehouse en R2 (dataspace-inbox/…) | Prueba el escenario de verdad: EDC escribe donde vive Iceberg. |
| Payload | Un Parquet pequeño | Prefigura el lakehouse aunque aún lo tratemos como objeto. |
| Tipo de transferencia | AmazonS3-PUSH (provider empuja al S3 destino) | El patrón S3→S3 nativo del data plane. |
endpointOverride | En el DataAddress (source y destination) | Es donde EDC lo lee para apuntar a MinIO/R2 (no solo config global). |
| Credenciales | Vía vault seeding / .env (gitignored), nunca en el repo | R2 keys son secretos; .env fuera de git + inyección al contenedor. |
| Management API | Revisar v4alpha; mantener v3 donde v4 no esté estable | v3 deprecada pero v4 aún alpha y con entidades que colisionan. Pin + documentar. |
4. Prerrequisitos
- Lo de Fase 1 (Docker, JDK vía imagen).
- Credenciales R2: access key / secret + account id + nombre del bucket del lakehouse (las mismas que usa
ml-runner, verS3_ENDPOINT/S3_ACCESS_KEY_IDdel lakehouse). Se cargan por.env, no se commitean. mc(MinIO client) oawsCLI dentro de un contenedor auxiliar para verificar buckets (no dependemos del host).
5. Runbook paso a paso
Paso 1 — Launcher propio (services/edc-service/)
Crear nuestro build.gradle.kts a partir de los módulos del poc/ + data-plane-aws-s3. Dockerfile análogo (multi-stage). Arranca provider + consumer igual que en Fase 1, pero ahora con el módulo S3 dentro. Hito: docker compose up con nuestro conector.
Paso 2 — MinIO como R2 local
Añadir MinIO a docker-compose.yml (http://minio:9000, consola :9001). Crear buckets provider-src y consumer-inbox. Subir un sample.parquet a provider-src. Hito: MinIO arriba con el objeto fuente.
Paso 3 — Seed de credenciales S3 en el vault
Sembrar en el vault del conector las claves de acceso a MinIO (patrón SeedVaultExtension de Fase 1), o pasarlas por el DataAddress para el PoC. Hito: el conector puede autenticar contra MinIO.
Paso 4 — Recaptura de API en v4alpha
Antes de crear el asset S3, consultar al conector las versiones de API que expone y revisar los paths v4alpha. Documentar deltas vs v3 (ojo: en transfer v4 se eliminó dataDestination del cuerpo — el destino se maneja distinto). Ajustar payloads. Hito: superficie de API v4 anotada.
Paso 5 — Publicar un Asset S3 (source en MinIO)
Crear el asset con dataAddress tipo AmazonS3 apuntando a provider-src con endpointOverride:
// create-asset (S3) — ilustrativo, verificar contra la versión de API vigente
{
"@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" },
"@id": "parquet-asset",
"properties": { "name": "sample parquet", "contenttype": "application/octet-stream" },
"dataAddress": {
"type": "AmazonS3",
"region": "us-east-1",
"bucketName": "provider-src",
"keyName": "sample.parquet",
"endpointOverride": "http://minio:9000"
}
}
+ policy + contract definition (como en Fase 1). Hito: el asset S3 aparece en el catálogo.
Paso 6 — Ciclo + transferencia S3→S3 (destino MinIO)
Catálogo → negociación → acuerdo (reutilizamos el flujo de Fase 1). Iniciar transfer AmazonS3-PUSH con destino consumer-inbox:
// destino S3 (según la versión de API; en v4 el destino puede ir aparte)
{
"type": "AmazonS3",
"region": "us-east-1",
"bucketName": "consumer-inbox",
"keyName": "sample.parquet",
"endpointOverride": "http://minio:9000"
}
Hito: transfer COMPLETED.
Paso 7 — Verificar el objeto en destino
mc ls consumer-inbox/ → el sample.parquet está ahí, con el mismo tamaño/checksum. Hito: copia S3→S3 demostrada.
Paso 8 — El money shot: contra R2 real
Repetir el paso 6 con el destino apuntando a R2 (prefijo dataspace-inbox/ del bucket del lakehouse):
region=auto
endpointOverride=https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com # jurisdicción EU = residencia europea
+ path-style addressing (forcePathStyle)
Verificar el objeto en R2 (mismo bucket que Iceberg). Hito: EDC escribe en el R2 del lakehouse bajo contrato. Riesgo #1 despejado.
Paso 9 — Findings
fase-2-findings.md: versiones, config R2 que funcionó, deltas v3→v4, y las sondas respondidas.
6. R2: gotchas y el ángulo soberanía
| Gotcha | Qué hacer |
|---|---|
| Región | R2 sólo acepta region=auto (el SDK la exige pero R2 la ignora). |
| Addressing | Forzar path-style (forcePathStyle) o saltan 403/HeadBucket. |
| Endpoint | https://<ACCOUNT_ID>.r2.cloudflarestorage.com. |
| Residencia EU ⭐ | Usar el endpoint de jurisdicción: https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com → el dato se queda en la UE. Esto es exactamente el foso: soberanía verificable a nivel de infraestructura. Documentarlo como feature vendible. |
| Multipart/checksums | R2 soporta S3 pero con matices; validar objetos grandes en Fase 4/8. |
7. Entregables (el cimiento que suma)
services/edc-service/— nuestro launcher real (build.gradle.kts + Dockerfile) con el data plane S3. Elpoc/queda como referencia histórica.docker-compose.ymlampliado con MinIO + seed de buckets.- Superficie de API recapturada (v4alpha) + requests S3 (
requests/). - Config R2 probada (endpoint EU, region auto, path-style, credenciales vía
.env/vault) — snippet reutilizable. docs/dataspaces/fase-2-findings.md.
8. Definition of Done
- Nuestro launcher (
services/edc-service/) compila y arranca 2 conectores con el módulo S3. - Transfer S3→S3 contra MinIO verificado (objeto en
consumer-inbox). - Transfer contra R2 real verificado (objeto en
dataspace-inbox/del bucket del lakehouse, endpoint EU). - Superficie de API recapturada en v4alpha con deltas documentados.
- Credenciales R2 fuera de git; config documentada.
-
fase-2-findings.mdcierra las sondas.
9. Riesgos
| Riesgo | Mitigación |
|---|---|
| Desalineación de versiones core vs Technology-Aws | Pinear ambos a la misma release de EDC; verificar en el build. |
v4alpha inestable (entidades v3/v4 colisionan; dataDestination fuera en transfer v4) | Pin de versión; recapturar y documentar; mantener v3 donde v4 no esté listo. |
| Fuga de credenciales R2 | .env gitignored + inyección a contenedor + .gitignore revisado. |
| Paridad MinIO ↔ R2 | MinIO es buen stand-in, pero el paso 8 valida contra R2 real (no asumir). |
| Escribir en el bucket productivo | Usar un prefijo scratch aislado (dataspace-inbox/), nunca rutas de datasets reales. |
| endpointOverride ignorado | Confirmar que va en el DataAddress (no solo config global); es un bug histórico ya resuelto pero a verificar. |
10. Qué desbloquea
- Fase 3 (
edc-client.ts+ executor): superficie de API v4 + patrón de asset S3 listos para el cliente Node. - Fase 4 (consumir → lakehouse): el aterrizaje en R2 ya probado → sólo falta registrar el snapshot Iceberg con
withNativeControlPlane(). - Fase 5 (zero-copy Lakekeeper): la fontanería S3/R2 y el gancho de data plane custom (visto en Fase 1) son la base del DataSource Iceberg.
11. Fuentes
- EDC Technology-Aws (data plane S3) · endpointOverride en DataAddress (#24) · MinIO como store (#224)
- EDC Connector — releases · colisión entidades v3/v4 (#5303)
- Cloudflare R2 — S3 API compatibility · R2 data location / jurisdicción EU
- Set up a minimum viable data space (AWS Prescriptive Guidance)
Siguiente acción concreta al ejecutar: Paso 1 — crear el launcher services/edc-service/ con el módulo data-plane-aws-s3 y confirmar que arranca.