ADR-001 — Tenant source of truth e propagazione
Stato: accettata · Proposta il: 15 agosto 2026 · Accettata il: 15 agosto 2026
Contesto
Il POC legava i dati all'utente che li aveva creati. Le tabelle
rendicontazione.organizations e organization_members esistevano ma non
erano collegate a nulla: modellavano l'organizzazione su un bigint locale,
mentre l'identità di piattaforma usa un id testuale. Due nozioni di tenant
nello stesso schema, nessuna delle quali applicata.
Serviva decidere quale fosse quella autorevole prima di aggiungere qualsiasi funzionalità, perché ogni tabella e ogni query aggiunta prima avrebbe moltiplicato il costo della bonifica.
Decisione
backbone_auth.organization è l'unica fonte autorevole del tenant.
- Ogni entità operativa del verticale porta
organization_id text NOT NULLcon foreign key versobackbone_auth.organization(id). - Le relazioni fra entità usano foreign key composite
(organization_id, id): una relazione cross-tenant è impossibile a livello di database, non solo sconsigliata a livello applicativo. - L'organizzazione è risolta server-side dal token, tramite
backbone_auth.member. L'headerx-organization-idinviato dal client viene confrontato con la membership e mai usato come sorgente. - Le unicità sono tenant-local:
UNIQUE (organization_id, ...). - Il layer di sicurezza è
data-service, che filtra ogni lettura e scrittura per organizzazione. Il browser non parla mai condatahub.
Le tabelle placeholder sono state eliminate: una seconda nozione di tenant è un rischio, non un'alternativa.
Conseguenze
Accettate. Ogni query di dominio richiede il contesto organizzazione; non esiste un modo legittimo di leggere una tabella tenant senza scope. Le migrazioni verso nuovi ambienti richiedono che l'organizzazione destinataria esista prima del backfill.
Positive. L'isolamento è verificabile con query sullo schema, non per
ispezione del codice: lo script
rendicontazione_tenant_invariants.sql controlla
colonne mancanti, righe senza organizzazione, relazioni cross-tenant e orfane
su tutte le relazioni con chiave composita. Il gate di release fallisce se una
di queste condizioni non regge.
Da sorvegliare. Il filtro tenant è applicato da data-service passando
organization_id a datahub. Perché non possa degradare in silenzio, i
chiamanti tenant-aware richiedono __strict_filters: datahub rifiuta la
richiesta se la colonna non esiste, invece di restituire tutte le righe.
Alternative considerate
Un tenant locale al verticale, con sincronizzazione dall'identità di piattaforma. Scartata: introduce uno stato duplicato che può divergere, e la divergenza si manifesterebbe come data leak.
Row Level Security di PostgreSQL. Non scartata in linea di principio, ma
rimandata: richiede che ogni connessione porti l'identità del tenant, cosa che
oggi non avviene perché datahub usa un pool applicativo condiviso. Resta
un'opzione per irrobustire il confine senza cambiarne la semantica.
Verifica
Il gate P0 è considerato superato quando: due utenti della stessa
organizzazione lavorano sugli stessi dati; un utente di un'altra
organizzazione riceve 404 sugli stessi id; nessuna tabella operativa ha
organization_id nullo; lo script invarianti non segnala nulla in DEV, TEST e
PROD.
Approvazione
| Campo | Valore |
|---|---|
| Approvata da | Vincenzo Esposito |
| Data | 15 agosto 2026 |
| Note o riserve | Nessuna. La decisione è già implementata e verificata in DEV e TEST; resta da eseguire il gate invarianti in PROD. |
Da qui in avanti la scelta è vincolante: cambiarla richiede un nuovo ADR che superi questo, non una modifica a questa pagina.