Files
ThothII/docs/research/2026-08-31-relationship-management-thothai-to-thothii.md
T
2026-09-15 14:37:29 +02:00

24 KiB

Gestione delle relationship: da ThothAI a ThothII

Stato: analisi e direzione funzionale/UX confermate nel grill with docs; non costituisce ancora un piano di implementazione. Revisione esaminata: commit ThothII f586152636b1fd653b0bc1d40b54be7f89bbd2bb. I sorgenti legacy di ThothAI citati sotto sono versionati nello stesso repository, sotto Thoth/ThothAI/. Ambito: import delle foreign key fisiche, inferenza di relationship logiche, modifica umana, pubblicazione verso il workflow NL→SQL e principali gap tra i due sistemi.

Sintesi fattuale

  1. In ThothAI le foreign key dichiarate nel database venivano importate e una procedura separata proponeva relationship basate sul nome dei campi e su una verifica dei valori. Entrambi i percorsi scrivevano però nello stesso modello Relationship (Thoth/ThothAI/backend/thoth_core/dbmanagement.py:484-640; Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:571-826; Thoth/ThothAI/backend/thoth_core/models.py:488-530).
  2. Il ricordo di una relationship inferita persistita come generated non trova riscontro nel modello esaminato: Relationship contiene solo quattro foreign key verso tabelle e colonne, senza provenienza, stato, confidenza o flag generated. La procedura di inferenza calcola localmente pattern e tasso di validazione, ma non li salva (Thoth/ThothAI/backend/thoth_core/models.py:488-530; Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:697-801).
  3. L'amministratore Django di ThothAI permetteva CRUD manuale sul medesimo insieme di relationship e verificava che gli endpoint appartenessero allo stesso database e alle tabelle selezionate; non distingueva visivamente o semanticamente relationship fisiche, inferite e manuali (Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:19-313).
  4. ThothII oggi separa già i due concetti: il catalogo backend conserva solo le foreign key fisiche dichiarate, mentre il core/harness possiede annotazioni di foreign key logiche curate e un comando che suggerisce candidate per nome, primary key e SQL osservato (CONTEXT.md:291-300; docs/adr/0006-separate-physical-and-logical-relationships.md:3-8; harness/tht/cli/schema_cmd.py:203-299).
  5. I due mondi ThothII non sono ancora integrati: i record di Database Management non modificano il workflow NL→SQL e l'integrazione catalogo→core/schema-linking è ancora un gate di design esplicitamente differito (PROJECT_STATE.md:116-124; PROJECT_STATE.md:161-165).

Evidenze ThothAI

Foreign key ufficiali

Il percorso di import legge le foreign key dal database, limita l'import alle tabelle già presenti nel catalogo, crea le colonne mancanti, crea o riusa un record Relationship e aggiorna anche le stringhe denormalizzate pk_field/fk_field sulle colonne (Thoth/ThothAI/backend/thoth_core/dbmanagement.py:484-640, in particolare :501-513, :526-591 e :606-607).

Fatto: una foreign key fisica diventa quindi un record applicativo, non rimane soltanto un fatto letto al momento dal database.

Relationship inferite

La routine legacy dichiara sei famiglie di confronto tra il nome della colonna candidata e quello della primary key: corrispondenza esatta, snake case, kebab case, camel case, concatenazione e solo nome tabella (Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:571-689).

Per una candidata, la routine:

  • legge fino a 20 valori distinti e non nulli dalla colonna candidata;
  • verifica ogni valore contro la primary key bersaglio;
  • accetta la candidata quando almeno il 70% dei valori esaminati trova riscontro;
  • crea o recupera un normale Relationship e aggiorna i campi denormalizzati delle tabelle (Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:697-801).

Correzione documentale: un commento parla di campionamento casuale, ma la query mostrata non contiene un ordinamento casuale; il comportamento verificabile dal codice è “fino a 20 valori distinti e non nulli”, non un campione statisticamente casuale (Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:697-716).

