Published

Fase 3 — Hallazgos (en progreso)

Connect any source, model it as an ontology, transform it, and operationalize it, analytics, automation and machine learning, under one governed, self-hostable roof. --- Most teams stitch the...

Fase 3 — Hallazgos (en progreso)


Paso 0 — Prerrequisitos ✅ (2026-07-02)

A. Management API protegida con api-key ✅

  • Módulo: org.eclipse.edc:auth-tokenbased:0.17.0 añadido al launcher.
  • Config (por contexto): en provider/consumer .properties:
    web.http.management.auth.type=tokenbased
    web.http.management.auth.key=dev-edc-mgmt-key
    
  • Cliente: manda header x-api-key: <key>.
  • Verificado (probe-api-versions.sh):
    CasoResultado
    sin key401 ✅ (protegida)
    key incorrecta401
    key correctapasa auth → llega al handler (400 por body vacío; 200 con body válido) ✅
  • Regresión: run-cycle-s3.sh (ya con x-api-key) → ciclo completo COMPLETED y objeto verificado en consumer-inbox. La auth no rompe el flujo.
  • Dev key dev-edc-mgmt-key es local (no secreto); en prod viene de env/secret.

B. Recaptura de la superficie de API → v4

Descubrimiento contra el conector local (0.17.0):

  • EDC 0.17.0 sirve v3 (deprecada) y v4 (actual) bajo /management/.
  • v4 vive en /management/v4/, NO en /management/v4alpha/ (eso da 404). (La búsqueda web mencionaba "v4alpha"; en 0.17.0 el path estable es v4.)
  • Superficie v4 confirmada (todos responden 400 = existen, con body inválido): POST /management/v4/{assets/request, policydefinitions, contractdefinitions, catalog/request, contractnegotiations, transferprocesses} + GET /management/v4/{contractnegotiations,transferprocesses}/{id} + GET /management/v4/edrs/{id}/dataaddress.
  • No hay endpoint de descubrimiento de versiones (/.well-known/api/version, /api/version, /management/version → 404; el módulo version-api no está en controlplane-base-bom). Sondeo directo de paths, suficiente.

Decisión: el cliente de Fase 3 (edc-client.ts) apunta a /management/v4/.

⚠️ Delta a verificar al implementar startTransfer (Paso 1): en v4 se retiró dataDestination del cuerpo del transfer (release notes EDC). Confirmar la forma del TransferRequest v4 (probablemente el destino va referenciado de otra forma) cuando se escriba el método. El resto de payloads (assets/policy/contractdef/catalog/negotiation) mantienen la estructura JSON-LD de v3.

Config para el cliente (dev)

EDC_MANAGEMENT_URL=http://localhost:19193/management   # provider local
EDC_MANAGEMENT_API_KEY=dev-edc-mgmt-key                # header x-api-key

