feat: implement metadata catalog database management

This commit is contained in:
Codex
2026-08-27 22:43:54 +02:00
parent 705af3aeb2
commit 79c4c925b5
86 changed files with 12566 additions and 135 deletions
+10 -5
View File
@@ -2,10 +2,12 @@
ThothII userà un Metadata Catalog PostgreSQL interno per conservare, per ogni workspace, la
struttura fisica acquisita interrogando il relativo database e i metadati semantici generati con
l'AI. Ogni workspace avrà un solo Workspace Database; identità e lista dei workspace resteranno
autorevoli in `thoth-workspaces.yaml`, mentre il catalogo ne conserverà soltanto il riferimento
stabile. `schema/annotations.yaml` verrà sostituito come input del core in uno step successivo;
l'interfaccia e il lifecycle amministrativi resteranno separati dal workflow NL→SQL.
l'AI. Ogni Workspace Database conserverà un `workspace_id` obbligatorio e univoco: questo realizza
l'associazione uno-a-uno senza introdurre nel catalogo una tabella Workspace o una foreign key SQL.
Identità e lista dei workspace resteranno autorevoli in `thoth-workspaces.yaml`; il servizio
validerà il riferimento contro quel catalogo. `schema/annotations.yaml` verrà sostituito come input
del core in uno step successivo; l'interfaccia e il lifecycle amministrativi resteranno separati dal
workflow NL→SQL.
## Considered Options
@@ -18,4 +20,7 @@ l'interfaccia e il lifecycle amministrativi resteranno separati dal workflow NL
PSD importerà le annotations esistenti; gli altri workspace genereranno i metadati da zero. Il
cutover futuro dovrà sostituire consapevolmente i consumatori delle annotations e verificarne
l'equivalenza semantica. Le sessioni di test esistenti non sono un vincolo di migrazione.
l'equivalenza semantica. PostgreSQL può garantire che uno stesso `workspace_id` non sia assegnato a
due database, ma l'esistenza del workspace e la gestione di rename o rimozioni restano responsabilità
del confine applicativo con il catalogo YAML. Le sessioni di test esistenti non sono un vincolo di
migrazione.
@@ -0,0 +1,19 @@
# Riferimenti al secret store per i Workspace Database
Il Metadata Catalog non conserverà credenziali o chiavi dei Workspace Database. Riuserà il secret
store cifrato già posseduto da ThothII e conserverà soltanto riferimenti ai requisiti segreti del
workspace, evitando un secondo vault e impedendo che API, esportazioni o log espongano i valori.
## Considered Options
- Copiare i campi testuali del modello ThothAI avrebbe semplificato il CRUD, ma avrebbe conservato
password e passphrase in chiaro.
- Introdurre subito un secondo vault avrebbe separato il catalogo dal runtime workspace, ma avrebbe
duplicato cifratura, rotazione, autorizzazioni e procedure operative senza un'esigenza distinta.
## Consequences
Il catalogo e il runtime condividono l'identità dei requisiti segreti, mentre permessi e API del
catalogo restano separati. Sostituzione e cancellazione di un Workspace Database dovranno definire
esplicitamente il lifecycle dei relativi riferimenti senza includere i valori nelle transazioni del
catalogo PostgreSQL.
@@ -0,0 +1,26 @@
# Binding di database specifiche dell'installazione
Il Metadata Catalog separa il Workspace Database logico dalla Database Binding che lo rende
raggiungibile in una specifica installazione. Esiste un solo Workspace Database per `workspace_id`
e una sola binding attiva nel catalogo di ciascuna installazione; per esempio PSD usa REST in locale
e PostgreSQL diretto sul server senza diventare due database distinti.
Nel modello finale il catalogo è autorevole per engine, nome fisico, schema, capability e binding,
mentre `thoth-workspaces.yaml` conserva l'identità del workspace. Gli attuali campi DWH dei
descriptor sono una sorgente di bootstrap da importare e confrontare durante un cutover esplicito,
non una seconda fonte di verità permanente.
## Considered Options
- Conservare un record database per ogni trasporto avrebbe duplicato identità, struttura e
metadati dello stesso DWH fra locale e server.
- Conservare permanentemente i dati DWH sia nello YAML sia nel catalogo avrebbe introdotto
conflitti non risolvibili deterministicamente.
- Rendere globali le binding avrebbe mescolato endpoint e credenziali che appartengono a
installazioni con topologie e confini di sicurezza differenti.
## Consequences
La lista amministrativa unisce workspace YAML, database configurati e record orphaned. Il cutover
deve importare e confrontare la configurazione esistente prima di rimuoverla dai descriptor; il
runtime non deve osservare simultaneamente due autorità discordanti.
@@ -0,0 +1,22 @@
# Metadata Catalog nello stesso backend con Kysely
Il Metadata Catalog vive nello stesso processo Fastify come modulo isolato, invece di introdurre un
microservizio. Usa il driver `pg` già presente attraverso Kysely per query, transazioni e migrazioni
tipizzate; route, repository, service, readiness e diagnostica restano separati dal workflow e
l'indisponibilità del catalogo non rende indisponibili sessioni o SSE.
## Considered Options
- Un microservizio avrebbe conservato letteralmente il backend bridge senza database, ma avrebbe
aggiunto deployment, autenticazione e failure mode per un solo contesto amministrativo.
- Usare soltanto `pg` avrebbe evitato una dipendenza, ma avrebbe richiesto infrastruttura locale per
transazioni, tipi delle righe, ordinamento e locking delle migrazioni.
- Drizzle o Prisma avrebbero aggiunto schema DSL, generatori e toolchain non necessari a un servizio
che vuole mantenere SQL e constraint PostgreSQL espliciti.
## Consequences
Il backend possiede una connection pool del catalogo e la chiude con il lifecycle Fastify. Le
migrazioni timestampate sono compilate insieme al backend ma vengono eseguite soltanto da
`catalog:migrate`, con credenziali migrator separate dal ruolo DML usato a runtime. Il modulo resta
dietro un'interfaccia repository per mantenere unit test e route test indipendenti da PostgreSQL.
@@ -0,0 +1,27 @@
# Hard-delete catalog tables during synchronization
An explicit Table Synchronization makes the Catalog Table membership exactly match a successful
observation of the Workspace Database: new tables are created, source metadata is refreshed, and
absent tables plus their future column and relationship children are permanently deleted. Physical
membership cannot be edited manually.
The external scan runs without holding a catalog transaction. Its diff is applied atomically only
while the Workspace Database version still matches the scanned binding. Failed scans change
nothing, and a non-empty removal set must exactly match the names confirmed by the operator; a
changed second scan therefore requires a new confirmation.
## Considered Options
- Soft deletion would preserve descriptions across accidental removals, but would add hidden state,
restore rules, and ambiguity about whether the catalog still represents the physical schema.
- Rename detection based on similarity would preserve metadata in some cases, but could silently
attach curated semantics to the wrong physical table.
- Append-only introspection, as in the legacy importer, would leave stale tables in the catalog and
make downstream schema linking unreliable.
## Consequences
A physical rename is delete plus create and loses curated metadata. The UI previews permanent
deletions, and future Catalog Column and Relationship records must cascade with their table. The
catalog remains an exact projection of the last accepted successful scan without tombstones or
restore lifecycle.
@@ -0,0 +1,8 @@
# Separate physical and logical relationships
ThothII persists each database-declared foreign-key constraint as an immutable Catalog
Relationship with ordered column pairs, so composite keys retain their identity and the database
remains the authority for physical structure. Curated or AI-inferred Logical Relationships will
use a separate future model and lifecycle rather than being mixed with physical constraints or
denormalized into textual column fields; this keeps synchronization authoritative without
preventing later semantic enrichment.
@@ -0,0 +1,9 @@
# Use durable runs for authoritative schema synchronization
All table, column, relationship, and full-schema synchronizations run as durable background
Catalog Sync Runs rather than separate synchronous and asynchronous implementations. Each scope
is authoritative within its boundary, while Synchronize All observes one complete schema snapshot;
destructive diffs require confirmation and source revalidation before an atomic, fail-closed
catalog transaction. A persistent per-database lock, progress events, interruption handling, and
binding-version freshness make long operations observable and prevent two processes or stale
configuration from producing a partially trusted catalog.
+7
View File
@@ -0,0 +1,7 @@
# Domain Docs
This is a single-context repository.
Before exploring, read `CONTEXT.md` at the repository root and the relevant decisions in
`docs/adr/`. Use the project's terminology from `CONTEXT.md` in issue titles, proposals, and
tests. Surface conflicts with an ADR instead of silently overriding it.
+28
View File
@@ -0,0 +1,28 @@
# Issue tracker: Gitea
Issues and specs for this repository live in the self-hosted Gitea repository:
`https://git.tylconsulting.it/mptyl/ThothII`.
## Conventions
- **Create an issue**: use the repository's Gitea web UI, or the Gitea REST API when an
authenticated token with issue scope is available.
- **Read and list issues**: use the Gitea web UI or authenticated API; include labels and comments.
- **Apply or remove labels**: use the issue's label controls or the Gitea API.
- **Comment and close**: use the issue page or the Gitea API.
- Do not use `gh issue ...` for this repository: the `github` remote is a mirror, not the canonical
issue tracker.
## Repository identity
- Canonical Git remote: `origin` → `https://git.tylconsulting.it/mptyl/ThothII.git`
- GitHub mirror: `github` → `https://github.com/mptyl/ThothII.git`
- Canonical issue URL: `https://git.tylconsulting.it/mptyl/ThothII/issues`
## When a skill says “publish to the issue tracker”
Create an issue in the canonical Gitea repository.
## When a skill says “fetch the relevant ticket”
Read the referenced issue in the canonical Gitea repository.
+19
View File
@@ -0,0 +1,19 @@
# Triage Labels
The skills speak in terms of five canonical triage roles. This file maps those roles to the labels
used in the canonical Gitea issue tracker.
| Label in mattpocock/skills | Label in Gitea | Meaning |
| --- | --- | --- |
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
| `needs-info` | `needs-info` | Waiting on reporter for more information |
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for AFK agent work |
| `ready-for-human` | `ready-for-human` | Requires human implementation |
| `wontfix` | `wontfix` | Will not be actioned |
Issue type labels:
| Label in Gitea | Meaning |
| --- | --- |
| `bug` | Something is not working |
| `enhancement` | New feature or request |
+7 -2
View File
@@ -4,7 +4,10 @@ This page complements the [architecture overview](overview.md) with the module s
## Modules and dependencies
The frontend communicates with the backend through REST and SSE. The backend does not own session persistence: it starts Pi, invokes the `tht` CLI, and forwards events. The harness contains the workflow, the Python CLI, and adapters for the DWH and vector store.
The frontend communicates with the backend through REST and SSE. The backend does not own session
persistence: it starts Pi, invokes the `tht` CLI, and forwards events. It does own the separate
installation-local database catalog. The harness contains the workflow, the Python CLI, and
adapters for the DWH and vector store.
```mermaid
flowchart LR
@@ -18,6 +21,8 @@ flowchart LR
THT --> DWH["DWH\nread-only"]
THT --> VDB["Qdrant / vector store"]
BE --> CFG["settings.json\nworkspace registry"]
BE --> CAT["catalog-db\nPostgreSQL + Kysely"]
BE -->|catalog Test + Table Sync| DWH
FE -.->|renders widgets| EXT
```
@@ -26,7 +31,7 @@ Dipendenze principali:
| Module | Depends on | Responsibility |
| --- | --- | --- |
| `frontend/` | Backend REST and SSE APIs | UI, gate widgets, and in-memory transcript |
| `backend/src/` | Pi, `tht`, configuration, and workspace registry | Transport, session lifecycle, and APIs |
| `backend/src/` | Pi, `tht`, configuration, workspace registry, catalog PostgreSQL, and read-only DWH connectors | Transport, session lifecycle, catalog CRUD, connection tests, table introspection, and APIs |
| `harness/.pi/` | Pi and `tht phase` | Workflow orchestration and human-in-the-loop gates |
| `harness/tht/` | Filesystem, DWH, and vector store | Persistence, CLI, Evidence, schema, and preprocessing |
| workspace repository | `source/`, `curated/`, manifest, and artifacts | Versioned Evidence source and session output |
+13 -4
View File
@@ -11,6 +11,7 @@ For sessions, roles, groups, diagnostics, and recovery, see the [authentication
flowchart LR
USER["Reviewer"] --> FE["Frontend\nReact and SSE"]
FE --> BE["Backend\nFastify"]
BE --> CATALOG["Metadata catalog\nPostgreSQL"]
BE --> PI["Pi\nRPC per sessione"]
PI --> THT["tht and harness\nworkflow and persistence"]
THT --> DWH["DWH\nread only"]
@@ -27,8 +28,8 @@ frontend (React/SSE) → backend (Fastify) → pi --mode rpc → tht/harness →
| Layer | Stack | Ruolo |
|---|---|---|
| **harness/** | Python (`tht` CLI) + Pi gate extension (JS) | Owns the workflow and **all** persistence |
| **backend/** | Fastify + TypeScript | Thin bridge with no database of its own |
| **harness/** | Python (`tht` CLI) + Pi gate extension (JS) | Owns the workflow and all session persistence |
| **backend/** | Fastify + TypeScript + Kysely | Session bridge plus the isolated administrative metadata catalog |
| **frontend/** | React 18 + Vite | UI that renders gate widgets and rebuilds the live transcript from the SSE stream |
## The harness owns the workflow
@@ -39,14 +40,22 @@ frontend (React/SSE) → backend (Fastify) → pi --mode rpc → tht/harness →
A session is a directory under `sessions/` (the workspace defines the path): `session_manifest.yaml`, phase artifacts (`question.md`, `schema_linking.json`, `sql_final.sql`, and others), and `review_decisions.jsonl`. The contract says: *"persisted state is the truth; what is not recorded did not happen"*. There is no verbatim transcript store. A resumed Pi process rebuilds context from `tht session show <id>` and the artifacts on disk.
## The backend is a thin bridge with no database
## The backend bridges sessions and owns the metadata catalog
- `ThtRunner` runs `tht` subcommands in a shell.
- `PiProcessManager` runs one Pi child process per session and bridges its RPC stream.
- `SessionBridge` maps Pi RPC events to client events (`ui_request` / `text_delta` / `info`).
- `SseHub` distributes these events to the browser over SSE.
- `CatalogService` joins authoritative YAML workspace identities with installation-local database
configurations stored in PostgreSQL through Kysely.
- `CatalogTableService` reconciles persisted Catalog Tables with a successful external schema scan;
`ConcreteCatalogTableIntrospector` isolates direct PostgreSQL, typed REST, and SSH-tunnel access.
Application settings live in a JSON file (`backend/data/settings.json`), not in a database.
Application settings remain in `backend/data/settings.json`; session state remains in harness phase
documents. PostgreSQL stores only the administrative database catalog, bindings, observed tables,
and curated descriptions. Connector secrets remain write-only in the encrypted workspace secret
store. Catalog SSH support is limited to connection tests and table synchronization; it does not
change the session runtime binding contract.
## Human-in-the-loop gate contract
+88
View File
@@ -0,0 +1,88 @@
# Catalog schema snapshot RPC
Il percorso preferito per un binding `rest_api` espone al catalogo un'unica fotografia tipizzata
dello schema:
```http
POST /rpc/schema_snapshot
Content-Type: application/json
{"schema_name":"datawarehouse"}
```
La risposta è un oggetto JSON con `schemaVersion: 1`, capability esplicite e tre collezioni. Una
capability non disponibile deve essere dichiarata `unavailable`: non deve essere simulata con una
lista vuota.
```json
{
"schemaVersion": 1,
"capabilities": {
"tables": "available",
"columns": "available",
"relationships": "available"
},
"tables": [
{ "name": "patients", "sourceComment": "Clinical patients" }
],
"columns": [
{
"tableName": "patients",
"name": "id",
"ordinalPosition": 1,
"dataType": "bigint",
"isNullable": false,
"defaultExpression": null,
"primaryKeyPosition": 1,
"sourceComment": "Patient identifier"
}
],
"relationships": [
{
"constraintName": "visits_patient_id_fkey",
"sourceTableName": "visits",
"targetTableName": "patients",
"updateRule": "NO ACTION",
"deleteRule": "CASCADE",
"deferrable": false,
"initiallyDeferred": false,
"columns": [
{ "position": 1, "sourceColumnName": "patient_id", "targetColumnName": "id" }
]
}
]
}
```
## Fallback compatibile tramite `run_query`
Se e soltanto se il server non espone `POST /rpc/schema_snapshot`, il catalogo può ottenere la
stessa fotografia mediante una singola istruzione read-only inviata all'RPC già esistente:
```http
POST /rpc/run_query
Content-Type: application/json
{"query_text":"WITH ... SELECT ..."}
```
La query è costruita dal catalogo, interroga soltanto il catalogo PostgreSQL dello schema
configurato e aggrega tabelle, colonne, primary key e foreign key nella stessa istruzione. Non sono
ammessi più round trip, query per tabella o assemblaggi client-side di osservazioni effettuate in
momenti diversi. Il nome schema deve essere validato come identificatore e quotato come valore SQL,
non interpolato come SQL libero.
`run_query` restituisce un array JSON di righe. Per questo fallback l'array deve contenere
esattamente una riga e quella riga deve essere esattamente l'oggetto snapshot v1 sopra descritto,
con `schemaVersion`, `capabilities`, `tables`, `columns` e `relationships`; campi mancanti,
aggiuntivi o di tipo diverso rendono invalida l'intera fotografia. La risposta non è un contratto
alternativo o più permissivo: cambia soltanto il trasporto della stessa snapshot stretta.
`position` e `primaryKeyPosition` sono uno-based. Le coppie ordinate permettono foreign key
composte. Il catalogo rifiuta l'intera fotografia se il JSON non rispetta il contratto o se la
capability richiesta dal tipo di sincronizzazione è `unavailable`; in entrambi i casi non applica
alcuna modifica. Il fallback viene tentato soltanto quando l'RPC preferito risulta assente, non per
nascondere una snapshot malformata o un errore operativo del server. Se anche `run_query` non è
disponibile, la query viene rifiutata, la risposta non contiene una singola snapshot v1 valida o una
capability richiesta è `unavailable`, il run fallisce senza aggiornamenti parziali e senza esporre
il corpo remoto.
+4 -2
View File
@@ -92,8 +92,10 @@ evidence:
Changes from older workspaces:
- the database is reached only through **REST** or **direct Postgres** (`rest_api` /
`postgres_direct`); the SSH tunnel remains disabled;
- NL→SQL sessions reach the database only through **REST** or **direct Postgres** (`rest_api` /
`postgres_direct`); `ssh_tunnel` remains disabled for session runtime. The separate Database
management surface supports SSH for **Test connection** and **Sync tables**, with a private key,
mandatory `known_hosts`, and an optional key passphrase;
- the semantic index is **internal** (Qdrant plus `qwen3-embedding:0.6b`, 1024 dimensions, cosine);
- **filesystem** Evidence lives in the repository (`<id>/evidence`) and is materialized from the
pinned Git commit (P6). HTTP Evidence is also supported.
@@ -1,8 +1,9 @@
# Metadata Catalog di ThothII: ricognizione ThothAI e percorso incrementale
Data: 2026-08-26
Stato: ricognizione completata; step 1 implementato; scelte tecnologiche degli step successivi
deliberatamente rinviate.
Data: 2026-08-26; aggiornato 2026-08-27
Stato: ricognizione e progettazione completate; navigazione, CRUD Workspace Database, Catalog
Table, Catalog Column, Catalog Relationship e sincronizzazione durevole dello schema implementati
il 2026-08-27. Generazione AI e integrazione con il workflow core restano negli step successivi.
## Obiettivo
@@ -10,9 +11,11 @@ ThothII deve introdurre un contesto amministrativo separato, il **Metadata Catal
il database associato a ciascun workspace, la sua struttura fisica introspezionata e i metadati
semantici oggi rappresentati da `schema/annotations.yaml`.
Il programma procede per step indipendenti. Il primo step aggiunge soltanto l'accesso dalla sidebar
destra a una superficie centrale vuota. Non introduce PostgreSQL, API CRUD, introspezione o
integrazioni con il workflow core.
Il programma procede per step indipendenti. Il primo step ha aggiunto l'accesso dalla sidebar; il
secondo ha sostituito la superficie vuota con il CRUD di configurazione, il PostgreSQL interno e i
test di connessione; gli step successivi hanno aggiunto navigazione gerarchica, colonne, relazioni
fisiche e sincronizzazione durevole dell'intero schema. Non introduce ancora generazione AI o
integrazione con il workflow core.
Questa analisi usa come riferimento il working tree legacy osservato in
`Thoth/ThothAI`. Non è stato verificato che quel contenuto corrisponda a una release o a un tag
@@ -20,8 +23,10 @@ canonico; i percorsi e i comportamenti descrivono il sorgente disponibile il 202
## Decisioni già confermate
1. Ogni workspace è associato a un solo Workspace Database e ogni Workspace Database appartiene a
un solo workspace.
1. Ogni Workspace Database appartiene a un solo workspace tramite un `workspace_id` obbligatorio e
univoco; un workspace può avere al massimo un Workspace Database. Poiché i workspace non sono
righe del catalogo PostgreSQL, l'associazione è un riferimento logico validato contro
`thoth-workspaces.yaml`, non una foreign key SQL.
2. Il CRUD non crea né rinomina workspace. Identità e lista ordinata dei workspace restano
autorevoli in `thoth-workspaces.yaml`; il catalogo conserva il loro identificatore stabile.
3. La struttura fisica viene acquisita interrogando il database esterno tramite i dati di
@@ -38,6 +43,146 @@ canonico; i percorsi e i comportamenti descrivono il sorgente disponibile il 202
introduce un router.
9. La pagina iniziale è vuota, segue il tema, nasconde l'intera colonna core e non interrompe una
sessione live. Le azioni di apertura, resume o creazione sessione riportano al core.
10. La compatibilità con il modello ThothAI è semantica, non una copia letterale: configurazione e
contenuti semantici sono campi relazionali mutabili, mentre identità e appartenenza della
struttura fisica derivano dall'introspezione; i segreti restano nel secret store e lo stato dei
job non viene mescolato ai dati amministrativi.
11. Il CRUD amministra il Metadata Catalog e non esegue DDL sul database esterno, che resta
read-only.
12. La prima versione supporta PostgreSQL; il confine di introspezione dovrà permettere di
aggiungere altri dialetti senza cambiare il modello del catalogo.
13. I segreti dei Workspace Database riusano il secret store cifrato di ThothII. Il catalogo
conserva riferimenti ai segreti e nessuna API, esportazione o log ne restituisce i valori.
14. La UI usa AG Grid Community per la lista master e un pannello React separato per il dettaglio;
non dipende dalle funzionalità master-detail di AG Grid Enterprise.
15. Un Workspace Database il cui `workspace_id` scompare dal catalogo YAML non viene cancellato
automaticamente: diventa orphaned e può soltanto essere recuperato, riassegnato o eliminato
esplicitamente da un amministratore.
16. La prima vertical slice gestisce configurazione del Workspace Database, riferimenti ai segreti,
test di connessione e stato. La seconda gestisce le Catalog Table: la collezione e i nomi sono
controllati dall'introspezione, mentre la descrizione curata è modificabile. Le slice successive
hanno aggiunto Catalog Column, Catalog Relationship e sincronizzazione durevole dello schema.
17. Il modello non conserva il `name` libero di ThothAI: nome e ID visualizzati appartengono al
workspace YAML, mentre `database_name` identifica il database PostgreSQL esterno.
18. Database management supporta i tre trasporti già riconosciuti da ThothII: `postgres_direct`,
`rest_api` e `ssh_tunnel`. PSD rimane un solo Workspace Database: usa la connessione diretta sul
server e l'endpoint REST in locale tramite una Database Binding specifica dell'installazione.
Questo supporto non abilita automaticamente `ssh_tunnel` nel runtime NL→SQL.
19. Una configurazione può essere salvata prima di una connessione riuscita. Il test separato
produce uno stato `untested`, `reachable` o `failed`; attivazione e introspezione richiedono uno
stato raggiungibile.
20. Il CRUD e il test di connessione richiedono `database.manage`; inserimento e sostituzione dei
segreti continuano a richiedere `workspace.secrets.manage`.
21. Il Workspace Database e il modo di raggiungerlo sono entità distinte. Ogni catalogo di
installazione conserva una sola Database Binding attiva per workspace: PSD usa `rest_api` in
locale e `postgres_direct` sul server senza duplicare il Workspace Database.
22. Nel modello finale il Metadata Catalog è autorevole per engine, `database_name`, schema,
capacità e binding. Lo YAML resta autorevole per identità e contenuti del workspace; i campi
DWH correnti saranno importati, confrontati e rimossi soltanto durante un cutover esplicito.
23. La lista master è l'unione fra workspace YAML e record del catalogo: mostra workspace
`unconfigured`, database configurati e record `orphaned`.
24. Ogni introspezione registra le capability disponibili. Una capability `unavailable` non viene
rappresentata come una collezione osservata ma vuota; REST può completare con successo anche
quando indici o enum non sono supportati.
25. Il Metadata Catalog non introduce snapshot, draft o pubblicazioni. Configurazione e contenuti
semantici, inclusi quelli futuri generati dall'AI, sono normali campi modificabili; la struttura
osservata cambia soltanto con una sincronizzazione esplicita.
26. Il normale Delete elimina realmente il Workspace Database, la Database Binding e i relativi
record catalogo e segreti. Non modifica il DWH esterno né il repository YAML; il workspace torna
visibile nella lista master come `unconfigured`.
27. La prima versione gestisce un solo schema obbligatorio per Workspace Database, identificato
dalla coppia `database_name + schema`; per PSD la coppia è `postgres + datawarehouse`.
28. I record mantengono soltanto `created_at`, `updated_at` e un contatore `version` per optimistic
concurrency. Non esistono storico delle revisioni, rollback o audit applicativo delle modifiche.
29. `workspace_databases` conserva soltanto UUID, `workspace_id` unique, engine, `database_name`,
schema, timestamp e version. Il nome visualizzato appartiene al workspace YAML.
30. Ogni Workspace Database ha al massimo una riga `database_bindings`. Una singola tabella usa
check constraint dipendenti da `transport` per i campi direct, REST e SSH; non esiste un flag
`active`, perché ciascuna installazione conserva una sola binding.
31. `rest_api` configura il Thoth REST Connector tipizzato: base URL, autenticazione e TLS sono dati
della binding, mentre path RPC e shape delle risposte appartengono al contratto applicativo e non
sono liberamente configurabili.
32. Il test connessione usa soltanto una configurazione già salvata ed è associato alla sua
`version`. Ogni modifica della binding o dei segreti invalida il risultato precedente e riporta
lo stato a `untested`.
33. Password, API key e chiavi sono write-only: l'API espone soltanto `configured`, un campo vuoto
conserva il valore esistente e la sostituzione è un'azione esplicita. Delete rimuove anche i
segreti associati.
34. La pagina usa AG Grid come master e un form React come detail, con sezioni Database, Connection
e TLS/SSH condizionali. La toolbar offre `Add database`; le righe `unconfigured` offrono
`Configure`. Entrambe selezionano esclusivamente workspace YAML senza un database e creano il
record soltanto al Save; `workspace_id` diventa immutabile dopo la creazione.
35. La grid mostra workspace, database, schema, transport, endpoint, stato connessione e ultimo
aggiornamento. Su schermi piccoli il dettaglio occupa il pannello completo. Il cambio riga con
modifiche non salvate e Delete richiedono conferma, senza conferma testuale tipizzata.
36. La Database Binding conserva `connection_status`, `tested_version`, `last_tested_at`, un codice
errore e un messaggio breve sanificato. Non conserva stack trace, DSN, credenziali o output grezzo
del driver.
37. Le API vivono sotto `/api/catalog`: list/create di `/databases`, get/patch/delete di
`/databases/:id`, sostituzione dei segreti sotto `/databases/:id/secrets`, test connessione sotto
`/databases/:id/test` e list/patch/sync delle tabelle sotto `/databases/:id/tables`.
38. `GET /api/catalog/databases` restituisce l'intera master list unificata; AG Grid Community applica
client-side ricerca, filtri e ordinamento. La prima versione non introduce paginazione server o
funzionalità AG Grid Enterprise.
39. Il Metadata Catalog vive nello stesso processo Fastify come modulo isolato con repository,
service, route, diagnostica e readiness proprie. L'indisponibilità del catalogo non modifica
sessioni, SSE o health del core e non giustifica ancora un microservizio separato.
40. Il backend mantiene `pg@8.22.0` e aggiunge `kysely@0.29.5` per query e transazioni tipizzate. Le
migrazioni Kysely sono timestampate, compilate con il backend ed eseguite da un comando
`catalog:migrate` separato; l'applicazione non migra automaticamente il database all'avvio.
41. Lo stack aggiunge un servizio interno `catalog-db` con volume persistente, ruolo runtime DML,
ruolo migrator DDL e job one-shot `catalog-migrate`. Un catalogo indisponibile produce 503 sulle
sole route catalogo.
42. La prima vertical slice è amministrativa: scrive il catalogo ma non cambia ancora il runtime di
sessioni e workflow, che continua a usare YAML e binding correnti fino al cutover esplicito.
43. `Configure` precompila senza salvare engine, database e schema dal descriptor e i dati non
sensibili dalla binding effettiva. L'amministratore verifica, inserisce i segreti e salva; non
esiste importazione silenziosa.
44. Unit e route test usano un repository fake; una suite PostgreSQL Testcontainers separata verifica
migrazioni, constraint, transazioni, optimistic concurrency e cascade. SQLite ed emulatori non
sono sostituti ammessi per questi test.
45. La navigazione delle entità catalogo è gerarchica e senza scorciatoie globali: `Databases →
Database → Overview | Tables → Table`. Non esistono una voce globale Tables, un filtro globale
Database o una preselezione implicita; Columns continuerà sotto Table e Relationships sotto
Database.
46. Una Catalog Table conserva nome fisico, `source_comment`, descrizione curata nullable,
`generated_description` nullable per lo step AI futuro, version e timestamp. La UI mostra come
tre campi indipendenti senza fallback visivo: source comment read-only, generated description
modificabile e description modificabile. I valori null restano celle e controlli vuoti.
47. Le Catalog Table non possono essere aggiunte, rinominate o cancellate manualmente. `Sync tables`
legge dal database esterno le tabelle PostgreSQL ordinarie e partizionate dello schema scelto;
viste e materialized view sono escluse.
48. La sincronizzazione è esplicita. La scansione avviene fuori dalla transazione del catalogo; il
diff viene applicato atomicamente soltanto se la version del Workspace Database è ancora quella
sottoposta a scansione. Una scansione fallita non modifica il catalogo.
49. Tabelle nuove vengono create, i commenti sorgente vengono aggiornati e quelle non più osservate
vengono eliminate definitivamente. La rimozione di tabelle, colonne o relazioni richiede la
conferma dell'esatto piano distruttivo; se il secondo scan produce una fotografia differente,
l'applicazione richiede una nuova conferma.
50. Un rename fisico è intenzionalmente delete più create e perde i metadati curati. Le colonne e
relazioni dipendenti vengono eliminate in cascade insieme alla Catalog Table.
51. L'introspezione vive nel modulo catalogo Fastify dietro un adapter. PostgreSQL diretto e tunnel
SSH usano il catalogo `pg_catalog`; REST preferisce il contratto tipizzato
`POST /rpc/schema_snapshot` e, quando quell'RPC non è esposto, usa come fallback compatibile una
singola query read-only tramite `POST /rpc/run_query`. Entrambi i percorsi devono produrre la
stessa fotografia v1 stretta descritta in `docs/contracts/catalog-schema-snapshot.md`.
52. Test connessione e sincronizzazione sono serializzati per Workspace Database, hanno timeout e
richiedono che la binding nella version corrente abbia un test `reachable` prima di qualsiasi
Catalog Sync Run. La scansione asincrona ha un timeout separato, di default dieci minuti.
53. Il tunnel SSH usa OpenSSH in modalità stdio `-W`, chiave privata e passphrase opzionale dal
secret store, `known_hosts` obbligatorio, `StrictHostKeyChecking=yes`, agent e configurazione
globale disabilitati. Non è ammesso TOFU. TLS PostgreSQL con CA e server name resta verificato
anche attraverso il tunnel.
54. In questo slice `ssh_tunnel` è una binding supportata da Database management per Test connection
e Schema Sync. Il renderer e il runtime delle sessioni NL→SQL restano fuori scope e continuano a
rifiutarla finché non verrà deciso il relativo cutover.
55. I menu di azione a livello Workspace Database espongono separatamente `Synchronize tables`,
`Synchronize all columns`, `Synchronize relationships` e `Synchronize all`. Su una selezione di
database lo scope scelto viene avviato per ogni database idoneo; non viene sostituito
implicitamente con una sincronizzazione completa.
56. Per lo scope Columns, `tableIds` vuoto significa tutte le Catalog Table correnti del Workspace
Database; `tableIds` valorizzato limita invece la riconciliazione alle tabelle indicate. La grid
Tables espone `Synchronize columns` sulle tabelle selezionate.
## Correzione del modello mentale corrente
@@ -205,8 +350,8 @@ template Django.
Il comportamento è principalmente additivo: usa `get_or_create` o controlli `exists`, aggiorna
alcuni commenti, ma non riconcilia in modo completo rename, rimozioni o drift. Non va copiato così
com'è. Il futuro processo ThothII dovrà almeno distinguere scansione, differenze osservate e
applicazione della nuova snapshot.
com'è. Il processo ThothII implementato distingue scansione, differenze osservate e applicazione
della nuova snapshot.
### Generazione AI legacy
@@ -318,7 +463,8 @@ rendering, retrieval, LSH o SQL generation.
### Vincoli minimi da progettare
- `workspace_id` unico sul Workspace Database;
- `workspace_id` obbligatorio e unico sul Workspace Database, con esistenza validata contro il
catalogo YAML dal servizio applicativo;
- nome tabella unico nel database e schema appropriato;
- nome colonna unico nella tabella;
- relationship unica secondo il modello, anche per chiavi composite;
@@ -348,8 +494,7 @@ L'export legacy della struttura include username e password in chiaro. Il modell
password, passphrase SSH e altri segreti in `CharField`; non è stata trovata cifratura applicativa,
nonostante un testo admin affermi il contrario.
Per ThothII resta da decidere nello step infrastrutturale quali dati di connessione siano normali
metadati e quali siano secret reference. In ogni caso:
ThothII distingue i metadati di connessione dai riferimenti al secret store cifrato. In ogni caso:
- nessun endpoint o export deve restituire segreti;
- log ed errori devono sanificare DSN e credenziali;
@@ -357,6 +502,11 @@ metadati e quali siano secret reference. In ogni caso:
- il catalogo non deve riusare credenziali del DWH, delle sessioni o di Qdrant;
- test connessione e introspezione devono usare timeout e privilegi read-only.
La binding REST corrente richiede una verifica prima del cutover: il renderer emette
`ssl_ca_file`, mentre il modello Python espone `ssl_ca`; il percorso della CA privata potrebbe quindi
non essere consumato. PSD richiede TLS con CA privata in locale, perciò questo disallineamento deve
essere corretto e coperto da un test end-to-end prima di affidare il profilo REST al catalogo.
## Percorso incrementale
### Step 1: accesso alla superficie vuota
@@ -384,15 +534,17 @@ npx tsc -b
### Step 2: contratto di dominio e schema relazionale
Da progettare con un nuovo round decisionale: campi, secret reference, dialetti supportati,
namespace/schema, snapshot fisiche, relazioni fisiche/logiche e lifecycle dell'output AI. Nessuna
tecnologia ORM o migration tool è stata scelta in questo documento.
Progettazione della vertical slice completata: Workspace Database, Database Binding, singolo schema,
riferimenti al secret store, optimistic concurrency e capability per trasporto hanno contratti
espliciti. Configurazione e contenuti semantici restano mutabili; la struttura fisica osservata è
sincronizzata e non modificabile manualmente.
### Step 3: PostgreSQL interno e migrazioni
Da progettare separatamente dal core: servizio, volume, ruoli runtime/migrator/backup, health e
readiness dedicati, backup/restore e diagnostica. La sua indisponibilità non dovrà cambiare
`core /health` o interrompere una sessione.
PostgreSQL interno con volume e ruoli runtime/migrator separati. Il modulo catalogo usa Kysely sopra
il driver `pg`; le migrazioni compilate vengono applicate soltanto dal comando `catalog:migrate` e
mai allo startup Fastify. Health, readiness e diagnostica restano dedicate; l'indisponibilità del
catalogo non cambia `core /health` e non interrompe una sessione.
### Step 4: API CRUD
@@ -401,20 +553,56 @@ Gli endpoint dovranno vivere sotto un namespace catalogo e non riutilizzare le r
### Step 5: UI CRUD
Liste e form per Workspace Database, tabelle, colonne e relazioni, costruiti con React/Vite e il
design system ThothII. La gerarchia e i filtri ThothAI sono il riferimento funzionale; Django Admin
non è il riferimento tecnologico o visuale.
Workspace Database, Catalog Table, Catalog Column e Catalog Relationship sono implementati con
React/Vite e il design system ThothII.
La navigazione è gerarchica e locale al database (`Overview | Tables`), senza menu o filtri globali
per tipo di entità. La grid delle tabelle non offre Add/Delete; il dettaglio full-width mantiene
immutabili i fatti fisici e consente di modificare separatamente Description e Generated
Description. Colonne e relazioni seguono la stessa gerarchia: Columns appartiene al dettaglio
della tabella, Relationships al database. I valori descrittivi null sono mostrati come celle e
campi vuoti, senza fallback visivi o placeholder `Not set` che nascondano quale sorgente è
effettivamente valorizzata.
Le griglie che dispongono di azioni massive usano checkbox e una toolbar contestuale con conteggio,
menu `Actions` e cancellazione della selezione. La selezione identifica ID espliciti, può essere
accumulata attraverso i filtri e viene azzerata dopo successo, nuova sincronizzazione o uscita
dalla pagina; un'azione è all-or-nothing se un elemento non è idoneo. I menu a livello database
espongono gli scope fisici come azioni distinte: `Synchronize tables`, `Synchronize all columns`,
`Synchronize relationships` e `Synchronize all`. La grid Tables espone invece `Synchronize
columns` per le tabelle selezionate. Test connection resta un'azione distinta; griglie senza azioni
non mostrano controlli di selezione inerti.
### Step 6: introspezione
Connessione read-only, preview delle differenze, acquisizione di una Physical Schema Snapshot,
policy per rename/rimozioni e stato del job. Nessuna chiamata lunga dovrà mantenere aperta una
transazione CRUD.
Catalog Table, Catalog Column e Catalog Relationship sono implementate per PostgreSQL diretto,
Thoth REST Connector e tunnel SSH. La scansione read-only è separata dalla transazione; una
riconciliazione atomica crea, aggiorna i commenti sorgente ed elimina, dopo conferma, i fatti fisici
assenti senza rendere modificabile manualmente la struttura osservata. Gli scope autorevoli sono
Tables per database e Physical Relationships per database. Per Columns, `tableIds` vuoto include
tutte le Catalog Table correnti, mentre una lista di ID limita lo scope al sottoinsieme esplicito;
`Synchronize all` osserva tutti e tre gli scope in un unico snapshot e li riconcilia insieme. Tutti
gli scope sono eseguiti come Catalog Sync Run durevoli in background, non attraverso implementazioni
sincrone e asincrone separate. Un run che prevede cancellazioni conserva il diff, attende una
conferma esplicita e verifica nuovamente lo snapshot prima dell'applicazione; se la sorgente è
cambiata, invalida la conferma. Ogni applicazione è atomica e fail-closed: errori, timeout o
capability non disponibili non producono aggiornamenti parziali.
PK e FK devono essere visibili sulle Catalog Column senza duplicare le stringhe denormalizzate di
ThothAI. La posizione nella primary key è un fatto osservato della colonna; membership e conteggio
FK sono proiezioni derivate dalle Catalog Relationship e dalle loro coppie ordinate, aggiornate
nella stessa transazione di riconciliazione.
Ogni scope registra la versione della Database Binding osservata e l'istante dell'ultima
sincronizzazione. Una modifica della binding conserva il catalogo precedente ma lo marca stale;
solo un `Synchronize all` riuscito rende nuovamente corrente l'intero schema.
### Step 7: generazione AI dei metadati
Generazione di descrizioni e altri campi equivalenti alle annotations, editing umano e gestione
esplicita di errori o output non validi. Approvazione/versioning saranno decisi in questo step.
Generated Description è una proposta distinta e modificabile: un revisore può correggerla prima
di consolidarla esplicitamente come Description. Lo slice AI dovrà decidere e implementare anche
alias semantici, descrizioni dei valori, sinonimi e concetti per tabelle e colonne, oltre alla
gestione esplicita di errori e output non validi. La generazione AI e l'azione di consolidamento non
appartengono allo slice di introspezione dello schema.
### Step 8: migrazione PSD
@@ -427,11 +615,24 @@ ricevono import legacy.
Rimuovere la dipendenza da `annotations.yaml` soltanto dopo avere un contratto equivalente,
test di rendering/search/Qdrant e una policy di disponibilità. Le sessioni di test esistenti
possono essere eliminate, ma le nuove sessioni non devono osservare aggiornamenti parziali.
Questo cutover è esplicitamente rinviato fino al completamento del database dei metadati. Il primo
gate successivo obbligatorio sarà valutare l'integrazione del Catalog Schema Snapshot con il
workflow core e lo schema-linking corrente; il rinvio non autorizza a dimenticare o assorbire
implicitamente il lavoro in altri slice.
### Step 10: operazioni e accettazione
Backup/restore reale, diagnostica, metriche, audit, permessi definitivi, hardening degli export e
test di failure isolation fra catalogo e workflow.
Backup/restore reale, diagnostica, metriche, permessi definitivi, hardening degli export e
test di failure isolation fra catalogo e workflow. I Catalog Sync Run hanno un solo job attivo per
Workspace Database, sono concorrenti fra database diversi e usano un lock persistente. Un pannello
operativo non modale rimane visibile durante la navigazione del database, mostra fasi, contatori,
tempo trascorso e log sanitizzato via SSE con polling di fallback, e offre Confirm, Cancel e Retry
quando consentiti. Un restart marca `interrupted` i run rimasti attivi; il retry crea un nuovo run.
Le modifiche ai metadati restano consentite durante la scansione e sono preservate dall'applicazione.
Il worker gira inizialmente nello stesso servizio Fastify ma dietro un'interfaccia estraibile, con
coda, lease e heartbeat persistiti nel catalog-db. I riepiloghi dei run non scadono; gli eventi
dettagliati sono conservati per 30 giorni, mentre snapshot e diff completi vengono eliminati dopo
la conclusione lasciando conteggi, decisioni e una sintesi sanitizzata dell'esito.
## Verifiche del core da conservare per il cutover
@@ -476,15 +677,11 @@ Definiscono gli effetti semantici e le guardie da mantenere o sostituire consape
Le seguenti scelte non appartengono allo step 1:
- framework del servizio catalogo e libreria di accesso PostgreSQL;
- collocazione e protezione delle credenziali dei Workspace Database;
- supporto iniziale di dialetti diversi da PostgreSQL;
- uno o più schema namespace per database;
- policy di reconciliation per rename e delete;
- modello delle foreign key composite;
- distinzione persistente fra relationship fisiche e logiche;
- lifecycle draft/review/approval dell'output AI;
- versionamento, audit e rollback;
- lifecycle dei riferimenti ai segreti durante sostituzione e cancellazione;
- criteri per aggiungere dialetti successivi a PostgreSQL;
- criteri per un'eventuale estensione futura a più schemi per database;
- lifecycle e gestione amministrativa delle future Logical Relationship;
- alias semantici, descrizioni dei valori, sinonimi e concetti prodotti o assistiti dall'AI;
- formato e momento del cutover dal file al database interno;
- permission definitiva separata da `workspace.manage`.