Rischio legacy: nomi di tabelle e colonne sono interpolati direttamente in SQL in questo percorso. Portare la logica letteralmente in ThothII riprodurrebbe un problema di quoting/sicurezza e richiederebbe inoltre una policy esplicita per l'accesso ai valori del DWH (Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:709-750).

Un solo modello per tre origini

Il modello Relationship legacy contiene soltanto source_table, target_table, source_column e target_column, più metodi di rappresentazione/aggiornamento. Non contiene campi per origine, algoritmo, evidenza, confidenza, approvazione o disabilitazione, né un vincolo di unicità dichiarato nel modello (Thoth/ThothAI/backend/thoth_core/models.py:488-530).

L'admin consente aggiunta, modifica e cancellazione ordinarie e valida la coerenza tra database, tabelle e colonne (Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:30-38, :125-157, :218-245).

Conclusione fattuale: relationship importate, inferite e create manualmente convergono nello stesso tipo persistito. Dal record finale non è possibile ricostruirne con certezza l'origine. Il termine generated, se usato nell'interfaccia o nel linguaggio operativo dell'epoca, non era una qualificazione persistita dal modello esaminato.

Uso nella comprensione dello schema

La generazione M-Schema conserva colonne PK/FK e ricostruisce la sezione 【Foreign keys】 analizzando le stringhe denormalizzate fk_field (Thoth/ThothAI/frontend/sql_generator/helpers/main_helpers/main_generate_mschema.py:25-61, :94-101, :160-187).

Fatto: le relationship curate nel catalogo legacy influenzavano la rappresentazione dello schema consumata dal generatore SQL, anche se tramite una proiezione denormalizzata.

Stato attuale di ThothII

Catalogo backend: relationship fisiche

Il modello di dominio corrente distingue esplicitamente:

  • Physical Relationship: vincolo dichiarato nel database;
  • Catalog Relationship: copia persistita di quel fatto fisico, non creabile o modificabile manualmente ma eliminabile per cleanup;
  • Logical Relationship: relazione semantica curata o inferita, con ownership e lifecycle separati (CONTEXT.md:291-300; docs/adr/0006-separate-physical-and-logical-relationships.md:3-8).

La migrazione del catalogo crea catalog_relationships e le coppie ordinate in catalog_relationship_columns. L'identità della relazione fisica è (source_table_id, constraint_name); non esistono campi di origine, stato o confidenza (backend/src/catalog/migrations/003_catalog_schema_sync.ts:42-74). Il tipo TypeScript è solo strutturale e le descrizioni generate riguardano esclusivamente tabelle o colonne (backend/src/catalog/types.ts:123-152).

L'introspezione PostgreSQL legge soltanto pg_constraint con contype = 'f' e mantiene l'ordine delle coppie per le chiavi composite (backend/src/catalog/schema-introspector.ts:110-171; backend/src/catalog/schema-introspector.ts:360-416; docs/contracts/catalog-schema-snapshot.md:40-53). La sincronizzazione autoritativa crea, aggiorna o elimina le copie fisiche in base allo snapshot (backend/src/catalog/repository.ts:1165-1218; backend/src/catalog/repository.ts:1316-1393).

Le API e la UI espongono lettura, sync e cleanup in massa, non CRUD logico per singola relationship (backend/src/routes/catalog-schema.ts:25-37; backend/src/routes/catalog-schema.ts:134-150; frontend/src/api/catalog-databases.ts:452-489; frontend/src/shell/database-management/DatabaseRelationships.tsx:114-123; frontend/src/shell/database-management/DatabaseRelationships.tsx:160-225).

Effetto rilevante: il cleanup è intenzionalmente reversibile tramite una sync successiva e può lasciare temporaneamente un catalogo incompleto (docs/adr/0008-allow-manual-catalog-metadata-cleanup.md:7-16). Un test di integrazione ammette anche una relationship rimasta senza coppie di colonne dopo la cancellazione delle colonne catalogate (backend/test/catalog-repository.integration.test.ts:194-264, in particolare :246-252). Un futuro consumer non può quindi assumere che ogni stato intermedio del catalogo sia pubblicabile così com'è.

