Skip to content

0027. Dead-letter visibile nella DAG (nodo sintetico + edge d'errore)

  • Stato: Accettata
  • Data: 2026-07-22
  • Decisori: Team BI/ETL

Contesto

Le pipeline a grafo isolano gli errori con dead-letter per nodo (ADR-0016): un deadLetter?: Sink<DeadLetter> su un nodo transform/join instrada i record scartati senza abortire la run (status partial). Ma questa gestione errori era invisibile nella UI del DAG: il deadLetter è una proprietà del nodo (src/core/graph.ts), non un nodo/edge del GraphSpec, e toPlanSpec lo scartava — nessun PlanNode/PlanEdge lo rappresentava. L'utente non vedeva, guardando il diagramma, che una pipeline gestisce gli errori né dove.

Decisione

toPlanSpec proietta il deadLetter di ogni nodo in topologia sintetica (deadLetterProjection): un PlanNode con stepType: "dead-letter" per ogni sink di scarto (dedup per name) e un PlanEdge con kind: "dead-letter" da ogni nodo che lo alimenta. Questi nodi/edge non esistono nel GraphSpec: sono una vista per la UI.

Contratto additivo (src/core/graph.ts):

  • PlanStepType = GraphNodeKind | "dead-letter" — nuovo tipo di step, usato da PlanNode.stepType.
  • PlanEdge.kind?: "dead-letter" — assente sugli edge normali del flusso dati.

Il nodo dead-letter ha columns: []: porta l'envelope DeadLetter (record + causa), non lo schema del record — coerente con ADR-0026 ("non si inventano colonne dove non esistono"). Le pipeline che vogliono mostrare il branch collegano il dead-letter anche in getSpec() (non solo in run()): vedi orders-enriched-daily.

Lato UI (noeva-etl-ui, submodule indipendente, sync manuale via src/lib/types.ts): ETLStepType e lo schema Zod di plan-loader accettano "dead-letter"; il nodo è reso in destructive (icona PackageX) e l'edge tratteggiato (strokeDasharray) in destructive.

Alternative scartate

  • Rendere il dead-letter un vero nodo/edge del GraphSpec (non sintetico) — scartato: cambierebbe la semantica dell'esecuzione e le validazioni di validateGraph (un sink con un edge in ingresso da un nodo che non è "a valle" nel senso del flusso dati). Il dead-letter è un canale laterale, non un ramo del flusso; proiettarlo solo in toPlanSpec lo mantiene tale.
  • Mostrare i conteggi reject live sul nodo (dal run report: rejected/rejectedReasons) invece di un branch topologico — feature complementare, non alternativa: riguarda la vista run, non la topologia. Qui si mostra che il branch esiste; quanti record ci finiscono è un'altra iterazione.
  • Un flag booleano hasDeadLetter sul nodo invece di un nodo+edge dedicati — scartato: non si vedrebbe dove va lo scarto (il sink di destinazione, il suo descriptor), e non si potrebbe stilare un edge d'errore distinto.

Conseguenze

  • Pro: la gestione errori diventa visibile nel diagramma senza cambiare l'esecuzione; cambiamento additivo (nuovo stepType/kind, retrocompatibile); riusa il descriptor del sink di scarto (ADR-0020) per dire dove finiscono gli scarti.
  • Contro: piccola duplicazione di contratto cross-repo (il "dead-letter" va aggiunto sia in noeva-etl sia in noeva-etl-ui, che restano submodule separati); mitigata da test su entrambi i lati e dalla nota di sync in types.ts.
  • Una pipeline che ha il deadLetter solo in run() e non in getSpec() non mostra il branch: per vederlo va collegato anche nella topologia statica (fatto per orders-enriched-daily).

Riferimenti

  • src/core/graph.ts (PlanStepType, PlanEdge.kind, toPlanSpec, deadLetterProjection), src/pipelines/orders-enriched-daily/run.ts (getSpec con dead-letter).
  • UI: noeva-etl-ui/src/lib/types.ts (ETLStepType, dagPlanToFlow), noeva-etl-ui/src/lib/plan-loader.ts, noeva-etl-ui/src/lib/step-visuals.ts, noeva-etl-ui/src/components/dag/DatasetNode.tsx.
  • Correlati: ADR-0016 (dead-letter per nodo), ADR-0020 (descriptor), ADR-0026 (colonne).
  • Piano di implementazione: docs/plans/2026-07-22-dag-dead-letter-branch.md.

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