From fc5fbe6b656a00d5c0dd3256e2930b576ed41d91 Mon Sep 17 00:00:00 2001 From: mptyl Date: Sat, 27 Jun 2026 10:26:06 +0200 Subject: [PATCH] refactor(harness): renaming prodotto tht (Onda -1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thoth (tht) è il prodotto, PSD è il cliente. Nessun riferimento al contesto clinico nel codice. Rinomine: - comando+package nsp→tht (dir nsp/→tht/, 46 import, pyproject entry point) - gate nsp-gate.js→tht-gate.js (+ rewrite token, relayIfNspFails→relayIfThtFails) - workspace chirone.{example,test}.yaml→tht.{example,test}.yaml (generici) - env THOTH_→THT_ (19 var) + NSP_ stragglers (NSP_HARNESS_ROOT, NSP_SESSION) - commenti/docstring chirone/psdwp3/policlinico neutralizzati ('the reference implementation', 'the DWH') Aggiunto [tool.setuptools.packages.find] include=['tht*'] (necessario: l'auto- discovery rompeva con tht/ + workspaces/ come top-level multipli). .env operatore aggiornato in-place (prefissi THT_, valori preservati, gitignored). Verifica: pytest 109 passed, npm test 14 pass, tht phase meta --json OK, zero residui nsp/THOTH_/NSP_/chirone nel package. --- harness/.env.example | 38 ++++----- harness/.pi/extensions/gate/builders.js | 2 +- harness/.pi/extensions/reserved-labels.mjs | 2 +- .../extensions/{nsp-gate.js => tht-gate.js} | 82 +++++++++---------- harness/README.md | 28 +++---- harness/docs/workflow-editing.md | 8 +- harness/pyproject.toml | 7 +- harness/tests/conftest.py | 10 +-- harness/tests/l0/test_db_connection.py | 2 +- harness/tests/l0/test_db_introspect.py | 2 +- harness/tests/l0/test_db_sampling.py | 6 +- harness/tests/l2/test_memory_save_one_real.py | 12 +-- harness/tests/l2/test_session_ablazione.py | 12 +-- harness/tests/l2/test_value_grounding_real.py | 20 ++--- harness/tests/test_cli_phase_meta.py | 4 +- harness/tests/test_decisions_retract.py | 4 +- harness/tests/test_formula.py | 4 +- harness/tests/test_freetext_interpretation.py | 2 +- harness/tests/test_memory_save_one.py | 6 +- harness/tests/test_mschema_eligibility.py | 6 +- harness/tests/test_mschema_render.py | 6 +- harness/tests/test_phase_effective.py | 4 +- harness/tests/test_rest_client.py | 10 +-- harness/tests/test_session_coherence_smoke.py | 10 +-- harness/tests/test_taskdoc.py | 6 +- harness/tests/test_teardown.py | 2 +- harness/tests/test_value_grounding.py | 4 +- harness/tests/test_vector_dual_key.py | 6 +- harness/tests/test_workflow.py | 2 +- harness/tests/test_workspace.py | 72 ++++++++-------- harness/{nsp => tht}/__init__.py | 0 harness/{nsp => tht}/cli/__init__.py | 10 +-- harness/{nsp => tht}/cli/_guards.py | 0 harness/{nsp => tht}/cli/phase_cmd.py | 6 +- harness/{nsp => tht}/config.py | 12 +-- harness/{nsp => tht}/db/__init__.py | 0 harness/{nsp => tht}/db/connection.py | 2 +- harness/{nsp => tht}/db/fetch_ca.py | 4 +- harness/{nsp => tht}/db/introspect.py | 4 +- harness/{nsp => tht}/db/sampling.py | 8 +- harness/{nsp => tht}/decisions.py | 2 +- harness/{nsp => tht}/evidence/__init__.py | 0 .../{nsp => tht}/evidence/formula_store.py | 0 harness/{nsp => tht}/evidence/model.py | 0 harness/{nsp => tht}/memory.py | 12 +-- harness/{nsp => tht}/mschema/__init__.py | 0 harness/{nsp => tht}/mschema/eligibility.py | 10 +-- harness/{nsp => tht}/mschema/merge.py | 2 +- harness/{nsp => tht}/mschema/models.py | 0 harness/{nsp => tht}/mschema/render.py | 4 +- harness/{nsp => tht}/phase.py | 8 +- harness/{nsp => tht}/rest/__init__.py | 0 harness/{nsp => tht}/rest/client.py | 4 +- harness/{nsp => tht}/search/__init__.py | 2 +- harness/{nsp => tht}/session/__init__.py | 0 harness/{nsp => tht}/session/artifacts.py | 4 +- harness/{nsp => tht}/session/models.py | 2 +- harness/{nsp => tht}/session/store.py | 6 +- harness/{nsp => tht}/taskdoc.py | 4 +- harness/{nsp => tht}/teardown.py | 4 +- harness/{nsp => tht}/textutil.py | 0 harness/{nsp => tht}/vectorstore/__init__.py | 0 .../{nsp => tht}/vectorstore/embeddings.py | 2 +- harness/{nsp => tht}/vectorstore/reader.py | 6 +- harness/{nsp => tht}/vectorstore/records.py | 4 +- .../{nsp => tht}/vectorstore/rest_client.py | 2 +- .../{nsp => tht}/vectorstore/rest_writer.py | 6 +- harness/{nsp => tht}/vectorstore/store.py | 4 +- harness/{nsp => tht}/workflow.py | 4 +- harness/{nsp => tht}/workspace.py | 8 +- .../{chirone-test.yaml => tht-test.yaml} | 44 +++++----- ...{chirone.example.yaml => tht.example.yaml} | 46 +++++------ 72 files changed, 309 insertions(+), 306 deletions(-) rename harness/.pi/extensions/{nsp-gate.js => tht-gate.js} (89%) rename harness/{nsp => tht}/__init__.py (100%) rename harness/{nsp => tht}/cli/__init__.py (77%) rename harness/{nsp => tht}/cli/_guards.py (100%) rename harness/{nsp => tht}/cli/phase_cmd.py (90%) rename harness/{nsp => tht}/config.py (94%) rename harness/{nsp => tht}/db/__init__.py (100%) rename harness/{nsp => tht}/db/connection.py (96%) rename harness/{nsp => tht}/db/fetch_ca.py (97%) rename harness/{nsp => tht}/db/introspect.py (98%) rename harness/{nsp => tht}/db/sampling.py (96%) rename harness/{nsp => tht}/decisions.py (96%) rename harness/{nsp => tht}/evidence/__init__.py (100%) rename harness/{nsp => tht}/evidence/formula_store.py (100%) rename harness/{nsp => tht}/evidence/model.py (100%) rename harness/{nsp => tht}/memory.py (96%) rename harness/{nsp => tht}/mschema/__init__.py (100%) rename harness/{nsp => tht}/mschema/eligibility.py (93%) rename harness/{nsp => tht}/mschema/merge.py (90%) rename harness/{nsp => tht}/mschema/models.py (100%) rename harness/{nsp => tht}/mschema/render.py (97%) rename harness/{nsp => tht}/phase.py (97%) rename harness/{nsp => tht}/rest/__init__.py (100%) rename harness/{nsp => tht}/rest/client.py (97%) rename harness/{nsp => tht}/search/__init__.py (99%) rename harness/{nsp => tht}/session/__init__.py (100%) rename harness/{nsp => tht}/session/artifacts.py (93%) rename harness/{nsp => tht}/session/models.py (95%) rename harness/{nsp => tht}/session/store.py (95%) rename harness/{nsp => tht}/taskdoc.py (96%) rename harness/{nsp => tht}/teardown.py (96%) rename harness/{nsp => tht}/textutil.py (100%) rename harness/{nsp => tht}/vectorstore/__init__.py (100%) rename harness/{nsp => tht}/vectorstore/embeddings.py (97%) rename harness/{nsp => tht}/vectorstore/reader.py (93%) rename harness/{nsp => tht}/vectorstore/records.py (97%) rename harness/{nsp => tht}/vectorstore/rest_client.py (99%) rename harness/{nsp => tht}/vectorstore/rest_writer.py (94%) rename harness/{nsp => tht}/vectorstore/store.py (98%) rename harness/{nsp => tht}/workflow.py (95%) rename harness/{nsp => tht}/workspace.py (72%) rename harness/workspaces/{chirone-test.yaml => tht-test.yaml} (61%) rename harness/workspaces/{chirone.example.yaml => tht.example.yaml} (55%) diff --git a/harness/.env.example b/harness/.env.example index 74d49a13..1ea607f5 100644 --- a/harness/.env.example +++ b/harness/.env.example @@ -1,38 +1,38 @@ # ThothII profile: server (full rebuild) | workstation (read REST + optional upsert) -THOTH_PROFILE=server +THT_PROFILE=server # Workspace DB (relational, direct transport) — example: chirone -THOTH_DB_HOST= -THOTH_DB_PORT=5432 -THOTH_DB_NAME= -THOTH_DB_USER= -THOTH_DB_PASSWORD= +THT_DB_HOST= +THT_DB_PORT=5432 +THT_DB_NAME= +THT_DB_USER= +THT_DB_PASSWORD= # DWH via REST (richiesto se database.transport = rest) -THOTH_DWH_REST_URL=https://supabase-aritmolab.policlinicosandonato.it/dwh/ -THOTH_DWH_API_KEY= +THT_DWH_REST_URL=https://supabase-aritmolab.policlinicosandonato.it/dwh/ +THT_DWH_API_KEY= # Evidence source root (cartella curata a mano con le evidence) -THOTH_DOCS_ROOT= +THT_DOCS_ROOT= # Vector REST — LETTURA (rpc search_similar) sul Supabase remoto -THOTH_VEC_REST_URL=https://host/vector/v1/ -THOTH_VEC_API_KEY= +THT_VEC_REST_URL=https://host/vector/v1/ +THT_VEC_API_KEY= # Vector REST — SCRITTURA controllata (upsert only) da client remoti autorizzati # Path DEDICATO: /vector/write/v1/ (NON /vector/v1/). Le due chiavi valgono su path # separati: la writer key su /vector/v1/ -> 401, e viceversa. Solo postazioni che indicizzano. -THOTH_VEC_WRITE_REST_URL=https://host/vector/write/v1/ -THOTH_VEC_WRITE_API_KEY= +THT_VEC_WRITE_REST_URL=https://host/vector/write/v1/ +THT_VEC_WRITE_API_KEY= # Vector direct loading (server-only) -THOTH_VEC_HOST=localhost -THOTH_VEC_PORT=5438 -THOTH_VEC_USER=postgres -THOTH_VEC_PASSWORD= +THT_VEC_HOST=localhost +THT_VEC_PORT=5438 +THT_VEC_USER=postgres +THT_VEC_PASSWORD= # Embeddings (Ollama) -THOTH_OLLAMA_URL=http://localhost:11434 +THT_OLLAMA_URL=http://localhost:11434 # TLS (optional, internal CA) -THOTH_SSL_CA= +THT_SSL_CA= diff --git a/harness/.pi/extensions/gate/builders.js b/harness/.pi/extensions/gate/builders.js index 7d13eb63..912ac766 100644 --- a/harness/.pi/extensions/gate/builders.js +++ b/harness/.pi/extensions/gate/builders.js @@ -3,7 +3,7 @@ // Each function turns plain params into a ui_request descriptor object. No Pi // context, no I/O -- this is the part of the gate that is fully testable in L1 // (in JS, in-language, no Python mirror). The glue (emission via ctx.sendRaw, -// anti-bypass, the no-limbo loop) is in nsp-gate.js and is verified at L2. +// anti-bypass, the no-limbo loop) is in tht-gate.js and is verified at L2. // // The 6 widget kinds: info, select, multiselect, freetext, artifact-gate, artifact. // `widget` is an open field (a new kind needs a renderer, not infra changes); diff --git a/harness/.pi/extensions/reserved-labels.mjs b/harness/.pi/extensions/reserved-labels.mjs index e929bcd4..bbb97070 100644 --- a/harness/.pi/extensions/reserved-labels.mjs +++ b/harness/.pi/extensions/reserved-labels.mjs @@ -1,5 +1,5 @@ // Etichette riservate del gate HITL e helper di deduplica. Modulo PURO (nessuna -// dipendenza da pi-tui): condiviso da nsp-gate.js e testabile in isolamento con +// dipendenza da pi-tui): condiviso da tht-gate.js e testabile in isolamento con // node --test .pi/extensions/test_reserved_labels.mjs // `Altro — specifica…` e le due vie di fuga (torna indietro / esci) vengono // SEMPRE aggiunte dal gate alle opzioni in arrivo: se il modello ri-propone uno diff --git a/harness/.pi/extensions/nsp-gate.js b/harness/.pi/extensions/tht-gate.js similarity index 89% rename from harness/.pi/extensions/nsp-gate.js rename to harness/.pi/extensions/tht-gate.js index 0aae71e5..8fb87e6b 100644 --- a/harness/.pi/extensions/nsp-gate.js +++ b/harness/.pi/extensions/tht-gate.js @@ -1,9 +1,9 @@ -// nsp-gate.js -- Pi extension: the HITL gate for the ThothII NL->SQL workflow. +// tht-gate.js -- Pi extension: the HITL gate for the ThothII NL->SQL workflow. // -// REWRITE of ChironeWp3's nsp-gate.js. Two changes vs the source: +// REWRITE of the reference implementation's tht-gate.js. Two changes vs the source: // (1) F2 single source: workflow facts (max_phase, phase names, the schema-linking -// phase) come from `nsp phase meta --json`, NOT from JS-mirrored constants. -// The ChironeWp3 PHASE_NAMES array (truncated to 7) is gone; F8/datamart can no +// phase) come from `tht phase meta --json`, NOT from JS-mirrored constants. +// the reference implementation PHASE_NAMES array (truncated to 7) is gone; F8/datamart can no // longer drift out of sync. // (2) D2/D4 widget-descriptor: the reviewer interaction is emitted as a // widget-descriptor JSON (built by ./gate/builders.js) and awaited by id, instead @@ -16,7 +16,7 @@ // - the agent_end prose safety net // - the exit-code contracts with the CLI (5 = gate refusal, 6 = needs human, // 7 = not-ready silent no-op) -// - textResult / nsp() / relayIfNspFails / advanceIfReady helpers +// - textResult / tht() / relayIfThtFails / advanceIfReady helpers // // TESTING: the pure builders are L1-tested (./gate/__tests__/). This file is the // GLUE -- it depends on the Pi runtime (pi.on, pi.registerTool, ctx.sendRaw) and is @@ -37,44 +37,44 @@ import { ALTRO, BACK_LABEL, QUIT_LABEL, CONTROL_LABELS, isReserved, stripReserve // --- anti-bypass block lists (spec D4, verbatim from source L169-177) ----------- const FORBIDDEN = [ - /\bnsp\s+phase\s+(advance|reopen)\b/, - /\bnsp\s+decision\s+add\b/, - /\bnsp\s+cte\s+plan\b/, + /\btht\s+phase\s+(advance|reopen)\b/, + /\btht\s+decision\s+add\b/, + /\btht\s+cte\s+plan\b/, ]; const PROTECTED_FILES = /(review_decisions\.jsonl|session_manifest\.yaml|cte_plan\.json)/; // --- kickoff payloads (verbatim from source L184-212, load-bearing model prose) - const NUOVA_DOMANDA_KICKOFF = "Istruzioni operative — nuova sessione ThothII (workflow human-in-the-middle: tu " + - "orchestri, il reviewer decide, la CLI `nsp` persiste; NON sei in modalita' autonoma).\n" + - '1. Esegui `nsp session new ""` e annota ' + + "orchestri, il reviewer decide, la CLI `tht` persiste; NON sei in modalita' autonoma).\n" + + '1. Esegui `tht session new ""` e annota ' + "l'id stampato nell'ultima riga.\n" + - "2. Carica la skill leggendo il file con il tool `read`: `.pi/skills/nsp-sessione/SKILL.md` " + + "2. Carica la skill leggendo il file con il tool `read`: `.pi/skills/tht-sessione/SKILL.md` " + "(NON come comando di shell ne' come `/skill:...`). Poi segui il suo workflow dalla Fase 1 " + - "(Chiarimento), usando l'id di sessione in ogni comando `nsp`.\n" + + "(Chiarimento), usando l'id di sessione in ogni comando `tht`.\n" + "Se la skill non si carica (per qualsiasi motivo): FERMATI. Non proseguire da solo, non " + - "improvvisare analisi o query. Comunica al reviewer che la skill nsp-sessione non e' " + + "improvvisare analisi o query. Comunica al reviewer che la skill tht-sessione non e' " + "disponibile e attendi istruzioni.\n" + "Regole non negoziabili (valgono SEMPRE, anche senza la skill):\n" + "- Una domanda al reviewer per volta; attendi la sua risposta prima di proseguire.\n" + "- MAI promuovere, escludere, correggere o applicare alcunche' senza conferma esplicita.\n" + "- Le interazioni col reviewer passano dai tool reviewer_select/reviewer_decide/" + "reviewer_confirm (widget-descriptor); il reviewer invia testo libero prefissando '!'. " + - "NON eseguire mai `nsp phase advance|reopen` ne' `nsp decision add` da shell.\n" + + "NON eseguire mai `tht phase advance|reopen` ne' `tht decision add` da shell.\n" + "- Procedi una fase alla volta: a fine fase cedi il turno al reviewer, non incatenare le fasi."; const RIPRENDI_KICKOFF = "Istruzioni operative — ripresa di una sessione ThothII esistente (id nel messaggio sopra).\n" + - "1. Esegui `nsp session show ` e leggi: stato, domanda, decisioni registrate, presenza " + + "1. Esegui `tht session show ` e leggi: stato, domanda, decisioni registrate, presenza " + "di schema_linking.json.\n" + - "2. Carica la skill leggendo `.pi/skills/nsp-sessione/SKILL.md` con il tool `read` (non come " + + "2. Carica la skill leggendo `.pi/skills/tht-sessione/SKILL.md` con il tool `read` (non come " + "comando di shell ne' `/skill:...`).\n" + "3. Determina l'ultima fase completata dai fatti persistiti (le decisioni sono la verita': " + "cio' che non e' registrato non e' avvenuto) e riprendi da li'.\n" + "Valgono le stesse regole non negoziabili: una domanda per volta, conferma esplicita, tool " + "reviewer_*, niente phase advance/reopen o decision add da shell, una fase alla volta."; -// Recovery hint shown when `nsp phase advance` refuses with exit 5 (gate not satisfied). +// Recovery hint shown when `tht phase advance` refuses with exit 5 (gate not satisfied). const PHASE_RECOVERY = "Completa i prerequisiti della fase (decisioni/artefatti) e riprova."; // --- helpers (verbatim from source) ------------------------------------------- @@ -84,15 +84,15 @@ function textResult(text) { } // Single chokepoint for all CLI calls. cwd is the Pi project root (harness/). -function nsp(ctx, args) { - return execFileSync("nsp", args, { cwd: ctx.cwd, encoding: "utf8" }); +function tht(ctx, args) { + return execFileSync("tht", args, { cwd: ctx.cwd, encoding: "utf8" }); } -// Runs a privileged nsp call; converts any failure into an actionable textResult +// Runs a privileged tht call; converts any failure into an actionable textResult // (never propagates a raw "Command failed" to the model). -function relayIfNspFails(ctx, args, recovery) { +function relayIfThtFails(ctx, args, recovery) { try { - nsp(ctx, args); + tht(ctx, args); return null; } catch (e) { const cliMsg = (e.stderr || e.message || String(e)).toString().trim(); @@ -100,13 +100,13 @@ function relayIfNspFails(ctx, args, recovery) { } } -// F2 single-source: workflow facts from `nsp phase meta --json`. Cached per session. +// F2 single-source: workflow facts from `tht phase meta --json`. Cached per session. // Replaces the source's mirrored PHASE_NAMES constant (which drifted to 7 entries) -// and the regex-parse of `nsp phase show` text. +// and the regex-parse of `tht phase show` text. let _phaseMetaCache = null; function phaseMeta(ctx) { if (_phaseMetaCache) return _phaseMetaCache; - const raw = nsp(ctx, ["phase", "meta", "--json"]); + const raw = tht(ctx, ["phase", "meta", "--json"]); const meta = JSON.parse(raw); _phaseMetaCache = meta; return meta; @@ -127,15 +127,15 @@ function schemaLinkingPhase(ctx) { } function currentPhase(ctx, session) { - const out = nsp(ctx, ["phase", "show", "--session", session]); + const out = tht(ctx, ["phase", "show", "--session", session]); const m = out.match(/Fase corrente:\s*(\d+)/); return m ? parseInt(m[1], 10) : 1; } -// nsp phase advance --if-ready: exit 7 = not ready (silent no-op), others propagated. +// tht phase advance --if-ready: exit 7 = not ready (silent no-op), others propagated. function advanceIfReady(ctx, session) { try { - nsp(ctx, ["phase", "advance", "--if-ready", "--session", session]); + tht(ctx, ["phase", "advance", "--if-ready", "--session", session]); return { advanced: true }; } catch (e) { if (e.status === 7) return { advanced: false }; @@ -146,7 +146,7 @@ function advanceIfReady(ctx, session) { // --- widget emission + wait (NEW: replaces ctx.ui.* blocking primitives) ------- // // emitAndWait(ctx, descriptor) sends a ui_request widget and awaits the correlated -// ui_response by id. This is the correlation-by-id layer ChironeWp3 never had (its +// ui_response by id. This is the correlation-by-id layer the reference implementation never had (its // native TUI primitives handled it implicitly). The descriptor is built by the pure // ./gate/builders.js (L1-tested). // @@ -199,8 +199,8 @@ export default function (pi) { pi.on("tool_call", (event) => { if (event.toolName === "bash") { const cmd = event.input?.command ?? ""; - // nsp session finalize: legit session-close path -- unlock and let through. - if (/\bnsp\s+session\s+finalize\b/.test(cmd)) { + // tht session finalize: legit session-close path -- unlock and let through. + if (/\btht\s+session\s+finalize\b/.test(cmd)) { lockActive = false; lastSteered = false; return; @@ -350,8 +350,8 @@ export default function (pi) { label: "Decisione di merito (reviewer)", description: "Pone una decisione di merito al reviewer via widget multiselect e PERSISTE le " + - "scelte (nsp decision add). Ogni opzione porta un payload decision {type, subject, " + - "detail, rationale}. Dopo la conferma, se p.advance e' vero tenta nsp phase advance --if-ready.", + "scelte (tht decision add). Ogni opzione porta un payload decision {type, subject, " + + "detail, rationale}. Dopo la conferma, se p.advance e' vero tenta tht phase advance --if-ready.", parameters: Type.Object({ session: Type.String(), title: Type.String(), @@ -397,7 +397,7 @@ export default function (pi) { "--subject", d.subject]; if (d.detail) args.push("--detail", d.detail); if (d.rationale) args.push("--rationale", d.rationale); - const err = relayIfNspFails(ctx, args, ""); + const err = relayIfThtFails(ctx, args, ""); if (err) return err; toAdd.push(d); } @@ -415,7 +415,7 @@ export default function (pi) { label: "Gate di avanzamento (reviewer)", description: "Checkpoint di fase/CTE/SQL: presenta un widget artifact-gate (artefatto + " + - "Approva/Rifiuta/Altro) ed esegue l'azione privilegiata (nsp phase advance, cte plan, " + + "Approva/Rifiuta/Altro) ed esegue l'azione privilegiata (tht phase advance, cte plan, " + "decision add sql_approved) solo su approvazione. kind: phase | cte_plan | cte_result | sql.", parameters: Type.Object({ session: Type.String(), @@ -459,13 +459,13 @@ export default function (pi) { return textResult(`Fase approvata (sessione ${session}).`); } if (kind === "cte_plan") { - const err = relayIfNspFails(ctx, ["cte", "plan", "--session", session], ""); + const err = relayIfThtFails(ctx, ["cte", "plan", "--session", session], ""); if (err) return err; return textResult(`CTE plan approvato (sessione ${session}).`); } if (kind === "cte_result" || kind === "sql") { const dt = kind === "sql" ? "sql_approved" : "cte_approved"; - const err = relayIfNspFails( + const err = relayIfThtFails( ctx, ["decision", "add", "--session", session, "--type", dt, "--subject", `phase:${currentPhase(ctx, session)}`], "", @@ -481,7 +481,7 @@ export default function (pi) { name: "rewrite_question", label: "Riscrittura domanda (deterministica)", description: - "Scrive deterministicamente question.md via nsp session set-question (evita il tool " + + "Scrive deterministicamente question.md via tht session set-question (evita il tool " + "di edit unreliable). assumptions puo' essere array o stringa JSON.", parameters: Type.Object({ session: Type.String(), @@ -503,7 +503,7 @@ export default function (pi) { if (Array.isArray(assumps)) { for (const a of assumps) args.push("--assumption", String(a)); } - const err = relayIfNspFails(ctx, args, ""); + const err = relayIfThtFails(ctx, args, ""); if (err) return err; return textResult(`Domanda riscritta per la sessione ${session}.`); }, @@ -516,7 +516,7 @@ export default function (pi) { const parts = args.trim().split(/\s+/).filter(Boolean); const sessionId = parts.length >= 2 ? parts[0] : parts.length === 1 && /^\d/.test(parts[0]) ? undefined : parts[0]; - const sid = sessionId ?? activeSessionId ?? process.env.NSP_SESSION; + const sid = sessionId ?? activeSessionId ?? process.env.THT_SESSION; if (!sid) { await ctx.ui.notify("Uso: /torna [N]", "warning"); return; @@ -528,7 +528,7 @@ export default function (pi) { await ctx.ui.notify(`Target non valido (fase corrente ${cur}).`, "warning"); return; } - nsp(ctx, ["phase", "reopen", "--session", sid, "--phase", String(target)]); + tht(ctx, ["phase", "reopen", "--session", sid, "--phase", String(target)]); await pi.sendUserMessage( `Ho riaperto la Fase ${target} della sessione ${sid}. Riprendi il protocollo da quella fase.`, { deliverAs: "followUp" }, diff --git a/harness/README.md b/harness/README.md index bdfc38c1..4ec1a4d3 100644 --- a/harness/README.md +++ b/harness/README.md @@ -1,8 +1,8 @@ -# ThothII harness — `nsp` +# ThothII harness — `tht` -The self-contained Pi layer of ThothII: a deterministic Python CLI (`nsp`) + a Pi gate -extension (`.pi/extensions/nsp-gate.js`) that runs the 8-phase NL→SQL workflow, -emitting and consuming widget-descriptor JSON. Derived from ChironeWp3 as a validated +The self-contained Pi layer of ThothII: a deterministic Python CLI (`tht`) + a Pi gate +extension (`.pi/extensions/tht-gate.js`) that runs the 8-phase NL→SQL workflow, +emitting and consuming widget-descriptor JSON. Derived from the reference implementation as a validated starting point, **adapted** (not assumed reliable) to the ThothII contract. ## Install @@ -13,7 +13,7 @@ python -m venv .venv && source .venv/bin/activate pip install -e ".[dev]" ``` -The `nsp` command is now on PATH. Node ≥ 20 is needed for the gate JS tests +The `tht` command is now on PATH. Node ≥ 20 is needed for the gate JS tests (`npm test`). ## Configure @@ -22,8 +22,8 @@ The `nsp` command is now on PATH. Node ≥ 20 is needed for the gate JS tests ```bash cp .env.example .env -# fill in: THOTH_PROFILE, THOTH_DB_*, THOTH_DWH_API_KEY, THOTH_VEC_API_KEY, -# THOTH_VEC_WRITE_API_KEY, THOTH_SSL_CA, THOTH_OLLAMA_URL, ... +# fill in: THT_PROFILE, THT_DB_*, THT_DWH_API_KEY, THT_VEC_API_KEY, +# THT_VEC_WRITE_API_KEY, THT_SSL_CA, THT_OLLAMA_URL, ... ``` Keys are never logged; URLs are fine. Rotate any key that appeared in chat. @@ -31,7 +31,7 @@ Keys are never logged; URLs are fine. Rotate any key that appeared in chat. ### `workspaces/.yaml` A workspace wires the relational DWH + the pgvector (dual-key) + embeddings + evidence. -See `workspaces/chirone.example.yaml`. `${THOTH_*}` tokens expand from `.env`. +See `workspaces/tht.example.yaml`. `${THT_*}}` tokens expand from `.env`. ## The workflow @@ -40,8 +40,8 @@ See `workspaces/chirone.example.yaml`. `${THOTH_*}` tokens expand from `.env`. and the skill all read from it — no mirrored constants to drift. ```bash -nsp phase meta --json # the gate reads workflow facts from here -nsp phase meta # human-readable +tht phase meta --json # the gate reads workflow facts from here +tht phase meta # human-readable ``` ## Run @@ -53,8 +53,8 @@ pi --mode rpc # in Pi: /nuova-domanda "" ``` -The gate (`nsp-gate.js`) presents reviewer widgets (widget-descriptor), the reviewer -decides, and `nsp` persists decisions to the append-only ledger (`review_decisions.jsonl`). +The gate (`tht-gate.js`) presents reviewer widgets (widget-descriptor), the reviewer +decides, and `tht` persists decisions to the append-only ledger (`review_decisions.jsonl`). See `docs/testing.md` for what each interaction level validates. ## Test @@ -72,9 +72,9 @@ level covers and — crucially — does **not** cover. ## Layout ``` -nsp/ Python package (CLI + workflow + phase + decisions + db/rest/mschema/ +tht/ Python package (CLI + workflow + phase + decisions + db/rest/mschema/ vectorstore/evidence/search/session + memory) -.pi/ Pi project (settings, prompts, themes, extensions/nsp-gate.js + gate/) +.pi/ Pi project (settings, prompts, themes, extensions/tht-gate.js + gate/) workflow.yaml single source of workflow truth (F2) workspaces/ workspace YAML definitions (D3) scripts/ reader/writer RPC SQL for pgvector (D11) diff --git a/harness/docs/workflow-editing.md b/harness/docs/workflow-editing.md index c3f5075c..f5b69858 100644 --- a/harness/docs/workflow-editing.md +++ b/harness/docs/workflow-editing.md @@ -1,8 +1,8 @@ # Editing the workflow `harness/workflow.yaml` is the **single source of workflow truth** (spec F2, §5.3). -`phase.py`, the gate (`nsp-gate.js`), and the skill all read from it. There are **no -mirrored constants** in JS or Python — that was the ChironeWp3 drift bug +`phase.py`, the gate (`tht-gate.js`), and the skill all read from it. There are **no +mirrored constants** in JS or Python — that was the the reference implementation drift bug (`PHASE_NAMES` truncated to 7 entries in JS, F8/datamart silently dropped). Editing this one file is the only place the workflow changes. @@ -12,7 +12,7 @@ this one file is the only place the workflow changes. schema_version: 1 phases: - id: F3 # stable id (referenced by the gate) - name: riscrittura # human label (nsp phase show / gate UI) + name: riscrittura # human label (tht phase show / gate UI) advance: kind:phase # how the phase advances (see below) prerequisites: # what must hold before advancing (gate checks these) - decision_exists: question_rewritten @@ -68,7 +68,7 @@ preserved. This is the D15 fix for the orphaned-CTE-blocks-finalize bug. ## After editing ```bash -nsp phase meta --json # confirm the new shape (max_phase, phases, artifacts_out) +tht phase meta --json # confirm the new shape (max_phase, phases, artifacts_out) pytest # L1 coherence smoke re-derives from the new workflow.yaml ``` diff --git a/harness/pyproject.toml b/harness/pyproject.toml index 11a8ea79..c0faff7b 100644 --- a/harness/pyproject.toml +++ b/harness/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "nsp" +name = "tht" version = "0.1.0" description = "ThothII harness — deterministic CLI for the NL→SQL workflow" requires-python = ">=3.12" @@ -18,7 +18,7 @@ dependencies = [ ] [project.scripts] -nsp = "nsp.cli:app" +tht = "tht.cli:app" [project.optional-dependencies] dev = [ @@ -27,6 +27,9 @@ dev = [ "ruff>=0.5", ] +[tool.setuptools.packages.find] +include = ["tht*"] + [tool.ruff] line-length = 100 diff --git a/harness/tests/conftest.py b/harness/tests/conftest.py index cb6e904d..84b94376 100644 --- a/harness/tests/conftest.py +++ b/harness/tests/conftest.py @@ -7,14 +7,14 @@ from dotenv import load_dotenv from sqlalchemy import create_engine from testcontainers.postgres import PostgresContainer -# Permetti `pytest` lanciato da qualsiasi directory di trovare il package `nsp` +# Permetti `pytest` lanciato da qualsiasi directory di trovare il package `tht` # (installato in modalità editable nella venv, ma utile anche senza attivazione). _ROOT = Path(__file__).resolve().parent.parent if str(_ROOT) not in sys.path: sys.path.insert(0, str(_ROOT)) # Directory sessions/ risolta relativamente alla root harness (per i test che creano sessioni) -os.environ.setdefault("NSP_HARNESS_ROOT", str(_ROOT)) +os.environ.setdefault("THT_HARNESS_ROOT", str(_ROOT)) # L2 tests need the remote credentials from harness/.env (gitignored). Loaded once, # autouse, before any test. L0/L1 tests don't read these vars; missing .env only @@ -23,10 +23,10 @@ load_dotenv(_ROOT / ".env") # L2 connection prerequisites (spec Testing Strategy). If any is missing/empty, L2 # tests are SKIPPED (not failed) so the default run (pytest = L0+L1) stays green. -# NOTE: THOTH_SSL_CA is NOT required -- the DWH endpoint presents a public cert -# (*.policlinicosandonato.it signed by GoDaddy), already in the certifi bundle, so +# NOTE: THT_SSL_CA is NOT required -- the DWH endpoint presents a public cert +# signed by a public CA (GoDaddy), already in the certifi bundle, so # the clients fall back to verify=True and TLS validates without a CA file. -REQUIRED_L2 = ["THOTH_DWH_API_KEY", "THOTH_VEC_API_KEY", "THOTH_VEC_WRITE_API_KEY"] +REQUIRED_L2 = ["THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY"] @pytest.fixture(scope="session") diff --git a/harness/tests/l0/test_db_connection.py b/harness/tests/l0/test_db_connection.py index 5871f4b9..f762427f 100644 --- a/harness/tests/l0/test_db_connection.py +++ b/harness/tests/l0/test_db_connection.py @@ -7,7 +7,7 @@ is not assumed reliable' gains real teeth for the data layer. import pytest from sqlalchemy import create_engine, text -from nsp.db.connection import can_create_in_schema, make_engine, ping, writable_tables +from tht.db.connection import can_create_in_schema, make_engine, ping, writable_tables pytestmark = [pytest.mark.l0] diff --git a/harness/tests/l0/test_db_introspect.py b/harness/tests/l0/test_db_introspect.py index 86b95f16..dc995330 100644 --- a/harness/tests/l0/test_db_introspect.py +++ b/harness/tests/l0/test_db_introspect.py @@ -4,7 +4,7 @@ FKs, and indexes from a real Postgres catalog. """ import pytest -from nsp.db.introspect import IntrospectionError, introspect +from tht.db.introspect import IntrospectionError, introspect pytestmark = [pytest.mark.l0] diff --git a/harness/tests/l0/test_db_sampling.py b/harness/tests/l0/test_db_sampling.py index dc44bdec..66fe41ca 100644 --- a/harness/tests/l0/test_db_sampling.py +++ b/harness/tests/l0/test_db_sampling.py @@ -4,9 +4,9 @@ columns, and that wide_text / non-text columns are excluded. """ import pytest -from nsp.config import LshConfig -from nsp.db.introspect import introspect -from nsp.db.sampling import is_text_type, unique_values_for_lsh +from tht.config import LshConfig +from tht.db.introspect import introspect +from tht.db.sampling import is_text_type, unique_values_for_lsh pytestmark = [pytest.mark.l0] diff --git a/harness/tests/l2/test_memory_save_one_real.py b/harness/tests/l2/test_memory_save_one_real.py index 34d66c31..bc98c283 100644 --- a/harness/tests/l2/test_memory_save_one_real.py +++ b/harness/tests/l2/test_memory_save_one_real.py @@ -1,4 +1,4 @@ -"""L2: nsp memory save-one against real pgvector (spec D11, L2). +"""L2: tht memory save-one against real pgvector (spec D11, L2). Validates D11 end-to-end: a single promoted decision is upserted to the real pgvector via the WRITER key (not a full resync), and a subsequent search_similar @@ -13,18 +13,18 @@ from unittest.mock import MagicMock import pytest -from nsp.memory import MemoryRecord, save_one_memory -from nsp.workspace import load_workspace +from tht.memory import MemoryRecord, save_one_memory +from tht.workspace import load_workspace pytestmark = [pytest.mark.l2] -WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml" +WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml" def test_save_one_upserts_to_real_pgvector(l2_env): """save_one_memory pushes one row to the real pgvector via the writer key, and a subsequent search_similar retrieves it. Idempotent (re-running upserts >= 0).""" - from nsp.vectorstore.embeddings import OllamaEmbeddings - from nsp.vectorstore.rest_client import VectorRestClient + from tht.vectorstore.embeddings import OllamaEmbeddings + from tht.vectorstore.rest_client import VectorRestClient ws = load_workspace(WORKSPACE) if not ws.vector_write_rest or not ws.vector_write_rest.api_key.strip(): diff --git a/harness/tests/l2/test_session_ablazione.py b/harness/tests/l2/test_session_ablazione.py index 541e84a4..ef3172c0 100644 --- a/harness/tests/l2/test_session_ablazione.py +++ b/harness/tests/l2/test_session_ablazione.py @@ -11,8 +11,8 @@ MODES (both manual, non-deterministic, pre-release -- NOT a regression gate): Altro/value-grounding/rollback paths so specific behaviors assert deterministically. HOW TO RUN (operator, before release): - 1. Populate harness/.env (THOTH_DWH_API_KEY, THOTH_VEC_API_KEY, - THOTH_VEC_WRITE_API_KEY, THOTH_SSL_CA, THOTH_*_REST_URL, THOTH_OLLAMA_URL). + 1. Populate harness/.env (THT_DWH_API_KEY, THT_VEC_API_KEY, + THT_VEC_WRITE_API_KEY, THT_SSL_CA, THT_*_REST_URL, THT_OLLAMA_URL). 2. Connect VPN. Ensure Pi is configured locally with GLM 5.2. 3. Run: pytest -m l2 tests/l2/test_session_ablazione.py -s @@ -30,13 +30,13 @@ import pytest pytestmark = [pytest.mark.l2] -WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml" +WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml" QUESTION = "dammi la lista dei pazienti che hanno fatto un'ablazione nel 2025" def test_workspace_chirone_test_loads(l2_env): - """The L2 workspace YAML loads and expands ${THOTH_*} from .env.""" - from nsp.workspace import load_workspace + """The L2 workspace YAML loads and expands ${THT_*} from .env.""" + from tht.workspace import load_workspace ws = load_workspace(WORKSPACE) # secrets must be expanded (not the literal ${...} token) @@ -68,7 +68,7 @@ def test_ablazione_session_manual(l2_env, tmp_path): # assertion. We surface the run instructions and assert only that the launch # context is ready; the operator drives the conversation and inspects the outcome. env_ok = all(os.environ.get(v, "").strip() for v in - ["THOTH_DWH_API_KEY", "THOTH_VEC_API_KEY", "THOTH_VEC_WRITE_API_KEY"]) + ["THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY"]) assert env_ok assert WORKSPACE.exists() print(f"\n[L2 manual] launch: pi --mode rpc (cwd=harness/)") diff --git a/harness/tests/l2/test_value_grounding_real.py b/harness/tests/l2/test_value_grounding_real.py index c941c081..d364814f 100644 --- a/harness/tests/l2/test_value_grounding_real.py +++ b/harness/tests/l2/test_value_grounding_real.py @@ -10,31 +10,31 @@ from pathlib import Path import pytest -from nsp.workspace import load_workspace +from tht.workspace import load_workspace pytestmark = [pytest.mark.l2] -WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "chirone-test.yaml" +WORKSPACE = Path(__file__).resolve().parents[2] / "workspaces" / "tht-test.yaml" def test_ablazione_returns_multiple_columns(l2_env): """On the real schema, 'ablazione' should ground to more than one column (e.g. a flag and a free-text patologia field) -- the whole point of D14a's - non-collapsing aggregation. Requires a built LSH index (nsp lsh build).""" - from nsp.config import LshConfig + non-collapsing aggregation. Requires a built LSH index (tht lsh build).""" + from tht.config import LshConfig try: - from nsp.lshindex import load_index, query_index # ported with the lsh build path + from tht.lshindex import load_index, query_index # ported with the lsh build path except ModuleNotFoundError: - pytest.skip("nsp.lshindex not yet ported (deferred from B3; lands with nsp lsh build)") - from nsp.search import aggregate_lsh_multi + pytest.skip("tht.lshindex not yet ported (deferred from B3; lands with tht lsh build)") + from tht.search import aggregate_lsh_multi - # NOTE: this test assumes the LSH index was built (nsp lsh build --workspace - # chirone-test). If absent, build it first. The index path comes from the config. + # NOTE: this test assumes the LSH index was built (tht lsh build --workspace + # tht-test). If absent, build it first. The index path comes from the config. ws = load_workspace(WORKSPACE) index_dir = ws.paths.indexes try: lsh, minhashes, meta = load_index(index_dir, "datawarehouse") except Exception as e: - pytest.skip(f"LSH index not built yet (run nsp lsh build): {e}") + pytest.skip(f"LSH index not built yet (run tht lsh build): {e}") hits = query_index(lsh, minhashes, "ablazione", meta, top_n=20) grouped = aggregate_lsh_multi( diff --git a/harness/tests/test_cli_phase_meta.py b/harness/tests/test_cli_phase_meta.py index 3e2a051d..ebcfc5a3 100644 --- a/harness/tests/test_cli_phase_meta.py +++ b/harness/tests/test_cli_phase_meta.py @@ -1,11 +1,11 @@ -"""L1: nsp phase meta --json -- the gate reads workflow facts from here (F2). +"""L1: tht phase meta --json -- the gate reads workflow facts from here (F2). Kills the JS/Python drift (no mirrored constants): workflow.yaml is the single source. """ import json from typer.testing import CliRunner -from nsp.cli import app +from tht.cli import app runner = CliRunner() diff --git a/harness/tests/test_decisions_retract.py b/harness/tests/test_decisions_retract.py index c29ebe7f..0d5bc696 100644 --- a/harness/tests/test_decisions_retract.py +++ b/harness/tests/test_decisions_retract.py @@ -1,4 +1,4 @@ -from nsp.decisions import append_decision, list_decisions +from tht.decisions import append_decision, list_decisions def test_decision_retracted_in_audit_but_marked(tmp_path): @@ -57,7 +57,7 @@ def test_empty_session_returns_empty_list(tmp_path): def test_decision_type_literal_includes_retracted(): """decision_retracted e' un tipo valido (pydantic lo accetta).""" - from nsp.decisions import DecisionRecord + from tht.decisions import DecisionRecord from datetime import datetime, UTC d = DecisionRecord( seq=1, ts=datetime.now(UTC), type="decision_retracted", diff --git a/harness/tests/test_formula.py b/harness/tests/test_formula.py index 10eb6370..ce2a2d12 100644 --- a/harness/tests/test_formula.py +++ b/harness/tests/test_formula.py @@ -10,7 +10,7 @@ from pathlib import Path import pytest -from nsp.evidence.formula_store import ConceptFormula, retrieve_formula, save_formula +from tht.evidence.formula_store import ConceptFormula, retrieve_formula, save_formula def test_formula_retrieval_by_concept(tmp_path): @@ -72,7 +72,7 @@ def test_retrieve_empty_on_missing_dir(tmp_path): def test_concept_formula_decision_types_exist(): import typing - from nsp.decisions import DecisionType + from tht.decisions import DecisionType args = typing.get_args(DecisionType) assert "concept_formula_approved" in args assert "concept_formula_rejected" in args diff --git a/harness/tests/test_freetext_interpretation.py b/harness/tests/test_freetext_interpretation.py index 211fa3a2..0c49f2ea 100644 --- a/harness/tests/test_freetext_interpretation.py +++ b/harness/tests/test_freetext_interpretation.py @@ -9,7 +9,7 @@ decision rationale and survive persistence, never silently discarded. """ from pathlib import Path -from nsp.decisions import append_decision, list_decisions +from tht.decisions import append_decision, list_decisions def test_altro_freetext_recorded_in_rationale(tmp_path): diff --git a/harness/tests/test_memory_save_one.py b/harness/tests/test_memory_save_one.py index 866acb4d..e686cc6a 100644 --- a/harness/tests/test_memory_save_one.py +++ b/harness/tests/test_memory_save_one.py @@ -1,6 +1,6 @@ -"""L1: nsp memory save-one -- targeted upsert via the writer key (spec D11). +"""L1: tht memory save-one -- targeted upsert via the writer key (spec D11). -The D11 deviation: instead of a full vectorstore resync (nsp memory index / sync), +The D11 deviation: instead of a full vectorstore resync (tht memory index / sync), a remote workstation with a writer key can push a SINGLE promoted decision to pgvector as a one-row upsert. This test pins the pure core of that behavior: - exactly one VectorRecord is built for the chosen decision_seq @@ -13,7 +13,7 @@ from unittest.mock import MagicMock import pytest -from nsp.memory import MemoryRecord, memory_vector_record_for_decision, save_one_memory +from tht.memory import MemoryRecord, memory_vector_record_for_decision, save_one_memory def _record(seq: int = 7, **kw) -> MemoryRecord: diff --git a/harness/tests/test_mschema_eligibility.py b/harness/tests/test_mschema_eligibility.py index 4319cf04..d71ec4cf 100644 --- a/harness/tests/test_mschema_eligibility.py +++ b/harness/tests/test_mschema_eligibility.py @@ -6,9 +6,9 @@ override wins over the physical classification. """ from datetime import datetime -from nsp.config import EligibilityConfig -from nsp.mschema.eligibility import classify_all, classify_column, effective_eligibility -from nsp.mschema.models import ( +from tht.config import EligibilityConfig +from tht.mschema.eligibility import classify_all, classify_column, effective_eligibility +from tht.mschema.models import ( Annotations, ColumnAnnotation, ColumnPhysical, diff --git a/harness/tests/test_mschema_render.py b/harness/tests/test_mschema_render.py index 46b6c194..4c8f808b 100644 --- a/harness/tests/test_mschema_render.py +++ b/harness/tests/test_mschema_render.py @@ -5,7 +5,7 @@ ThothAI style, schema-dict for AV-SQL). Pure logic, no I/O. """ from datetime import datetime -from nsp.mschema.models import ( +from tht.mschema.models import ( Annotations, ColumnAnnotation, ColumnPhysical, @@ -13,7 +13,7 @@ from nsp.mschema.models import ( PhysicalSchema, TablePhysical, ) -from nsp.mschema.render import to_markdown, to_mschema_text, to_schema_dict +from tht.mschema.render import to_markdown, to_mschema_text, to_schema_dict def _fake_schema() -> PhysicalSchema: @@ -84,7 +84,7 @@ def test_to_schema_dict_avsql_shape(): def test_annotations_override_description_in_render(): schema = _fake_schema() - from nsp.mschema.models import TableAnnotation + from tht.mschema.models import TableAnnotation ann = Annotations(tables={ "dim_pazienti": TableAnnotation(columns={ "citta": ColumnAnnotation(description="Comune di residenza"), diff --git a/harness/tests/test_phase_effective.py b/harness/tests/test_phase_effective.py index c419ff3c..ef65b529 100644 --- a/harness/tests/test_phase_effective.py +++ b/harness/tests/test_phase_effective.py @@ -1,5 +1,5 @@ -from nsp.decisions import append_decision, DecisionRecord -from nsp.phase import current_phase, effective_decisions +from tht.decisions import append_decision, DecisionRecord +from tht.phase import current_phase, effective_decisions def _d(session, dtype, subject, **kw) -> DecisionRecord: diff --git a/harness/tests/test_rest_client.py b/harness/tests/test_rest_client.py index f2d76634..7a0974ad 100644 --- a/harness/tests/test_rest_client.py +++ b/harness/tests/test_rest_client.py @@ -2,13 +2,13 @@ Verifies the ported RestClient: each rpc carries the X-API-Key header, the right payload, base_url slash handling, and HTTP/network errors surface as RestError. -Ported (renamed psdwp3->nsp) from ChironeWp3/tests/test_rest_client.py. +Ported (renamed psdwp3->tht) from ChironeWp3/tests/test_rest_client.py. """ import pytest import requests -from nsp.config import RestConfig -from nsp.rest.client import RestClient, RestError +from tht.config import RestConfig +from tht.rest.client import RestClient, RestError class FakeResponse: @@ -39,7 +39,7 @@ def _capture(monkeypatch, response): "timeout": timeout, "verify": verify}) return response - monkeypatch.setattr("nsp.rest.client.requests.post", fake_post) + monkeypatch.setattr("tht.rest.client.requests.post", fake_post) return calls @@ -104,7 +104,7 @@ def test_network_error_actionable(monkeypatch): def boom(*a, **k): raise requests.ConnectionError("refused") - monkeypatch.setattr("nsp.rest.client.requests.post", boom) + monkeypatch.setattr("tht.rest.client.requests.post", boom) with pytest.raises(RestError, match="raggiungibile"): _client().ping() diff --git a/harness/tests/test_session_coherence_smoke.py b/harness/tests/test_session_coherence_smoke.py index ef6f908c..155e25dc 100644 --- a/harness/tests/test_session_coherence_smoke.py +++ b/harness/tests/test_session_coherence_smoke.py @@ -8,11 +8,11 @@ effective view, teardown, taskdoc byte budget). """ from pathlib import Path -from nsp.decisions import append_decision -from nsp.phase import current_phase, effective_decisions -from nsp.taskdoc import generate_task_doc -from nsp.teardown import teardown_to_phase -from nsp.workflow import load_workflow +from tht.decisions import append_decision +from tht.phase import current_phase, effective_decisions +from tht.taskdoc import generate_task_doc +from tht.teardown import teardown_to_phase +from tht.workflow import load_workflow def _approve(session: Path, phase: int) -> None: diff --git a/harness/tests/test_taskdoc.py b/harness/tests/test_taskdoc.py index 977216e1..62a62d7a 100644 --- a/harness/tests/test_taskdoc.py +++ b/harness/tests/test_taskdoc.py @@ -1,6 +1,6 @@ from pathlib import Path -from nsp.taskdoc import generate_task_doc +from tht.taskdoc import generate_task_doc def test_task_doc_includes_question_and_schema_scope(tmp_path): @@ -52,8 +52,8 @@ def test_task_doc_carries_phase_header(tmp_path): def test_task_doc_excludes_stale_decisions_post_rollback(tmp_path): """D15+D16: il task doc riflette lo stato effective, non quello stale. Dopo rollback a F4, una sql_approved:7 stale non appare nel brief delle decisioni.""" - from nsp.decisions import append_decision - from nsp.phase import current_phase + from tht.decisions import append_decision + from tht.phase import current_phase s = tmp_path / "sess"; s.mkdir() (s / "question.md").write_text("q") # simula: lavoro fino a F7, poi rollback a F4 diff --git a/harness/tests/test_teardown.py b/harness/tests/test_teardown.py index e3a63f47..8fe668c1 100644 --- a/harness/tests/test_teardown.py +++ b/harness/tests/test_teardown.py @@ -1,4 +1,4 @@ -from nsp.teardown import teardown_to_phase +from tht.teardown import teardown_to_phase def test_teardown_to_phase_4_deletes_phase5plus_artifacts(tmp_path): diff --git a/harness/tests/test_value_grounding.py b/harness/tests/test_value_grounding.py index df3e17a8..100917d7 100644 --- a/harness/tests/test_value_grounding.py +++ b/harness/tests/test_value_grounding.py @@ -7,7 +7,7 @@ aggregate_lsh_multi exposes every column where the value appears, grouped by tab so the value-grounding widget can let the reviewer choose which column(s) anchor the value. """ -from nsp.search import aggregate_lsh_multi +from tht.search import aggregate_lsh_multi def test_value_in_multiple_columns_returns_all(): @@ -64,7 +64,7 @@ def test_empty_hits_returns_empty(): def test_value_grounded_decision_type_exists(): # D14a adds the value_grounded decision type so the gate can record the # reviewer's choice of which column(s) anchor a cited value. - from nsp.decisions import DecisionType + from tht.decisions import DecisionType import typing args = typing.get_args(DecisionType) assert "value_grounded" in args diff --git a/harness/tests/test_vector_dual_key.py b/harness/tests/test_vector_dual_key.py index e79d98cb..290a6551 100644 --- a/harness/tests/test_vector_dual_key.py +++ b/harness/tests/test_vector_dual_key.py @@ -5,9 +5,9 @@ API keys against the same pgvector REST endpoint, with distinct roles (vector_reader / vector_writer). This test pins the dual-key construction and the workstation write-guard. """ -from nsp.cli._guards import has_vector_write_rest, require_vector_write_allowed -from nsp.config import Config, DatabaseConfig, RestConfig -from nsp.vectorstore.rest_client import VectorRestClient +from tht.cli._guards import has_vector_write_rest, require_vector_write_allowed +from tht.config import Config, DatabaseConfig, RestConfig +from tht.vectorstore.rest_client import VectorRestClient def _minimal_config(**kw) -> Config: diff --git a/harness/tests/test_workflow.py b/harness/tests/test_workflow.py index ac7aeb0c..8b0fe4cc 100644 --- a/harness/tests/test_workflow.py +++ b/harness/tests/test_workflow.py @@ -1,4 +1,4 @@ -from nsp.workflow import load_workflow +from tht.workflow import load_workflow def test_workflow_loads_8_phases(): diff --git a/harness/tests/test_workspace.py b/harness/tests/test_workspace.py index 4332f8fa..e373b475 100644 --- a/harness/tests/test_workspace.py +++ b/harness/tests/test_workspace.py @@ -1,59 +1,59 @@ from pathlib import Path -from nsp.workspace import load_workspace, WorkspaceError +from tht.workspace import load_workspace, WorkspaceError def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): - # Isolate profile: load_dotenv (conftest D3) injects THOTH_PROFILE from the real + # Isolate profile: load_dotenv (conftest D3) injects THT_PROFILE from the real # harness/.env into os.environ; this test asserts the *default* (server), so it - # must del THOTH_PROFILE rather than inherit whatever the operator set. - monkeypatch.delenv("THOTH_PROFILE", raising=False) - monkeypatch.setenv("THOTH_VEC_API_KEY", "secret-reader") - monkeypatch.setenv("THOTH_VEC_WRITE_API_KEY", "secret-writer") - monkeypatch.setenv("THOTH_VEC_REST_URL", "https://example/vector/v1/") - monkeypatch.setenv("THOTH_DWH_REST_URL", "https://example/dwh/") - monkeypatch.setenv("THOTH_DWH_API_KEY", "dwh-key") - monkeypatch.setenv("THOTH_DB_HOST", "h") - monkeypatch.setenv("THOTH_DB_NAME", "db") - monkeypatch.setenv("THOTH_DB_USER", "u") - monkeypatch.setenv("THOTH_DB_PASSWORD", "p") - monkeypatch.setenv("THOTH_VEC_HOST", "vh") - monkeypatch.setenv("THOTH_VEC_USER", "vu") - monkeypatch.setenv("THOTH_VEC_PASSWORD", "vp") - monkeypatch.setenv("THOTH_OLLAMA_URL", "http://ollama") - monkeypatch.setenv("THOTH_DOCS_ROOT", str(tmp_path / "docs")) + # must del THT_PROFILE rather than inherit whatever the operator set. + monkeypatch.delenv("THT_PROFILE", raising=False) + monkeypatch.setenv("THT_VEC_API_KEY", "secret-reader") + monkeypatch.setenv("THT_VEC_WRITE_API_KEY", "secret-writer") + monkeypatch.setenv("THT_VEC_REST_URL", "https://example/vector/v1/") + monkeypatch.setenv("THT_DWH_REST_URL", "https://example/dwh/") + monkeypatch.setenv("THT_DWH_API_KEY", "dwh-key") + monkeypatch.setenv("THT_DB_HOST", "h") + monkeypatch.setenv("THT_DB_NAME", "db") + monkeypatch.setenv("THT_DB_USER", "u") + monkeypatch.setenv("THT_DB_PASSWORD", "p") + monkeypatch.setenv("THT_VEC_HOST", "vh") + monkeypatch.setenv("THT_VEC_USER", "vu") + monkeypatch.setenv("THT_VEC_PASSWORD", "vp") + monkeypatch.setenv("THT_OLLAMA_URL", "http://ollama") + monkeypatch.setenv("THT_DOCS_ROOT", str(tmp_path / "docs")) yaml = tmp_path / "w.yaml" yaml.write_text( "database:\n" - " host: ${THOTH_DB_HOST}\n" + " host: ${THT_DB_HOST}\n" " port: 5432\n" - " database: ${THOTH_DB_NAME}\n" + " database: ${THT_DB_NAME}\n" " schema: datawarehouse\n" - " user: ${THOTH_DB_USER}\n" - " password: ${THOTH_DB_PASSWORD}\n" + " user: ${THT_DB_USER}\n" + " password: ${THT_DB_PASSWORD}\n" " transport: rest\n" "rest:\n" - " base_url: ${THOTH_DWH_REST_URL}\n" - " api_key: ${THOTH_DWH_API_KEY}\n" + " base_url: ${THT_DWH_REST_URL}\n" + " api_key: ${THT_DWH_API_KEY}\n" "vector_db:\n" - " host: ${THOTH_VEC_HOST}\n" + " host: ${THT_VEC_HOST}\n" " port: 5438\n" " database: postgres\n" " schema: vectors\n" - " user: ${THOTH_VEC_USER}\n" - " password: ${THOTH_VEC_PASSWORD}\n" + " user: ${THT_VEC_USER}\n" + " password: ${THT_VEC_PASSWORD}\n" "vector_rest:\n" - " base_url: ${THOTH_VEC_REST_URL}\n" - " api_key: ${THOTH_VEC_API_KEY}\n" + " base_url: ${THT_VEC_REST_URL}\n" + " api_key: ${THT_VEC_API_KEY}\n" "vector_write_rest:\n" - " base_url: ${THOTH_VEC_REST_URL}\n" - " api_key: ${THOTH_VEC_WRITE_API_KEY}\n" + " base_url: ${THT_VEC_REST_URL}\n" + " api_key: ${THT_VEC_WRITE_API_KEY}\n" "embeddings:\n" - " base_url: ${THOTH_OLLAMA_URL}\n" + " base_url: ${THT_OLLAMA_URL}\n" " model: nomic-embed-text-v2-moe\n" " dim: 768\n" "evidence:\n" - " source_root: ${THOTH_DOCS_ROOT}\n" + " source_root: ${THT_DOCS_ROOT}\n" ) ws = load_workspace(yaml) # Dual vector key (top-level, per Config reale): reader and writer separate @@ -66,7 +66,7 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): def test_load_workspace_missing_env_raises(monkeypatch, tmp_path): - monkeypatch.delenv("THOTH_VEC_API_KEY", raising=False) + monkeypatch.delenv("THT_VEC_API_KEY", raising=False) yaml = tmp_path / "w.yaml" yaml.write_text( "database:\n" @@ -74,10 +74,10 @@ def test_load_workspace_missing_env_raises(monkeypatch, tmp_path): " user: u\n password: p\n transport: direct\n" "vector_rest:\n" " base_url: 'https://v/'\n" - " api_key: '${THOTH_VEC_API_KEY}'\n" + " api_key: '${THT_VEC_API_KEY}'\n" ) try: load_workspace(yaml) assert False, "should have raised" except WorkspaceError as e: - assert "THOTH_VEC_API_KEY" in str(e) + assert "THT_VEC_API_KEY" in str(e) diff --git a/harness/nsp/__init__.py b/harness/tht/__init__.py similarity index 100% rename from harness/nsp/__init__.py rename to harness/tht/__init__.py diff --git a/harness/nsp/cli/__init__.py b/harness/tht/cli/__init__.py similarity index 77% rename from harness/nsp/cli/__init__.py rename to harness/tht/cli/__init__.py index 4bea3ecf..f444f0e2 100644 --- a/harness/nsp/cli/__init__.py +++ b/harness/tht/cli/__init__.py @@ -1,6 +1,6 @@ -"""nsp CLI -- Typer app `nsp`. +"""tht CLI -- Typer app `tht`. -Minimal skeleton for now: registers `phase_app` (the gate needs `nsp phase meta --json`). +Minimal skeleton for now: registers `phase_app` (the gate needs `tht phase meta --json`). Other command groups (db, schema, lsh, vector, memory, session, decision, sql, cte, evidence, search) are wired in their porting tasks, once their backend deps land (A9 ports db/mschema/_guards; B1 ports vectorstore; B3 ports search/lshindex/sampling). @@ -9,10 +9,10 @@ from __future__ import annotations import typer -from nsp import __version__ +from tht import __version__ app = typer.Typer( - help="nsp (Natural Semantic Platform) -- kit operativo per generazione SQL assistita da Pi", + help="tht (Thoth) -- kit operativo per generazione SQL assistito da Pi", no_args_is_help=True, ) @@ -36,6 +36,6 @@ def main( pass -from nsp.cli.phase_cmd import phase_app # noqa: E402 +from tht.cli.phase_cmd import phase_app # noqa: E402 app.add_typer(phase_app, name="phase") diff --git a/harness/nsp/cli/_guards.py b/harness/tht/cli/_guards.py similarity index 100% rename from harness/nsp/cli/_guards.py rename to harness/tht/cli/_guards.py diff --git a/harness/nsp/cli/phase_cmd.py b/harness/tht/cli/phase_cmd.py similarity index 90% rename from harness/nsp/cli/phase_cmd.py rename to harness/tht/cli/phase_cmd.py index 54edd120..07e89adb 100644 --- a/harness/nsp/cli/phase_cmd.py +++ b/harness/tht/cli/phase_cmd.py @@ -1,6 +1,6 @@ -"""nsp phase -- workflow HITL gate commands. +"""tht phase -- workflow HITL gate commands. -`meta` is the single source the gate (nsp-gate.js) reads workflow facts from, +`meta` is the single source the gate (tht-gate.js) reads workflow facts from, killing the JS/Python PHASE_NAMES drift (F2). advance/reopen/show land with the session/db ports (A9) -- they need current_phase + load_session_or_exit which depend on the not-yet-ported session store helpers. @@ -11,7 +11,7 @@ import json import typer -from nsp.workflow import load_workflow +from tht.workflow import load_workflow phase_app = typer.Typer(help="Fase del workflow HITL (gate di avanzamento/ritorno)") diff --git a/harness/nsp/config.py b/harness/tht/config.py similarity index 94% rename from harness/nsp/config.py rename to harness/tht/config.py index d3bc8b23..53a7b6f9 100644 --- a/harness/nsp/config.py +++ b/harness/tht/config.py @@ -68,7 +68,7 @@ class ExamplesConfig(BaseModel): class LshSkipConfig(BaseModel): # DEPRECATO: l'euristica di lunghezza (skip_column vendored) non è più usata. La # selezione delle colonne da indicizzare segue il principio di column eligibility - # (sezione `eligibility`). Mantenuto solo per compatibilità con nsp.yaml esistenti. + # (sezione `eligibility`). Mantenuto solo per compatibilità con tht.yaml esistenti. max_total_chars: int = 50000 max_avg_length: int = 20 @@ -114,7 +114,7 @@ class VectorConfig(BaseModel): class SearchConfig(BaseModel): rrf_k: int = 60 - top_schema_tables: int = 12 # default `--top` per `nsp search --kind schema` (n. tabelle) + top_schema_tables: int = 12 # default `--top` per `tht search --kind schema` (n. tabelle) schema_chunk_pool: int = 150 # chunk tabella/colonna fusi prima dell'aggregazione a tabella @@ -135,7 +135,7 @@ class ExecutionConfig(BaseModel): class Config(BaseModel): database: DatabaseConfig - # Profilo dell'installazione, letto da THOTH_PROFILE (.env), non dallo yaml versionato. + # Profilo dell'installazione, letto da THT_PROFILE (.env), non dallo yaml versionato. # server: ricostruisce i derivati (artefatti, LSH, vettori schema nel vectordb). # workstation: postazione locale che legge il vectordb via REST; gli upsert remoti # richiedono vector_write_rest, mentre init/clear/rebuild restano solo-server. @@ -152,7 +152,7 @@ class Config(BaseModel): execution: ExecutionConfig = ExecutionConfig() rest: RestConfig | None = None # Endpoint REST dedicato per la similarity search del pgvector (rpc search_similar). - # Se presente, `nsp search` legge via REST; altrimenti legge in diretto (dev/test). + # Se presente, `tht search` legge via REST; altrimenti legge in diretto (dev/test). vector_rest: RestConfig | None = None # Endpoint REST dedicato alle scritture controllate del pgvector. E' opzionale e usa # una API key separata dalla lettura; espone solo upsert/hash via RPC allowlist. @@ -169,11 +169,11 @@ def load_config(path: Path) -> Config: cfg = Config.model_validate(_expand_env(raw)) except ValidationError as e: raise ConfigError(f"Configurazione non valida in {path}:\n{e}") from e - env_profile = os.environ.get("THOTH_PROFILE") + env_profile = os.environ.get("THT_PROFILE") if env_profile is not None: if env_profile not in ("server", "workstation"): raise ConfigError( - f"THOTH_PROFILE non valido: {env_profile!r} (atteso 'server' o 'workstation')." + f"THT_PROFILE non valido: {env_profile!r} (atteso 'server' o 'workstation')." ) cfg = cfg.model_copy(update={"profile": env_profile}) if cfg.database.transport == "rest" and cfg.rest is None: diff --git a/harness/nsp/db/__init__.py b/harness/tht/db/__init__.py similarity index 100% rename from harness/nsp/db/__init__.py rename to harness/tht/db/__init__.py diff --git a/harness/nsp/db/connection.py b/harness/tht/db/connection.py similarity index 96% rename from harness/nsp/db/connection.py rename to harness/tht/db/connection.py index e6411ec9..04abedcd 100644 --- a/harness/nsp/db/connection.py +++ b/harness/tht/db/connection.py @@ -1,6 +1,6 @@ from sqlalchemy import Engine, create_engine, text -from nsp.config import DatabaseConfig +from tht.config import DatabaseConfig def make_engine(cfg: DatabaseConfig) -> Engine: diff --git a/harness/nsp/db/fetch_ca.py b/harness/tht/db/fetch_ca.py similarity index 97% rename from harness/nsp/db/fetch_ca.py rename to harness/tht/db/fetch_ca.py index d6b56a89..52543e7d 100644 --- a/harness/nsp/db/fetch_ca.py +++ b/harness/tht/db/fetch_ca.py @@ -1,7 +1,7 @@ """Recupero della catena di certificati presentata da un endpoint HTTPS. Serve al setup di una postazione *workstation* dietro una CA interna: scarica la -catena TLS del server REST e la salva in un bundle PEM da puntare con `PSD_SSL_CA` +catena TLS del server REST e la salva in un bundle PEM da puntare con `THT_SSL_CA` (consumato da `requests` via `verify=`). NON installa nulla nel trust store dell'OS. """ @@ -39,7 +39,7 @@ def fetch_chain_pem(host: str, port: int = 443, timeout: int = 30) -> list[str]: L'handshake è volutamente *non verificato* (CERT_NONE): stiamo recuperando la catena per poter poi *stabilire* la fiducia, non per fidarci adesso. La verifica vera avviene - in seguito quando `PSD_SSL_CA` punta al bundle salvato (es. `nsp db ping`). + in seguito quando `THT_SSL_CA` punta al bundle salvato (es. `tht db ping`). """ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname = False diff --git a/harness/nsp/db/introspect.py b/harness/tht/db/introspect.py similarity index 98% rename from harness/nsp/db/introspect.py rename to harness/tht/db/introspect.py index 92b6a9da..09b3feff 100644 --- a/harness/nsp/db/introspect.py +++ b/harness/tht/db/introspect.py @@ -2,7 +2,7 @@ from datetime import UTC, datetime from sqlalchemy import Engine, text -from nsp.mschema.models import ( +from tht.mschema.models import ( ColumnPhysical, ForeignKey, Index, @@ -10,7 +10,7 @@ from nsp.mschema.models import ( TablePhysical, ) -# Query adattate da thoth_sqldb2 (Apache 2.0) — vedi src/nsp/vendor/VENDORED.md. +# Query adattate da thoth_sqldb2 (Apache 2.0) — vedi src/tht/vendor/VENDORED.md. _TABLES_Q = text(""" SELECT c.relname AS table_name, diff --git a/harness/nsp/db/sampling.py b/harness/tht/db/sampling.py similarity index 96% rename from harness/nsp/db/sampling.py rename to harness/tht/db/sampling.py index 0c49717c..9fd916fb 100644 --- a/harness/nsp/db/sampling.py +++ b/harness/tht/db/sampling.py @@ -3,8 +3,8 @@ from dataclasses import dataclass from sqlalchemy import Engine, text -from nsp.config import ExamplesConfig, LshConfig -from nsp.mschema.models import Annotations, PhysicalSchema +from tht.config import ExamplesConfig, LshConfig +from tht.mschema.models import Annotations, PhysicalSchema logger = logging.getLogger(__name__) @@ -79,7 +79,7 @@ def unique_values_for_lsh( (ORDER BY frequenza); se superano `max_values_per_column` la colonna è troncata e segnalata (mai tagliata in silenzio). """ - from nsp.mschema.eligibility import effective_eligibility + from tht.mschema.eligibility import effective_eligibility annotations = annotations or Annotations() schema = physical.db_schema @@ -129,7 +129,7 @@ def unique_values_for_lsh_rest( Stessa logica di eligibility e di segnalazione del troncamento del transport diretto. """ - from nsp.mschema.eligibility import effective_eligibility + from tht.mschema.eligibility import effective_eligibility annotations = annotations or Annotations() schema = physical.db_schema diff --git a/harness/nsp/decisions.py b/harness/tht/decisions.py similarity index 96% rename from harness/nsp/decisions.py rename to harness/tht/decisions.py index 496fb0c6..3be675ae 100644 --- a/harness/nsp/decisions.py +++ b/harness/tht/decisions.py @@ -6,7 +6,7 @@ from pydantic import BaseModel DECISIONS_FILE = "review_decisions.jsonl" -# 22 tipi di ChironeWp3 (verified leggendo session/decisions.py) + 1 nuovo (D15): +# 22 tipi di the reference implementation (verified leggendo session/decisions.py) + 1 nuovo (D15): # `decision_retracted` per il rollback a granularità step (ritira una decisione # senza cancellarne la riga dal log di audit; effective_decisions la onora). DecisionType = Literal[ diff --git a/harness/nsp/evidence/__init__.py b/harness/tht/evidence/__init__.py similarity index 100% rename from harness/nsp/evidence/__init__.py rename to harness/tht/evidence/__init__.py diff --git a/harness/nsp/evidence/formula_store.py b/harness/tht/evidence/formula_store.py similarity index 100% rename from harness/nsp/evidence/formula_store.py rename to harness/tht/evidence/formula_store.py diff --git a/harness/nsp/evidence/model.py b/harness/tht/evidence/model.py similarity index 100% rename from harness/nsp/evidence/model.py rename to harness/tht/evidence/model.py diff --git a/harness/nsp/memory.py b/harness/tht/memory.py similarity index 96% rename from harness/nsp/memory.py rename to harness/tht/memory.py index 50770264..e9d5a8b7 100644 --- a/harness/nsp/memory.py +++ b/harness/tht/memory.py @@ -6,9 +6,9 @@ from pathlib import Path from pydantic import BaseModel -from nsp.decisions import DecisionRecord, DecisionType, list_decisions -from nsp.session.models import SessionManifest -from nsp.vectorstore.records import VectorRecord +from tht.decisions import DecisionRecord, DecisionType, list_decisions +from tht.session.models import SessionManifest +from tht.vectorstore.records import VectorRecord class MemoryRecord(BaseModel): @@ -32,7 +32,7 @@ def decided_memory_ids(decisions: list[DecisionRecord]) -> set[str]: """Id delle memorie gia' DECISE nella sessione: rifiutate (decisione `memory_rejected`, id nel subject) o applicate (citate come `mem-XXXX` nel rationale, per convenzione della checklist memorie). Servono a - `nsp memory search --session` per non riproporre cio' che il reviewer ha + `tht memory search --session` per non riproporre cio' che il reviewer ha gia' deciso (fix: memorie scartate riproposte).""" out: set[str] = set() for d in decisions: @@ -239,8 +239,8 @@ def save_one_memory( The destructive cleanup (sync's delete-stale step) is intentionally absent: it remains a server-side-only operation via the direct vectordb connection. """ - from nsp.vectorstore.rest_writer import pack_metadata - from nsp.vectorstore.store import content_hash + from tht.vectorstore.rest_writer import pack_metadata + from tht.vectorstore.store import content_hash record = memory_vector_record_for_decision(records, decision_seq) if record is None: diff --git a/harness/nsp/mschema/__init__.py b/harness/tht/mschema/__init__.py similarity index 100% rename from harness/nsp/mschema/__init__.py rename to harness/tht/mschema/__init__.py diff --git a/harness/nsp/mschema/eligibility.py b/harness/tht/mschema/eligibility.py similarity index 93% rename from harness/nsp/mschema/eligibility.py rename to harness/tht/mschema/eligibility.py index e81f2736..15a3299d 100644 --- a/harness/nsp/mschema/eligibility.py +++ b/harness/tht/mschema/eligibility.py @@ -1,15 +1,15 @@ -"""Classificazione di column eligibility (principio trasversale PsdWp3). +"""Classificazione di column eligibility (principio trasversale Thoth). -Vedi docs/superpowers/specs/2026-06-13-nsp-column-eligibility-principle.md. +Vedi docs/superpowers/specs/2026-06-13-tht-column-eligibility-principle.md. Il testo ampio (lettere di dimissione, note, anamnesi) è ignorato ovunque; i dati provengono solo da numerici, enum, temporali, booleani e testo breve. """ import re -from nsp.config import EligibilityConfig -from nsp.db.sampling import is_text_type -from nsp.mschema.models import ColumnAnnotation, ColumnPhysical, PhysicalSchema +from tht.config import EligibilityConfig +from tht.db.sampling import is_text_type +from tht.mschema.models import ColumnAnnotation, ColumnPhysical, PhysicalSchema _NUMERIC_PREFIXES = ( "smallint", "integer", "bigint", "numeric", "decimal", "real", "double", "money", diff --git a/harness/nsp/mschema/merge.py b/harness/tht/mschema/merge.py similarity index 90% rename from harness/nsp/mschema/merge.py rename to harness/tht/mschema/merge.py index bd7225e7..bc9a6b7d 100644 --- a/harness/nsp/mschema/merge.py +++ b/harness/tht/mschema/merge.py @@ -1,4 +1,4 @@ -from nsp.mschema.models import Annotations, PhysicalSchema +from tht.mschema.models import Annotations, PhysicalSchema def find_orphans(physical: PhysicalSchema, annotations: Annotations) -> list[str]: diff --git a/harness/nsp/mschema/models.py b/harness/tht/mschema/models.py similarity index 100% rename from harness/nsp/mschema/models.py rename to harness/tht/mschema/models.py diff --git a/harness/nsp/mschema/render.py b/harness/tht/mschema/render.py similarity index 97% rename from harness/nsp/mschema/render.py rename to harness/tht/mschema/render.py index 95627cbb..81e606d9 100644 --- a/harness/nsp/mschema/render.py +++ b/harness/tht/mschema/render.py @@ -1,7 +1,7 @@ from typing import Any -from nsp.mschema.eligibility import effective_eligibility -from nsp.mschema.models import Annotations, ColumnAnnotation, PhysicalSchema +from tht.mschema.eligibility import effective_eligibility +from tht.mschema.models import Annotations, ColumnAnnotation, PhysicalSchema MAX_EXAMPLES_IN_PROMPT = 5 diff --git a/harness/nsp/phase.py b/harness/tht/phase.py similarity index 97% rename from harness/nsp/phase.py rename to harness/tht/phase.py index bb2a92b8..c95c84bd 100644 --- a/harness/nsp/phase.py +++ b/harness/tht/phase.py @@ -1,6 +1,6 @@ """Phase machinery -- data-driven + effective_decisions (spec D15, F2, §4.8). -This is the single most important architectural fix vs ChironeWp3: ALL helpers +This is the single most important architectural fix vs the reference implementation: ALL helpers consult effective_decisions() instead of raw list_decisions(), so the reopen-aware view is consistent everywhere (fixes the bug where approved_ctes / advance_problems / build_evidence conflated stale pre-reopen decisions with new ones). @@ -16,9 +16,9 @@ from pathlib import Path from pydantic import ValidationError -from nsp.decisions import DecisionRecord, list_decisions -from nsp.session.models import SchemaLinking -from nsp.workflow import load_workflow +from tht.decisions import DecisionRecord, list_decisions +from tht.session.models import SchemaLinking +from tht.workflow import load_workflow def _phase_num(subject: str) -> int | None: diff --git a/harness/nsp/rest/__init__.py b/harness/tht/rest/__init__.py similarity index 100% rename from harness/nsp/rest/__init__.py rename to harness/tht/rest/__init__.py diff --git a/harness/nsp/rest/client.py b/harness/tht/rest/client.py similarity index 97% rename from harness/nsp/rest/client.py rename to harness/tht/rest/client.py index dde5fa99..085a4b4d 100644 --- a/harness/nsp/rest/client.py +++ b/harness/tht/rest/client.py @@ -1,4 +1,4 @@ -"""Client per il DWH Chirone esposto via Supabase/PostgREST. +"""Client per il the DWH esposto via Supabase/PostgREST. Incapsula i 10 rpc verificati contro produzione. Errori sempre in italiano e azionabili (stile `vectorstore/embeddings.py`). Lettura sola: l'API ammette solo SELECT/WITH. @@ -6,7 +6,7 @@ Incapsula i 10 rpc verificati contro produzione. Errori sempre in italiano e azi import requests -from nsp.config import RestConfig +from tht.config import RestConfig class RestError(Exception): diff --git a/harness/nsp/search/__init__.py b/harness/tht/search/__init__.py similarity index 99% rename from harness/nsp/search/__init__.py rename to harness/tht/search/__init__.py index d2c47a30..469e0ecc 100644 --- a/harness/nsp/search/__init__.py +++ b/harness/tht/search/__init__.py @@ -1,6 +1,6 @@ from pydantic import BaseModel -from nsp.vectorstore.store import VectorStore +from tht.vectorstore.store import VectorStore class SearchResult(BaseModel): diff --git a/harness/nsp/session/__init__.py b/harness/tht/session/__init__.py similarity index 100% rename from harness/nsp/session/__init__.py rename to harness/tht/session/__init__.py diff --git a/harness/nsp/session/artifacts.py b/harness/tht/session/artifacts.py similarity index 93% rename from harness/nsp/session/artifacts.py rename to harness/tht/session/artifacts.py index 3d42d3e7..2758ba06 100644 --- a/harness/nsp/session/artifacts.py +++ b/harness/tht/session/artifacts.py @@ -1,7 +1,7 @@ from pathlib import Path -from nsp.decisions import DecisionRecord -from nsp.session.models import SchemaLinking +from tht.decisions import DecisionRecord +from tht.session.models import SchemaLinking def _find_evidence_file(evidence_root: Path, evidence_id: str) -> str: diff --git a/harness/nsp/session/models.py b/harness/tht/session/models.py similarity index 95% rename from harness/nsp/session/models.py rename to harness/tht/session/models.py index 718b3c20..beb0c22e 100644 --- a/harness/nsp/session/models.py +++ b/harness/tht/session/models.py @@ -4,7 +4,7 @@ from typing import Literal from pydantic import BaseModel, Field, ConfigDict -# Stub locale di _YamlModel (in ChironeWp3 vive in mschema/models.py). +# Stub locale di _YamlModel (in the reference implementation vive in mschema/models.py). # Qui serve solo come base con populate_by_name; mschema/ sara' portato nel Task A9. class _YamlModel(BaseModel): model_config = ConfigDict(populate_by_name=True) diff --git a/harness/nsp/session/store.py b/harness/tht/session/store.py similarity index 95% rename from harness/nsp/session/store.py rename to harness/tht/session/store.py index eefb3971..3019abf2 100644 --- a/harness/nsp/session/store.py +++ b/harness/tht/session/store.py @@ -1,9 +1,9 @@ from datetime import UTC, datetime from pathlib import Path -from nsp.config import DatabaseConfig -from nsp.session.models import SessionManifest -from nsp.textutil import slugify +from tht.config import DatabaseConfig +from tht.session.models import SessionManifest +from tht.textutil import slugify MANIFEST = "session_manifest.yaml" MAX_SLUG_CHARS = 40 diff --git a/harness/nsp/taskdoc.py b/harness/tht/taskdoc.py similarity index 96% rename from harness/nsp/taskdoc.py rename to harness/tht/taskdoc.py index 25182f39..ad615c20 100644 --- a/harness/nsp/taskdoc.py +++ b/harness/tht/taskdoc.py @@ -13,8 +13,8 @@ from __future__ import annotations from dataclasses import dataclass from pathlib import Path -from nsp.phase import effective_decisions -from nsp.workflow import load_workflow +from tht.phase import effective_decisions +from tht.workflow import load_workflow MAX_BODY_BYTES = 80_000 # ~20k token (target per task document di una fase) diff --git a/harness/nsp/teardown.py b/harness/tht/teardown.py similarity index 96% rename from harness/nsp/teardown.py rename to harness/tht/teardown.py index a11c0947..0072c549 100644 --- a/harness/nsp/teardown.py +++ b/harness/tht/teardown.py @@ -1,7 +1,7 @@ """Artifact teardown on rollback (spec D15, §4.8). teardown_to_phase cancella ogni artefatto la cui fase produttrice > target, usando la -mappa artifacts_out di workflow.yaml. Risolve il bug latente di ChironeWp3: dopo un +mappa artifacts_out di workflow.yaml. Risolve il bug latente di the reference implementation: dopo un re-derive con piano CTE diverso, i vecchi ctes/*.sql orfani restavano su disco e bloccavano finalize (che itera glob('*.sql') esigendo che ognuno sia testato). @@ -14,7 +14,7 @@ import shutil from dataclasses import dataclass, field from pathlib import Path -from nsp.workflow import load_workflow +from tht.workflow import load_workflow @dataclass diff --git a/harness/nsp/textutil.py b/harness/tht/textutil.py similarity index 100% rename from harness/nsp/textutil.py rename to harness/tht/textutil.py diff --git a/harness/nsp/vectorstore/__init__.py b/harness/tht/vectorstore/__init__.py similarity index 100% rename from harness/nsp/vectorstore/__init__.py rename to harness/tht/vectorstore/__init__.py diff --git a/harness/nsp/vectorstore/embeddings.py b/harness/tht/vectorstore/embeddings.py similarity index 97% rename from harness/nsp/vectorstore/embeddings.py rename to harness/tht/vectorstore/embeddings.py index 131416ff..147016ee 100644 --- a/harness/nsp/vectorstore/embeddings.py +++ b/harness/tht/vectorstore/embeddings.py @@ -1,6 +1,6 @@ import requests -from nsp.config import EmbeddingsConfig +from tht.config import EmbeddingsConfig DOC_PREFIX = "search_document: " QUERY_PREFIX = "search_query: " diff --git a/harness/nsp/vectorstore/reader.py b/harness/tht/vectorstore/reader.py similarity index 93% rename from harness/nsp/vectorstore/reader.py rename to harness/tht/vectorstore/reader.py index f65e0680..083521d1 100644 --- a/harness/nsp/vectorstore/reader.py +++ b/harness/tht/vectorstore/reader.py @@ -9,10 +9,10 @@ Entrambe mappano i `kind` sulle tabelle per-dominio dello schema `vectors`. from sqlalchemy import Engine -from nsp.vectorstore.rest_client import VectorRestClient -from nsp.vectorstore.store import VectorHit, VectorStore, hit_from_metadata +from tht.vectorstore.rest_client import VectorRestClient +from tht.vectorstore.store import VectorHit, VectorStore, hit_from_metadata -# kind PsdWp3 → tabella dello schema `vectors`. +# kind Thoth → tabella dello schema `vectors`. KIND_TO_TABLE = { "schema_table": "schema_records", "schema_column": "schema_records", diff --git a/harness/nsp/vectorstore/records.py b/harness/tht/vectorstore/records.py similarity index 97% rename from harness/nsp/vectorstore/records.py rename to harness/tht/vectorstore/records.py index 91397a77..d7d9e9b2 100644 --- a/harness/nsp/vectorstore/records.py +++ b/harness/tht/vectorstore/records.py @@ -2,8 +2,8 @@ import re from pydantic import BaseModel -from nsp.evidence.model import EvidenceDoc -from nsp.mschema.models import Annotations, PhysicalSchema +from tht.evidence.model import EvidenceDoc +from tht.mschema.models import Annotations, PhysicalSchema MAX_EXAMPLES_IN_RECORD = 5 diff --git a/harness/nsp/vectorstore/rest_client.py b/harness/tht/vectorstore/rest_client.py similarity index 99% rename from harness/nsp/vectorstore/rest_client.py rename to harness/tht/vectorstore/rest_client.py index 33472a2c..4e041910 100644 --- a/harness/nsp/vectorstore/rest_client.py +++ b/harness/tht/vectorstore/rest_client.py @@ -7,7 +7,7 @@ Errori in italiano e azionabili, stile `rest/client.py`. import requests -from nsp.config import RestConfig +from tht.config import RestConfig class VectorRestError(Exception): diff --git a/harness/nsp/vectorstore/rest_writer.py b/harness/tht/vectorstore/rest_writer.py similarity index 94% rename from harness/nsp/vectorstore/rest_writer.py rename to harness/tht/vectorstore/rest_writer.py index c5474e5b..d83d84bf 100644 --- a/harness/nsp/vectorstore/rest_writer.py +++ b/harness/tht/vectorstore/rest_writer.py @@ -5,9 +5,9 @@ Mantiene l'upsert incrementale del VectorStore diretto, ma non esegue delete/cle operazioni distruttive restano solo-server via connessione Postgres diretta. """ -from nsp.vectorstore.records import VectorRecord -from nsp.vectorstore.rest_client import VectorRestClient -from nsp.vectorstore.store import SyncStats, content_hash +from tht.vectorstore.records import VectorRecord +from tht.vectorstore.rest_client import VectorRestClient +from tht.vectorstore.store import SyncStats, content_hash KIND_TO_TABLE = { diff --git a/harness/nsp/vectorstore/store.py b/harness/tht/vectorstore/store.py similarity index 98% rename from harness/nsp/vectorstore/store.py rename to harness/tht/vectorstore/store.py index 8f7fe44b..f485e241 100644 --- a/harness/nsp/vectorstore/store.py +++ b/harness/tht/vectorstore/store.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from sqlalchemy import Engine, text -from nsp.vectorstore.records import VectorRecord +from tht.vectorstore.records import VectorRecord def content_hash(content: str) -> str: @@ -180,7 +180,7 @@ class VectorStore: def _pack_metadata(r: VectorRecord) -> dict: - """Impacchetta nel `metadata` (unica colonna letta via REST) tutta la semantica PsdWp3.""" + """Impacchetta nel `metadata` (unica colonna letta via REST) tutta la semantica Thoth.""" return { "kind": r.kind, "ref": r.ref, diff --git a/harness/nsp/workflow.py b/harness/tht/workflow.py similarity index 95% rename from harness/nsp/workflow.py rename to harness/tht/workflow.py index 52017980..a973d505 100644 --- a/harness/nsp/workflow.py +++ b/harness/tht/workflow.py @@ -1,7 +1,7 @@ """Reads workflow.yaml -- the SINGLE source of workflow truth (spec F2, §5.3). -phase.py, the gate (nsp-gate.js), and the skill all read from here. -No more duplicated constants (the JS/Python drift bug in ChironeWp3 -- PHASE_NAMES +phase.py, the gate (tht-gate.js), and the skill all read from here. +No more duplicated constants (the JS/Python drift bug in the reference implementation -- PHASE_NAMES truncated to 7 in JS -- is structurally impossible because there is one source). Edit workflow.yaml to change the workflow: add/reorder/merge/skip phases. diff --git a/harness/nsp/workspace.py b/harness/tht/workspace.py similarity index 72% rename from harness/nsp/workspace.py rename to harness/tht/workspace.py index 9206d24b..b9a258d3 100644 --- a/harness/nsp/workspace.py +++ b/harness/tht/workspace.py @@ -1,9 +1,9 @@ """Workspace YAML loading — the single boundary for workspace configuration (spec D3). Reads workspaces/.yaml, expands ${VAR} from env, validates via the Config model -(ported from ChironeWp3). Future migration to a DB store would replace only this module. +(ported from the reference implementation). Future migration to a DB store would replace only this module. -La struttura YAML rispecchia esattamente nsp/config.py: +La struttura YAML rispecchia esattamente tht/config.py: database + rest (DWH), vector_rest + vector_write_rest (pgvector, doppia key top-level), vector_db (loading diretto, server-only), embeddings, evidence, execution. """ @@ -11,7 +11,7 @@ from __future__ import annotations from pathlib import Path -from nsp.config import Config, ConfigError, load_config +from tht.config import Config, ConfigError, load_config class WorkspaceError(Exception): @@ -22,7 +22,7 @@ def load_workspace(path: str | Path) -> Config: """Carica e valida un workspace YAML. Espande ${VAR} dall'ambiente; se una variabile referenziata manca, raises WorkspaceError. - Delega a load_config (portato da ChironeWp3) per la validazione del modello Config. + Delega a load_config (portato da the reference implementation) per la validazione del modello Config. """ path = Path(path) try: diff --git a/harness/workspaces/chirone-test.yaml b/harness/workspaces/tht-test.yaml similarity index 61% rename from harness/workspaces/chirone-test.yaml rename to harness/workspaces/tht-test.yaml index 1a5687ea..f44483fd 100644 --- a/harness/workspaces/chirone-test.yaml +++ b/harness/workspaces/tht-test.yaml @@ -1,20 +1,20 @@ -# Workspace ThothII — Chirone (DWH remoto) per i test L2. -# Struttura allineata a workspaces/chirone.example.yaml e nsp/config.py. -# I segreti vivono SOLO in .env (${THOTH_*}). +# Workspace ThothII — test (DWH remoto) per i test L2. +# Struttura allineata a workspaces/tht.example.yaml e tht/config.py. +# I segreti vivono SOLO in .env (${THT_*}). database: - host: ${THOTH_DB_HOST} - port: ${THOTH_DB_PORT} - database: ${THOTH_DB_NAME} + host: ${THT_DB_HOST} + port: ${THT_DB_PORT} + database: ${THT_DB_NAME} schema: datawarehouse - user: ${THOTH_DB_USER} - password: ${THOTH_DB_PASSWORD} + user: ${THT_DB_USER} + password: ${THT_DB_PASSWORD} transport: rest rest: - base_url: ${THOTH_DWH_REST_URL} - api_key: ${THOTH_DWH_API_KEY} - ssl_ca: ${THOTH_SSL_CA} + base_url: ${THT_DWH_REST_URL} + api_key: ${THT_DWH_API_KEY} + ssl_ca: ${THT_SSL_CA} paths: artifacts: artifacts @@ -22,7 +22,7 @@ paths: sessions: sessions embeddings: - base_url: ${THOTH_OLLAMA_URL} + base_url: ${THT_OLLAMA_URL} model: nomic-embed-text-v2-moe dim: 768 batch_size: 64 @@ -32,26 +32,26 @@ embeddings: # li richiede, per cui si appoggiano alle stesse variabili del server (popolate nel # .env del server; sul workstation restano dummy/inediti finche' transport=rest). vector_db: - host: ${THOTH_VEC_HOST} - port: ${THOTH_VEC_PORT} + host: ${THT_VEC_HOST} + port: ${THT_VEC_PORT} database: postgres schema: vectors - user: ${THOTH_VEC_USER} - password: ${THOTH_VEC_PASSWORD} + user: ${THT_VEC_USER} + password: ${THT_VEC_PASSWORD} # LETTURA del pgvector via REST remota (rpc search_similar), ruolo vector_reader. vector_rest: - base_url: ${THOTH_VEC_REST_URL} - api_key: ${THOTH_VEC_API_KEY} - ssl_ca: ${THOTH_SSL_CA} + base_url: ${THT_VEC_REST_URL} + api_key: ${THT_VEC_API_KEY} + ssl_ca: ${THT_SSL_CA} # SCRITTURA controllata del pgvector via REST remota (upsert/hash via RPC allowlist), # ruolo vector_writer. Path DEDICATO /vector/write/v1/ (diverso dal reader): la chiave # writer vale solo qui, la reader solo su /vector/v1/. Key SEPARATA dalla lettura. vector_write_rest: - base_url: ${THOTH_VEC_WRITE_REST_URL} - api_key: ${THOTH_VEC_WRITE_API_KEY} - ssl_ca: ${THOTH_SSL_CA} + base_url: ${THT_VEC_WRITE_REST_URL} + api_key: ${THT_VEC_WRITE_API_KEY} + ssl_ca: ${THT_SSL_CA} vector: max_chunk_chars: 4000 diff --git a/harness/workspaces/chirone.example.yaml b/harness/workspaces/tht.example.yaml similarity index 55% rename from harness/workspaces/chirone.example.yaml rename to harness/workspaces/tht.example.yaml index d363f079..97109d9b 100644 --- a/harness/workspaces/chirone.example.yaml +++ b/harness/workspaces/tht.example.yaml @@ -1,22 +1,22 @@ -# Workspace ThothII — Chirone (esempio). I segreti vivono SOLO in .env (${THOTH_*}). -# La struttura rispecchia esattamente nsp/config.py (portato da ChironeWp3): +# Workspace ThothII (esempio). I segreti vivono SOLO in .env (${THT_*}). +# La struttura rispecchia esattamente tht/config.py: # database + rest per il DWH; vector_rest/vector_write_rest per il pgvector (doppia key); # vector_db per il loading diretto (server-only); embeddings + evidence + execution. database: - host: ${THOTH_DB_HOST} - port: ${THOTH_DB_PORT} # es. 5437 (Postgres diretto Supabase; 5432 = pooler) - database: ${THOTH_DB_NAME} # es. postgres (lo schema a stella vive in `datawarehouse`) + host: ${THT_DB_HOST} + port: ${THT_DB_PORT} # es. 5437 (Postgres diretto Supabase; 5432 = pooler) + database: ${THT_DB_NAME} # es. postgres (lo schema a stella vive in `datawarehouse`) schema: datawarehouse - user: ${THOTH_DB_USER} - password: ${THOTH_DB_PASSWORD} + user: ${THT_DB_USER} + password: ${THT_DB_PASSWORD} transport: rest # direct (Postgres) | rest (Supabase/PostgREST) # Accesso al DWH via REST (richiesto se database.transport = rest). rest: - base_url: ${THOTH_DWH_REST_URL} # es. https://supabase-aritmolab.policlinicosandonato.it/dwh/ - api_key: ${THOTH_DWH_API_KEY} # header X-API-Key, ruolo dwh_reader (read-only) - ssl_ca: ${THOTH_SSL_CA} # path al certificato CA (per server con CA interna) + base_url: ${THT_DWH_REST_URL} # es. https://supabase-aritmolab.policlinicosandonato.it/dwh/ + api_key: ${THT_DWH_API_KEY} # header X-API-Key, ruolo dwh_reader (read-only) + ssl_ca: ${THT_SSL_CA} # path al certificato CA (per server con CA interna) paths: artifacts: artifacts @@ -39,11 +39,11 @@ eligibility: ignore_columns: [etl_last_update] evidence: - source_root: ${THOTH_DOCS_ROOT} # es. /Users/mp/Chirone/chirone/etl/docs + source_root: ${THT_DOCS_ROOT} # es. harness/evidence (cartella curata dentro ThothII) evidence_dir: evidence # cartella curata a mano: unica fonte delle evidence embeddings: - base_url: ${THOTH_OLLAMA_URL} # es. http://localhost:11434 + base_url: ${THT_OLLAMA_URL} # es. http://localhost:11434 model: nomic-embed-text-v2-moe dim: 768 batch_size: 32 @@ -51,27 +51,27 @@ embeddings: # LOADING del pgvector: connessione diretta, eseguita sul server (profilo server). # Opzionale su postazione remota (lì la lettura passa da vector_rest). vector_db: - host: ${THOTH_VEC_HOST} # Postgres locale del server - port: ${THOTH_VEC_PORT} # es. 5437 + host: ${THT_VEC_HOST} # Postgres locale del server + port: ${THT_VEC_PORT} # es. 5437 database: postgres schema: vectors - user: ${THOTH_VEC_USER} - password: ${THOTH_VEC_PASSWORD} + user: ${THT_VEC_USER} + password: ${THT_VEC_PASSWORD} # LETTURA (similarity search) del pgvector via REST remota: rpc search_similar. vector_rest: - base_url: ${THOTH_VEC_REST_URL} # es. https://host/vector/v1/ - api_key: ${THOTH_VEC_API_KEY} # header X-API-Key, ruolo vector_reader (read-only) - ssl_ca: ${THOTH_SSL_CA} + base_url: ${THT_VEC_REST_URL} # es. https://host/vector/v1/ + api_key: ${THT_VEC_API_KEY} # header X-API-Key, ruolo vector_reader (read-only) + ssl_ca: ${THT_SSL_CA} # SCRITTURA controllata del pgvector via REST remota: upsert/hash via RPC allowlist, # niente delete/clear. Usa una API key SEPARATA dalla lettura (ruolo vector_writer). # OPZIONALE: assente o key vuota = scrittura non abilitata (solo lettura). -# Abilita nsp memory save-one / vector index-schema da postazione remota. +# Abilita tht memory save-one / vector index-schema da postazione remota. vector_write_rest: - base_url: ${THOTH_VEC_REST_URL} - api_key: ${THOTH_VEC_WRITE_API_KEY} - ssl_ca: ${THOTH_SSL_CA} + base_url: ${THT_VEC_REST_URL} + api_key: ${THT_VEC_WRITE_API_KEY} + ssl_ca: ${THT_SSL_CA} vector: max_chunk_chars: 4000