This commit is contained in:
@@ -5,6 +5,20 @@ surface is one CLI, `tht`; there is no separate authentication executable. The b
|
||||
opaque browser sessions and authorization, while `tht` owns protected configuration and local-user
|
||||
files.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
BROWSER["Browser"] --> BOUNDARY["Authentication boundary"]
|
||||
BOUNDARY --> LOCAL["Local users\nArgon2id hashes"]
|
||||
BOUNDARY --> OIDC["OIDC provider\nAuthorization Code PKCE"]
|
||||
OIDC --> GROUPS["Groups claim\nexact mapping"]
|
||||
LOCAL --> PRINCIPAL["Thoth principal"]
|
||||
GROUPS --> PRINCIPAL
|
||||
PRINCIPAL --> ROLES["Roles"]
|
||||
ROLES --> PERMISSIONS["Permissions"]
|
||||
PERMISSIONS --> ROUTES["Protected routes"]
|
||||
SECRETS["Mounted secret bundle"] -.-> BOUNDARY
|
||||
```
|
||||
|
||||
## Configuration and trust boundaries
|
||||
|
||||
The installation descriptor points to an operator-controlled authentication directory. It contains
|
||||
@@ -70,8 +84,8 @@ Workspace Validate performs static authentication validation without provider co
|
||||
`tht auth check` performs live, non-interactive diagnosis: static safety plus OIDC discovery,
|
||||
issuer/JWKS, group-catalog authentication, and exact configured-group existence. Adding
|
||||
`--interactive` runs that same live diagnosis and then validates a device-flow identity when the
|
||||
provider supports Device Authorization. Workspace Test is the aggregate live workspace and
|
||||
authentication validation.
|
||||
provider supports Device Authorization. Aggregate live workspace and authentication validation is
|
||||
available through the installation diagnostics.
|
||||
|
||||
The ordered `tht doctor` report is exactly: `descriptor`, `files`, `docker`, `compose`,
|
||||
`configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
# Components, modules, and flows
|
||||
|
||||
This page complements the [architecture overview](overview.md) with the module structure and flows through ThothII. The diagrams describe the current code, not a future architecture.
|
||||
|
||||
## Modules and dependencies
|
||||
|
||||
The frontend communicates with the backend through REST and SSE. The backend does not own session persistence: it starts Pi, invokes the `tht` CLI, and forwards events. The harness contains the workflow, the Python CLI, and adapters for the DWH and vector store.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
FE["frontend/\nReact + Vite"] -->|REST + SSE| BE["backend/\nFastify + TypeScript"]
|
||||
BE -->|RPC stdin/stdout| PI["Pi\n--mode rpc"]
|
||||
BE -->|subprocess\nJSON stdout| THT["harness/tht\nCLI Python"]
|
||||
PI --> EXT["harness/.pi/extensions/\ntht-gate.js"]
|
||||
EXT --> SKILL["harness/.pi/skills/\ntht-sessione"]
|
||||
EXT --> THT
|
||||
THT --> FS["Sessions and artifacts\nworkspace repository"]
|
||||
THT --> DWH["DWH\nread-only"]
|
||||
THT --> VDB["Qdrant / vector store"]
|
||||
BE --> CFG["settings.json\nworkspace registry"]
|
||||
FE -.->|renders widgets| EXT
|
||||
```
|
||||
|
||||
Dipendenze principali:
|
||||
|
||||
| Module | Depends on | Responsibility |
|
||||
| --- | --- | --- |
|
||||
| `frontend/` | Backend REST and SSE APIs | UI, gate widgets, and in-memory transcript |
|
||||
| `backend/src/` | Pi, `tht`, configuration, and workspace registry | Transport, session lifecycle, and APIs |
|
||||
| `harness/.pi/` | Pi and `tht phase` | Workflow orchestration and human-in-the-loop gates |
|
||||
| `harness/tht/` | Filesystem, DWH, and vector store | Persistence, CLI, Evidence, schema, and preprocessing |
|
||||
| workspace repository | `source/`, `curated/`, manifest, and artifacts | Versioned Evidence source and session output |
|
||||
|
||||
## Session sequence
|
||||
|
||||
The main path starts with a user question and ends with an SSE event. Reviewer decisions use the same channel and are persisted by the harness.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor U as User or reviewer
|
||||
participant FE as Frontend
|
||||
participant BE as Backend
|
||||
participant PI as Pi RPC
|
||||
participant THT as CLI tht
|
||||
participant WS as Workspace
|
||||
participant DWH as DWH
|
||||
|
||||
U->>FE: Send question or gate decision
|
||||
FE->>BE: POST session / risposta widget
|
||||
BE->>PI: RPC input o prompt di resume
|
||||
PI->>THT: phase/session/evidence commands
|
||||
THT->>WS: Read and write phase artifacts
|
||||
THT->>DWH: Introspection or read-only query
|
||||
DWH-->>THT: Schema, results, or diagnostics
|
||||
THT-->>PI: JSON and phase state
|
||||
PI-->>BE: RPC events and widget descriptor
|
||||
BE-->>FE: SSE text_delta, info, ui_request
|
||||
FE-->>U: Text, artifact, or review request
|
||||
```
|
||||
|
||||
The backend uses `ThtRunner` for CLI subprocesses, `PiProcessManager` for one Pi process per session, `SessionBridge` to adapt RPC events, and `SseHub` to distribute them to clients.
|
||||
|
||||
## Main backend classes
|
||||
|
||||
The diagram shows the classes that form the bridge between the browser, Pi, and `tht`. Fastify routes receive requests and delegate to these services.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class ThtRunner {
|
||||
+buildArgv(command, args) string[]
|
||||
+run(args) Promise~ThtResult~
|
||||
+sessionShow(id) Promise~unknown~
|
||||
}
|
||||
class PiProcessManager {
|
||||
-runtimes Map
|
||||
+spawnFor(sessionId, mode) SessionRuntime
|
||||
+resume(sessionId, tht) Promise~SessionRuntime~
|
||||
+stop(sessionId) Promise~void~
|
||||
}
|
||||
class SessionBridge {
|
||||
+handleRpcEvent(event) ClientEvent
|
||||
+handleUiResponse(response) Promise~void~
|
||||
}
|
||||
class SseHub {
|
||||
+subscribe(sessionId) AsyncIterable
|
||||
+publish(sessionId, event) void
|
||||
+close(sessionId) void
|
||||
}
|
||||
class SessionRoutes {
|
||||
+createSession(request) Response
|
||||
+resumeSession(id) Response
|
||||
+postInput(id, input) Response
|
||||
}
|
||||
class WorkspaceRegistry {
|
||||
+list() Workspace[]
|
||||
+resolve(id) Workspace
|
||||
}
|
||||
class SettingsStore {
|
||||
+get() Settings
|
||||
+update(patch) Settings
|
||||
}
|
||||
class App {
|
||||
+buildApp() FastifyInstance
|
||||
}
|
||||
|
||||
App --> SessionRoutes
|
||||
App --> WorkspaceRegistry
|
||||
App --> SettingsStore
|
||||
SessionRoutes --> PiProcessManager
|
||||
SessionRoutes --> ThtRunner
|
||||
SessionRoutes --> SseHub
|
||||
PiProcessManager --> SessionBridge
|
||||
PiProcessManager --> ThtRunner
|
||||
SessionBridge --> SseHub
|
||||
```
|
||||
|
||||
## Python modules in the `tht` CLI
|
||||
|
||||
The CLI consists of Typer commands and domain modules. `cli/` turns arguments into operations; `evidence/`, `session/`, `db/`, `adapters/`, and the other packages contain the application logic.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
MAIN["tht/cli/__init__.py"] --> CMD["tht/cli/*_cmd.py"]
|
||||
CMD --> CONFIG["config.py\nworkspace.py\npaths.py"]
|
||||
CMD --> SESSION["session_cmd.py\nsession/"]
|
||||
CMD --> EVIDENCE["evidence_cmd.py\nevidence/"]
|
||||
CMD --> PRE["preprocess_cmd.py\nevidence/corpus/"]
|
||||
CMD --> PHASE["phase_cmd.py\nphase.py\nworkflow.py"]
|
||||
CMD --> SQL["sql_cmd.py\ndb/\nrest/"]
|
||||
EVIDENCE --> ACQ["evidence/acquisition.py\nadapters/ filesystem/http/s3"]
|
||||
EVIDENCE --> CANON["evidence/canonical.py\ncontracts.py\nmodel.py"]
|
||||
EVIDENCE --> AUTHOR["evidence/authoring.py"]
|
||||
PRE --> PIPE["evidence/corpus/pipeline.py\nchunk.py normalize.py store.py"]
|
||||
PRE --> VECTOR["adapters/vector/qdrant.py"]
|
||||
PRE --> DWH["jobs/dwh_pipeline.py\nadapters/dwh/"]
|
||||
SESSION --> REPO["session/filesystem_repository.py\npostgres_repository.py"]
|
||||
PHASE --> LEDGER["decisions.py\nreview_decisions"]
|
||||
```
|
||||
|
||||
The operator command `tht` in `tools/tht/` is separate from the harness Python CLI. The former handles installation, lifecycle, authentication, and workspaces; the latter runs the workflow and data operations.
|
||||
|
||||
## Eight-phase workflow and gates
|
||||
|
||||
The source of truth is `harness/workflow.yaml`. The current phase is computed from the decision ledger, not from a manually updated field.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
F1["F1\nChiarimento"] --> F2["F2\nMemoria"]
|
||||
F2 --> F3["F3\nRiscrittura"]
|
||||
F3 --> F4["F4\nSchema linking\nreviewer_decide"]
|
||||
F4 --> F5["F5\nSintesi"]
|
||||
F5 --> F6["F6\nCTE\nauto o skip"]
|
||||
F6 --> F7["F7\nSQL finale\nreviewer_confirm"]
|
||||
F7 --> F8["F8\nDatamart\nreviewer_decide"]
|
||||
F1 -.->|reviewer_confirm| F1
|
||||
F3 -.->|reviewer_confirm| F3
|
||||
F4 -.->|decisioni su tabelle, colonne, evidence| F4
|
||||
F6 -.->|cte_approved o cte_rejected| F6
|
||||
F7 -.->|sql_approved o sql_rejected| F7
|
||||
F8 -.->|datamart_requested o declined| F8
|
||||
```
|
||||
|
||||
| Fase | Nome | Avanzamento | Artefatti principali |
|
||||
| --- | --- | --- | --- |
|
||||
| F1 | chiarimento | `kind:phase` | decisioni di chiarimento |
|
||||
| F2 | memoria | automatico se vuota | decisioni memoria |
|
||||
| F3 | riscrittura | `kind:phase` | `question.md` |
|
||||
| F4 | schema linking | `reviewer_decide` | `schema_linking.json` |
|
||||
| F5 | sintesi | `kind:phase` | verifica dello schema linking |
|
||||
| F6 | CTE | automatico, oppure skip | `cte_plan.json`, `ctes/`, `cte_tests.json` |
|
||||
| F7 | SQL finale | `kind:phase` dopo `sql_approved` | `sql_final.sql` |
|
||||
| F8 | datamart | `reviewer_decide` | decisione su richiesta o rifiuto |
|
||||
|
||||
Un `reviewer_select` con decisione incorporata può confermare direttamente. Un `reviewer_decide` registra le scelte multiple. Un `reviewer_confirm` conferma un artefatto o la chiusura della fase. Il modello propone; il revisore decide e il ledger registrato è la fonte dello stato.
|
||||
@@ -1,64 +1,89 @@
|
||||
# Panoramica dell'architettura
|
||||
# Architecture overview
|
||||
|
||||
> Sintesi ad uso documentazione. Per il dettaglio storico delle decisioni di design vedi le [Specifiche di Design](../superpowers/specs/2026-06-25-thothii-architecture-design.md) e i [Piani di Implementazione](../superpowers/plans/2026-06-25-harness-implementation.md). Per lo stato corrente del progetto (gate manuali pendenti, layout workspace/secret) vedi `PROJECT_STATE.md` nella radice del repo.
|
||||
> For details about modules and flows, see [Components, modules, and flows](components.md).
|
||||
|
||||
ThothII è un **datamart builder human-in-the-loop**: trasforma una domanda in linguaggio naturale in SQL validato (ed eventualmente un datamart dbt) attraverso un **workflow deterministico a 8 fasi NL→SQL**, in cui il modello *propone* e un revisore umano *decide* ai gate.
|
||||
ThothII is a **human-in-the-loop datamart builder**. It turns a natural-language question into validated SQL, and optionally a dbt datamart, through a **deterministic eight-phase NL-to-SQL workflow** in which the model *proposes* and a human reviewer *decides* at gates.
|
||||
|
||||
L'autenticazione di produzione usa local oppure OIDC generico; il solo CLI operatore è tht.
|
||||
Per sessioni, ruoli, gruppi, diagnostica e ripristino vedere la [documentazione autenticazione](authentication.md).
|
||||
Production authentication uses local authentication or generic OIDC. `tht` is the only operator CLI.
|
||||
For sessions, roles, groups, diagnostics, and recovery, see the [authentication documentation](authentication.md).
|
||||
|
||||
## I tre progetti indipendenti
|
||||
```mermaid
|
||||
flowchart LR
|
||||
USER["Reviewer"] --> FE["Frontend\nReact and SSE"]
|
||||
FE --> BE["Backend\nFastify"]
|
||||
BE --> PI["Pi\nRPC per sessione"]
|
||||
PI --> THT["tht and harness\nworkflow and persistence"]
|
||||
THT --> DWH["DWH\nread only"]
|
||||
THT --> EVIDENCE["Evidence\ncurated corpus"]
|
||||
EVIDENCE --> THT
|
||||
THT --> FE
|
||||
```
|
||||
|
||||
## The three independent projects
|
||||
|
||||
```
|
||||
frontend (React/SSE) → backend (Fastify) → pi --mode rpc → tht/harness → DWH (sola lettura)
|
||||
frontend (React/SSE) → backend (Fastify) → pi --mode rpc → tht/harness → DWH (read-only)
|
||||
```
|
||||
|
||||
| Layer | Stack | Ruolo |
|
||||
|---|---|---|
|
||||
| **harness/** | Python (`tht` CLI) + Pi gate extension (JS) | Possiede il workflow e **tutta** la persistenza |
|
||||
| **backend/** | Fastify + TypeScript | Ponte sottile senza database proprio |
|
||||
| **frontend/** | React 18 + Vite | UI che renderizza i widget di gate e ricostruisce il transcript live dallo stream SSE |
|
||||
| **harness/** | Python (`tht` CLI) + Pi gate extension (JS) | Owns the workflow and **all** persistence |
|
||||
| **backend/** | Fastify + TypeScript | Thin bridge with no database of its own |
|
||||
| **frontend/** | React 18 + Vite | UI that renders gate widgets and rebuilds the live transcript from the SSE stream |
|
||||
|
||||
## L'harness possiede il workflow
|
||||
## The harness owns the workflow
|
||||
|
||||
`tht` (Python) è una CLI deterministica; `harness/.pi/extensions/tht-gate.js` è un'estensione Pi che guida il workflow a 8 fasi. La fonte di verità unica del workflow è `harness/workflow.yaml`; le regole di orchestrazione che il modello deve seguire sono in `harness/.pi/skills/tht-sessione/SKILL.md`. La "fase corrente" **non è memorizzata**: viene calcolata piegando il decision ledger (`harness/tht/phase.py`) — va letta prima di ragionare sulla logica di fase.
|
||||
`tht` (Python) is a deterministic CLI. `harness/.pi/extensions/tht-gate.js` is a Pi extension that guides the eight-phase workflow. `harness/workflow.yaml` is the single source of workflow truth, and `harness/.pi/skills/tht-sessione/SKILL.md` contains the orchestration rules the model must follow. The "current phase" is **not stored**. It is computed by folding the decision ledger (`harness/tht/phase.py`), which must be read before reasoning about phase logic.
|
||||
|
||||
## Persistenza = documenti di fase, non chat
|
||||
## Persistence means phase documents, not chat
|
||||
|
||||
Una sessione è una directory sotto `sessions/` (path definito dal workspace): `session_manifest.yaml` + artefatti per fase (`question.md`, `schema_linking.json`, `sql_final.sql`, …) + `review_decisions.jsonl`. Il contratto (SKILL.md): *"lo stato persistito è la verità — ciò che non è registrato non è accaduto"*. Non esiste uno store di transcript verbatim. Un processo Pi ripreso ricostruisce il contesto da `tht session show <id>` + gli artefatti su disco.
|
||||
A session is a directory under `sessions/` (the workspace defines the path): `session_manifest.yaml`, phase artifacts (`question.md`, `schema_linking.json`, `sql_final.sql`, and others), and `review_decisions.jsonl`. The contract says: *"persisted state is the truth; what is not recorded did not happen"*. There is no verbatim transcript store. A resumed Pi process rebuilds context from `tht session show <id>` and the artifacts on disk.
|
||||
|
||||
## Il backend è un ponte sottile senza database
|
||||
## The backend is a thin bridge with no database
|
||||
|
||||
- `ThtRunner` esegue subcommand `tht` in shell
|
||||
- `PiProcessManager` esegue un processo Pi figlio per sessione e fa da bridge al suo stream RPC
|
||||
- `SessionBridge` mappa eventi RPC di Pi → eventi client (`ui_request` / `text_delta` / `info`)
|
||||
- `SseHub` distribuisce questi eventi via SSE al browser
|
||||
- `ThtRunner` runs `tht` subcommands in a shell.
|
||||
- `PiProcessManager` runs one Pi child process per session and bridges its RPC stream.
|
||||
- `SessionBridge` maps Pi RPC events to client events (`ui_request` / `text_delta` / `info`).
|
||||
- `SseHub` distributes these events to the browser over SSE.
|
||||
|
||||
Le impostazioni applicative vivono in un file JSON (`backend/data/settings.json`), non in un database.
|
||||
Application settings live in a JSON file (`backend/data/settings.json`), not in a database.
|
||||
|
||||
## Contratto del gate human-in-the-loop
|
||||
## Human-in-the-loop gate contract
|
||||
|
||||
Il modello propone; un revisore umano decide ai gate tramite widget:
|
||||
The model proposes; a human reviewer decides at gates through widgets:
|
||||
|
||||
- **`reviewer_select`** — scelta singola: un'opzione con `decision` payload auto-conferma/persiste direttamente; un'opzione senza payload chiede soltanto
|
||||
- **`reviewer_decide`** — multiselect: ogni scelta È una decisione
|
||||
- **`reviewer_confirm`** — gate su artefatto/fase
|
||||
- **`reviewer_select`**: single choice. An option with a `decision` payload confirms and persists directly; an option without a payload only asks.
|
||||
- **`reviewer_decide`**: multiselect. Each choice is a decision.
|
||||
- **`reviewer_confirm`**: artifact or phase gate.
|
||||
|
||||
Il frontend renderizza questi widget-descriptor (registro in `src/widgets/`); il transcript live viene ricostruito in memoria dallo stream SSE (`src/store/sessionStore.ts`) — **non è persistito**.
|
||||
The frontend renders these widget descriptors (registry in `src/widgets/`). It rebuilds the live transcript in memory from the SSE stream (`src/store/sessionStore.ts`); it is **not persisted**.
|
||||
|
||||
## Punti di attenzione ricorrenti
|
||||
## Curated and immutable Evidence
|
||||
|
||||
- `tht -c`/`--config` è un'opzione **per-comando**: deve seguire il subcommand, mai precederlo (`ThtRunner.buildArgv` lo impone).
|
||||
- L'output `--json` deve essere JSON puro su stdout — è un contratto machine-readable.
|
||||
- Le stringhe UI sono in inglese; il *contenuto* dei documenti resta nella lingua del workspace (italiano per `psd`), perché è il dato reale — solo chrome/label sono in inglese.
|
||||
- I workspace (`harness/workspaces/*.yaml`) impostano il target DB e i path **assoluti** `paths.sessions/artifacts/indexes` — per `psd` puntano a un repo separato e non versionato (`tht-workspace-psd/`). I segreti vivono solo in `harness/.env` (gitignored).
|
||||
- Le impostazioni sono globali (`backend/data/settings.json`: workspace/provider/modello/thinking); il form di nuova sessione richiede solo la domanda.
|
||||
- **Resume**: una sessione riprendibile rientra all'ultima fase incompleta. Il backend rifiuta il resume con 409 se `finalized` o `archived`; `PiProcessManager.spawnFor` deve inviare `/riprendi-sessione <id>` (resume) vs `/nuova-domanda` (nuova) — il prompt sbagliato trasforma silenziosamente un resume in una nuova domanda.
|
||||
The workspace repository is the publication boundary. The curator prepares `evidence/source/`,
|
||||
reviews units in `evidence/curated/`, validates them, and merges them. With `evidence.schema_version: 2`,
|
||||
the runtime materializes the full `evidence/` tree from the exact Git commit, but the renderer passes
|
||||
only `curated/**/*.md` from the immutable revision root to preprocessing. Sources, manifests, and
|
||||
evaluation data remain available for traceability. The runtime never modifies, stages, commits, or
|
||||
publishes the authoring repository.
|
||||
|
||||
## Come si lancia lo stack
|
||||
Before indexing, the curated corpus from the pinned revision is validated. The shared Qdrant
|
||||
collection keeps the unnamed dense vector used by Schema and Memory. Evidence preprocessing may
|
||||
add only the sparse `bm25` vector with `idf`, without deleting, renaming, or recreating the collection.
|
||||
`workspace preprocess evidence` and the Evidence part of `workspace preprocess run` are the only public
|
||||
operations that perform this upgrade.
|
||||
|
||||
Lo stack locale si avvia con `./scripts/run-stack.sh`, dopo aver creato
|
||||
`deploy/env/local.env` da `deploy/env/local.env.example`. Il core Compose include Pi; DWH,
|
||||
vector DB, embedding e LLM sono endpoint esterni configurati nel file locale.
|
||||
## Recurring points of attention
|
||||
|
||||
Comandi per singolo layer, test, lint: vedi il file `CLAUDE.md` nella radice del repo (guida operativa per Claude Code, tenuta sincronizzata con questa pagina).
|
||||
- `tht -c`/`--config` is a **per-command** option. It must follow the subcommand, never precede it (`ThtRunner.buildArgv` enforces this).
|
||||
- `--json` output must be plain JSON on stdout. It is a machine-readable contract.
|
||||
- UI strings are in English. Document *content* stays in the workspace language because it is the actual data; only chrome and labels are in English.
|
||||
- Each workspace defines its DWH target and working directories. Secrets remain in protected installation files, not in the workspace repository.
|
||||
- Settings are global (`backend/data/settings.json`: workspace/provider/model/thinking); the new-session form asks only for the question.
|
||||
- **Resume**: a resumable session returns to its last incomplete phase. The backend rejects resume with 409 when `finalized` or `archived`; `PiProcessManager.spawnFor` must send `/riprendi-sessione <id>` for resume and `/nuova-domanda` for a new session. The wrong prompt silently turns a resume into a new question.
|
||||
|
||||
## Starting the stack
|
||||
|
||||
Start the local stack with `./scripts/run-stack.sh` after creating
|
||||
`deploy/env/local.env` from `deploy/env/local.env.example`. The Compose core includes Pi; DWH,
|
||||
the vector database, embeddings, and the LLM are external endpoints configured in the local file.
|
||||
|
||||
Reference in New Issue
Block a user