Core/harness: relationship logiche e suggerimenti

Il modello M-Schema del core possiede già TableAnnotation.foreign_keys, descritte come foreign key logiche curate e unite alle foreign key fisiche (harness/tht/mschema/models.py:35-39; harness/tht/mschema/models.py:76-84). Il renderer fonde i due insiemi e li presenta insieme nella sezione 【Foreign keys】 (harness/tht/mschema/render.py:9-20; harness/tht/mschema/render.py:46-80).

Il comando schema suggest-fks costruisce candidate da:

  • uguaglianze trovate in SQL, quando esattamente un lato è una primary key;
  • convenzione speciale *time_key → dim_time.<PK singola>;
  • stesso nome tra colonna e primary key a proprietario univoco;
  • assunzioni esplicite per disambiguare;
  • esclusione di nomi PK generici come id, key e code e dei proprietari ambigui (harness/tht/cli/schema_cmd.py:203-299; harness/tht/mschema/fkmine.py:1-58).

Il comando può produrre un documento candidato e, con --write, aggiungere annotazioni mancanti in modo idempotente; il messaggio stesso chiede revisione manuale (harness/tht/cli/schema_cmd.py:406-429; harness/tht/cli/schema_cmd.py:519-540; harness/tests/test_schema_fk_annotations.py:131-146; harness/tests/test_schema_fk_annotations.py:454-470).

Fatto: ThothII non parte da zero sull'inferenza. Possiede già un motore più conservativo, basato su PK univoche e SQL osservato, ma non conserva per ogni relazione origine, evidenza, frequenza o confidenza. Il miner conta le occorrenze internamente, ma il modello candidato non promuove quel conteggio a lifecycle persistito (harness/tht/mschema/fkmine.py:1-58; harness/tht/mschema/models.py:35-39).

Rischi strutturali già visibili:

  • ForeignKey ammette liste di colonne, ma non valida che source e target abbiano la stessa cardinalità; il renderer usa zip, quindi una relazione malformata può essere troncata silenziosamente (harness/tht/mschema/models.py:35-39; harness/tht/mschema/render.py:76-78).
  • il merge evita duplicati rispetto alle FK fisiche iniziali, ma non aggiorna l'insieme seen dopo aver aggiunto un'annotazione; due annotazioni logiche uguali possono sopravvivere al merge (harness/tht/mschema/render.py:9-20).
  • il catalogo fisico supporta coppie composite ordinate, mentre le euristiche correnti e legacy sono sostanzialmente unary. La semantica delle candidate composite resta da decidere (backend/src/catalog/migrations/003_catalog_schema_sync.ts:56-74; harness/tht/cli/schema_cmd.py:203-299).

Revisione e pubblicazione correnti

Le annotazioni canoniche del workspace sono un blob Git. Il flusso pubblico produce candidate, verifica le annotazioni e richiede un'accettazione umana esplicita dopo commit/push/pull; l'accettazione registra digest del candidato e delle annotazioni, revisione e blob (docs/contracts/workspace-preprocessing-cli.md:89-106; backend/src/workspaces/preprocessing-service.ts:202-297). Il preprocessing successivo procede solo se il digest accettato coincide con le annotazioni correnti (backend/src/workspaces/preprocessing-service.ts:335-388).

Limite fattuale: il record di review conserva digest, revisione e blob, ma non attore, motivazione o decisioni per singola candidata (backend/src/workspaces/preprocessing-state.ts:67-73).