(consumer local: http://localhost:29193/management, misma key)


Paso 1 — Cliente Node edc-client.ts ✅ (2026-07-02)

lib/dataspaces/edc-client.ts (+ barrel index.ts) — cliente tipado contra /management/v4/, header x-api-key, withRetry+ClassifiedError de lib/executors/resilience, AbortSignal (timeout por intento + signal externo), logger Pino. Factory getEdcClientFromEnv().

Validado end-to-end contra el conector v4 en vivo (scripts/dataspaces/edc-smoke.ts vía tsx), enteramente desde Node, sin curl:

testConnection ✅ (provider + consumer)
createAsset / createPolicyDefinition / createContractDefinition ✅
requestCatalog → offer id ✅
startNegotiation → getNegotiation → FINALIZED ✅
startTransfer (AmazonS3-PUSH) → getTransfer → COMPLETED ✅ (dato real a MinIO)

⚠️ Recaptura v4 — deltas reales vs v3 (descubiertos en vivo)

  1. v4 exige @type en las entidades de management (v3 no):
    • Asset (+ dataAddress con @type: DataAddress), PolicyDefinition, ContractDefinition, CatalogRequest, TransferRequest.
  2. v4 responde JSON-LD "limpio" (compactado, sin prefijos): el catálogo usa dataset / hasPolicy (no dcat:dataset / odrl:hasPolicy), con @type Catalog/Dataset/Offer. El offer id está en dataset[].hasPolicy[].@id.
  3. Transfer: el @type es TransferRequest (en v3 era TransferRequestDto). dataDestination SIGUE usándose en 0.17.0 v4 → la nota web de que "se quitó dataDestination" NO aplica a esta versión (confirmado: transfer COMPLETED). Corregido el supuesto de Fase 2.
  4. Estados observados v4: negociación INITIAL→REQUESTED→AGREED→VERIFIED→FINALIZED; transfer INITIAL→REQUESTED→STARTED→COMPLETING_REQUESTED→COMPLETED.

Estas formas son la referencia canónica para construir payloads en el executor/rutas (Pasos 3/5). El cliente es transporte (pasa JsonLd); quien arma el payload (executor/rutas) debe incluir el @type v4.


Iteración 1 — Executor edc (nº88) + credencial de partner ✅ (2026-07-02)

(Ensambla los antiguos Paso 2 + Paso 3.)

  • Executor lib/executors/services/edc.executor.tsServiceExecutor thin y stateless sobre EdcManagementClient, construye payloads v4 (@type). Operaciones: catalog:browse, asset:publish, policy:create, contract:define, contract:negotiate, contract:status, transfer:initiate, transfer:status. Nunca lanza: mapea errores a {success:false,error}.
  • Registro en lib/executors/registry.ts (['edc', edcExecutor]).
  • Credencial de partner en lib/executors/utils/credential-map.ts: edc → { serviceType:'edc', credentialType:'edcPartner' }. Schema credentials_data = { counterPartyId, counterPartyAddress, protocol? }. El endpoint de NUESTRO conector va por env (no credencial).
  • Reparto de config validado en vivo: provider-side ops (publish/policy/define) contra el conector propio (env); consumer-side ops (catalog/negotiate/transfer) usando el partner de la credencial.

Verificado end-to-end (scripts/dataspaces/edc-executor-smoke.ts, todo vía edcExecutor.execute, sin curl):

testConnection ✓ · asset:publish/policy:create/contract:define ✓
catalog:browse → offerId ✓ · contract:negotiate ✓
contract:status → FINALIZED ✓ · transfer:initiate ✓ · transfer:status → COMPLETED ✓

Nota: el registry completo no carga en un script pelado (arrastra lib/db/client.ts que exige env Supabase); se verifica con dotenv -e .env.local o en la app. La prueba funcional del executor (arriba) es la garantía.


Iteración 2 — Estado + rutas app/api/dataspaces/* ✅ (2026-07-02)

  • Migración supabase/migrations/20261230_dataspace_tables.sqldataspace_assets + dataspace_transfers con workspace_id FK + RLS vía user_workspace_ids() (4 policies c/u) + trigger set_updated_at, mirroring el patrón probado de cdc_syncs. dataspace_transfers.config (jsonb) guarda lo que el worker necesita para avanzar (counterPartyId, protocol, offerId, transferType, dataDestination).
  • Rutas (patrón cdc-syncs/route.ts: withRLSContext + requirePermission + supabaseRLS con workspace_id):
    • GET /api/dataspaces/catalog?counterPartyAddress=… → browse (vía executor).
    • POST /api/dataspaces/publish → asset+policy+contractDef (409 idempotente) → persiste dataspace_assets.
    • POST /api/dataspaces/consume → negocia → persiste dataspace_transfers (state NEGOTIATING); worker lo avanzará (TODO Iter 3: encolar).
    • GET /api/dataspaces/transfers → lista del workspace.

Verificado: las 4 rutas cargan en el entorno real (dotenv -e .env.local + tsx) con sus handlers correctos y toda la cadena de imports resuelta (supabase-rls, auth, executor, client, resiliencia). No ejecutadas contra el server Next + DB en vivo (requeriría aplicar la migración a la Supabase real — eso va por el proceso de migraciones del proyecto, no lo forzamos). La migración replica un patrón RLS ya en producción → confianza por construcción.


Iteración 3 — edc-sync-worker (máquina de estados async) ✅ (2026-07-02)

  • Worker lib/workers/edc/sync-worker.ts (BullMQ, patrón iceberg/sync-worker): avanza dataspace_transfers NEGOTIATING → (FINALIZED) → TRANSFERRING → COMPLETED / TERMINATED, consultando el conector vía edcExecutor (una sola fuente de payloads v4) y actualizando la fila con supabaseAdmin (service role, bypass RLS). advanceTransfer(id) = lógica pura (testeable sin BullMQ); processJob la envuelve y re-encola con delay mientras no sea terminal.
  • Cola local dataspace-sync (nombre en QUEUE_NAMES, creada en el worker como peer — patrón iceberg-sync) + enqueueDataspaceSync().
  • consume ahora encola el worker tras persistir (best-effort).
  • Alta en scripts/start-workers.ts (import + ENABLE_DATASPACE_SYNC opt-in + start/stop).
  • Hook Fase 4 dejado en el paso COMPLETED (aterrizar en lakehouse con withNativeControlPlane(); recordar el hallazgo Fase 2 del prefijo keyName).

Verificado E2E (scripts/dataspaces/edc-worker-smoke.ts, dotenv -e .env.local + conector vivo + Supabase real): publish → negotiate → insertar dataspace_transfers (NEGOTIATING) → advanceTransfer en bucle → fila alcanza COMPLETED con edc_agreement_id+edc_transfer_id poblados → fila de prueba borrada. Estados observados: NEGOTIATING×4 → TRANSFERRING×3 → COMPLETED.


Iteración 4 — E2E de cola real + cierre ✅ (2026-07-02)

Verificado el camino REAL de BullMQ (scripts/dataspaces/edc-queue-e2e.ts): worker arrancado (startEdcSyncWorker) + enqueueDataspaceSync → el transfer avanzó de forma autónoma por la cola dataspace-sync hasta COMPLETED en Supabase (poll: NEGOTIATING → TRANSFERRING → COMPLETED), sin advanceTransfer manual. Prueba la cola + processJob + reschedule + worker lifecycle end-to-end.

Definition of Done (Fase 3)

  • Management API autenticada (api-key x-api-key) — Paso 0.
  • Superficie recapturada a v4 — Paso 0/1.
  • Executor edc registrado (nº88) — Iter 1 (visible en discovery por el alta en executorMap).
  • Ciclo browse→negotiate→transfer desde Node sin curl — Iter 1 (executor) + Iter 4 (cola real).
  • Estado en dataspace_transfers workspace-scoped + RLS — Iter 2/3.
  • edc-sync-worker arranca (start-workers) y avanza estados vía la cola — Iter 3/4.
  • Secretos fuera de git; escrituras con workspace_id; conector por env, partner por credencial.
  • [~] HTTP con Clerk-auth extremo a extremo (server Next corriendo + sesión de navegador): no ejecutado en este entorno (requiere login Clerk real). Mitigado: rutas load-verified (Iter 2) + su lógica (executor + inserts + enqueue) probada por los smokes + cola real (Iter 4). Queda como validación de smoke-en-app cuando se levante el server.

Smokes reproducibles en scripts/dataspaces/: edc-smoke (client v4), edc-executor-smoke (executor), edc-worker-smoke (advanceTransfer), edc-queue-e2e (cola+worker BullMQ real).


Estado

  • Fase 3 ✅ — Paso 0, Paso 1, Iteraciones 1-4 completas y verificadas (salvo el smoke HTTP-en-app, pendiente de server).
  • Siguiente: Fase 4 — enganchar el hook COMPLETED del worker a withNativeControlPlane() para aterrizar el dato consumido como snapshot Iceberg (recordar el hallazgo Fase 2 del prefijo keyName).