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 plan → plans.generated.json → UI.
Punti vincolanti della decisione:
- Solo
source/sink. Sono gli unici nodi che rappresentano un connettore verso un sistema esterno.transform/joinnon ricevono un descriptor:planNodeDescriptorritornaundefinedper 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. Lapassword/apiKeynon viene mai letta. host/port/database si leggono dalla config pubblica del pool (pool.optionsperpg,pool.configpermssql), 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 indetails(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 decisionecolumnsrestava[]. Deciso e implementato separatamente in ADR-0026 (introspezione dei contrattivalidate()+ 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 ilPipelinereale per non richiedere credenziali duranteetl 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
src/core/connector.ts(ConnectorDescriptor,descriptor?),src/core/graph.ts(PlanNode.descriptor,planNodeDescriptor,toPlanSpec).- Whitelist per connettore:
src/connectors/*/descriptor.tse relatividescriptor.test.ts. - How-to: docs/conventions/node-descriptor.md.
- Lato UI:
noeva-etl-ui/docs/dag-node-visualization.md. - Contratto DAG di base: ADR-0016.
- Piano: docs/plans/2026-07-15-dag-connector-descriptor.md.

