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:
+19
-19
@@ -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=
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
from nsp.workflow import load_workflow
|
||||
from tht.workflow import load_workflow
|
||||
|
||||
|
||||
def test_workflow_loads_8_phases():
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user