# Evidence canonica — struttura tipizzata per disambiguazione, schema linking e SQL ## Contesto e decisioni prese ThothII ha già due livelli separati che non si parlano: - **`EvidenceDoc`** (`harness/tht/evidence/model.py`) — runtime: frontmatter piatto (`id/title/tier/status/tables/concepts/sources`) + `body` markdown libero. `tier` distingue solo `structural|concept`, insufficiente rispetto ai 6 tipi reali del corpus. - **Pipeline corpus** (`harness/tht/corpus/`) — canonizzazione *tecnica* (hash, provenienza, chunking, vettorizzazione), agnostica rispetto al tipo di evidenza. Il corpus reale (`ChironeWp3/artifacts/evidence/`) ha una tassonomia implicita in 6 directory (`00-glossario`, `10-domini-clinici`, `20-valori-enum`, `30-esempi-nlq`, `40-mapping-semantico`, `50-metadati-normalizzazione`) ma: - frontmatter piatto, `tier` inadeguato; - file già rotti (`--` invece di `---`, bullet `•⁠ ⁠` invece di `-`) che `EvidenceDoc.parse` rifiuterebbe; - al retrieval la struttura si perde: `tht search pack` proietta solo `title` + 400 char. **Decisioni (confermate nel brainstorming):** 1. **Obiettivo**: strutturare il *contenuto* runtime, non toccare la pipeline corpus. 2. **Forma**: ibrido — frontmatter tipizzato + sezioni canoniche per `kind`. 3. **Tassonomia**: doppia — `kind` (contenuto) + `applies_to` (destinazioni: `disambiguation`, `rewriting`, `schema_linking`, `sql_generation`, `memory`). 4. **Anchors come fonte primaria** per il value-grounding in F4; LSH solo fallback. 5. **Approccio**: contratto in `tht` + authoring sottile (niente app separata, niente client LLM diretto in `tht`). 6. **Modello sorgente→canonizzato** (non sovrascrittura in place): file umani in `source_root/evidence/`, canonizzati derivati in `artifacts/evidence/`, coerente con l'esistente `tht evidence extract`. 7. **Review**: batch con diff aggregato (approvazione per file). 8. **Rielaborazione**: LLM-assistita via Pi (sessione di manutenzione + gate), con parte deterministica in `tht`. ## Modello canonico v2 ### `CanonicalEvidence` (frontmatter tipizzato) ```yaml --- schema_version: 2 id: ev-dom-ablazione-see title: Dominio Ablazione e SEE kind: domain # glossario | domain | enum | example | mapping | normalization applies_to: # destinazioni d'uso - disambiguation # F1 - schema_linking # F4 - sql_generation # F6/F7 status: reviewed language: it concepts: - term: ablazione synonyms: [ablazione transcatetere, SEE, studio elettrofisiologico] - term: fibrillazione atriale synonyms: [FA] tables: - name: datawarehouse.fact_studio_elettrofisiologico_endocavitario_ablazione role: fact columns: [ablazione_transcatetere, cod_paz, num] anchors: - column: datawarehouse.fact_see_ablazione_procedura_patologia.patologia value: ablazione match: exact sources: [...] # provenienza del derivato (aggiunta dal canonicalize, non dall'umano): source_file: 10-domini-clinici/ablazione.md source_fingerprint: sha256:... canonicalized_at: 2026-08-18T... --- ``` Il `body` resta markdown, ma con **titoli di sezione canonici per `kind`**: - `glossario`: `## Definizione` - `domain`: `## Cosa rappresenta`, `## Schema a stella`, `## Granularità`, `## Domande di business tipiche` - `enum`: `## Valori ammessi` - `example`: `## Domanda → SQL` + `## Nota clinica` - `mapping`: `## Trasformazioni disponibili` - `normalization`: `## Regole di normalizzazione` Le sezioni canoniche permettono al retrieval di estrarre solo la sezione rilevante per fase invece di 400 caratteri generici. ### `kind` vs `applies_to` - `kind` descrive il *contenuto* (i 6 tipi già impliciti). - `applies_to` descrive *quando usarlo*. Esempi: `enum-tipo-intervento` è `kind: enum` ma `applies_to: [disambiguation, sql_generation]`; un `example` è `applies_to: [sql_generation, memory]`. ## Modello a 3 livelli ``` source_root/evidence/*.md (umano, libero, può essere sporco) │ tht evidence canonicalize (deterministico + LLM via Pi + review batch) ▼ artifacts/evidence/*.md (canonico, validato, derivato, rigenerabile) │ tht evidence index (esistente) ▼ corpus / vector store (vettorializzato dal canonico, mai dal sorgente) ``` Il canonizzato porta `source_file` + `source_fingerprint`: se il sorgente cambia, la canonizzazione ripropone il diff; se invariato, no-op. ## Componenti da creare/modificare ### 1. `harness/tht/evidence/model.py` (modifica) - Aggiungere `CanonicalEvidence` (pydantic, v2) con `schema_version`, `kind`, `applies_to`, `concepts[]` (`{term, synonyms[]}`), `tables[]` (`{name, role, columns[]}`), `anchors[]` (`{column, value, match}`), `source_file`, `source_fingerprint`, `canonicalized_at`. - Validatore strict: `kind` ammesso, `applies_to` ammesso, `anchors[].column` deve essere `schema.colonna` ben formato, `match` in `{exact, contains, regex, substring}`. - `EvidenceDoc` v1 resta per leggere il sorgente e per retro-compatibilità dei vecchi canonizzati; `CanonicalEvidence` è un modello separato, non una sottoclasse. - Validatore che garantisce `source_fingerprint` = sha256 del contenuto sorgente. ### 2. `harness/tht/evidence/lint.py` (nuovo, deterministico) - `lint_source(path) -> list[Diagnostic]`: frontmatter rotto, `tier`/`status` non validi, bullet Unicode, campi mancanti, `tables` non qualificate con schema, concetti senza sinonimi, sezioni non canoniche per il `kind` atteso. - Exit code 0 se nessun errore, 1 se warning, 2 se errori. Nessun LLM. ### 3. `harness/tht/evidence/canonicalize.py` (nuovo) - `plan(sources, artifacts) -> CanonicalizePlan`: confronta fingerprint dei sorgenti con i canonizzati esistenti, produce la lista dei file da (ri)canonizzare. - `render_proposal(source, llm_completions) -> CanonicalEvidence`: assembla il canonizzato da parsing deterministico + campi semantici forniti da Pi. - `apply(plan, approved_ids) -> None`: scrive solo i canonizzati approvati in `artifacts/evidence/`, atomico per file, mantiene la gerarchia per dominio. - `diff(source, candidate) -> str`: diff markdown per il gate. ### 4. `harness/tht/vectorstore/records.py` (modifica) - `evidence_records()`: metadata ora include `kind`, `applies_to`, `anchors` (non solo `status/tier/tables/concepts`). - Il `content` di ogni record resta title+body, ma per file con sezioni canoniche si indicizza anche un record per sezione (id `evidence::`) così il retrieval può restringere per sezione oltre che per documento. ### 5. `harness/tht/search/__init__.py` + `cli/search_cmd.py` (modifica) - `SearchResult` e `combined_search` accettano `applies_to` come filtro metadata. - `tht search pack`: la sezione "Evidence rilevanti" ora proietta `kind` + sezione canonica pertinente (non solo excerpt generico); usa `applies_to` per non mischiare destinazioni. - `tht search find --kind evidence --applies-to ` per il retrieval mirato. ### 6. `harness/tht/cli/evidence_cmd.py` (modifica) - Nuovi comandi: - `tht evidence lint --source ` — diagnostica deterministica. - `tht evidence canonicalize plan --source-root --json` — piano di (ri)canonizzazione. - `tht evidence canonicalize apply --plan --approved --json` — applica. - `--json` sempre pristine (solo JSON su stdout), come da contratto di progetto. ### 7. `harness/.pi/extensions/tht-gate.js` (modifica) - Nuovo tool `reviewer_evidence_batch`: riceve il piano + i diff aggregati e presenta un multiselect per file (approva/rifiuta/richiedi modifica). Le scelte approvate vengono persistite tramite `tht evidence canonicalize apply`. - Aggiungere `tht evidence canonicalize apply` alla FORBIDDEN anti-bypass list (il modello non può applicare canonizzazioni senza review). ### 8. Sessione di manutenzione Pi (nuova skill o sezione SKILL) - Una skill dedicata `tht-evidence-canonicalize` (o un comando slash nel gate) descrive la sessione di manutenzione: il modello legge i sorgenti marcati dal piano, propone i campi semantici (`kind`, `applies_to`, `concepts` con sinonimi, `tables` con ruolo, `anchors`) e li invia al `reviewer_evidence_batch`. - Il gate è l'unico canale di review; il modello non scrive mai direttamente `artifacts/evidence/`. ### 9. `harness/.pi/skills/tht-sessione/SKILL.md` (modifica) - Aggiornare i punti F1/F4/F6-F7 dove il modello legge evidence: spiegare che l'evidence canonica espone `kind`/`applies_to`/`anchors`, che gli `anchors` sono la fonte primaria per il value-grounding in F4, e che le sezioni canoniche vanno citate per destinazione. - Aggiungere il nuovo tool `reviewer_evidence_batch` alla lista dei tool disponibili. ## Migrazione del corpus (incrementale, v1+v2 convivono) - `CanonicalEvidence` (v2) convive con `EvidenceDoc` (v1): `lint` segnala i non-canonici ma nulla si rompe; `load_evidence_dir` carica entrambi. - Convertire a lotti, partendo da `20-valori-enum` e `10-domini-clinici` (impatto maggiore su F1/F4, anchors più ricchi). - Il canonizzato derivato in `artifacts/evidence/` sostituisce progressivamente il file sorgente mirrorato; finché un sorgente non è canonizzato, `extract` continua a rispecchiare il v1 come oggi. ## Ordine di implementazione 1. `CanonicalEvidence` + `lint` (TDD, nessun LLM). 2. `canonicalize.py` (plan/apply/diff) + comandi CLI (TDD). 3. `records.py` + filtro `applies_to` in `search` + proiezione pack (TDD). 4. Gate `reviewer_evidence_batch` + skill manutenzione Pi. 5. Aggiornamento `SKILL.md` tht-sessione. 6. Conversione primo lotto (`enum` + `domain`) con review batch. ## Verifica - `cd harness && .venv/bin/pytest -q` — test nuovi per model/lint/canonicalize/records/search. - `node --test` sul gate per `reviewer_evidence_batch` e anti-bypass. - Su un campione del corpus: `tht evidence lint` riporta i file rotti (frontmatter `--`, bullet Unicode) senza crash; `tht evidence canonicalize plan --json` produce piano corretto; `apply` con fingerprint invariato è no-op. - `tht search pack ""` mostra evidence con `kind`/sezione pertinente e gli anchor presenti; `tht search find --kind evidence --applies-to schema_linking` restituisce solo evidence pertinenti. - Live: una sessione F4 su psd usa un anchor canonico per il value-grounding invece del solo LSH (verificabile dal `reviewer_decide` con opzioni `value_grounded` provenienti dall'evidence, non dal ranking LSH). - `git diff --check` e typecheck/ruff puliti.