# Piano di test: metadati di schema, campi sensibili e descrizioni AI - Data: 2026-08-31 - Stato: proposto - Baseline analizzata: `f586152` sul branch `test/database-baseline` ## 1. Obiettivo Validare insieme le tre capacità recentemente introdotte in Database Management: 1. acquisizione autorevole dei metadati fisici di uno schema esterno; 2. proposta e revisione umana dei campi sensibili dal punto di vista privacy; 3. generazione, revisione e consolidamento delle descrizioni di tabelle e colonne. Il piano è risk-based: perdita o corruzione di metadati, lettura o invio di valori protetti e applicazione di una selezione al target sbagliato sono rischi bloccanti. La qualità linguistica dei testi AI è invece valutata separatamente dal contratto tecnico, perché l'output del modello è una proposta soggetta a revisione umana. Questo documento non costituisce una certificazione normativa o GDPR: verifica i controlli tecnici e il flusso operativo implementati dal prodotto. ### 1.1 Assunzione operativa: ambiente completamente sacrificabile Per indicazione esplicita del proprietario, l'installazione sul Mac è esclusivamente di sviluppo e test. Non contiene produzione e non esiste alcun requisito di conservazione dello stato locale. - `catalog-db`, migrazioni, configurazioni locali, run, eventi, flag, descrizioni curate e generate possono essere cancellati e ricreati tutte le volte necessarie; - il database reale già collegato è la fonte autorevole dalla quale ricostruire il catalogo ed è la sorgente primaria per validare schema, classificazione privacy e generazione delle descrizioni; - reset completi, failure injection, dati incoerenti deliberati e prove distruttive sul catalogo locale sono ammessi senza backup o procedura di rollback dell'ambiente; - compatibilità con stato locale preesistente, sessioni legacy e vecchie revisioni del catalogo non è un gate di questo piano; - le prove di atomicità, idempotenza e preservazione dei campi restano necessarie perché verificano il comportamento del prodotto dentro un ciclo di test, non perché debbano proteggere il Mac. Il database sorgente resta normalmente read-only: quando una prova richiede DDL o mutazioni fra scan e conferma si usa uno schema di test esplicitamente scrivibile o la fixture supplementare. La disponibilità a buttare lo stato locale non elimina il rischio di inviare dati personali a un provider esterno: proprio questa non-esfiltrazione è uno degli esiti principali da collaudare. ## 2. Terminologia e risultato atteso I tre campi testuali del catalogo hanno autorità diverse e non devono essere confusi: | Campo | Origine e autorità | Comportamento atteso | | --- | --- | --- | | `sourceComment` | Commento fisico acquisito dalla sorgente | Si aggiorna con la sincronizzazione e non è modificabile come contenuto curato. | | `generatedDescription` | Proposta prodotta dall'AI o corretta nel catalogo | È salvata separatamente, può essere rigenerata esplicitamente e non sovrascrive gli altri due campi. | | `description` | Descrizione curata dall'amministratore | Cambia solo tramite modifica esplicita o consolidamento di una proposta selezionata. | Nel seguito, “commento generato” indica `generatedDescription`. La generazione non deve mai modificare `sourceComment`; il passaggio a `description` avviene solo con il consolidamento umano. ## 3. Riferimenti e baseline Il comportamento da verificare deriva da: - stato corrente del progetto in `PROJECT_STATE.md`; - [contratto Catalog Schema Snapshot](../contracts/catalog-schema-snapshot.md); - [piano del Metadata Catalog](../plans/2026-08-26-metadata-catalog-from-thothai.md); - [specifica della generazione descrizioni](../plans/2026-08-28-ai-catalog-description-generation-spec.md); - [ADR 0007: sincronizzazione autorevole durevole](../adr/0007-durable-authoritative-schema-synchronization.md); - [ADR 0009: un solo run sequenziale di generazione](../adr/0009-use-one-sequential-description-generation-run.md); - [ADR 0010: campioni reali limitati](../adr/0010-allow-bounded-real-source-samples-for-description-generation.md); - [ADR 0011: Sensitive Data Flag](../adr/0011-gate-source-samples-with-a-sensitive-data-flag.md); - [accettazione AI del 2026-08-29](./2026-08-29-ai-catalog-description-generation-acceptance.md). L'accettazione del 2026-08-29 è una baseline utile ma non chiude il gate attuale: precede i commit `0736983` e `cb40c09`, inviava campioni reali inventati e documentava che un valore campione era stato ripreso nella descrizione. Deve quindi essere ripetuta sul comportamento privacy corrente. ## 4. Perimetro ### Incluso - trasporti `postgres_direct`, `rest_api` e `ssh_tunnel`; - RPC REST tipizzato `POST /rpc/schema_snapshot` e fallback singolo read-only su `POST /rpc/run_query`; - metadati di tabelle, colonne, commenti sorgente, tipi, default, nullabilità, posizioni PK e coppie FK ordinate; - scope di sincronizzazione `tables`, `columns`, `relationships` e `all`; - run durevoli, conferma delle differenze distruttive, cancellazione, recovery, eventi SSE e fallback polling; - Sensitive Data Flag, analisi locale di metadati e contenuti, review draft e storico operativo; - scope di generazione `selected_columns`, `selected_tables`, `all` e `missing`; - campionamento read-only, valori sintetici per colonne protette, batching, retry, stop, Unlock, storico e consolidamento; - uso controllato del database reale già collegato, inclusi schema e valori non sensibili, per il collaudo end-to-end con fake provider e provider configurati; - permessi, minimizzazione dei dati, redazione di log/errori e resistenza a input ostili. ### Escluso o rinviato - applicazione del Sensitive Data Flag allo schema-linking/LSH del core, esplicitamente rinviata; - sostituzione di `annotations.yaml`, pubblicazione Qdrant e cutover del runtime NL→SQL; - alias, sinonimi, concetti, value descriptions e relazioni logiche; - audit delle decisioni umane sul flag: per disegno si conserva solo il booleano corrente; - DDL o scritture sul database sorgente; - conservazione di cataloghi, run, descrizioni o sessioni locali precedenti al reset; - compatibilità all'indietro con formati o migrazioni legacy non appartenenti alla baseline corrente. ## 5. Priorità e strategia | Priorità | Significato | Esempi | | --- | --- | --- | | P0 | Gate bloccante di sicurezza o integrità | Nessuna lettura/invio di valori protetti; applicazione atomica; scope esatto; segreti non esposti. | | P1 | Contratto funzionale necessario al rilascio | Trasporti, stati dei run, recovery, batching, review e consolidamento. | | P2 | Qualità, UX e robustezza non distruttiva | Copy, focus, benchmark del modello, carico e degrado SSE. | Le prove sono distribuite su quattro livelli: 1. **Contratto/unità**: repository in memoria, finti connector e Model Completer; nessuna rete. 2. **Integrazione**: catalogo PostgreSQL ricreabile via Docker, database reale come sorgente, fixture supplementare, fake REST/SSH e Model Completer osservabile. 3. **UI/E2E locale**: component test con MSW e almeno un flusso Playwright sullo stack locale. 4. **Accettazione L2**: provider configurato realmente e database reale dopo review dei flag. I test deterministici verificano il contratto. Le prove con un modello reale verificano compatibilità e qualità, ma non sostituiscono i gate P0. ## 6. Ambiente e dati di test ### 6.1 Ambiente minimo - stack locale con `catalog-db` eliminabile, migrazioni ripetibili e backend/frontend della stessa baseline; - database reale già collegato come sorgente primaria, con utenza capace di `SELECT` ma non di `INSERT`, `UPDATE`, `DELETE`, DDL o cambio di schema; - PostgreSQL effimero supplementare solo per le mutazioni controllate che non devono essere fatte sul database reale; - server REST fake in grado di servire snapshot valido, capability `unavailable`, 404, risposta parziale/malformata e fallback `run_query`; - server OpenSSH effimero con `known_hosts` esatto e varianti host key errata/assente; - Model Completer fake che registra transitoriamente i messaggi e restituisce esiti programmabili; - browser senza segreti in Web Storage e raccolta di log backend/SSE/API per le scansioni canary. Le prove PostgreSQL di integrazione non devono risultare `skip`: la disponibilità di Docker è una precondizione del gate. Il catalogo locale può essere azzerato prima di ogni wave senza snapshot o backup del suo stato precedente. ### 6.2 Database reale e fixture supplementare `catalog_qa` La prima baseline viene acquisita dal database reale collegato. Il test registra soltanto inventario strutturale, conteggi e risultati sanitizzati; poi svuota il catalogo locale e dimostra di poterlo ricostruire dalla stessa sorgente. Non è richiesto preservare alcun metadato locale precedente. La fixture `catalog_qa` integra il database reale soltanto quando servono casi controllabili o mutazioni che la sorgente reale non contiene. Deve includere almeno: - `customers`: UUID PK, nome, email, codice fiscale, telefono, data di nascita, indirizzo e note; - `orders`: FK verso `customers`, importo numerico, stato, timestamp, default e campi nullable; - `order_lines`: PK composta e relazione composta ordinata; - `clinical_events`: campi sanitari evidenti e tabella partizionata; - `products`: SKU pubblico, categoria, prezzo e flag booleano; - `empty_table`: nessuna riga ma struttura valida; - `wide_entity`: almeno 23 colonne e metadati lunghi, per forzare batch `10 + 10 + 3` e il limite dimensionale del messaggio; - identificatori quotati, commenti Unicode/italiani, commenti null e oggetti fuori dallo schema. Usare valori canary inventati e univoci, per esempio: - `PRIV_EMAIL_CANARY_...`, `PRIV_TAX_CANARY_...`, `PRIV_HEALTH_CANARY_...` nelle colonne protette; - `PUBLIC_SKU_CANARY_...` in una colonna esplicitamente non sensibile; - `SECRET_API_CANARY_...` solo nel secret store del test. I canary protetti non devono comparire nei messaggi al modello, nel catalogo, negli eventi, nelle API, nel DOM o nei log. Il canary pubblico può apparire nel messaggio al provider entro i limiti documentati e dopo la disclosure esplicita dell'utente. Sul database reale la stessa proprietà va provata soprattutto osservando la proiezione SQL e il payload transitorio: i valori protetti non devono essere letti, e nessun valore grezzo deve entrare nell'evidenza conservata. ### 6.3 Mutazioni della sorgente Preparare tre revisioni dello schema: - **A — iniziale**: struttura completa e commenti sorgente valorizzati; - **B — distruttiva**: rimozione di una tabella, una colonna e una FK, più aggiunta di una nuova colonna sensibile per nome; - **C — race di conferma**: modifica ulteriore fra piano distruttivo e conferma, per provare il re-scan. Queste revisioni possono vivere nella fixture o in uno schema reale esplicitamente dichiarato scrivibile. Fra una prova e l'altra è consentito eliminare completamente il catalogo locale, riapplicare le migrazioni e ripartire dal database reale. ## 7. Casi di test — acquisizione dei metadati | ID | P | Livello | Scenario | Risultato atteso | | --- | --- | --- | --- | --- | | MET-01 | P0 | API/Integrazione | Avvio senza binding raggiungibile, con versione database obsoleta o senza `database.manage`. | Il run non parte; risposta sicura e catalogo invariato. I segreti restano write-only. | | MET-02 | P0 | Integrazione | Reset completo del catalogo e `all` sul database reale collegato, senza mock del client `pg_catalog`; ripetizione sulla fixture A per gli edge case assenti. | Il catalogo viene ricostruito da zero con snapshot esatta di tabelle, colonne, `sourceComment`, tipo, default, nullabilità, PK e FK ordinate; stato `succeeded`; nessuna scrittura alla sorgente. | | MET-03 | P1 | Contratto/Integrazione | Stessa fixture via RPC REST tipizzato. | `schemaVersion: 1` e capability sono validate strettamente; risultato normalizzato uguale a MET-02. `unavailable` non è interpretato come collezione vuota. | | MET-04 | P0 | Contratto/Integrazione | `/schema_snapshot` assente, fallback `run_query`; poi fallback assente, parziale, non JSON o con campi extra/mancanti. | Il fallback usa una sola query read-only. Ogni errore o snapshot invalida fallisce senza modifiche parziali; nessun fallback nasconde un errore operativo diverso da capability assente. | | MET-05 | P1 | Integrazione | Accesso `ssh_tunnel` con host key corretta, errata e assente; errore durante apertura/chiusura. | Parità con MET-02 nel caso valido; fail-closed negli altri casi; processi, lease e file-segreto sempre rilasciati. | | MET-06 | P0 | API | Esecuzione separata di `tables`, `columns`, `relationships` e `all`, con e senza selezione tabelle. | Ogni scope è autorevole solo nel proprio confine; nessun record fuori scope cambia o viene eliminato. Selezioni duplicate/inesistenti sono rifiutate atomicamente. | | MET-07 | P0 | API/Integrazione | Ripetizione idempotente della fixture A dopo modifica di `description`, `generatedDescription` e `sensitive`. | Nessun diff fisico spurio; contenuti curati, proposte AI e flag delle entità ancora presenti sono preservati. Una nuova colonna nasce con `sensitive=false`. | | MET-08 | P0 | API/Integrazione | Passaggio A→B, conferma assente/errata/scaduta e passaggio A→B→C prima della conferma. | Stato `awaiting_confirmation`, piano visibile e nessuna applicazione anticipata. La conferma valida provoca re-scan; se il diff cambia viene emesso un nuovo piano/token e il vecchio non applica nulla. Apply atomica oppure zero modifiche. | | MET-09 | P0 | API/Integrazione | Timeout, disconnessione, capability incompleta o eccezione durante scan/apply. | Stato terminale coerente, errore sanitizzato, catalogo precedente intatto e lock rilasciato. Nessun segreto, SQL sensibile o stack trace nelle API/eventi. | | MET-10 | P1 | Worker | Cancel in `queued`, `running`, `awaiting_confirmation` e `applying`; retry, restart con run attivo e lease scaduto. | Cancel è efficace solo prima di apply ed è rifiutato durante apply; retry crea un nuovo run. Recovery non duplica l'apply, marca correttamente i run interrotti, rilascia il lock e rimuove snapshot/diff/token interni non più necessari. | | MET-11 | P0 | API | Due operazioni sullo stesso database: sync, cleanup, connection test, edit o generazione; in parallelo, operazioni su database diversi. | Una sola operazione possiede il database; conflitto 409 sicuro sullo stesso target. Nessun lock cross-database non previsto e nessuna release del token altrui. | | MET-12 | P1 | UI | Avvio dai menu database/tabella, visualizzazione piano, conferma, history drawer, chiusura drawer, perdita SSE e polling. | Scope e selezione inviati sono esatti; azioni non eleggibili restano visibili con motivo; chiudere il drawer non ferma il run; replay/polling deduplicano gli eventi e aggiornano griglie/KPI. | | MET-13 | P1 | E2E | Cleanup manuale di tabelle/colonne/relazioni e successiva sincronizzazione. | Cleanup modifica solo il catalogo; la sorgente resta invariata; un sync autorevole ripristina gli oggetti ancora presenti in sorgente. | | MET-14 | P0 | Integrazione | Cambio binding/versione fra scan e apply ed errore iniettato a metà transazione. | La freshness viene ricontrollata sotto lock; il run fallisce senza righe parziali e conserva integralmente il catalogo precedente. | | MET-15 | P1 | API/Worker | Replay SSE con `Last-Event-ID`/`after`, polling concorrente e retention oltre 30 giorni. | Cursori monotoni e nessun duplicato; gli eventi scaduti vengono potati senza corrompere run e stato finale. | ## 8. Casi di test — identificazione e protezione dei campi sensibili | ID | P | Livello | Scenario | Risultato atteso | | --- | --- | --- | --- | --- | | PRV-01 | P0 | Repository/API | Prima sincronizzazione, re-sync e aggiunta di una colonna. | Il default è `false`; il valore umano delle colonne esistenti è preservato; la nuova colonna è esplicitamente da riesaminare ma non riceve uno stato audit inventato. | | PRV-02 | P0 | API | Analisi per un database, tabelle selezionate e colonne selezionate; database multipli, target duplicati o mancanti. | Solo i target dello scope raggiungono l'adapter read-only. Input ambigui sono rifiutati prima della lettura e nessun flag cambia. | | PRV-03 | P0 | Unit/Contratto | Valori con email, codice fiscale italiano valido, IBAN, carta con Luhn, chiave privata, chiave JSON sensibile e testo oltre 500 caratteri. | Un solo riscontro validato rende l'intera colonna `sensitive`; l'evidenza contiene solo rule ID e conteggi sanitizzati, mai il valore. | | PRV-04 | P0 | Integrazione | Scansione completa oltre cinque secondi, timeout PostgreSQL e budget globale di sessanta secondi. | L'adapter passa al campionamento, ripristina la transazione dopo `statement_timeout`, resta read-only e non supera la deadline. Copertura incompleta senza match produce `unknown`. | | PRV-05 | P0 | Unit/API | Tabella vuota, colonna all-null, binario non ispezionabile, scan completo senza match e scan incompleto senza match. | Gli esiti sono rispettivamente `unknown`, `unknown`, `unknown`, `non_sensitive` e `unknown`; `unknown` conserva la scelta umana corrente. | | PRV-06 | P0 | UI/API | Apertura draft, modifica manuale, chiusura/reload e salvataggio. | La proposta non è persistita prima di Save; reload la scarta. Il reviewer può invertire scelte; si salvano solo colonne cambiate con versione ottimistica; un conflitto richiede reload. | | PRV-07 | P1 | Repository/UI | Tentativi completati, falliti e attivi al restart. | Ogni tentativo ha un run distinto con scope, engine `local`, versione policy, tre contatori ed eventi sanitizzati; startup marca `interrupted` i run attivi. Storico newest-first senza target ID, proposte, valori o diagnostica worker. | | PRV-08 | P0 | Integrazione | Generazione descrizioni su target con canary protetti. | Le colonne protette sono assenti dalla proiezione SQL, non semplicemente filtrate dopo la lettura. Se non rimangono colonne leggibili non viene eseguita una `SELECT`. Nessun canary protetto esce dal processo. | | PRV-09 | P0 | Contratto/Integrazione | Tabella mista con colonne sensibili e pubbliche. | Per le sensibili il prompt contiene valori plausibili, deterministici e limitati derivati dai soli metadati, nello stesso formato dei campioni e senza etichettarli al modello come sintetici. Per le pubbliche: massimo cinque righe e cinque valori rappresentativi, valori troncati e transazione read-only chiusa con rollback. | | PRV-10 | P1 | API | Cambio `false→true→false` dopo una descrizione già generata. | Il testo esistente non viene rigenerato retroattivamente. Solo le generazioni future cambiano fonte del contesto; tornando `false` il campionamento reale torna eleggibile. | | PRV-11 | P0 | API/UI | Utente senza `database.manage`, sorgente/adapter indisponibile, NER assente o in timeout e richiesta interrotta. | Controlli nascosti/disabilitati in UI e rifiuto server-side; errori non espongono dettagli. Il NER opzionale degrada alle regole/coverage senza selezionare un LLM. | | PRV-12 | P1 | L2 | Corpus etichettato con identificatori personali, credenziali/token, salute, finanza, localizzazione e controlli non sensibili/ambigui, in inglese e italiano. | Si misurano precisione, recall, falsi negativi, copertura e latenza separatamente per policy deterministica e NER CPU. La soglia va ratificata prima di abilitare NER per default; il classifier resta advisory e human-in-the-loop. | | PRV-13 | P0 | UI/E2E | Modifica di un flag nella review senza Save e tentativo immediato di generare descrizioni. | Gate di rilascio da formalizzare: la generazione deve essere bloccata finché il draft non è salvato o scartato. In alternativa la UI deve dichiarare inequivocabilmente che verrà usato il valore persistito; non è accettabile mostrare “protetto” e campionare come non protetto. | ## 9. Casi di test — generazione e consolidamento dei commenti | ID | P | Livello | Scenario | Risultato atteso | | --- | --- | --- | --- | --- | | GEN-01 | P0 | Config/API | Modelli validi, default, endpoint anonimo esplicito, secret ref mancante/errato e nessun modello. | Al browser arrivano solo ID, label e default. Nessuna chiave, provider payload o configurazione privata è esposta; feature disabilitata in modo comprensibile se non configurata. | | GEN-02 | P0 | API | `selected_columns`, `selected_tables`, `all`, `missing`, target duplicati/inesistenti e zero eleggibili. | Scope esatto; `missing` include null/vuoto/whitespace e salta proposte esistenti; `all` sostituisce solo dopo conferma esplicita; input invalido non crea run. | | GEN-03 | P1 | Worker | 23 colonne più tabelle, metadati e campioni lunghi. | Colonne prima delle tabelle per lo scope globale; richieste omogenee, sequenziali, massimo dieci target e sotto i limiti byte; contesto tabella aggiornato dopo le colonne. | | GEN-04 | P0 | Contratto | JSON puro, un solo code fence JSON completo, prosa extra, payload multipli, target mancante/duplicato/ignoto, descrizione vuota o oltre limite. | Sono accettati solo i primi due formati validi. Una mappatura ambigua non applica alcun risultato del batch e conta come errore tecnico. | | GEN-05 | P1 | API | Esito `generated` e `non_generatable` in workspace italiano/inglese. | Testo generato trimmato e salvato; il testo standard non generabile è localizzato dall'applicazione, non copiato dal provider. Gli errori tecnici non scrivono tale testo. | | GEN-06 | P0 | Worker | Errore transiente, errore esaurito isolato, tre batch falliti consecutivi e successo fra due errori. | Helper con al massimo un retry e nessun fallback modello. Errori isolati portano a `completed_with_errors`; tre consecutivi a `failed`; un successo azzera il contatore. | | GEN-07 | P0 | Integrazione | Successi seguiti da cancel, interruption o failure. | Ogni risultato valido è persistito subito e resta disponibile; il target fallito non riceve dati ambigui. “Generate Missing” consente il recupero naturale senza resume automatico. | | GEN-08 | P0 | API | Secondo run durante un run attivo e modifica/sync/cleanup/consolidamento sul database posseduto. | Un solo run di generazione attivo nell'installazione; 409 chiaro al secondo Start. Il database target resta riservato e le operazioni incompatibili non alterano selezione o dati. | | GEN-09 | P0 | Integrazione/Worker/UI | Stop in queued/running con helper attivo e con una `SELECT` sorgente deliberatamente bloccata/lenta. | Helper e query/connessione vengono terminati entro 5 secondi, stato `cancelled`, nessuna chiamata modello dopo Stop, risultati precedenti conservati ed eventi consultabili. Un test con sampler fake non è sufficiente. | | GEN-10 | P0 | Worker/API | Restart con run `queued/running`; Unlock con worker/helper vivo e con run realmente stale; race Unlock/Start. | Startup marca `interrupted` senza replay. Unlock è rifiutato se esiste lavoro locale vivo e non può liberare la reservation di un nuovo run. | | GEN-11 | P1 | Integrazione | Sorgente campioni indisponibile ma metadati validi. | La generazione prosegue metadata-only con un solo warning sicuro; nessun tentativo alternativo espone dettagli di connessione. | | GEN-12 | P1 | API/UI | SSE disconnesso, replay da sequence, polling concorrente e riapertura storico. | Eventi persistiti, ordinati e deduplicati; history newest-first; contatori/stato finali coerenti; nessun prompt, campione, risposta completa o stack trace. | | GEN-13 | P0 | UI/API | Revisione manuale della proposta e consolidamento selettivo di tabelle/colonne, inclusi target vuoti/stale. | Si copia solo `generatedDescription` non vuota dei target risolti; conteggio `copied/skipped` corretto; operazione atomica; `sourceComment` invariato. | | GEN-14 | P0 | UI/Sicurezza | Tutti gli scope di generazione da database, tabelle e colonne selezionate, utente senza permesso, metadata contenente istruzioni ostili. | Prima di ogni Start, inclusi `selected_tables` e `selected_columns`, compare la disclosure “fino a cinque righe e cinque valori”. Il server applica comunque l'autorizzazione. Metadati e valori sono trattati come dati non fidati e l'output resta nel contratto JSON. | | GEN-15 | P1 | L2 | Run reale sul modello di default e almeno un endpoint alternativo supportato, usando il database reale dopo la review dei flag e un role read-only. | Descrizioni nella lingua workspace, coerenti con struttura/commenti e senza fatti inventati critici; almeno una colonna e una tabella consolidate. Nessun valore protetto o segreto compare in request osservabile, eventi, API, persistenza o log. | | GEN-16 | P1 | Integrazione | Fastify→worker→processo Python→LiteLLM→endpoint OpenAI-compatible locale di cattura. | Routing provider/model, header API key, `disableThinking`, singolo retry, timeout, limite output e payload sono corretti; chiave e diagnostica non risalgono a stdout, API o log applicativi. | ## 10. Percorso E2E prioritario Il caso `E2E-01` deve attraversare le tre feature senza sostituire i test di contratto: 1. azzerare `catalog-db`, riapplicare le migrazioni e verificare che il role del database reale sia realmente read-only; 2. eseguire `all` sul database reale e confrontare catalogo e snapshot sorgente; usare la fixture A in una seconda esecuzione per gli edge case mancanti; 3. richiedere suggerimenti privacy sull'intero database; 4. modificare almeno una proposta e salvare i flag revisionati; 5. avviare `missing` dopo la disclosure, usando un Model Completer osservabile; 6. dimostrare che i canary protetti non sono letti né inviati e che il canary pubblico rispetta i limiti; 7. correggere una `generatedDescription` e consolidare una tabella e una colonna; 8. applicare la fixture B, controllare il piano distruttivo e introdurre C prima della conferma; 9. confermare dopo il re-scan e verificare atomicità, preservazione di descrizioni/flag delle entità superstiti e `sensitive=false` sulla nuova colonna; 10. perdere la connessione SSE, riaprire entrambi gli storici e verificare replay, polling e KPI. Il percorso deve essere eseguito con fake provider dopo ogni reset rilevante e, in forma ridotta, con il provider configurato sul database reale dopo che i flag sono stati revisionati e salvati. Non è richiesto ripristinare lo stato locale precedente al test. ## 11. Valutazione qualitativa dei modelli La qualità non deve confondersi con la sicurezza: anche un classifier perfetto non autorizza l'esfiltrazione di un canary protetto e una descrizione elegante non rende valido un payload ambiguo. ### 11.1 Classificazione privacy Per ogni modello registrare matrice di confusione, precisione, recall e falsi negativi per categoria. Eseguire almeno tre iterazioni sul corpus fisso per rilevare instabilità. Fino alla ratifica di una soglia da parte del product owner, il risultato è un gate di review: ogni falso negativo su credenziali/token, identificatori fiscali, dati sanitari o finanziari richiede accettazione esplicita o correzione prima del rilascio operativo. ### 11.2 Descrizioni generate Valutare ogni testo da 0 a 2 su: - correttezza rispetto a struttura e commenti sorgente; - specificità e utilità per un revisore; - lingua e chiarezza; - assenza di istruzioni seguite dai dati non fidati o fatti inventati; - assenza di valori protetti e segreti. Soglia proposta da ratificare: almeno 8/10, nessun punteggio 0 su correttezza o sicurezza. Un valore esplicitamente non sensibile, reale o inventato, può essere ripreso entro il perimetro dichiarato; un valore protetto non può mai esserlo. ## 12. Copertura esistente e gap da chiudere | Area | Evidenza automatica già presente | Gap principale | | --- | --- | --- | | Snapshot e sincronizzazione | `backend/test/catalog-schema-introspector.test.ts`, `catalog-schema-routes.test.ts`, `catalog-table-introspector.test.ts`, `catalog-repository.integration.test.ts` | Introspezione `pg_catalog` realmente end-to-end, parità live dei tre trasporti e un unico E2E con re-scan distruttivo. | | Privacy | `catalog-sensitivity-classifier.test.ts`, `catalog-sensitivity-value-source.test.ts`, `catalog-local-ner-detector.test.ts` e i test di route/review | Prova shadow PSD con report solo aggregato, corpus italiano etichettato e benchmark NER CPU post-ADR 0014. | | Generazione | `catalog-description-generation-routes.test.ts`, `catalog-description-generation-worker.test.ts`, `catalog-description-generation.integration.test.ts` e test del helper | Accettazione reale aggiornata, cancellazione di una query PostgreSQL bloccata e integrazione ermetica fino all'endpoint LiteLLM locale. | | UI | `DatabaseManagementPage.test.tsx`, `DescriptionGenerationDrawer.test.tsx`, `SensitivityAnalysisHistoryDrawer.test.tsx` | L'E2E Playwright corrente verifica soprattutto il layout, non il workflow funzionale. | Nuovi asset consigliati: - `backend/test/catalog-metadata-privacy-workflow.integration.test.ts`; - `backend/test/fixtures/catalog-privacy-schema.sql` e snapshot REST equivalenti; - integrazione a due PostgreSQL per le query reali `pg_catalog` e lo stop del sampler; - endpoint OpenAI-compatible locale di cattura per GEN-16; - `frontend/e2e/database-management-workflow.spec.ts`; - corpus strutturale versionato per PRV-12, senza valori business; - nuovo report di accettazione L2 che sostituisca il gate privacy del 2026-08-29. ## 13. Ordine di esecuzione ### Wave 1 — contratto rapido - parser snapshot, introspector, scope e diff; - classifier locale, validatori/checksum, copertura e fallback al campionamento; - sampler, valori sintetici, prompt bounds e parser descrizioni; - autorizzazione, redazione e race del coordinator. ### Wave 2 — integrazione PostgreSQL - reset totale di `catalog-db`, bootstrap delle migrazioni e ricostruzione dal database reale; - migrazioni e vincoli del repository; - atomicità sincronizzazione/consolidamento; - run ed eventi persistiti, restart e cancellation; - `E2E-01` con fake provider e canary. ### Wave 3 — frontend e stack locale - component test MSW; - Playwright funzionale, perdita SSE e polling; - verifica disclosure, review draft, history e consolidamento. ### Wave 4 — accettazione L2 - analisi shadow sul database collegato e review dei flag, senza scritture in sorgente; - benchmark NER CPU PRV-12 e rubric GEN-15 per la generazione descrizioni; - scansione finale di canary e segreti; - approvazione del product owner. Comandi di regressione: ```bash cd backend && npx vitest run cd backend && npx tsc --noEmit -p . cd backend && npm run build cd frontend && npx vitest run cd frontend && npx tsc -b cd frontend && npm run build cd frontend && npm run e2e ./scripts/build-docs.sh ``` Il report deve evidenziare esplicitamente eventuali test Docker/L2 saltati; un `skip` non equivale a PASS del relativo gate. ## 14. Evidenze da conservare Per ogni esecuzione registrare: - commit, configurazione pubblica dei modelli, versione fixture e trasporto; - ID e stato finale dei run, contatori ed eventi sanitizzati; - snapshot catalogo prima/dopo e piano distruttivo con metadati e conteggi sanitizzati, senza valori grezzi delle righe sorgente; - report test/JUnit, screenshot dei gate UI e risultato della scansione canary; - matrice di confusione privacy e rubric delle descrizioni per le prove L2; - difetti con ID del caso, severità, riproducibilità e decisione finale. Non allegare prompt completi, righe campione, output grezzi del provider, chiavi, digest o frammenti di segreti. Il Model Completer spy deve verificare in memoria le asserzioni e scartare il payload al termine del test. Non serve conservare backup del catalogo locale, run precedenti o descrizioni generate durante una wave: l'evidenza è il report sanitizzato e la capacità di ricostruire nuovamente il risultato dalla sorgente reale. ## 15. Criteri di ingresso e uscita ### Ingresso - baseline unica per backend/frontend e procedura verificata per eliminare e ricreare `catalog-db`; - accesso al database reale collegato e inventario delle tabelle/colonne da includere nella review; - fixture e canary supplementari approvati per i soli edge case controllati; - role sorgente read-only verificato con una scrittura deliberatamente negata; - fake connector/provider disponibili e log collection attiva; - per L2, secret reference configurato senza materializzare il valore nell'evidenza. ### Uscita - 100% dei casi P0 e P1 applicabili superati; nessun difetto Sev-1/Sev-2 aperto; - almeno due ricostruzioni complete e coerenti del catalogo a partire dal database reale dopo reset indipendenti; - zero comparsa dei canary protetti e dei segreti fuori dalla sorgente/secret store del test; - nessuna modifica parziale dopo errori, cancel o conferme stale; - parità normalizzata dei trasporti supportati e nessuna integration PostgreSQL richiesta saltata; - stati, contatori, eventi e history coerenti dopo success, partial failure, stop e restart; - `sourceComment`, `generatedDescription` e `description` mantengono l'autorità prevista; - accettazione L2 sul database collegato approvata dal product owner e build/test/typecheck/documentazione verdi; - ogni deviazione P2 o soglia qualitativa non ancora ratificata è documentata con owner e data. ## 16. Rischi residui da rendere espliciti - `sensitive=false` è il default e il prodotto non conserva uno stato “review completata”: dopo ogni reset, classificazione strutturale e salvataggio umano dei flag devono precedere qualunque run con provider reale. Il catalogo è ricostruibile; un invio errato a un provider non lo è. - Il flag protegge i valori campionati. Nomi, `sourceComment` e descrizioni sono metadati inviabili al modello e possono contenere testo libero: se nel database reale includono PII serve una decisione aggiuntiva di redazione, non una diversa aspettativa di test. - La UI deve risolvere il caso di un flag modificato ma non salvato prima della generazione e deve mostrare la disclosure anche per tabelle/colonne selezionate; il piano considera entrambi P0. - L'AbortSignal corrente va provato contro una query PostgreSQL realmente bloccata: la sola cancellazione del helper non dimostra che la lettura sorgente sia interrompibile. - Lo storico dei Sensitivity Analysis Run è operativo, non un audit delle decisioni umane. - La policy privacy non è ancora applicata allo schema-linking/LSH; nessun risultato di questo piano deve essere presentato come copertura di quel percorso. - Un provider reale resta non deterministico: il rilascio deve dipendere dai gate tecnici e dalla review umana, non dalla ripetizione byte-identica delle descrizioni. - L'esclusione fra operazioni e Unlock è in parte locale al processo. Se il deployment ammetterà più repliche backend, servirà un gate aggiuntivo con due istanze contro lo stesso catalogo; non va dedotta sicurezza multi-replica dai test single-process.