0018. Worker daemon su processes: polling mediato da noeva-server-api, non più Supabase diretto
- Stato: Accettata
- Data: 2026-07-15
- Decisori: Team BI/ETL
Contesto
ADR-0017 aveva scelto di far parlare etl worker direttamente con Supabase tramite @my-ginkgo/noeva-shared (ProcessManager.claimProcess per il claim CAS, updateProcess per lo stato/ progress/report per nodo), bypassando la RPC condivisa claim_next_process per poter filtrare anche per workspace_id.
Questo però comporta che ogni istanza di etl worker porti con sé credenziali Supabase dirette (SUPABASE_URL, SUPABASE_SERVICE_KEY — una service role key, che bypassa RLS). A differenza di noeva-server-api, che è un backend condiviso unico per tutti i workspace, etl worker è deployato come istanza indipendente per workspace, potenzialmente su infrastruttura controllata dal cliente. Qualunque credenziale data al worker è quindi una credenziale esposta sull'ambiente del cliente — per una service role key Supabase questo è un rischio di sicurezza inaccettabile: sparpaglia le chiavi più privilegiate del nostro Supabase fuori dal nostro perimetro.
Decisione
Il polling/claim/report di etl worker verso processes passa esclusivamente per noeva-server-api, via HTTP autenticato (stesso pattern header Authorization: Bearer <NOEVA_API_KEY> + x-noeva-workspace già usato dal connettore workspace files):
noeva-server-apiesponePOST /api/processes/etl/claim(claim CAS + filtroworker_type/workspace_id, fatto server-side, dove le credenziali Supabase vivono legittimamente) ePATCH /api/processes/etl/:processId/progress(scrittura di stato/progress/output/error).etl worker(src/worker/processes-api-client.ts,ProcessesApiClient) non ha più alcuna dipendenza né da@my-ginkgo/noeva-sharedné da@supabase/supabase-js— entrambe rimosse dapackage.json. Il worker non detiene più nessuna credenziale Supabase, solo la sua API keyNOEVA_API_KEY(già scoped al workspace).src/worker/claim.ts(il wrapper di claim lato worker) è stato rimosso anziché riscritto: con CAS e filtro spostati server-side, un passthrough di una riga attorno aProcessesApiClient.claimNextEtlProcesssarebbe un'astrazione senza scopo.- Osservabilità: il claim ora porta anche un
workerId(claimed_by, defaulthostname:pid), assente nel design precedente.
Alternative considerate
- Mantenere
@my-ginkgo/noeva-sharedma con una service-role key ristretta/ scoped — scartata: Supabase non offre service role key con permessi ristretti a livello di riga per questo caso d'uso senza reimplementare RLS custom, e comunque non risolverebbe il problema di fondo (chiavi Supabase su infrastruttura del cliente). - Un livello di proxy separato solo per Supabase (non
noeva-server-api) — scartata:noeva-server-apigià espone endpoint autenticati per workspace, aggiungere un secondo servizio solo per questo caso d'uso sarebbe una duplicazione di infrastruttura senza benefici.
Conseguenze
etl workernon ha più@my-ginkgo/noeva-shared/@supabase/supabase-jstra le dipendenze, né alcuna variabileSUPABASE_*in configurazione — soloNOEVA_API_BASE_URL/NOEVA_API_KEY/NOEVA_WORKSPACE_ID.- Il claim CAS e il filtro per
worker_type/workspace_idsono ora responsabilità dinoeva-server-api(etl-worker.controller.ts), coperti dai suoi test di integrazione — il worker si limita a consumare il contratto HTTP. - Il test di integrazione del worker (
tests/integration/etl-worker.integration.test.ts) non mocka più Supabase: simula il contratto HTTP dinoeva-server-apicon unfetchImplin-memory, verificando l'uso lato worker del client, non la logica di claim/isolamento (già coperta lato server). - Deploy su infrastruttura cliente: l'unico segreto da distribuire è
NOEVA_API_KEY, una API key applicativa scoped al workspace — non più una service role key Supabase.