Il runtime usa le annotazioni quando renderizza lo schema, ma la ricerca vettoriale indicizza record di tabelle e colonne senza contenuto esplicito delle relationship (harness/tht/vectorstore/records.py:106-138; harness/tht/cli/search_cmd.py:233-273). Inoltre la vista colonne usata in F4 continua a leggere i commenti fisici, non le annotazioni (harness/tht/cli/schema_cmd.py:592-621).

La review dei join durante una sessione è distinta dalla curatela globale: il reviewer conferma l'insieme dei join oppure chiede una revisione completa; non modifica la mappa canonica delle relationship (harness/.pi/skills/tht-sessione/SKILL.md:301-341; frontend/src/widgets/JoinReviewWidget.tsx:5-84). Il modello di sessione registra join come due stringhe e una decisione opzionale, senza ID stabile della relationship o coppie ordinate strutturate (harness/tht/session/models.py:147-170).

Delta e rischi da sottoporre al grill

Tema Fatto documentato Delta/rischio aperto
Origine ThothAI perdeva l'origine; ThothII separa fisico e logico a livello concettuale Decidere quale provenienza debba essere persistita per manuale, euristica, SQL osservato e import fisico
generated Non era un flag del modello ThothAI; in ThothII “Generated Description” è già un termine del catalogo (CONTEXT.md:302-311) Usare generated anche per relationship potrebbe creare ambiguità terminologica
Cancellazione utente In ThothAI era CRUD sul record unico; in ThothII il cleanup fisico viene ricostruito dalla sync “Eliminare” può significare cancellare una relazione logica, sopprimere un fatto fisico per il core oppure pulire temporaneamente la copia catalogata: sono operazioni diverse
Inferenza ThothAI usava sei pattern e valori DWH; ThothII usa PK univoche, nomi e SQL osservato Stabilire se sostituire, integrare o non portare il campionamento dei valori; servono policy di dati sensibili, query read-only, quoting e limiti
Approvazione ThothII dispone di review Git/digest dell'intero artefatto Mancano decisioni per candidata, motivazione, attore, sticky rejection, versione algoritmo e gestione dello stale
Compositi Il catalogo fisico conserva coppie ordinate; le annotazioni accettano liste Mancano invarianti forti e una strategia di inferenza/review per join compositi
Pubblicazione Catalogo management e runtime core sono oggi separati Va stabilito se pubblicare tutto, una selezione esplicita o una revisione immutabile; il catalogo può essere incompleto durante cleanup/sync
UI e ownership UI catalogo fisico read-only; curatela logica via CLI/Git; review join per sessione separata Va scelto chi cura la mappa e in quale superficie, senza confondere amministrazione globale e correzione della singola sessione
Retrieval Le relationship entrano nel render M-Schema ma non nei record vettoriali Va deciso se e come influenzano selezione tabelle, ranking e descrizioni, oltre al rendering finale
Drift Sync fisica è autoritativa; annotazioni sono una revisione separata Servono semantiche per endpoint rinominati/eliminati, candidate stale, orphan e riapprovazione

Nota storica: questa analisi precede il passaggio PostgreSQL catalog-to-core. Per il contratto implementato usare architettura corrente e ADR 0016; le vecchie ipotesi di pubblicazione non sono istruzioni operative.

Direzione confermata nel grill

