Fase 3 — Hallazgos (en progreso)
Approach: fase-3-approach.md · Arnés:
services/edc-service/
Paso 0 — Prerrequisitos ✅ (2026-07-02)
A. Management API protegida con api-key ✅
- Módulo:
org.eclipse.edc:auth-tokenbased:0.17.0añ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):Caso Resultado sin key 401 ✅ (protegida) key incorrecta 401 ✅ key correcta pasa auth → llega al handler (400 por body vacío; 200 con body válido) ✅ - Regresión:
run-cycle-s3.sh(ya conx-api-key) → ciclo completoCOMPLETEDy objeto verificado enconsumer-inbox. La auth no rompe el flujo. - Dev key
dev-edc-mgmt-keyes 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 esv4.) - 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óduloversion-apino está encontrolplane-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)
- v4 exige
@typeen las entidades de management (v3 no):Asset(+dataAddresscon@type: DataAddress),PolicyDefinition,ContractDefinition,CatalogRequest,TransferRequest.
- v4 responde JSON-LD "limpio" (compactado, sin prefijos): el catálogo usa
dataset/hasPolicy(nodcat:dataset/odrl:hasPolicy), con@typeCatalog/Dataset/Offer. El offer id está endataset[].hasPolicy[].@id. - Transfer: el
@typeesTransferRequest(en v3 eraTransferRequestDto).dataDestinationSIGUE usándose en 0.17.0 v4 → la nota web de que "se quitó dataDestination" NO aplica a esta versión (confirmado: transferCOMPLETED). Corregido el supuesto de Fase 2. - Estados observados v4: negociación
INITIAL→REQUESTED→AGREED→VERIFIED→FINALIZED; transferINITIAL→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
@typev4.
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.ts—ServiceExecutorthin y stateless sobreEdcManagementClient, 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' }. Schemacredentials_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.sql—dataspace_assets+dataspace_transfersconworkspace_idFK + RLS víauser_workspace_ids()(4 policies c/u) + triggerset_updated_at, mirroring el patrón probado decdc_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+supabaseRLSconworkspace_id):GET /api/dataspaces/catalog?counterPartyAddress=…→ browse (vía executor).POST /api/dataspaces/publish→ asset+policy+contractDef (409 idempotente) → persistedataspace_assets.POST /api/dataspaces/consume→ negocia → persistedataspace_transfers(stateNEGOTIATING); 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óniceberg/sync-worker): avanzadataspace_transfersNEGOTIATING → (FINALIZED) → TRANSFERRING → COMPLETED / TERMINATED, consultando el conector víaedcExecutor(una sola fuente de payloads v4) y actualizando la fila consupabaseAdmin(service role, bypass RLS).advanceTransfer(id)= lógica pura (testeable sin BullMQ);processJobla envuelve y re-encola con delay mientras no sea terminal. - Cola local
dataspace-sync(nombre enQUEUE_NAMES, creada en el worker como peer — patróniceberg-sync) +enqueueDataspaceSync(). consumeahora encola el worker tras persistir (best-effort).- Alta en
scripts/start-workers.ts(import +ENABLE_DATASPACE_SYNCopt-in + start/stop). - Hook Fase 4 dejado en el paso
COMPLETED(aterrizar en lakehouse conwithNativeControlPlane(); 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
edcregistrado (nº88) — Iter 1 (visible en discovery por el alta enexecutorMap). - Ciclo browse→negotiate→transfer desde Node sin curl — Iter 1 (executor) + Iter 4 (cola real).
- Estado en
dataspace_transfersworkspace-scoped + RLS — Iter 2/3. -
edc-sync-workerarranca (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
COMPLETEDdel worker awithNativeControlPlane()para aterrizar el dato consumido como snapshot Iceberg (recordar el hallazgo Fase 2 del prefijo keyName).