refactor(harness): renaming prodotto tht (Onda -1)

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.
This commit is contained in:
2026-06-27 10:33:16 +02:00
parent 0dcc0246dc
commit fc5fbe6b65
72 changed files with 309 additions and 306 deletions
+19 -19
View File
@@ -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=
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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
@@ -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 "<la domanda dell\'utente nel messaggio sopra>"` e annota ' +
"orchestri, il reviewer decide, la CLI `tht` persiste; NON sei in modalita' autonoma).\n" +
'1. Esegui `tht session new "<la domanda dell\'utente nel messaggio sopra>"` 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 <id>` e leggi: stato, domanda, decisioni registrate, presenza " +
"1. Esegui `tht session show <id>` 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 <session_id> [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" },
+14 -14
View File
@@ -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/<name>.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 "<your question in natural language>"
```
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)
+4 -4
View File
@@ -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
```
+5 -2
View File
@@ -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
+5 -5
View File
@@ -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")
+1 -1
View File
@@ -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]
+1 -1
View File
@@ -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]
+3 -3
View File
@@ -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]
@@ -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():
+6 -6
View File
@@ -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/)")
+10 -10
View File
@@ -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(
+2 -2
View File
@@ -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()
+2 -2
View File
@@ -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",
+2 -2
View File
@@ -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
@@ -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):
+3 -3
View File
@@ -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:
+3 -3
View File
@@ -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,
+3 -3
View File
@@ -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"),
+2 -2
View File
@@ -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:
+5 -5
View File
@@ -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()
@@ -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:
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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):
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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:
+1 -1
View File
@@ -1,4 +1,4 @@
from nsp.workflow import load_workflow
from tht.workflow import load_workflow
def test_workflow_loads_8_phases():
+36 -36
View File
@@ -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)
@@ -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")
@@ -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)")
@@ -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:
@@ -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:
@@ -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
@@ -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,
@@ -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
@@ -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[
@@ -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:
@@ -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",
@@ -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]:
@@ -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
@@ -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:
@@ -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):
@@ -1,6 +1,6 @@
from pydantic import BaseModel
from nsp.vectorstore.store import VectorStore
from tht.vectorstore.store import VectorStore
class SearchResult(BaseModel):
@@ -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:
@@ -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)
@@ -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
@@ -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)
@@ -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
@@ -1,6 +1,6 @@
import requests
from nsp.config import EmbeddingsConfig
from tht.config import EmbeddingsConfig
DOC_PREFIX = "search_document: "
QUERY_PREFIX = "search_query: "
@@ -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",
@@ -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
@@ -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):
@@ -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 = {
@@ -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,
@@ -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.
@@ -1,9 +1,9 @@
"""Workspace YAML loading — the single boundary for workspace configuration (spec D3).
Reads workspaces/<name>.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:
@@ -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
@@ -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