Files
ThothII/docs/superpowers/specs/2026-06-27-cli-port-completo-skill-riscritta-design.md
T
marcopan 292048f777 feat(harness): language workspace param + skill riscritta in inglese (semantica completa)
Due cambiamenti interconnessi da user review:

1. language come parametro workspace (spec decisione 9):
   - Config.language (default 'en') + workspaces PSD con 'language: it'
   - Generalizza Thoth oltre l'italiano: descrizioni tabelle/colonne ed evidence
     sono nel workspace language; le istruzioni della skill restano in inglese
     (piu' affidabili per modelli piccoli, meno ambigue)

2. Skill riscritta in INGLESE preservando la semantica COMPLETA dell'originale
   (autocritica: la mia riscrittura precedente aveva perso ~10 vincoli precisi):
   - 'promuovere' ambiguo (3 accezioni: phase advance / recommend / memory promote)
     -> 'never advance a phase or record a decision without confirmation'
   - recuperati vincoli persi: choice-is-confirmation (no reviewer_confirm dopo
     reviewer_decide), reviewer_select SOLO per iterazione no-decision, messaggi
     auto-contenuti obbligatori, artefatto = superficie di decisione (gate rilegge
     da disco per CTE/SQL), candidati con provenienza+score non verita', opzione
     'leave ambiguity open', F1 passa lista completa non solo ultima
   - language contract esplicito (istruzioni EN, output nel workspace language)

Sottomoduli cte/memoria/rewriting/sql-generation in inglese, semantica tecnica
intatta (regole AV-SQL, dim_time trick, max 5 memorie solo 3 tipi riusabili).

Verifica: 0 residui nsp/chirone, tutti i tht <cmd> citati registrati, 165 passed.
2026-06-27 14:50:30 +02:00

25 KiB
Raw Blame History

Design: porting CLI completo + riscrittura skill nsp-sessione

Data: 2026-06-27 Stato: bozza, in attesa di approvazione Contesto: il piano harness (A8–D6) ha portato nsp phase meta (1/12 comandi CLI) e i modelli dati/backend core, ma il loop skill→LLM→gate resta non esercitato perché mancano gli altri 11 comandi CLI + la skill nsp-sessione. Questo design porta il resto per rendere il loop testabile end-to-end.

Decisioni (approvate in brainstorming)

  1. Scope: F1→F8 completo. Tutti gli 11 cmd CLI mancanti + i 3 cluster backend mancanti + skill riscritta.
  2. Drift phase.py: riscrittura diretta sui 12 siti cmd che usano le vecchie costanti (MAX_PHASE/PHASE_NAMES/SCHEMA_LINKING_PHASE/DECISION_MIN_PHASE) → load_workflow() + metodi su Workflow. Niente strato wrapper.
  3. Skill: riscrittura completa ex-novo che riflette l'architettura ThothII (widget-descriptor, D11 save-one, D13 free-text, D14 value-grounding/formula, D15 rollback), prendendo spunto dalla struttura ChironeWp3 ma non copiandola.
  4. Indipendenza da ChironeWp3 (proprietà architetturale): quando ThothII è pronto, il server non deve avere ChironeWp3 installato — solo Supabase (DWH + pgvector con le RPC SECURITY DEFINER, indipendenti dal codice app) e la cartella evidence (dentro ThothII). Nessuna dipendenza dal repo chirone. Verificato: il codice ThothII non ha riferimenti a ChironeWp3/psdwp3 (rename completo); le RPC vivono nel DB.
  5. Memory: SOLO pgvector, niente registry. Le memory vivono esclusivamente nella tabella vectors.memory (vectordb condiviso). Il registry.jsonl di ChironeWp3 è eliminato (vestigiale: una volta che il metadata del vectordb contiene subject/detail/rationale, il registry non serve più). La promotion (F5) = upsert diretto a vectordb (save-one per una memoria, batch per molte). Le decisioni restano nel ledger di sessione (review_decisions.jsonl, per-sessione, separato dalle memory). Multi-workstation OK per costruzione: niente file locale da sincronizzare.
    • Listare memory → scan del vectordb.
    • "Cancellare" → il writer REST è upsert-only; non si cancella fisicamente, si marca metadata.status="superseded" (audit trail).
    • Re-index (modello embedding cambia) → operazione server-side che rilegge dal vectordb stesso.
    • Conseguenza nel codice: tht/memory.py NON porta load_registry/save_registry/update_record/delete_record/registry_path/promote(registry_path) (6 funzioni droppate). Restano MemoryRecord, memory_vector_records, save_one_memory. La promotion (F5) si riscrive come upsert batch al vectordb (Onda 3).
  6. Arricchimento metadata memory vectordb: subject/detail/rationale nel jsonb (vedi Sezione 3 punto 2). Correzione del gap ereditato da ChironeWp3. Reso possibile dal fatto che la tabella memory è vuota al momento del porting. Reso necessario dalla decisione 5 (senza registry, il vectordb è l'unica fonte).
  7. Evidence: repository separato per-cliente, NON dentro ThothII. Il contenuto evidence è specifico del dominio cliente (es. aritmologia @ policlinico). Struttura: ThothII è generico (zero contenuto cliente); un repo workspace separato per-cliente (es. tht-workspace-psd/) contiene evidence/, il workspace YAML cliente, e gli indici LSH (vedi punto 8). Deploy = checkout ThothII + checkout tht-workspace-<cliente>, con .env che punta THT_DOCS_ROOT al repo cliente. Niente "copia in harness/".
  8. LSH: scarica TUTTI i valori distinti, non "campiona". tht lsh build scarica tutti i valori distinti scaricabili delle colonne di testo dal DWH (parametrico, dipende dal workspace), costruisce i MinHash+LSH dentro harness come attività di preprocessing. L'indice risultante è per-cliente: vive nel repo workspace separato (decisione 7), non in harness/indexes/ generico.
  9. language come parametro del workspace. Il workspace dichiara la lingua in cui sono scritte le descrizioni di tabelle/colonne e le evidence (es. language: it per PSD). La skill lo legge e lo usa per: (a) interpretare i match LSH/evidence nel linguaggio giusto, (b) indirizzare il reviewer in quella lingua. Le istruzioni della skill restano in inglese (più affidabili per i modelli, meno ambigue); solo contenuto e output seguono il language. Default en. Rende Thoth non legato all'italiano (generalizzazione per altri clienti/domini).
  10. Renaming prodotto tht (Thoth = prodotto, PSD = cliente). Il codice non porta traccia del contesto clinico. Rinomine: comando nsp→tht, package nsp/→tht/ (46 file import), skill nsp-sessione→tht-sessione, gate nsp-gate.js→tht-gate.js, workspace chirone.*→tht.example.yaml/tht-test.yaml (generici-prodotto; un deploy cliente crea il suo psd.yaml non-committato). Variabili env THOTH_*→THT_* (uniformate al comando).
  11. Neutralizzazione riferimenti cliente nel codice. Commenti/docstring che menzionano "ChironeWp3", "PsdWp3", "DWH Chirone", "policlinico", "sandonato" vengono resi generici ("the reference implementation", "the DWH") o rimossi. Il contesto cliente (endpoint supabase-...policlinicosandonato.it, nomi schema datawarehouse) vive SOLO nei file di configurazione reali (.env gitignored, workspace cliente non-committato) — mai nel codice versionato né nei template.
  12. Renaming come Onda -1 (isolata, prima del porting CLI). Si fa come passo separato e verificato: dopo il rename, pytest deve restare 109 passed (verifica che il renaming non ha rotto nulla). Poi il porting CLI (Onde 0-4) avviene già col nome nuovo tht — niente doppio lavoro. Isolare il renaming dal porting permette di debuggare l'uno indipendentemente dall'altro.

Sezione 1 — Architettura e strategia di porting

Porting fedele con riscritture chirurgiche del drift, in ordine topologico (dipendenze radice prima), così ogni passo ha un'app che carica. Sei onde:

Onda -1 — renaming prodotto tht (ISOLATA, prima di tutto)
  nsp->tht (comando+package+46 import), nsp-sessione->tht-sessione,
  nsp-gate.js->tht-gate.js, chirone.*->tht.* workspace, THOTH_*->THT_* env.
  Neutralizzazione riferimenti chirone/psd/policlinico nei commenti/docstring.
  Verification: pytest resta 109 passed (rename non rompe nulla).

Onda 0 — backend mancanti (foglie)
  vendor/thoth_lsh, lshindex/, sqlcheck/, execute/, rest/execute.py,
  rest/explain.py, ctetest.py, report.py, datamart.py(stub)

Onda 1 — radici intra-CLI (espongono helper importati da tutti)
  config_cmd (CONFIG_OPT), schema_cmd (_load_config_or_exit, physical_path,
            annotations_path), session_cmd (session_dir, load_session_or_exit)
  + require_phase_or_exit riscritta ex-novo in phase_cmd
  + comandi advance/reopen/show di phase_cmd (oggi c'è solo meta)

Onda 2 — vector layer
  vector_cmd (make_embedder, open_store, open_searcher, require_vector_cfg)

Onda 3 — cmd foglia (usano radici + vector)
  memory_cmd, search_cmd, evidence_cmd, db_cmd, decision_cmd
  + arricchimento metadata memory (subject/detail/rationale in tht/memory.py:
    memory_vector_records) — correzione gap ereditato, vedi Sezione 3 punto 2

Onda 4 — cmd SQL/CTE (cluster sql_cmd)
  sql_cmd (do_run, do_explain, promoted_tables_for, _load_physical_or_exit)
  cte_cmd, datamart_cmd(stub), lsh_cmd (usa lshindex Onda 0)

Onda 0b — setup pre-sessione (dopo Onda 0 + Onda 4, prima della sessione L2)
  Cabla evidence nel workspace + .env, builda indice LSH sul workspace tht-test.
  NECESSARIO: senza di questo i claim D14/evidence della sessione L2 sono falsi
  (la F4 gira degradata, solo segnali vettoriali).

Dopo ogni onda: pytest verde (109, nessuna regressione) + import smoke (tht <sub> --help esce 0 per ogni nuovo sottocomando).

Sezione 2 — Porting CLI e drift (dettaglio)

Onda 0b — Setup pre-sessione (repo workspace per-cliente + LSH build)

Perché la sessione L2 validi davvero D14 (value-grounding) e F4 evidence-based (come claims la Sezione 4), servono due setup che oggi mancano. Entrambi sono per-cliente (decisioni 7+8), NON dentro ThothII.

Repo workspace per-cliente (decisione 7):

  • ThothII è generico. Il contenuto evidence è specifico del dominio cliente (aritmologia @ PSD nel caso di test). Struttura di deploy:
    checkout ThothII          # generico, niente contenuto cliente
    checkout tht-workspace-psd # repo separato: evidence/ + workspace YAML + indici LSH
    
    Con .env che punta THT_DOCS_ROOT=<repo-workspace>/evidence e THT_WORKSPACE=<repo-workspace>/psd.yaml.
  • Non si copia evidence in harness/. Ogni cliente ha il suo repo workspace.
  • Per il test L2 locale: il repo workspace PSD è /Users/mp/Chirone/chirone/etl/docs (la cartella esiste). Si crea un repo tht-workspace-psd/ che la referenzia/conteiene, separato da ThothII. Operazione one-shot del deploy, documentata in README, non nel codice ThothII.
  • Verifica: tht search --kind evidence "<termine>" ritorna risultati non vuoti su un termine noto.

LSH index (value-grounding D14a, decisione 8):

  • Dipende da tht.lshindex (portato in Onda 0) + tht lsh build (lsh_cmd, portato in Onda 4).
  • Build sul workspace cliente (UNA volta, prima della sessione):
    tht lsh build --workspace <repo-workspace>/psd.yaml
    
    Scarica TUTTI i valori distinti scaricabili delle colonne di testo dal DWH (parametrico, via REST db.sampling.unique_values_for_lsh_rest), costruisce i MinHash + LSH, li serializza nel path indexes/ del repo workspace cliente (NON in harness/indexes/).
  • Verifica: tht search "<valore noto>" ritorna match multi-colonna (es. "ablazione" su più colonne) e test_value_grounding_real (L2) smette di skip-piare.
  • Prerequisito: la build richiede il DWH raggiungibile (VPN) + embeddings (Ollama attivo). Operazione one-shot per cliente, non a ogni sessione.

Ordine: Onda 0b si esegue DOPO Onda 0 (lshindex) e Onda 4 (lsh_cmd), e PRIMA della sessione L2. È un'operazione dell'operatore (crea repo workspace + build indice), non codice nuovo — ma va nel piano come step esplicito con la sua verifica.

Onda 0 — backend mancanti

Tutti piccoli, nessun drift (non toccano phase/workflow). Port verbatim con rename psdwp3→tht:

Modulo Righe Dipendenze Note
vendor/thoth_lsh.py + VENDORED.md ~85 datasketch, tqdm (leaf puro) copia in tht/vendor/
lshindex/__init__.py ~85 vendor + LshConfig (portato) sed rename
sqlcheck/__init__.py ~125 ExecutionConfig + mschema.models (portati) leaf
execute/__init__.py + execute/warnings.py ~150 ExecutionConfig (portato) leaf
rest/execute.py + rest/explain.py ~60 execute + rest.client (portato)
ctetest.py ~110 sqlglot, pydantic leaf
report.py ~80 execute + sqlcheck (stessa onda)
datamart.py ~30 — stub raise NotImplementedError

Onda 1 — radici intra-CLI + require_phase_or_exit

I 3 cmd radice si portano verbatim (rename), eccetto gli import di session.phase → phase. Poi require_phase_or_exit riscritta ex-novo:

# phase_cmd.py — aggiunta a quanto già portato (meta)
def require_phase_or_exit(cfg, session: str, min_phase: int) -> None:
    from tht.workflow import load_workflow
    cur = current_phase(session_dir(cfg, session))
    if cur < min_phase:
        wf = load_workflow()
        nome = wf.phase_name(min_phase)
        typer.secho(
            f"Impossibile: serve la Fase {min_phase} ({nome}), sessione '{session}' è alla Fase {cur}.",
            fg=typer.colors.RED, err=True,
        )
        raise typer.Exit(1)

In questa onda si portano anche i comandi advance/reopen/show di phase_cmd con lo stesso fix dei 2 drift (const → load_workflow(), import path).

Drift: i 12 siti di costanti phase (meccanici, stesso pattern)

# PRIMA (ChironeWp3)
from psdwp3.session.phase import MAX_PHASE, PHASE_NAMES, SCHEMA_LINKING_PHASE, DECISION_MIN_PHASE
... MAX_PHASE ... PHASE_NAMES.get(n) ... DECISION_MIN_PHASE.get(type, 1) ... cur < SCHEMA_LINKING_PHASE

# DOPO (ThothII)
from tht.workflow import load_workflow
wf = load_workflow()
... wf.max_phase ... wf.phase_name(n) ... wf.decision_min_phase(type) ... cur < wf.schema_linking_phase()

Siti: 5 file cmd (phase_cmd, session_cmd, decision_cmd, cte_cmd, datamart_cmd).

Gap da colmare in Workflow: manca il metodo schema_linking_phase(). Da aggiungere a tht/workflow.py:

def schema_linking_phase(self) -> int:
    """La fase che produce schema_linking.json (artifacts_out). Default 5."""
    for p in self.phases:
        if "schema_linking.json" in p.artifacts_out:
            return p.num
    return 5

Onde 2-4 — cmd foglia e SQL/CTE

Port verbatim (rename + fix import path session.phase→phase). Nessun altro drift: DecisionType è generico via get_args (i 4 nuovi tipi ThothII si pickano automaticamente), i modelli si costruiscono da JSON (model_validate), i campi ThothII-added (grounded_values, concept_formulas, author/summary) sono opzionali con default.

Registrazione nell'app: alla fine di ogni onda, app.add_typer(<sub>_app, name="<sub>") in cli/__init__.py. Smoke verification: nsp <sub> --help esce 0.

Cross-cutting: ordine intra-CLI obbligato

  • _load_config_or_exit, physical_path, annotations_path, session_dir, load_session_or_exit, CONFIG_OPT sono definiti nei cmd radice (config_cmd/schema_cmd/session_cmd) e importati dalla maggior parte degli altri → portati per primi (Onda 1).
  • vector_cmd exports (make_embedder, open_store, open_searcher, require_vector_cfg) consumati da memory_cmd/search_cmd/evidence_cmd → vector_cmd in Onda 2, prima di quei tre (Onda 3).
  • sql_cmd exports (do_run, do_explain, promoted_tables_for, _load_physical_or_exit) consumati da cte_cmd + session_cmd → sql_cmd (Onda 4) prima di cte_cmd. (session_cmd è radice in Onda 1 ma i suoi path SQL dipendono da sql_cmd; verificare in implementazione se session_cmd va spostato dopo sql_cmd, o se i due path si disaccoppiano.)

Sezione 3 — Riscrittura skill nsp-sessione

Riscrittura ex-novo che prende spunto dalla struttura ChironeWp3 ma riflette ThothII. Posizionamento: harness/.pi/skills/nsp-sessione/ con SKILL.md + 4 sottomoduli (cte.md, memoria.md, rewriting.md, sql-generation.md).

Cosa si conserva dall'originale (valido, non reinventare)

  • Struttura a 8 fasi F1→F8 con prerequisito/postcondizione di ciascuna
  • Discipline trasversali (un fatto per comando nsp, una domanda alla volta, accetta-la-proposta sempre fra le opzioni, nessuno step in limbo, artefatto = output di prima classe)
  • Modello decisionale: reviewer_decide per decisioni descrittive (la scelta È la conferma), reviewer_confirm solo per i gate F1/F5/F7
  • Semantica dei comandi nsp (quando chiamarli, in che ordine) — ogni vincolo preciso (es. "max 5 memorie candidate, solo 3 tipi riusabili: concept_clarified/table_promoted/table_excluded") va trasferito fedelmente leggendo l'originale riga per riga, non parafrasato

Cosa si riscrive per riflettere ThothII

  1. Modello gate → widget-descriptor. La prosa originale dice "dialog native / checklist a caselle". Riscritta in termini di widget: reviewer_select (single-pick), reviewer_decide (multiselect con payload decisione), reviewer_confirm (artifact-gate). Le invarianti (Altro sempre presente, no-limbo, recommended marker) sono garantite dai builder (C1). La disciplina resta identica, il vocabolario cambia.

  2. Fase 2 — save-one (D11). Originale: memory promote (scriveva il registry) poi memory index (full resync vectordb dal registry). ThothII droppa il registry (decisione 5): le memory vivono SOLO nel vectordb. La promozione di una singola memoria usa tht memory save-one (upsert mirato via writer key, implementato in B2). La skill F5 (promozione batch) si riscrive come upsert batch diretto a vectordb (niente registry intermedio). La F2 (applicazione) legge dal vectordb; grazie all'arricchimento metadata (decisione 6), l'hit basta per ricostruire la decisione. Onesta sul limite: il writer REST è upsert-only, la "cancellazione" è logica (metadata.status="superseded").

    Arricchimento metadata vectordb (correzione del gap + reso necessario dal drop del registry). La tabella vectors.memory aveva metadata = {type, session_id, tables, concepts} — mancavano subject/detail/rationale strutturati. ThothII arricchisce: in memory_vector_records (tht/memory.py) si aggiungono subject/detail/rationale al dict metadata del VectorRecord. pack_metadata (rest_writer.py:26) li serializza automaticamente nel jsonb via **record.metadata. Nessuna modifica al writer RPC, nessuna modifica allo schema DB. search_similar proietta già metadata completo → la F2 ricostruisce la decisione direttamente dall'hit. Senza registry, questo arricchimento è obbligatorio (non opzionale): il vectordb è l'unica fonte. Momento ideale: la tabella memory è vuota, niente re-indicizzazione di dati esistenti.

  3. Fase 3 — riscrittura (invariata nel modello, solo vocabolario gate). Prerequisito "devi essere già in Fase 3" (exit 5, tht/phase.py:171), question_rewritten come decisione, sequenza ordinata (decisione → rewrite_question tool → nsp session set-question scrive question.md deterministicamente, session/store.py:set_question). Allineamento widget-descriptor. Gestione modifica/rifiuto invariata ("Altro" itera, "Torna indietro" riapre F1).

  4. Fase 4 — value grounding (D14a) + formula (D14b). Originale F4 menziona solo table_promoted/excluded/column_corrected. ThothII aggiunge:

    • value_grounded: quando un valore citato (es. "ablazione") matcha più colonne (flag + testo), il modello presenta reviewer_decide con opzioni value_grounded per ogni colonna candidata (usando aggregate_lsh_multi che non collassa al miglior match). Il reviewer sceglie l'ancora.
    • concept_formula_approved/rejected: quando un concetto (es. "fascia pediatrica", "stesso anno") ha una formula SQL candidata (retrieve_formula), il modello la presenta e il reviewer approva/rifiuta. Per la domanda di test, candidati naturali: estrazione anno da data, calcolo età nell'anno dell'ablazione, predicato same-year fra due eventi.
  5. Discipline — free-text (D13). Aggiunta: quando il reviewer usa "Altro" con testo libero, il modello valuta il testo in contesto, agisce, ri-chiede se ambiguo (non defaulta). Il testo registrato nel rationale della decisione (contratto testato in B5).

  6. Discipline — rollback (D15). Aggiunta: dopo /torna N o "Torna indietro", il modello riprende dalla fase N rivedendo gli artefatti esistenti; teardown_to_phase cancella gli artefatti oltre il target. Il modello NON ri-esegue comandi nsp per artefatti ancora validi.

  7. Fase 8 — datamart resta stub. datamart.py è raise NotImplementedError. La skill lo guida come nell'originale (decisione sì/no + hook stub), onesta sul fatto che non genera nulla.

Sottomoduli

cte.md, memoria.md, rewriting.md, sql-generation.md portati adattandone la prosa gate (stessa logica del punto 1 sopra), mantenendo la sostanza tecnica.

Frontmatter

name: nsp-sessione, description riscritta per ThothII (NL→SQL su Chirone, fasi 1-8, widget-descriptor gate, evoluzioni D11/D13/D14/D15).

Metodo di scrittura (per preservare l'intento originale)

L'intento preciso di ogni fase (vincoli come "max 5 memorie candidate", "solo 3 tipi riusabili", "checklist pre-selezionata con la proposta", sequenze ordinate) si trasferisce leggendo l'originale riga per riga durante l'implementazione, non parafrasando. Se durante la scrittura un vincolo risulta poco chiaro, si chiede invece di indovinare.

Sezione 4 — Testing e criteri di successo

Test automatici (CI-runnabile, a ogni onda)

Porting CLI + backend (Onde 0-4):

  • Dopo ogni onda, pytest (L0+L1) deve restare 109 passed — nessuna regressione.
  • Import smoke per onda: python -c "from nsp.<modulo> import ..." per ogni nuovo backend; nsp <sub> --help esce 0 per ogni nuovo sottocomando.
  • Drift L1 (nuovo test): esercita require_phase_or_exit su fasi sintetiche — conferma che la riscrittura contro Workflow funziona. Pure logic, tmp_path, no DB.

Skill:

  • Non unit-testable in L1 (prosa per il modello).
  • Verifica automatica: grep -c "PsdWp3\|psdwp3" in harness/.pi/skills/nsp-sessione/ = 0; ogni tht <cmd> citato corrisponde a un sottocomando registrato in cli/__init__.py (script di coerenza).

Test L2 — la sessione di test (manuale, pre-rilascio)

Domanda di test (complessa, massima copertura):

Crea una lista con i pazienti che negli ultimi 20 anni hanno avuto una cardioversione elettrica ed una ablazione lo stesso anno. Per ogni paziente incluso nella lista esponi il sesso, l'età che aveva il paziente nell'anno in cui ha fatto l'ablazione, tutti i dati rilevanti dell'ablazione e tutti i dati rilevanti della cardioversione.

Verifica sullo schema reale: entrambi i concetti hanno fact table dedicate (fact_cardioversione_elettrica_transtoracica, fact_studio_elettrofisiologico_endocavitario_ablazione + ~13 fact_see_ablazione_* di dettaglio). La domanda esercita F4 complesso (2 fact + ~15 dim/detail, join multipli), D14 (value-grounding: "ablazione"/"cardioversione elettrica" matchano nomi-tabella e campi/patologie; formula: "stesso anno", "età nell'anno dell'ablazione"), F6 (decomposizione CTE naturale), F7 (SQL finale complesso).

Criteri di successo della sessione:

  1. Avvio: /nuova-domanda "<la domanda>" → il modello chiama nsp session new, legge la skill, inizia F1.
  2. Gate funziona: il modello chiama i tool reviewer_* e il gate presenta widget-descriptor (non prosa grezza). Il reviewer può rispondere.
  3. Persistenza: ogni decisione del reviewer produce una riga in review_decisions.jsonl (verificabile leggendo il file dopo).
  4. Avanzamento fasi: il workflow avanza F1→... secondo le decisioni, usando i comandi nsp corretti (non phase advance manuale, non decision add diretto).
  5. Anti-bypass: se il modello prova nsp phase advance da shell, il gate lo blocca (hook tool_call).
  6. Artefatti: per una domanda che arriva in fondo, schema_linking.json + cte_plan.json/ctes/*.sql + sql_final.sql prodotti e coerenti col ledger.

Copertura per evoluzione:

Evoluzione Esercitata? Come
F1/F3/F5 (base) sì chiarimento, riscrittura, sintesi
F4 complesso sì 2 fact + ~15 dim, join multipli
D14a value-grounding sì (se Onda 0b fatta) "ablazione"/"cardioversione" matchano tabella + campi; richiede indice LSH buildato (Onda 0b)
D14b formula sì "stesso anno", "età nell'anno ablazione"
D13 free-text se innesca se il reviewer corregge via "Altro"
D11 save-one (F2) solo se memorie trovate se nsp memory search in F2 trova match (NOTA: tabella memory azzerata di recente — la prima sessione probabilmente salta F2 senza match, quindi D11 va validato con una seconda sessione dopo aver popolato le memorie). L'arricchimento metadata (Onda 3) fa sì che quando F2 trova match, l'hit basta per applicare la memoria senza lookup registro.
F6 CTE sì decomposizione naturale
D15 rollback se innesca se il reviewer torna indietro

Onestà

  • Più copertura = più punti di rottura. Una domanda complessa ha più probabilità di fallire a metà in punti che una semplice non tocca. Un fallimento a metà non è un fallimento del porting — è il segnale che L2 è designed a cogliere. Va letto come tale.
  • Durata: sessione lunga (più decisioni, CTE uno a uno). Va bene per test pre-rilascio, non per ripeterlo spesso.
  • Gate glue in CI: resta verificato solo nella sessione L2 manuale senza un fake-Pi mock (follow-up cross-cutting noto).
  • Comportamento non deterministico di GLM 5.2: la sessione può andare diversamente a ogni run. L2 è safety-net pre-rilascio, non regression gate.

Ordine di esecuzione e definition of done

  1. Onda -1 (renaming tht) → pytest verde 109 passed + tht --help funziona + grep -rw nsp = 0 nel codice
  2. Onda 0 (backend) → pytest verde + import smoke
  3. Onda 1 (radici CLI + require_phase_or_exit + phase advance/reopen/show) → pytest verde + tht phase advance --help esce 0
  4. Onda 2-4 (cmd foglia + SQL/CTE) → pytest verde + tht --help mostra tutti i sottocomandi
  5. Skill riscritta (tht-sessione) → script di coerenza (no PsdWp3/chirone, cmd citati = cmd registrati)
  6. Onda 0b (setup pre-sessione) → copiare evidence in harness/evidence/ + tht lsh build su tht-test; verifica tht search ritorna LSH + evidence
  7. Sessione L2 (manuale, con l'operatore) → criteri 1-6 sopra

Definition of done: Onde 0-4 + skill completate con pytest verde + sessione L2 che soddisfa i criteri 1-4 (avvio, gate, persistenza, avanzamento). I criteri 5-6 (anti-bypass, artefatti finali) sono verification aggiuntiva nella stessa sessione.

Fuori scope (rimane aperto)

  • Gate glue in CI (fake-Pi mock): follow-up cross-cutting. Il glue nsp-gate.js resta verificato solo in L2 manuale.
  • L2 come regression gate: resta pre-rilascio, non CI.
  • RPC mancanti lato server (validate_select, current_user): rilevate 404 nei test. Non bloccano il loop base, ma vanno (ri)installate lato Supabase per i path pre-SQL.