- loadEnvFromDotenv: read ctx.cwd/.env into the process environment
- prepareReviewerArguments: normalize/parse reviewer tool inputs
- tidy reserved-option filtering and tht command argument assembly
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
workspace/provider/model/thinking now come from getSettings() injected into
sessionRoutes; the request body supplies only question+name. Also teaches
fake_pi_rpc to respond to set_model and set_thinking_level RPC commands so
tests that pass real model settings don't hang.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Harness:
- preview_cmd FILE positional arg made optional; when omitted with --session,
path is derived via _session_sql_file (mirrors export_cmd) — fixes the
deferred Task-5 bug where the backend passed sessions/<id>/sql_final.sql
relative to harnessDir, which broke for workspace-dependent paths.
- New pytest: test_preview_session_no_file_resolves_sql_final
Backend:
- ThtRunner.sqlPreview: drop positional file arg; use --session only
- New routes/sql.ts: POST /sessions/:id/sql/preview + /export
- New routes/meta.ts: GET /workspaces (yaml scan) + GET /models (injectable
seam + graceful fallback to {models:[]})
- app.ts: register sqlRoutes + metaRoutes; add listModels to BuildAppDeps
- tht-runner.test.ts: add sqlPreview argv assertion (no file path)
- test/routes-sql-meta.test.ts: 9 tests (sql preview/export + meta routes)
Tests: harness 233 passed; backend 29 passed; build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set quietStartup:true in .pi/settings.json to suppress Pi banner on RPC stdout.
Add test pinning both quietStartup and theme values. Document project-local trust
requirement (--approve) so no trust prompt blocks RPC loop iteration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- test_list_sessions_required_keys: assert full spec key set
(adds summary/updated_at/author) so dropping any goes caught.
- test_cli_show_json_valid: assert "schema" in / "db_schema" not in
data to pin by_alias=True on the alias-sensitive field.
Production code unchanged. 8 passed; full suite 230 passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When offset>0 the wrapper added an outer LIMIT N, so run_controlled's
_inject_limit bailed (a LIMIT IS present) and truncated was always False —
AGGrid could never detect more rows. Fix: for offset>0 probe with LIMIT (N+1)
OFFSET M, then compute truncated = len(rows) > N in do_run and slice back to N.
offset==0 path unchanged (delegates to extracted _run_transport helper). JSON
still reports the user's requested limit N and correct truncated. Adds 3 tests
exercising the real do_run offset>0 path (N+1 -> truncated True, N -> False,
offset==0 verbatim). 222/222 passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add inject_limit_offset (tht/execute/limit.py) — pure subquery wrapper that
applies LIMIT/OFFSET non-destructively without clobbering user-supplied LIMITs.
Wire offset param into do_run (pre-processing when offset>0) and add --offset /
--json flags to preview_cmd; JSON mode emits pristine stdout with columns, rows,
execution_ms, truncated, limit, offset. 5 new tests (4 unit + 1 JSON-purity),
219/219 total passing (no regressions).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When THT_SESSION env var is set, /nuova-domanda injects a kickoff that
tells the model to use the pre-created session id instead of running
`tht session new`. /riprendi-sessione and no-env-var paths unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed event.source === "interactive" guard from entry detection so
/nuova-domanda and /riprendi-sessione activate lockActive+pendingKickoff
regardless of source (TUI or RPC prompt).
- Removed event.source !== "interactive" from free-input filter; lock now
blocks/steers all user input when active, not only interactive keystrokes.
- Added typebox@1.1.38 devDep + fake_pi_runtime.registerCommand (gap from Task 2).
- New test: gate_entry.test.js (2 tests: lock activates on RPC; !-steer passes).
- Full suite: 19/19 pass, zero regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implementazione del piano di remediation progressiva sui difetti emersi
dall'analisi dell'harness. Tutto verificato: 214 test Python (incl. L0 su
Postgres reale), 14 test JS del gate, ruff pulito.
Blocco 1 (CRITICA, integrazione gate↔CLI):
- phase advance: gate usa --auto + exit 6; reviewer_confirm kind:phase fa
advance esplicito che applica i prerequisiti (prima non avanzava per le
fasi a conferma umana).
- cte plan riceve i --name dal gate (param names); set-question con id
posizionale; skill `tht search find`; nuovo comando `tht memory save-one`
con dedup hash client-side in save_one_memory.
Blocco 2 (D15, stato post-rollback):
- campo `phase` su DecisionRecord + effective_decisions phase-aware per i
subject "a nome" (cte_approved ecc.); _compute_promotions e finalize sulla
vista effective; finalize confronta col piano CTE effettivo, non glob;
`decision add --retracts` + comando `decision retract`.
Blocco 3 (D7 read-only + D6 manifest):
- assert_read_only su tutti e quattro i codepath (direct + REST);
- manifest author/summary/updated_at/updated_by/schema_version popolati +
helper touch_manifest sulle mutazioni.
Blocco 4-5 (D14a/D14b):
- decision_min_phase data-driven via `emits:` in workflow.yaml;
- formula evidence: status auto, search_formulas, gruppo CLI `tht formula`,
`search find --kind formula`, load_evidence_dir salta i .sql.md.
Blocco 6 (robustezza):
- taskdoc slice promoted_tables + bound enforced; report escaping/bound +
rsplit note; filtro kind reader REST/direct; conteggio upserted robusto;
guard REST run_query non-list; LSH disallineato -> LshIndexError.
Blocco 7 (pulizia):
- dead code gate e KIND_TO_TABLE morto rimossi; doc Postgres-only
(README + connection.py).
Blocco 0 (parziale): test di compatibilità firma gate↔CLI
(tests/integration). Rinviati: fake-Pi runtime completo, artifact-gate da
disco (#23), parità eligibility REST/direct (#28), unificazione
reserved-labels (#30), memory_rejected da deselezione (#33).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prima sessione L2 end-to-end dopo il porting. Il loop skill->LLM->gate funziona nel
dominio (ricerche, evidence, quadro corretto su fact_cardioversione/fact_see_ablazione)
ma si blocca a F1 sul bug fatale #4 (ctx.sendRaw non esiste nel runtime Pi).
Bug emersi (4):
#1 tht non nel PATH di Pi (basso, workaround wrapper)
#2 phase show non passava config (medio, FIXATO: _cfg() risolve env+default)
#3 session check signature inconsistente (basso, da verificare)
#4 ctx.sendRaw is not a function (FATALE, mismatch architetturale: il porting ha
sostituito i dialog nativi ctx.ui.* con ctx.sendRaw, API non esposta in questo Pi)
Analisi #4 (verificata sul runtime installato):
- ctx.sendRaw non esiste; extension_ui_request e' emesso solo dal runtime
(modes/rpc) come traduzione di ctx.ui.select/confirm/input, non come API extension
- canali RPC per decisioni: enum chiuso select/confirm/input/editor (no custom)
- ctx.ui.custom (multiselect TUI di ChironeWp3) e' no-op in RPC mode
- conseguenza: multiselect F4 non ha canale in RPC -> decisione di design del gate
aperta (opzioni A/B/C nel report, C=scartato), merita brainstorming dedicato
Fix#2 (dal modello in sessione, validato e ripulito): phase_cmd._cfg() ora risolve
THT_WORKSPACE/THT_CONFIG env poi fallback config/tht.yaml (stessa convenzione CONFIG_OPT).
Testato phase show OK, suite 165 passed.
Report: docs/l2-run-report-2026-06-27.md.
Bug di porting emerso in L2 prep: tht session new falliva con
'AttributeError: SessionManifest has no to_yaml'. Lo stub locale _YamlModel in
session/models.py (placeholder pre-porting mschema) definiva solo
populate_by_name, senza i metodi to_yaml/from_yaml che store.py e session_cmd.py
usano. Aggiunti gli stessi metodi della controparte mschema (mantenendo
populate_by_name, necessario per db_schema alias='schema').
config/tht.yaml: symlink locale al workspace cliente attivo (psd.yaml). Il gate
chiama tht senza -c (default config/tht.yaml), quindi serve questo ponte per il
deployment per-cliente. Gitignored (per-cliente). .gitignore: + config/tht.yaml,
+ artifacts/.
Verifica: pytest 165 passed; tht session new crea sessione nel repo cliente;
pi vede i 3 tool reviewer_* (gate caricato); GLM 5.2 e' il model default di pi.
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.
Skill ex-novo che riflette Thoth (non copia di ChironeWp3):
- vocabolario widget-descriptor (reviewer_select/decide/confirm) invece di 'dialog native'
- D11 save-one in F2 (upsert mirato vs full resync)
- D14a value_grounded (LSH multi-colonna non collassa) + D14b concept_formula in F4
- D13 free-text e D15 rollback nelle discipline trasversali
- memory vive SOLO nel vectordb (drop registry, spec 5): save-one/promote senza registry
- F8 datamart onesto (stub NotImplementedError)
Sottomoduli cte/memoria/rewriting/sql-generation portati adattando nsp->tht, con
i vincoli precisi trasferiti fedelmente (max 5 memorie, solo 3 tipi riusabili,
CTE solo WITH senza SELECT, dim_time join non aritmetica, sql_final pulito).
Verifica: zero residui nsp/chirone/psd nella skill; ogni 'tht <cmd>' citato e'
registrato (correzione: 'tht formula retrieve' era inesistente -> riformulato in
'ricerca nelle evidence'). Suite: 165 passed.
Ultima onda CLI. 4 cmd portati con rename + grep-per-file (3 residui nsp nei messaggi
fixati). Nessun drift costanti phase in questi cmd.
La CLI tht e' ora COMPLETA: 14 gruppi di comandi (phase config schema session vector
memory search evidence db decision sql cte datamart lsh). tht --help li list tutti.
Suite: 165 passed.
Il loop skill->LLM->gate ora ha tutti i comandi che la skill chiamera'. Resta:
skill riscritta (S), setup pre-sessione (0b), sessione L2 manuale.
Correzione del gap ereditato (resosi NECESSARIO dal drop del registry, spec 5): il
metadata del VectorRecord memory ora porta subject/detail/rationale oltre a
type/session_id/tables/concepts. pack_metadata li serializza nel jsonb via
**record.metadata. search_similar proietta metadata completo -> la F2 ricostruisce
la decisione direttamente dall'hit, senza lookup registro.
L1: 4 test (subject/detail/rationale presenti, campi esistenti preservati,
no cross-contamination multi-record, save_one_memory propaga il metadata alla riga).
Suite: 165 passed.
Stesso bug del precedente: VENDORED.md e' arrivato con Onda 0 (dopo l'Onda -1 che
aveva pulito i riferimenti cliente). Neutralizzato a 'il datawarehouse' (coerente
con le altre neutralizzazioni). Sweep completo tht/ ora vuoto per chirone/psdwp3/
policlinico/sandonato.
User review ha trovato 2 residui 'nsp' sfuggiti al renaming: erano nei moduli
portati in Onda 0 (DOPO l'Onda -1 che aveva pulito), in messaggi utente/docstring
non in import. L'import-smoke di Onda 0 non li catturava (verifica solo import, non
stringhe). Corretti a tht: 'tht lsh build' (lshindex:61), 'tht schema introspect'
(VENDORED.md:25).
Lesson: dopo ogni port di file sorgente, grep di nsp su quel file, non solo import-smoke.
Suite: 161 passed, zero residui nsp nel codice.
require_phase_or_exit: guard riscritto vs Workflow (load_workflow().phase_name invece
della costante PHASE_NAMES drift). Exit 1 se la sessione e' sotto soglia. Usato da
cte/decision/datamart cmd.
Comandi phase (portati + adattati al modello ThothII, non copia cieca):
- advance: persiste phase_approved; --auto exit 6 se la fase non e' completa
(contratto col gate)
- reopen: persiste phase_reopened + teardown_to_phase degli artefatti oltre il target
- show: stato sessione (fase corrente, ultime decisioni)
session_dir helper tenuto qui (mirror di session_cmd) per evitare circular import.
_cfg() fa fallback a THT_WORKSPACE env finche' _load_config_or_exit (Onda 1.4) non
sara' portato.
L1: 4 test require_phase_or_exit (allow at/above, exit below, message con nome fase
dal workflow). Suite: 161 passed.
Aggiunge il metodo che i cmd CLI useranno al posto della vecchia costante
SCHEMA_LINKING_PHASE (drift fix Onda 1). Ritorna il num della fase il cui
artifacts_out contiene schema_linking.json, default 5 se nessuna la dichiara.
L1: 4 test (fase reale F4, posizione arbitraria, default 5, artefatti multipli).
Suite: 157 passed.
44 test L1 sui 3 moduli backend con logica non banale (opzione 2 della user review):
- sqlcheck.validate_sql (16 test): parse/single-statement, read-only enforcement
(INSERT/UPDATE/DELETE/CREATE/DROP/ALTER/TRUNCATE/GRANT rifiutati, WITH/UNION ok),
forbidden functions (dblink default blacklist, custom set, allowed not flagged),
object-existence (tabella inesistente, CTE non flaggata, perimetro promoted warning,
colonna inesistente con alias). Documenta una limitazione reale: le funzioni
aggregate specializzate (count/sum/coalesce) NON sono catturate dal name-matcher
perche' sqlglot modella .name come argomento, non come nome funzione.
- ctetest (14 test): has_trailing_select (semantica controintuitiva: True = violazione),
last_cte_name, build_test_sql, ledger I/O (load/append roundtrip, JSON-array e
JSONL tolleranti, corrupt-ledger raise).
- execute._inject_limit (6 test): LIMIT iniettato quando assente (limit+1 per
troncamento), rispettato quando presente, non iniettato su non-query, UNION/WITH ok.
Suite: 153 passed (109 + 44). Bonus: __psd_probe__ -> __tht_probe__ (riferimento
cliente neutralizzato in ctetest).
Bug trovato provando la connessione reale col .env: il write endpoint vive su un
PATH DEDICATO /vector/write/v1/ (non /vector/v1/), e il modello Config ha write_rest/
vector_write_rest a TOP-LEVEL (non nidificati in vector_db).
- .env.example: aggiunge THOTH_VEC_WRITE_REST_URL (path dedicato del writer, con
avviso che le due chiavi valgono su path separati).
- workspaces/chirone-test.yaml: riscritto allineato a chirone.example.yaml + config.py
(vector_rest/vector_write_rest top-level; write_rest punta a THOTH_VEC_WRITE_REST_URL).
- tests/l2/*: corretti gli accessi strutturali (ws.vector_write_rest invece di
ws.vector_db.write_rest; ws.vector_rest invece di ws.vector_db.rest).
test_value_grounding_real skip-when-import-fails su nsp.lshindex (modulo deferred da B3).
Verificato end-to-end: save_one_memory (embeddings -> writer REST /vector/write/v1/
-> upsert pgvector -> read-back reader) PASSED. Suite L0+L1: 109 passed. Suite L2:
4 passed, 1 skipped (lshindex deferred).
Nota operativa: THOTH_SSL_CA va lasciato VUOTO sulla workstation (cert GoDaddy
pubblico in certifi). I campi direct-transport (THOTH_DB_*, THOTH_VEC_PASSWORD)
sono obbligatori per il modello ma inutilizzati in transport=rest: riempiti con
dummy nel .env locale (come faceva ChironeWp3).
Two fixes found while unblocking the L2 setup:
1. conftest: THOTH_SSL_CA is NOT an L2 prerequisite. The DWH endpoint presents a
public cert (*.policlinicosandonato.it, signed by GoDaddy), already in the
certifi bundle, so the REST clients validate TLS with verify=True -- no CA file
needed. The ssl_ca line was commented out in ChironeWp3's nsp.yaml too.
2. test_workspace: the profile-default assertion collided with the operator's real
harness/.env once load_dotenv (D3) started injecting THOTH_PROFILE into the
process env. The test now dels THOTH_PROFILE to assert the actual *default*
(server), regardless of what the operator set in .env.
Suite: 109 passed.
README: install, configure (.env + workspaces/), the workflow, run inside Pi, the
three-level test commands, layout, references.
docs/workflow-editing.md: how to edit workflow.yaml (add/reorder/merge/skip phases,
advance kinds, prerequisite predicates, decision_min_phase derivation, artifacts_out
+ teardown) -- referencing spec §5.3. Emphasizes no mirrored constants (the F2 point).
docs/testing.md: the honest L0/L1/L2 split in plain language -- what each covers and
does NOT. States the headline plainly: the skill->LLM->gate loop has NO automated
regression coverage (L2 only, pre-release). Documents the fake-Pi follow-up as the
gap-closer. Security note on keys (.env gitignored, never logged, rotate leaked keys).
Pre-release, non-deterministic tests (marker l2, skipped without .env + VPN). They
close the gaps L1 leaves open: real value grounding on the live schema, real memory
save-one upsert to pgvector, and the full GLM 5.2 -> gate conversation on the
'ablazione' question (which exercises D14 value grounding + formula on a multi-
column case + the gate glue L1 cannot reach).
workspaces/chirone-test.yaml points at the remote endpoints (DWH read-only +
pgvector dual-key, TLS self-signed); secrets via ${THOTH_*}.
- test_session_ablazione: precondition checks (workspace loads, env present, pi on
PATH) + the documented manual run protocol (human-in-the-loop; scripted-answers
variant is a follow-up). Default run skips cleanly.
- test_value_grounding_real: 'ablazione' grounds to multiple columns on the real
schema (D14a non-collapsing), needs a built LSH index.
- test_memory_save_one_real: save_one_memory upserts one row via the writer key
(D11) and search_similar retrieves it via the reader key.
Operator runs before release (pytest -m l2). Default run: 109 passed, 5 skipped.
conftest loads harness/.env once (session, autouse) via python-dotenv, and exposes
an l2_env fixture that SKIPS (not fails) when any L2 prerequisite var is missing/
empty: THOTH_DWH_API_KEY, THOTH_VEC_API_KEY, THOTH_VEC_WRITE_API_KEY, THOTH_SSL_CA.
So the default run (pytest = L0+L1, addopts '-m not l2') stays green without .env;
only pytest -m l2 (pre-release, with .env + VPN) exercises them. l0/l2 markers were
registered in A9. tests/l2/ package created for the L2 tests (D4, D5).
settings.json (theme: thothii-mono), the two slash-command prompts
(/nuova-domanda, /riprendi-sessione), and the theme JSON. Renamed PsdWp3 -> ThothII
in prompt prose and the theme name. No tests (config files). pi --mode rpc launched
with cwd=harness/ finds the .pi/ directory + the extensions (nsp-gate.js, gate/).
Pure-logic smoke (no LLM, no DB) that builds a synthetic ledger by hand and asserts
the Phase-A substrate stays coherent: full F1->F8 walk reaches terminal phase
(max+1); rollback truncates the effective view (stale phase-7 decision excluded
after reopen to F4) and resets current_phase; teardown deletes artifacts beyond the
target while preserving the target phase's; re-approve after rollback advances
correctly; taskdoc stays under byte budget across all phases; decision_retraction
excludes the retracted seq + the marker itself from effective_decisions.
This is the CI-runnable coherence net for the L2 session test (which exercises the
LLM->gate loop that L1 cannot).
Rewrite of ChironeWp3's gate extension. The pure widget-descriptor CONSTRUCTION
is in ./gate/builders.js (L1-tested, C1); this file is the GLUE -- it depends on
the Pi runtime (pi.on, pi.registerTool, ctx.sendRaw) and is verified end-to-end at
L2 (Task D4), NOT unit-tested here. A fake-Pi runtime mock (cross-cutting
follow-up) would let it run in CI.
PRESERVED VERBATIM (load-bearing runtime glue, spec D4):
- anti-bypass tool_call hook: FORBIDDEN (nsp phase advance|reopen, decision add,
cte plan) + PROTECTED_FILES (review_decisions.jsonl, session_manifest.yaml,
cte_plan.json)
- input lock + the input hook: /nuova-domanda|/riprendi-sessione entry detection,
free-input block, the `!`-prefixed steer channel
- before_agent_start kickoff injection + the two kickoff payloads (model prose)
- agent_end prose safety net (nudges the model back to reviewer_* tools)
- session_start state reset
- exit-code contracts with the CLI (5 = gate refusal, 6 = needs human,
7 = not-ready silent no-op)
- textResult / nsp() / relayIfNspFails / advanceIfReady helpers
TWO CORRECTIVE CHANGES vs source:
1. F2 single source: workflow facts (max_phase, phase names, schema-linking phase)
come from `nsp phase meta --json`, NOT from JS-mirrored constants. The source's
PHASE_NAMES array (truncated to 7) is gone; F8/datamart can no longer drift.
2. D2/D4 widget-descriptor: reviewer interaction is emitted as a widget-descriptor
(built by ./gate/builders.js) and awaited by id via emitAndWait + the
extension_ui_response dispatcher. This replaces the source's blocking native TUI
primitives (ctx.ui.select/custom) and introduces the correlation-by-id layer
ChironeWp3 never had.
Four tools wired: reviewer_select, reviewer_decide (persists via nsp decision add),
reviewer_confirm (gate; privileged action on approve), rewrite_question. Plus the
/torna slash command for rollback. No-limbo invariant preserved: cancel/undefined
re-presents the widget; real escapes are always in the descriptor's reserved field.