0017. Worker daemon su processes: dipendenza @my-ginkgo/noeva-shared
- Stato: Superata da ADR-0019
- Data: 2026-07-10
- Decisori: Team BI/ETL
Contesto
noeva-etl esponeva finora un solo modo di essere invocato: la CLI batch etl run <pipeline> (ADR-0005, ADR-0006) — stateless, un container/processo = una esecuzione, esito su stdout. Qualcuno esterno (cron, scheduler, operatore) deve invocare il container per ogni run.
Il resto dell'infrastruttura Noeva (noeva-server-worker) usa invece un pattern di poller long-running sulla tabella processes di Supabase: un'istanza resta in polling, rivendica atomicamente (CAS) le righe pending con un dato worker_type, esegue il lavoro e scrive l'esito sulla stessa riga. Questo pattern è quello con cui il resto del prodotto (UI, scheduled jobs, workspace) crea e osserva job.
Serve permettere che le pipeline ETL vengano lanciate da dentro il sistema di job di Noeva (uno scheduled job, un'azione utente in workspace) invece che solo da un cron esterno che invoca etl run — con un vincolo che il worker generico non ha: il poller ETL deve occuparsi solo dei processi con worker_type='etl' (configurabile) eworkspace_id uguale a quello dell'istanza (NOEVA_WORKSPACE_ID), cioè ogni istanza è scoped a un singolo workspace.
Decisione
Aggiungere un secondo entrypoint, additivo e non sostitutivo del primo:
etl worker(src/worker/) — daemon che fa polling suprocesses, filtrato esclusivamente perworker_type+workspace_id, e dispatcha la pipeline il cui nome è nella colonnafunction(match esatto suRunnablePipeline.namenel registry esistente, nessun layer di traduzione). L'esito (RunReport/GraphRunReport) viene scritto inprocesses.output_data/error_message/status, non su stdout.- Nuova dipendenza:
@my-ginkgo/noeva-shared(+@supabase/supabase-js), per riusareProcessManager.claimProcess/updateProcess(CASpending→running) egetSupabaseService()— la stessa libreria e lo stesso contratto di claim usati danoeva-server-worker, invece di reimplementare la logica di lock. - Nessuna estensione della RPC condivisa
claim_next_process: quella RPC non supporta filtro perworkspace_ided estenderla per un solo consumer (l'ETL) accoppierebbe un pezzo di infrastruttura condivisa a un caso d'uso specifico. Il claim ETL fa quindi unaSELECTfiltrata +ProcessManager.claimProcess(id)per riga candidata, invece della RPC. etl run(batch, ADR-0005/0006) resta invariato: il nucleo di esecuzione della pipeline (pipeline.run(ctx) → RunReport) è condiviso e resta una funzione pura; cambia solo chi la invoca e dove finisce il report.
Alternative considerate
- Estendere
claim_next_processcon un parametroworkspace_id— scartata: tutti gli altri consumer della RPC (noeva-server-worker) dovrebbero convivere con un parametro che non usano, e la RPC diventerebbe accoppiata a un solo caso d'uso. - Scheduler esterno generico (cron per workspace che invoca
etl run <pipeline>) — scartata: non permette il dispatch dinamico guidato dal sistema di job di Noeva (UI, scheduled jobs agent) e duplicherebbe la logica di polling/lock in cron esterni invece di riusare quella già pubblicata innoeva-shared. - Client SQL diretto fatto in casa verso
processes, senza dipendere danoeva-shared— scartata: duplicherebbe l'esatta logica di claim CAS già testata e pubblicata in quel pacchetto, divergendo dal contratto usato dal resto del prodotto.
Conseguenze
- Positive: le pipeline ETL sono ora lanciabili da dentro il sistema di job/workspace di Noeva esattamente come qualsiasi altra
ProcessFunction, senza bisogno di un cron esterno; il claim atomico riusa un meccanismo già collaudato invece di reinventare polling/locking. - Costi/limiti:
- Nuova dipendenza runtime
@my-ginkgo/noeva-shared(+@supabase/supabase-js): la modalitàetl workerrichiedeSUPABASE_URL/SUPABASE_SERVICE_KEY/SUPABASE_JWT_SECRETin ambiente (fail-fast se mancanti — vedi runbooks/etl-worker.md). - Due modalità di invocazione (batch vs daemon) coesistono con modelli operativi diversi (one-shot vs long-running); vanno tenute chiaramente separate in documentazione per non confondere gli operatori.
- Nessun timeout applicativo sul singolo dispatch dentro il worker (a differenza del limite fisso di 30 minuti di
noeva-server-worker): una pipeline molto lunga blocca il polling di quell'istanza finché non finisce. claimed_by/claimed_atrestano vuoti (non si estendeProcessManagerper questo); nessun indice DB dedicato aggiunto suprocesses.
- Nuova dipendenza runtime

