Skip to content

ADR-0020: descriptor del connettore per la visualizzazione dei nodi in UI

  • Stato: Accettata
  • Data: 2026-07-15

Contesto

etl plan proietta ogni pipeline in un PlanSpec (JSON-safe) consumato da noeva-etl-ui per disegnare il DAG. Fino a questa decisione un nodo esponeva solo id, label, stepType, columns (all'epoca sempre []; popolate poi da ADR-0026): nel canvas i nodi source e sink non dicevano quale connettore fossero (PostgreSQL, SQL Server, API Noeva, file) né dove puntassero (host, database, tabella, query, fileName). L'utente non poteva capire com'era configurata la pipeline guardando il diagramma.

Serviva esporre queste informazioni senza mai far uscire segreti (password, token, connection string con credenziali): il vincolo "niente segreti nei log/JSON" (CLAUDE.md §2) vale anche per il PlanSpec, che finisce in un file di dati e nella UI.

Decisione

Introdotto un ConnectorDescriptor { kind, details: {label, value}[] } (src/core/connector.ts), campo opzionale descriptor? su Source e Sink. Ogni connettore auto-dichiara il proprio descriptor con una whitelist esplicita di campi non sensibili; toPlanSpec (src/core/graph.ts) lo proietta su PlanNode.descriptor, da dove viaggia in etl planplans.generated.json → UI.

Punti vincolanti della decisione:

  • Solo source/sink. Sono gli unici nodi che rappresentano un connettore verso un sistema esterno. transform/join non ricevono un descriptor: planNodeDescriptor ritorna undefined per quei kind. Eventuali metadati per transform/join seguiranno un contratto diverso (non "connettore").
  • Whitelist, non dump. details è una lista esplicita di campi scelti dal connettore, mai una serializzazione dell'oggetto opzioni. La password/apiKey non viene mai letta. host/port/database si leggono dalla config pubblica del pool (pool.options per pg, pool.config per mssql), non da campi segreti.
  • Opzionale su entrambi i lati. Il campo è opzionale nel contratto backend e nello schema Zod della UI: un lato più vecchio dell'altro (i due repo sono submodule indipendenti) non crasha, il descriptor semplicemente non appare.
  • Invariante testata. Ogni connettore ha un test che verifica kind, i campi attesi e l'assenza di segreti in details (cfr. */descriptor.test.ts).

Il how-to pratico (forma del descriptor, campi per connettore, come aggiungerne uno nuovo, come la UI lo rende) vive in docs/conventions/node-descriptor.md.

Alternative scartate

  • Inferire il tipo dal solo name. name è spesso generico (postgres, memory, chain) e non porta alcun puntamento/config → non soddisfa il requisito ("che connettore è e dove punta").
  • Registry centrale di metadata (mappa esterna connettore→descriptor). Duplica e deriva dai connettori, indirezione, più fragile sul fronte segreti: il connettore resta l'unico punto che sa quali suoi campi sono non sensibili.
  • Introspezione degli schemi Zod per popolare columns. Fuori scope qui: il descriptor risponde a "che connettore/config", non "che colonne", e all'epoca di questa decisione columns restava []. Deciso e implementato separatamente in ADR-0026 (introspezione dei contratti validate() + propagazione ai nodi adiacenti) — columns è un campo gemello del descriptor, non lo sostituisce.

Conseguenze

  • Pro: proprietà chiara (il connettore possiede la propria descrizione), secret-safe per costruzione, estensibile a nuovi connettori senza toccare il core, retrocompatibile.
  • Contro: il contratto va tenuto in sync su due repo (backend + UI submodule); mitigato dal JSDoc di toPlanSpec, dallo schema Zod e dall'opzionalità del campo.
  • Caso statico: le pipeline che hand-authorano il getSpec() (es. langfuse-sessions-daily, che non costruisce il Pipeline reale per non richiedere credenziali durante etl plan) popolano il descriptor manualmente coi soli campi noti a compile-time (kind, query, tabella); host/database sono omessi (non derivabili senza pool live) — coerente con la regola "campo non derivabile ⇒ omesso".

Riferimenti

Noeva è un marchio registrato di 4D S.R.L.