Il 31 agosto 2026 è stato concordato il seguente flusso minimo:

  1. Le foreign key dichiarate continuano a essere sincronizzate come Catalog Relationship fisiche.
  2. Le foreign key ipotetiche sono salvate una sola volta come Logical Relationship fra colonna sorgente e colonna destinazione; le tabelle sono ricavate dalle colonne e la UI le presenta nel relativo contesto tabella.
  3. Una relationship inferita è marcata generated; una relationship aggiunta dall'utente non lo è.
  4. La cancellazione logica conserva il record e impedisce a una ricostruzione di riattivarlo.
  5. La cancellazione fisica rimuove il record; una ricostruzione può ricrearlo se viene nuovamente inferito.
  6. La ricostruzione è additiva: conserva le relationship attive già presenti, non rimuove quelle non più inferibili e non modifica le relationship manuali.
  7. L'inferenza usa nomi, primary key e compatibilità dei tipi. Non campiona valori del DWH.
  8. La relationship è l'unica fonte di verità: non viene duplicata in stringhe fk_field sulle colonne.
  9. I nomi vengono confrontati senza distinzione fra maiuscole/minuscole e normalizzando snake case, kebab case e camel case. La regola riconosce anche casi come user_id → users.id, richiede tipi compatibili e una sola destinazione possibile; riconosce inoltre un nome PK non generico con un unico proprietario e la convenzione *time_key → dim_time.<PK singola>. Le colonne sorgenti possono appartenere a PK composite, mentre nomi generici isolati come id, key, code e pk non costituiscono evidenza. I casi ambigui vengono ignorati e non si usa fuzzy matching o un LLM.
  10. La ricostruzione parte da un'azione amministrativa esplicita Rebuild generated relationships, separata dalla sincronizzazione dello schema.
  11. Le relationship cancellate logicamente restano consultabili tramite filtro e possono essere riattivate con un'azione Restore.

Non restano decisioni di dominio aperte per il flusso minimo. La progettazione UX e il seam tecnico sono descritti nelle sezioni seguenti.

Lacuna UX emersa nel grill

La vista Fleet Relationships esiste, ma nella UI di produzione non ha oggi un punto di ingresso raggiungibile. La riga del database espone sincronizzazione, tabelle, dettagli, modifica e rimozione, ma non le relationship; inoltre i tab Overview / Tables / Relationships appartengono soltanto alla presentazione legacy. Il test di navigazione esistente esercita anch'esso la presentazione legacy, non quella Fleet (frontend/src/shell/database-management/DatabaseGrid.tsx:122-180; frontend/src/shell/database-management/DatabaseForm.tsx:442-475; frontend/src/shell/database-management/DatabaseForm.tsx:517-546; frontend/src/shell/DatabaseManagementPage.test.tsx:2635-2703).

Se aperta programmaticamente, la vista mostra soltanto Physical relationships in sola lettura. La toolbar contiene ricerca, conteggio, Refresh, un selettore azione con esecuzione esplicita e Sync history; la griglia offre unicamente Details, che apre il drawer della relationship fisica. Sono assenti ingresso visibile, aggiunta manuale, ricostruzione delle generated relationship, origine, stato attivo/cancellato, cancellazione logica, cancellazione fisica e ripristino (frontend/src/shell/database-management/DatabaseRelationships.tsx:114-225).

I pattern Fleet già consolidati da riutilizzare sono:

  • una sola griglia nel livello corrente, con breadcrumb e controllo Back;
  • azioni di pagina nel selettore Choose an action… con Run action e motivo visibile quando indisponibili;
  • azioni della singola riga nella colonna finale fissata a destra;
  • form e dettagli in un drawer modeless che restituisce il focus al controllo di origine;
  • conferme distruttive inline o nel drawer, non tramite una nuova pagina;
  • toast per accettazione o errore e feedback persistente soltanto per le operazioni lunghe;
  • card di errore con Retry ed empty state che indica la prossima azione possibile.

ThothAI non offre un modello UX da copiare. L'inferenza è nascosta fra 21 bulk action della lista database, non mostra avanzamento in tempo reale e restituisce soltanto messaggi a fine richiesta. Il CRUD manuale vive in un'altra schermata Django Admin, non distingue origine o stato, offre soltanto la cancellazione fisica e il form di aggiunta ha una validazione server strutturalmente incoerente con le select popolate dal browser (Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:252-274; Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:58-119; Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:218-313).

UX confermata

