Files
ThothII/docs/prd/2026-08-09-workspace-preprocessing-prd.md
T
marcopan 4bcd4604fc docs: add per-workspace preprocessing PRD
PRD for making ThothII (Qdrant + Git workspace registry) configurable and
usable per workspace, covering the full preprocessing chain (tables/columns,
FK, evidence, memory). Decisions D1-D9 closed with the product owner;
plans P1-P10 (one per PRD point) will start only after owner review.
2026-08-09 14:24:33 +02:00

353 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PRD — Preprocessing per-workspace su ThothII (Qdrant + Git workspace registry)
**Status:** PRD in revisione — decisioni D1–D9 chiuse il 2026-08-09; i piani P1–P10 partiranno solo dopo
revisione e conferma del proprietario
**Data:** 2026-08-09
**Autore:** analisi dello stato attuale (branch `codex/git-workspace-registry`) + decisioni con il proprietario
**Uso:** riferimento stabile di requisiti e decisioni; da ogni punto nascerà un piano separato in
`docs/superpowers/plans/` (sez. 10) — questo documento non è un piano di lavoro
---
## 1. Contesto
ThothII è passato da un indice semantico **pgvector sul server PSD** (descrizioni di tabelle/colonne,
evidence e memory embeddate nella stessa istanza Postgres del DWH, lettura via RPC `search_similar`,
scrittura via REST dedicato o loading diretto) a un'architettura con:
- **infrastruttura semantica interna obbligatoria**: Qdrant + Ollama (`qwen3-embedding:0.6b`, 1024 dim,
cosine) come servizi Compose privati; una **collection Qdrant per workspace**, con schema/evidence/memory
separati dal payload `kind`;
- **workspace definito da un descriptor schema-v3 in un repo Git esterno** (id, DWH, collection, LLM
policy, diagnostics), con binding DWH locali all'installazione;
- **config harness renderizzata dal backend** a runtime (`runtime_identity` + `resources.vector` +
`resources.embeddings` + `roots` sotto `<dataRoot>/sessions/<wsId>/`).
La **macchina di preprocessing** (comandi, job a generazioni con publish atomico, adapter Qdrant, corpus
evidence, FK, memory) **esiste già ed è testata**: `tht preprocess evidence|dwh`, `tht vector init|index-schema`,
`tht schema introspect|suggest-fks|check`, `tht evidence extract|index`, `tht lsh build`, memory/solved.
Esistono job Compose fixture (`deploy/compose.preprocess.yaml` + `deploy/workspaces/preprocess-{dwh,evidence}.yaml`)
e uno smoke (`scripts/preprocess-smoke.sh`).
### Il problema
Il preprocessing **non è collegato al workspace reale del registry**:
1. i job fixture usano una collection fissa (`preprocess-evidence`), un workspace_id derivato dal nome
file (`preprocess-evidence`) e roots sotto `/data/workspaces/preprocess-*`, che **non coincidono** con
quelli del runtime (`<dataRoot>/sessions/<wsId>/`);
2. il backend **non espone alcun modo** di eseguire `tht preprocess` contro la config renderizzata di un
workspace (nessun endpoint, nessuno script, nessun comando documentato);
3. il **descriptor v3 e la config renderizzata non hanno la sezione `evidence`**: non c'è un posto canonico
dove dichiarare da dove arrivano le evidence di un workspace;
4. l'**ammissione sessione** (`ThtRunner.qdrantEnsure`) richiede la collection già esistente con 1024/cosine
e 8 payload keyword-index, ma **nessuno la crea esplicitamente** (`tht vector init` fallisce se manca);
5. le **generazioni `.tht-dwh` sono legate a un fingerprint della config completa** (`OWNER.json`:
workspace_id + config_fingerprint + input_fingerprint): se il preprocessing non usa la config identica a
quella renderizzata dal runtime, a runtime la generazione viene **rifiutata**;
6. la **cura FK** (`annotations.yaml`) è manuale e vive nel runtime artifacts; il registry **non sincronizza**
file dal repo ai roots runtime;
7. i **vecchi embedding pgvector (nomic 768d) non sono riusabili** (modello e dimensioni cambiati): serve
re-indicizzare i contenuti PSD.
**Sintesi:** la parte "motore" è pronta; manca il **collegamento per-workspace** (config, esecuzione,
bootstrap, sorgente evidence) e la **documentazione operator**.
---
## 2. Obiettivo
Rendere l'attuale versione di ThothII (Qdrant + workspace su repo esterno) **configurabile e utilizzabile**
per un workspace reale, inclusa l'intera catena di preprocessing: **tabelle/colonne (catalogo + embedding),
FK (cura), evidence (sorgente → corpus → embedding), memory/solved**, con un flusso operator riproducibile,
documentato e verificato da smoke end-to-end.
### Obiettivi secondari
- O1. Un solo modo canonico di eseguire il preprocessing per un workspace (niente più fixture "speciali").
- O2. Il preprocessing è **idempotente e ripristinabile**: rerun senza duplicati, publish atomico, GC.
- O3. Nessun segreto/endpoint entra nel repo workspace né nei descriptor (invariante attuale preservato).
- O4. Il flusso è **documentato nei manuali operator** (`local/server-workspace-registry.md`) e coperto da
smoke automatici.
- O5. La **migrazione PSD** è definita (cosa si riusa, cosa si rigenera, cosa si esporta dal pgvector).
## 3. Non-obiettivi (fuori scope di questo PRD)
- Riscrivere il workflow NL→SQL o i gate (F1..F8) — restano invariati.
- Cambiare modello/architettura semantica (Qdrant/Ollama/1024/cosine) — già deciso e verificato.
- Rifare la UI di gestione workspace oltre a quanto già esiste.
- Il **deploy reale sul server PSD** (VPN, credenziali, portale, auth upstream): è un progetto operativo
separato che userà questo PRD come prerequisito tecnico.
- Supportare di nuovo pgvector o endpoint embedding esterni come percorso operativo.
- Il **comando di preprocessing avviabile dalla GUI**: è una release futura (fuori scope della release 0,
che è CLI sul host — vedi D2).
---
## 4. Utenti
| Utente | Esigenza |
| --- | --- |
| **Operatore/amministratore** (chi installa e cura un workspace) | Configurare DWH+evidence, eseguire il preprocessing, curare le FK, verificare lo stato, fare backup/restore. |
| **Autore ETL / curatore dominio** (es. il cliente PSD) | Mantenere la cartella evidence e le annotazioni FK nel repo workspace con un flusso semplice. |
| **Reviewer umano** (usa l'app) | Vede search pack con tabelle/evidence/solved corretti: la qualità del retrieval dipende dal preprocessing. |
| **Sviluppatore ThothII** | Comandi/endpoint deterministici, testabili, senza sorprese di configurazione. |
---
## 5. Scenario target (end-to-end)
1. **Setup repo**: l'operatore crea `workspaces/<id>.yaml` (schema-v3: DWH, collection, LLM policy) + la
sorgente evidence dichiarata; push.
2. **Installazione**: `.env` + bindings `THT_WS_*` + secrets; `up` dello stack (frontend/core/qdrant/embedding).
3. **Registry**: pull → validazione → snapshot attivo; diagnostic DWH verdi.
4. **Preprocessing DWH**: introspezione (physical.yaml: tabelle/colonne/descrizioni/esempi/eligibility) +
LSH; generazione pubblicata sotto `.tht-dwh` del workspace.
5. **Cura FK**: `tht schema suggest-fks` → revisione umana → `annotations.yaml` (check senza orfani);
versionata dove deciso (vedi D5).
6. **Indice schema**: `tht vector index-schema` → record schema nella collection del workspace; la
collection, se inesistente, viene creata all'ammissione (self-heal) o dal primo write (vedi D4).
7. **Preprocessing evidence**: `tht preprocess evidence` → corpus generation (chunk+embed) nella collection
(kind `evidence`) + manifest ACTIVE nel corpus root del workspace.
8. **Memory/solved**: promozioni F8 (`memory promote/save-one`) e finalize (`solved-index`) scrivono nella
collection (kind `memory`).
9. **Uso**: nuova sessione → admission verde (collection+Ollama) → F1 `search pack` con tabelle, evidence e
solved del workspace; F4/F6 con FK curate.
10. **Operatività**: backup/restore volumi (Qdrant, corpus, `.tht-dwh`, registry), update, ripristino da
outage.
---
## 6. Requisiti funzionali
### RF1 — Configurazione per-workspace
- RF1.1 Un workspace del registry deve poter dichiarare **tutto ciò che serve al preprocessing** in un unico
posto canonico: DWH (già nel descriptor), **sorgente evidence completa** (protocollo/tipo, URI, parametri
non-secret), eventuali policy di chunk/retention.
- RF1.2 I segreti (password, API key, CA) restano fuori dal repo e dal descriptor (invariante attuale).
- RF1.3 La config harness usata dal preprocessing deve essere **derivata dalla stessa renderizzazione del
runtime** (stesso workspace_id, stessi roots, stessa configurazione effettiva).
- RF1.4 La configurazione (descriptor + bindings + config renderizzata) deve **prevedere i tre trasporti
DWH**: `postgres_direct`, `rest_api`, `ssh_tunnel`. PSD usa `rest_api`; altri database potranno usare
direct o tunnel.
### RF2 — Preprocessing DWH (tabelle/colonne)
- RF2.1 Comando/azione per eseguire `introspect` + `lsh` per un workspace del registry, contro la sua config
effettiva, con output JSON e resume.
- RF2.2 La CLI di preprocessing raggiunge il DWH **con il trasporto dichiarato dal workspace** (direct,
REST o tunnel SSH), come il runtime.
- RF2.3 Il catalogo risultante (`physical.yaml`) alimenta: cache `tht schema introspect`, render mschema
(F1/F4), record schema per l'embedding (RF4).
- RF2.4 Refreshing esplicito quando il DWH cambia (`--refresh`/nuova generazione), senza invalidare le
sessioni esistenti (generazioni + ACTIVE pointer, già implementato).
### RF3 — FK
- RF3.1 Flusso curato per-workspace: `tht schema suggest-fks` (+ `--from-sql`, `--assume`, `--write`),
revisione umana, `tht schema check` (zero orfani).
- RF3.2 Le FK curate devono essere **disponibili a runtime** (sezione `【Foreign keys】` del render mschema,
usata da F4/F6) e **versionate nel repo workspace** (D5).
- RF3.3 Nessuna FK derivata dal modello: il modello usa solo la lista curata (contratto SKILL invariato).
### RF4 — Indice semantico schema + bootstrap collection
- RF4.1 `tht vector index-schema` embedda i record schema (tabella+colonna, con descrizioni/esempi/sinonimi)
nella collection del workspace (kind `schema`), idempotente (hash → upsert solo del cambiato).
- RF4.2 **Bootstrap della collection**: se inesistente all'ammissione sessione, il runtime la crea
(self-heal) con 1024/cosine + i payload keyword-index richiesti (`content_hash, document_id, kind,
record_key, record_kind, vector_generation, workspace_id, workspace_revision`).
- RF4.3 La **CLI deve poter cancellare e ricreare** la collection di un workspace (rebuild esplicito con
guardie di sicurezza e conferma).
- RF4.4 Prima di una sessione, l'ammissione resta invariata (collection compatibile + Ollama).
### RF5 — Evidence
- RF5.1 Sorgente evidence dichiarabile per-workspace nel descriptor (protocollo/tipo + URI). Per PSD è un
**tree di file `.md` in una directory versionata nel repo del workspace** (`evidence/`); HTTP manifest e
S3 restano opzioni del motore per sorgenti esterne.
- RF5.2 `tht preprocess evidence` per-workspace: discover → acquire → normalize/chunk → embed → upsert
(kind `evidence`, payload `document_id`/`vector_generation`) → publish ACTIVE nel corpus root del workspace,
con resume e dry-run (già implementato nel motore).
- RF5.3 GC/retention delle generazioni evidence (filesystem + punti Qdrant) con le policy esistenti.
- RF5.4 A runtime la ricerca evidence è filtrata dalla generazione ACTIVE e dal workspace_id (già
implementato: `ActiveEvidenceSearcher`); il flusso RF5 deve garantire che il corpus ACTIVE appartenga al
workspace giusto.
### RF6 — Memory e domande risolte
- RF6.1 `memory promote/save-one` (F8) e `memory solved-index` (finalize) scrivono nella collection del
workspace (kind `memory`/`solved_question`) — verificare end-to-end con Qdrant e risolvere i TODO residui
in `memory_cmd.py`.
- RF6.2 Il registro JSONL resta la fonte canonica; Qdrant è proiezione di ricerca (invariante attuale).
### RF7 — Migrazione PSD
- RF7.1 Definire cosa si **riusa** (physical.yaml, annotations.yaml con le ~228 FK curate, le 895 evidence
`.md`), cosa si **rigenera** (tutti gli embedding, modello diverso) e cosa si **esporta** dal pgvector del
server prima della dismissione.
- RF7.2 La migrazione è un'operazione documentata e rieseguibile, non un one-shot nel codice.
### RF8 — Operatività e documentazione
- RF8.1 Manuali operator aggiornati con la sequenza completa per-workspace (config → preprocess → cura →
verifica → uso → backup/restore).
- RF8.2 La documentazione di progetto spiega **cos'è `.tht-dwh`** (generazioni, `OWNER.json`, `ACTIVE`,
vincolo di fingerprint) in modo comprensibile per l'operatore (D3).
- RF8.3 Smoke end-to-end automatico (workspace nuovo → tutto il ciclo → sessione reale → cleanup) che
sostituisce/completa `preprocess-smoke.sh` (oggi solo fixture).
- RF8.4 Backup/restore coprono Qdrant (già `vector-backup.sh`/`vector-restore.sh`), corpus, `.tht-dwh` e
registry.
---
## 7. Requisiti non funzionali
- **RNF1 Sicurezza**: nessun segreto in repo/descriptor/config renderizzata/log; la CLI di preprocessing
(D2) non espone credenziali, non le logga e non le scrive negli artefatti.
- **RNF2 Determinismo/idempotenza**: rerun del preprocessing = zero duplicati (hash content), publish
atomico, generazioni immutabili (già nel motore).
- **RNF3 Robustezza**: degradazione controllata (workspace senza evidence o senza collection funziona, con
warning); errori sanitizzati; nessun fallimento che corrompa la generazione attiva.
- **RNF4 Isolamento per-workspace**: ogni filtro Qdrant legato a workspace_id; rifiuto di namespace
conflittuali (già implementato nell'adapter).
- **RNF5 Compatibilità**: il preprocessing deve funzionare con la config renderizzata dal backend
(fingerprint `OWNER.json` compatibile) — è il vincolo chiave di design (vedi D3).
- **RNF6 Performance**: introspezione ~minuti (non nel path di sessione), embedding batch, LSH boundato;
il retrieval a runtime non cambia i costi attuali.
- **RNF7 Manutenibilità**: nessun fork dei fixture; un solo percorso canonico (O1).
---
## 8. Criteri di accettazione (bozza)
1. Da un repo workspace vuoto si arriva a una sessione funzionante seguendo **solo i manuali aggiornati**,
senza toccare file fixture.
2. La CLI di preprocessing funziona **sia sul PC/Mac dell'utente sia sul server che ospita il DWH**
(stesso comando, config derivata dal workspace).
3. La configurazione di un workspace dichiara e usa uno dei **tre trasporti DWH** (`postgres_direct`,
`rest_api`, `ssh_tunnel`); PSD usa `rest_api`.
4. `search pack` di una domanda reale restituisce tabelle (con descrizioni), evidence della generazione
ACTIVE e solved dello stesso workspace; F4/F6 mostrano le FK curate.
5. `qdrantEnsure`/`ollamaEnsure` verdi all'ammissione; la collection ha esattamente 1024/cosine + gli 8
keyword-index.
6. Rerun del preprocessing: `unchanged` (nessun duplicato); modifica di un'evidence → nuova generazione,
ACTIVE aggiornato, vecchie generazioni in GC.
7. Smoke end-to-end automatico verde in CI con cleanup esatto (stile `preprocess-smoke.sh`).
8. Migrazione PSD documentata e provata almeno in dry-run (re-introspection o riuso catalogo + re-embedding).
---
## 9. Decisioni chiuse (2026-08-09)
> La sezione nasceva come "punti di discussione"; le decisioni sono state prese con il proprietario del
> prodotto il 2026-08-09. Ogni punto resta il riferimento del proprio piano (sez. 10). Le opzioni scartate
> sono omesse; la motivazione della scelta è inclusa in ogni punto.
### D1 — Config per-workspace: **c) misto, con sorgente completa nel descriptor**
- Il descriptor v3 guadagna una sezione `evidence` che configura **tutta la lettura della sorgente**:
protocollo/tipo, URI/sorgente, eventuali parametri non-secret.
- Per PSD la sorgente è un **tree di file in una directory versionata nel repo del workspace**
(`evidence/`, come nella versione precedente).
- Eventuali segreti (HTTP autenticato, S3) restano in overlay d'installazione — invariante: nessun segreto
nel repo/descriptor.
### D2 — Esecuzione: **CLI sul host in release 0; GUI in release futura**
- **Release 0**: una **CLI installata con ThothII sul host** — sia il PC/Mac dell'utente sia il server che
ospita il DWH — che esegue **tutta la catena di preprocessing** (DWH introspect+LSH, FK, index-schema,
evidence e quanto serve) per un workspace del registry.
- La CLI deriva la config dal descriptor+bindings con la stessa identità del runtime → soddisfa il vincolo
D3 senza dipendere dal backend.
- **Release futura (fuori scope)**: comando avviabile dalla GUI (endpoint backend da progettare poi).
### D3 — Fingerprint `.tht-dwh`: **accettare il vincolo + documentarlo**
- Le generazioni DWH restano legate alla config effettiva (workspace_id + config_fingerprint +
input_fingerprint in `OWNER.json`).
- La CLI (D2) gira con la config derivata dal descriptor+bindings, quindi identica alla runtime.
- **La documentazione di progetto deve spiegare chiaramente cos'è `.tht-dwh`** (directory delle generazioni
catalogo/LSH, `OWNER.json`, `ACTIVE` pointer, perché il fingerprint protegge da artefatti di un'altra
config) — oggi non è chiaro.
### D4 — Bootstrap collection: **self-heal all'ammissione + CLI delete/recreate**
- Se la collection non esiste all'ammissione sessione, il runtime la crea (1024/cosine + payload
keyword-index) — self-heal.
- La **CLI deve poter cancellare e ricreare le collection** (rebuild esplicito, con guardie di sicurezza).
### D5 — Versioning artifacts curati: **c) misto**
- `annotations.yaml` (cura FK, cura umana) **versionata nel repo workspace** e sincronizzata ai roots
runtime (il registry copia il file negli snapshots → sync).
- `physical.yaml` (derivato dall'introspezione) rigenerato localmente, non versionato.
### D6 — Evidence: **a) nel repo workspace**
- Il workspace contiene la cartella evidence versionata (tree di file); le dimensioni non sono un vincolo.
- HTTP/S3 restano opzioni future per sorgenti esterne (il motore le supporta già).
### D7 — Migrazione PSD: **inclusa, con accesso al server**
- Il piano P7 copre: riuso di physical.yaml + annotations.yaml + evidence `.md` dal repo workspace; export
dal pgvector del server PSD (accesso disponibile); re-embedding con `qwen3-embedding:0.6b`; dry-run
documentato.
### D8 — Verifica end-to-end: **confermata; remote Git libero; DWH multi-trasporto**
- Smoke automatico su workspace sintetico (CI) + gate manuale L2 su PSD.
- Il workspace PSD sarà **prima generato come repository ed alimentato** (descriptor + evidence +
annotations), **poi** usato da ThothII. Accesso al server PSD disponibile.
- **Remote Git**: lo creiamo noi, nessun vincolo tecnico (consigliato GitHub via HTTPS; SSH resta
possibile se servirà).
- **Trasporto DWH**: PSD via **REST** (come oggi); **la configurazione deve prevedere le tre modalità** —
`rest_api`, `postgres_direct`, `ssh_tunnel` — perché altri database potrebbero richiedere accesso TCP
diretto o via tunnel. Oggi `ssh_tunnel` è solo diagnostico a runtime: va reso operativo dove serve
(vedi P10).
### D9 — Retention/GC: **confermata**
- Default invariati (`retain_published_generations: 3`, chunk 4000 char), configurabili per-workspace via
la sezione `evidence`/policy del descriptor (D1).
## 10. Mappa dei piani (uno per punto del PRD)
Questo PRD non diventa un unico piano: **ogni decisione/requisito produce un piano separato (P1–P10)** in
`docs/superpowers/plans/`, eseguibile in sequenza o come workstream indipendenti. Il PRD resta il
riferimento stabile (requisiti + decisioni); ogni piano cita il punto di origine e i criteri di
accettazione applicabili (sez. 8).
| Piano | Punto PRD | Contenuto sintetico | Dipende da |
| --- | --- | --- | --- |
| P1 | D1 | Descriptor v3: sezione `evidence` (protocollo/tipo, URI, sorgente tree nel repo) + policy; validazione contract (backend `schema.ts`, harness `config.py`) | — |
| P2 | D2 | **CLI di preprocessing sul host (release 0)**: comando per-workspace che esegue l'intera catena (DWH, FK, index-schema, evidence) con la config derivata da descriptor+bindings; funziona su PC/Mac utente e server DWH | P1 |
| P3 | D3 | Vincolo fingerprint `.tht-dwh` (test: preprocess con config identica alla runtime) + **documentazione di progetto su cos'è `.tht-dwh`** | P2 |
| P4 | D4 | Bootstrap collection: **self-heal all'ammissione** (creazione 1024/cosine + keyword-index) + **comandi CLI delete/recreate** con guardie | — |
| P5 | D5 | `annotations.yaml` versionata nel repo workspace + sync registry → roots runtime | P1 |
| P6 | D6 | Evidence tree nel repo workspace (path checkout → preprocess) | P1 |
| P7 | D7 | Migrazione PSD: riuso catalogo/annotations/evidence, **export pgvector (accesso server)**, re-embedding, dry-run | P1–P6 |
| P8 | D8 | Verifica end-to-end: smoke CI + gate L2 su PSD (**workspace repo alimentato prima dell'uso**; remote Git a scelta) | P1–P7 |
| P9 | D9 | GC/retention per-workspace: policy configurabili, default invariati | P1 |
| P10 | D8/RF1.4 | Trasporti DWH operativi: rendere `ssh_tunnel` utilizzabile a runtime e nella CLI di preprocessing (oggi solo diagnostico); verifica dei tre trasporti (direct, REST, tunnel) | P1, P2 |
Ordine consigliato: **P1 → P2 → P3** (catena config/esecuzione), **P4** e **P5/P6** in parallelo dopo P1,
poi **P7 → P8**; P9 può essere assorbito in P1 o restare autonomo; **P10** dopo P1+P2 (necessario solo se un
workspace target richiede davvero il tunnel — per PSD non serve, usa REST).
Ogni piano segue la prassi del repo: TDD, commit scoping, verifica layer (pytest/vitest/tsc/build), gate
deployment (`test-*`), poi smoke Docker. Lo stato di ogni piano (draft / in corso / fatto) verrà tracciato
in questa sezione man mano che i piani partiranno.
---
## 11. Storico revisioni
| Versione | Data | Contenuto |
| --- | --- | --- |
| v0.1 | 2026-08-09 | Bozza da analisi dello stato attuale (gap preprocessing per-workspace) |
| v0.2 | 2026-08-09 | Decisioni D1–D9 chiuse con il proprietario; mappa piani P1–P10; requisiti RF1–RF8 aggiornati (evidence nel descriptor, CLI sul host, self-heal collection, multi-trasporto DWH) |
| v0.3 | 2026-08-09 | Revisione di coerenza (numerazioni, riferimenti incrociati, header di stato) — pronto per revisione del proprietario |
---
## 12. Riferimenti
- Stato attuale: `PROJECT_STATE.md` (sezioni "Internal Qdrant + Ollama semantic infrastructure", snapshot
registry) e `AGENTS.md`.
- Design architettura semantica: `docs/plans/2026-08-08-internal-qdrant-ollama-design.md` e relativo piano.
- Registry: `docs/superpowers/specs/2026-08-03-git-workspace-registry-design.md`, manuali
`docs/install/local-workspace-registry.md` / `server-workspace-registry.md`.
- Motore preprocessing: `harness/tht/cli/preprocess_cmd.py`, `harness/tht/corpus/pipeline.py`,
`harness/tht/jobs/dwh_pipeline.py`, `harness/tht/adapters/vector/qdrant.py`,
`harness/tht/vectorstore/records.py`, `harness/tht/cli/{vector,schema,evidence,memory}_cmd.py`.
- Fixture attuali: `deploy/compose.preprocess.yaml`, `deploy/workspaces/preprocess-{dwh,evidence}.yaml`,
`scripts/preprocess-smoke.sh`.
- Ammissione runtime: `backend/src/tht/tht-runner.ts` (`qdrantEnsure`/`ollamaEnsure`),
`backend/src/workspaces/runtime-renderer.ts`.