chore: preserve root worktree documents

This commit is contained in:
Codex
2026-08-31 15:34:55 +02:00
parent b5db0cd3c1
commit fa7380b3a3
8 changed files with 1021 additions and 80 deletions
@@ -0,0 +1,279 @@
# PRD — Modularizzazione a moduli incapsulati: disambiguazione, evidence, memory
**Status:** riflessione architetturale di plausibilità — nessuna decisione implementativa presa qui; il
documento fissa confini, contratti condivisi e precondizioni da cui potranno nascere ticket e piani separati
**Data:** 2026-08-21
**Autore:** analisi del codice su main (`5f05754`), su richiesta del proprietario; incrocia e non sostituisce
i documenti evidence-canonica già esistenti (sez. 9)
**Uso:** riferimento per il programma di sviluppo parallelo dei tre sottosistemi (disambiguazione, gestione
evidence, gestione memory); definisce cosa deve essere vero *prima* che i tre flussi possano girare in
parallelo senza impatti incrociati
---
## 1. L'obiettivo del proprietario
Avviare uno sviluppo che cambi **in parallelo** tre aree del sistema:
1. **disambiguazione** (estratta e separata dal workflow);
2. **gestione delle evidence** — nuovo componente che espone un'API verso il resto del workflow ma resta
ben incapsulato al suo interno;
3. **gestione della memory** — con lo stesso grado di forte incapsulamento.
Vincolo accettato: le modifiche fatte su un modulo **non devono impattare gli altri moduli** se non per
eventuali ridefinizioni dei contratti (API) esposti.
## 2. Verdetto sintetico
**Plausibile, e il codebase è insolitamente ben predisposto per farlo.** Ma con tre precisazioni oneste:
1. i tre componenti sono oggi a **tre livelli di maturità diversi**: evidence è quasi un modulo, memory è
già incapsulata meglio di quanto sembri, la disambiguazione **non è un componente di codice** e
"estrarla" è un lavoro di natura diversa (sez. 4);
2. esistono **due "quinti moduli" nascosti** — il ledger dei tipi di decisione con `workflow.yaml`, e il
gate `tht-gate.js` con il vector store — che il piano a tre vie deve contemplare, altrimenti il
parallelismo si rompe dove non si sta guardando (ledger e gate sono spiegati in sez. 3; le implicazioni
in sez. 5);
3. gli accoppiamenti reali **non sono negli import Python**: sono nei decision type del ledger, nelle
proiezioni `search pack`/`search find`, nel modello di contenuto della memory e negli id evidence
citabili. Sono quelli i contratti da definire e versionare **prima** del parallelismo (sez. 6–7).
## 3. Ledger e gate: i due elementi cardine del workflow
Il resto del documento usa di continuo due concetti che conviene fissare qui, perché sono
i punti dove i tre moduli si incontrano davvero: il **ledger** (dove lo stato viene
registrato) e il **gate** (chi decide e chi scrive).
### 3.1 Il ledger — il registro delle decisioni (`review_decisions.jsonl`)
Una sessione è una directory: manifest + artefatti per fase (`question.md`,
`schema_linking.json`, `sql_final.sql`, …) + un file `review_decisions.jsonl`. Il ledger
è **append-only**: una riga JSON per ogni decisione del reviewer, con numero progressivo
`seq`, timestamp, `type`, `subject`, `detail`, `rationale` (più i campi di rollback
`retracts`/`phase`). I `DecisionType` definiti in `tht/decisions.py` sono esattamente 30.
Esempio concreto: a F1 il reviewer sceglie "per 'ablazione' intendo la colonna
`flag_ablazione` della fact delle procedure" → nel ledger viene accodata una riga come
`{"seq": 3, "ts": "…", "type": "concept_clarified", "subject": "ablazione", "detail": "…",
"rationale": "…"}`.
Due proprietà lo rendono cardine:
1. **la fase corrente non è memorizzata: è calcolata** rileggendo (fold) il ledger
(`tht/phase.py`). Il ledger è la fonte di verità — "ciò che non è registrato non è
accaduto". Il rollback non cancella nulla: aggiunge `decision_retracted`/
`phase_reopened` e la vista *effettiva* (`effective_decisions`) esclude le righe
ritirate e quelle rimaste stale oltre il punto di rollback;
2. **è il canale di comunicazione tra i moduli**: la disambiguazione *scrive*
`concept_clarified`/`ambiguity_open`/`value_grounded`; l'evidence *scrive*
`evidence_accepted`/`evidence_rejected`/`concept_formula_approved|rejected`; la memory
*legge* `concept_clarified` (per costruire le promozioni) e *scrive* `memory_promoted`/
`memory_promotion_declined`. Per questo l'insieme dei decision type **è** un contratto:
ridefinirlo (sez. 6.1) è l'equivalente di cambiare una API tra moduli.
### 3.2 Il gate — l'estensione che impone il human-in-the-loop (`tht-gate.js`)
ThothII non esegue il workflow con codice: lo esegue **un agente LLM (Pi)** che segue
SKILL.md. Il modello propone, ma non decide e non persiste direttamente. Il gate è
l'estensione JavaScript di Pi (~2000 righe, `harness/.pi/extensions/tht-gate.js`) che
svolge tre lavori:
1. **traduce le proposte del modello in widget per l'umano**: il modello chiama un tool
del gate — `reviewer_select` (scelta singola), `reviewer_decide` (multiselect),
`reviewer_confirm` (gate su artefatto/fase) più i specializzati `rewrite_question`,
`reviewer_schema_linking`, `reviewer_datamart`, `reviewer_memory_promote`,
`write_schema_linking`, `write_cte_sql`, `write_final_sql`; il gate costruisce il
descriptor del widget → RPC → backend → SSE → browser, dove il frontend lo renderizza
dal registry `src/widgets/`;
2. **persiste attraverso un unico canale**: alla risposta del reviewer, è il gate (non il
modello) a invocare la CLI `tht` (es. `tht memory promote --preview --json`,
`tht session sync-schema-linking`) e ad accodare la riga nel ledger;
3. **fa rispettare le regole (enforcement)**: l'hook anti-bypass blocca `tht phase
advance`, `tht decision add`, `tht memory promote|save-one` se il modello prova a
eseguirli direttamente dalla shell; i payload vengono validati (TypeBox) e gli
artefatti mostrati al reviewer sono ricostruiti in modo deterministico (es. il widget
CTE mostra il SQL persistito via `tht cte info`, non il testo del modello); le
chiusure di fase deterministiche sono automatiche.
Esempio concreto (F8): il modello chiama `reviewer_memory_promote`; il gate esegue
`tht memory promote --preview --json`, costruisce la checklist pre-selezionata; il
reviewer conferma; il gate salva le memory scelte (`memory save-one`), registra
`memory_promoted`/`memory_promotion_declined` nel ledger e finalizza la sessione. Il
modello non ha mai scritto nulla di propria mano.
Perché è cardine per la modularizzazione: il gate è **l'unico punto** in cui i tre moduli
appaiono all'utente e in cui le loro regole vengono imposte — ogni cambiamento a
disambiguazione/evidence/memory ha quasi sempre una controparte nel gate (sez. 5.2).
## 4. Stato attuale dei tre componenti
### 4.1 Evidence — il più vicino a essere un modulo
Fatti verificati nel codice:
- esiste già un **port vero** (`harness/tht/ports/evidence.py`: `discover`/`acquire`, credential-free,
contratti Pydantic congelati) con **tre adapter** dietro il port (`adapters/evidence/{filesystem,http,s3}.py`);
- la pipeline di canonizzazione/indicizzazione vive in `harness/tht/corpus/` (sorgente → normalize →
chunk → embed → vector store, generazioni isolate stage→publish);
- la **superficie di consumo del workflow è già quasi tutta incanalata in due comandi con JSON pulito**:
`tht search pack` (sezione evidence: top 5, proiezione `{title, status, excerpt[:400]}`) e
`tht search find [--kind evidence|formula]`. Quella proiezione è già di fatto un'API: evidence-canonica
può rifare tutto dietro di essa (card, involucro tipizzato, retrieval duale) senza che F1 se ne accorga,
finché il contratto di proiezione regge;
- l'**inventario dei punti di consumo è già fatto**: `verifica-workflow.md` elenca i 12 touchpoint (T1–T12)
con meccanismo e requisito. Quel documento **è** la bozza del contratto esterno del modulo.
Ciò che manca all'incapsulamento sono esattamente i tre GAP lì identificati:
- **GAP 1 — identità citabile**: il processo tratta l'id evidence come documento (F3 lo cita, F4 lo attacca
ai candidati, il report di sessione lo risolve in file via `resolve_evidence_file`/`rglob`). La decisione
già presa (id citabile `doc#card`) richiede l'estensione del resolver: è l'ultimo passo di contratto da
chiudere;
- **GAP 2 — formula store**: lettura cablata e funzionante, scrittura senza proprietario di produzione.
La decisione già presa (le card famiglia 3 del corpus canonico sono fonte di verità; i `.sql.md` sono
derivati della pipeline) assegna il proprietario senza toccare il contratto di lettura D14b;
- **GAP 3 — conoscenza di dominio nei testi istruzionali**: dialetto, ricette temporali PSD, esempi clinici
dentro SKILL.md e reference doc. È contenuto evidence sparso nel workflow: la migrazione verso il corpus
canonico **è parte del lavoro di confine**, non un'attività cosmetica successiva.
Nota trasversale: il ciclo di vita evidence attraversa **due linguaggi** — materializzazione e ciclo di vita
della collection stanno nel backend TypeScript (P4/P6), corpus e ricerca nello harness Python. L'API del
modulo deve quindi essere il contratto CLI/JSON (che è già lo stile di casa: `--json` pristine), non
un'interfaccia Python.
### 4.2 Memory — già incapsulata, con un accoppiamento semantico da esplicitare
Fatti verificati:
- il modulo vive in `harness/tht/memory.py` + `tht/solved.py` + `cli/memory_cmd.py`, con registro JSONL
a scrittura atomica e dedup del contenuto reviewer-visibile;
- i punti di contatto col workflow sono **esattamente tre**: F2 (ricerca + applicazione via gate), F8
(gate di promozione), finalize (indicizzazione `solved_question`, best-effort);
- il gate consuma la memory **solo tramite CLI**: `tht memory promote --preview --json` e
`memory save-one`; la logica di calcolo dei candidati (dedup, decisioni storie post-rollback, seq
rifiutate, esclusione delle già decise nella sessione) è tutta dentro memory.py. È incapsulamento vero,
già operativo.
L'accoppiamento che conta non è di codice ma **semantico**: `REUSABLE_TYPES = {"concept_clarified"}` —
il modello di contenuto della memory è definito dal tipo di output della disambiguazione. Se il ridisegno
F1 cambia cosa sia un chiarimento (target group, tassonomia dell'ambiguità, caso unanswerable), cambia
anche cosa la memory può promuovere e come. **Il contratto disambiguazione→memory va definito prima di
far girare i due flussi in parallelo**, o si scontreranno esattamente lì.
### 4.3 Disambiguazione — non è un componente di codice
Il punto onesto: **non c'è niente da "estrarre" nel senso classico**. Oggi la disambiguazione è il
comportamento dell'LLM in F1/F3/F4, governato da (ledger e gate: sez. 3):
- **prosa** (SKILL.md + `rewriting.md`): una sola ambiguità per volta, opzioni informate, "lascia aperta
l'ambiguità" esplicita;
- **retrieval**: pack iniettato (`retrieval_pack.md`), `tht search find`, `tht schema render`, LSH con
`aggregate_lsh_multi` che espone *tutte* le colonne dove un valore appare (es. "ablazione" su flag
booleano + campo patologia testuale);
- **ledger**: `concept_clarified`, `ambiguity_open`, `value_grounded`, `question_rewritten`;
- **gate**: i widget che presentano le interpretazioni candidate.
"Extractorla" significa quindi, in ordine:
1. **definire il suo contratto input/output** — in: domanda + contesto di retrieval; out: ambiguità con
target group, opzioni candidate, segnale unanswerable distinto dall'ambiguo. È ciò che
`impact-ridisegno-disambiguazione.md` §3 già elenca (retrieval duale lessicale+semantico, target group,
unanswerable);
2. **darle una superficie** che l'orchestratore consuma (sezione del pack, comando, sezione skill dedicata)
invece di comportamento libero del modello;
3. **spostare la conoscenza che usa** fuori da SKILL.md e dentro le evidence (GAP 3).
Rischio specifico e strutturale: l'LLM è colla che **può bypassare qualunque contratto su carta**. In
questo codebase l'unico enforcement che funziona è a livello gate/CLI — il pattern è già dimostrato
dall'hook anti-bypass che blocca `tht phase advance`/`decision add` da shell e `tht memory promote`/
`save-one` fuori dal gate. Finché il contratto di disambiguazione non è enforcement (hook + CLI + skill
come contratto completo), la proprietà "le modifiche non impattano altri moduli" è una speranza, non una
proprietà verificabile.
## 5. I due quinti moduli nascosti
### 5.1 Il ledger + `workflow.yaml` — la colonna vertebrale condivisa
Tutti e tre i moduli leggono/scrivono il decision ledger (`review_decisions.jsonl`, sez. 3.1): la
disambiguazione
emette `concept_clarified`/`value_grounded`; l'evidence emette `evidence_accepted`/`evidence_rejected`/
`concept_formula_approved|rejected`; la memory legge `concept_clarified` ed emette `memory_*`. Il campo
`emits` di `workflow.yaml` accoppia fasi e decision type; `phase.py` piega il ledger per calcolare la
fase corrente.
Questo è un **bene**: il ledger è già un contratto a eventi tra i moduli. Ma è superficie condivisa —
tutti e tre i flussi vorranno toccarla. Va trattata come contratto a sé (quarto), con controllo dei
cambiamenti, non come territorio libero.
### 5.2 Il gate (`tht-gate.js`) e il vector store — i punti caldi di merge
- **Il gate** (sez. 3; ~2000 righe + builders/enrich/artifact-contracts) renderizza candidati evidence, dedup
opzioni memory, costruisce il widget schema-linking con id evidence, guida F2/F8 memory e fa enforcement
anti-bypass. **Tutti e tre i flussi avranno bisogno di modifiche al gate**: se non viene spaccato lungo
gli stessi confini dei moduli, diventa il punto caldo di merge del programma parallelo.
- **Il vector store**: una collection Qdrant per workspace, kind-separata (`schema_table`, `schema_column`,
`evidence`, `memory`, `solved_question`); il contratto di collection e payload-index è di proprietà del
backend TS (P4), lettura/scrittura sono Python. I record schema/evidence sono revision-scoped (P3),
memory/solved workspace-wide. Se evidence-canonica cambia la forma dei record/payload (il frontmatter
già fluisce libero nei metadata), gli assunti di indicizzazione della memory ne risentono. Il vector
store va designato come **infrastruttura condivisa con contratto proprio**, non come territorio
implicito di uno dei tre.
## 6. I contratti che sono le vere API
Il parallelismo è protetto da quattro contratti, nessuno dei quali è un'interfaccia Python:
1. **Decision type del ledger** (`workflow.yaml` `emits` + `decisions.py`): l'interfaccia a eventi tra i
tre moduli e il workflow. Ogni ridefinizione è una modifica di contratto, va versionata e comunicata;
2. **Proiezioni di consumo evidence** (`search pack` / `search find --kind evidence|formula`): la forma di
ciò che F1/F3/F4 vedono. È il cuore del ticket "Consumo per fase", che già porta i requisiti della F1
ridisegnata e **non va congelato prima** che il disegno F1 abbia almeno uno schizzo;
3. **Modello di contenuto memory** (`REUSABLE_TYPES` + campi del record + proiezione `promote --preview
--json`): dipende semanticamente dal contratto di output della disambiguazione (sez. 4.2);
4. **Id evidence citabile** (`doc#card` + estensione del resolver id→file): il contratto che tiene insieme
F3 (citazioni), F4 (candidati), decisions (subject) e report di sessione.
## 7. Ordine di sviluppo consigliato
Coerente con quanto già deciso in `map.md` e nell'impact doc:
1. **schizzo del disegno F1** della disambiguazione (anche non finale): target group, retrieval duale,
unanswerable — perché il contratto di consumo va progettato *contro la F1 ridisegnata*;
2. **chiusura del contratto "Consumo per fase"** (cerniera tra evidence-canonica e F1): proiezioni,
filtri, id citabili `doc#card` con resolver esteso;
3. **definizione del contratto disambiguazione→memory** (cosa è riusabile, con quali campi);
4. **solo allora** i tre flussi girano in parallelo, ciascuno nel suo worktree.
Strumento di protezione del parallelismo già disponibile in casa: il **pattern dei port-contract test**
(`test_evidence_port_contract.py`, `test_vector_port_contract.py`, `test_dwh_port_contract.py`).
Estenderlo ai nuovi confini (proiezioni evidence, contratto memory, enforcement di disambiguazione) è il
modo più economico per rendere l'indipendenza dei moduli **verificabile** invece che sperata: un flusso
rompe l'altro se e solo se rompe un test di contratto, non per effetti nascosti.
## 8. Rischi e mitigazioni
| Rischio | Natura | Mitigazione |
|---|---|---|
| L'LLM orchestratore bypassa il contratto di disambiguazione | strutturale (il workflow è LLM-driven) | enforcement gate/CLI sul modello dell'anti-bypass esistente; SKILL come contratto completo, non consiglio |
| Collisione disambiguazione↔memory su `concept_clarified` | semantica | contratto esplicito (sez. 6.3) definito prima del parallelismo |
| `tht-gate.js` punto caldo di merge | organizzativa | spaccare il gate lungo gli stessi confini dei moduli (builders per modulo) |
| Deriva del contratto vector store tra TS e Python | infrastrutturale | contratto di collection/payload/kind di proprietà unica, test di contratto su entrambi i lati |
| modifiche contagiose a `workflow.yaml`/ledger | processo | change control sul ledger come quarto contratto; `emits` data-driven già aiuta |
| contratti congelati troppo presto (Consumo per fase) | sequenza | ordine sez. 7: prima schizzo F1, poi chiusura contratto |
## 9. Riferimenti
- `docs/wayfinder/evidence-canonica/map.md` — mappa del sottoprogetto evidence-canonica (destinazione,
decisioni chiuse, nebbie);
- `docs/wayfinder/evidence-canonica/prototypes/verifica-workflow.md` — i 12 touchpoint evidence (T1–T12) e
i GAP 1–3: l'inventario dell'interfaccia esterna del futuro modulo evidence;
- `docs/wayfinder/evidence-canonica/prototypes/impact-ridisegno-disambiguazione.md` — valutazione d'impatto
del ridisegno F1 (requisiti: retrieval duale, target group, unanswerable; coordinamento dei due progetti);
- `docs/wayfinder/evidence-canonica/prototypes/PROTOTIPO-formato-unita-canonica.md` e `tipologia-evidence.md`
— formato card+involucro chiuso il 2026-08-21 e casistica a 7 famiglie;
- `docs/disambiguazione-iniziale.md` — stato attuale della disambiguazione;
- `docs/gestione-memory.md` — disegno della gestione memory;
- `harness/workflow.yaml`, `harness/.pi/skills/tht-sessione/SKILL.md`, `harness/.pi/extensions/tht-gate.js`
— le fonti di codice citate (fasi, discipline, gate);
- `docs/prd/2026-08-09-workspace-preprocessing-prd.md` — il PRD da cui nasce l'attuale catena
preprocessing/registry (contesto P2–P6 citato in sez. 4.1 e 5.2).