Il 31 agosto 2026 sono state confermate le seguenti scelte:

  1. La colonna Actions della riga database espone un accesso diretto Relationships, accanto a Tables. La vista conserva breadcrumb e controllo Back to databases esistenti.
  2. La pagina presenta una sola griglia Relationship map, contenente relationship Physical, Generated e Manual. Le colonne sono Source table, Source column, Target table, Target column, Origin, Status e Actions. Constraint e regole update/delete rimangono nel drawer delle FK fisiche.
  3. Un filtro visibile seleziona Active, Excluded o All; il valore predefinito è Active. Le FK fisiche sono consultabili ma non modificabili da questa vista.
  4. Add relationship è il pulsante primario visibile nella toolbar. Apre il drawer standard con i quattro campi Source table, Source column, Target table e Target column e i comandi Cancel e Add relationship. Il flusso minimo gestisce una sola coppia di colonne e mostra la validazione accanto al campo interessato.
  5. Rebuild generated relationships entra nell'attuale selettore Choose an action…, insieme a Synchronize physical relationships e Synchronize full schema, con esecuzione esplicita tramite Run action. Refresh ricarica la griglia; Sync history resta riservato alla sincronizzazione fisica.
  6. Il drawer di dettaglio contiene le azioni sulle relationship logiche. Exclude realizza la cancellazione logica, Delete permanently quella fisica e Restore riattiva una relationship esclusa. La conferma avviene nel drawer e spiega rispettivamente che la ricostruzione non riattiverà un record escluso e potrà invece ricreare un record eliminato definitivamente.
  7. La ricostruzione non introduce un nuovo sistema di job o di storico. Durante l'esecuzione mostra Rebuilding…; al termine un toast riporta added, already present, excluded e ambiguous. Add, Exclude, Delete permanently e Restore producono toast specifici; gli errori mantengono aperto il contesto corrente. L'empty state propone di ricostruire dai nomi o aggiungere manualmente.
  8. L'inferenza non usa AI. È codice deterministico nel backend del catalogo, basato su normalizzazione dei nomi, primary key/unicità, compatibilità dei tipi e assenza di ambiguità. Non usa LLM, embedding o campionamento dei dati. L'AI consuma la mappa risultante per comprendere lo schema, ma non la costruisce.

Seam tecnico confermato

Il Catalog PostgreSQL è l'unica fonte modificabile delle Logical Relationship. Le relationship fisiche e logiche restano in modelli distinti, coerentemente con ADR-0006, mentre un servizio profondo espone a API e UI una sola mappa discriminata per origine e stato.

All'avvio o alla ripresa di una sessione, il backend materializza le sole relationship attive in una snapshot JSON canonica e immutabile, collegata alla stessa lease della configurazione runtime. La snapshot comprende anche le FK fisiche e conserva l'ordine delle coppie composite. Se due record hanno gli stessi endpoint, la FK fisica ha precedenza.

Quando la snapshot è presente, l'harness la usa come fonte esclusiva delle relationship e continua a leggere dalle annotazioni Git-pinned soltanto descrizioni, sinonimi, concetti e altri metadati. Non scrive annotations.yaml e non interroga direttamente il Catalog. Una snapshot dichiarata ma assente, invalida o incoerente con lo schema fisico fallisce esplicitamente; un runtime legacy che non dichiara la snapshot mantiene il precedente comportamento di compatibilità.

Questa proiezione non è un secondo store: non può essere modificata, viene eliminata insieme alla configurazione runtime e una sessione Pi vede una mappa stabile per tutta la propria vita. La decisione duratura è registrata in ADR-0012.

La proiezione e la ricostruzione sono ammesse soltanto dopo una sincronizzazione completa della versione corrente del database e vengono serializzate con le mutazioni del catalogo. Un catalogo mai sincronizzato, reso stale da una modifica della configurazione o invalidato da metadata cleanup non può quindi diventare accidentalmente la fonte esclusiva del runtime. Il cleanup esplicito di una tabella o colonna endpoint è anche il confine distruttivo del tombstone: rimuove definitivamente la relationship esclusa, perché conservarla richiederebbe una seconda identità testuale denormalizzata.