Merge origin/codex/portable-deployment into feat/docker-local-deploy

Unisce gli internals di Codex (secret-bundle, provider-credentials, auth upstream,
security hardening, CI multiarch) mantenendo le fix portal-specific:
- backend: configPath da THT_CONFIG (fix sessioni) + dataRoot di Codex; authMode 'upstream'
- Docker/compose: TENUTO il mio (verificato live: omics_network+alias, env_file, pi npm-g)
  perche' il compose/Dockerfile/entrypoint di Codex sono accoppiati al suo modello
  secret-bundle (tht doctor inesistente, secret-policy.sh). Adottabile in futuro.
- config.test.ts: preso Codex (superset)
Verificato: tsc clean, 132/132 vitest.
This commit is contained in:
User
2026-07-12 21:13:20 +02:00
211 changed files with 23270 additions and 415 deletions
+23
View File
@@ -2,6 +2,29 @@
Pi (il coding agent che orchestra il workflow NL→SQL) può risolvere un `provider/model` in tre modi diversi. Non sono alternativi: coesistono, e la scelta di quale usare dipende da **quanto è standard l'endpoint** e da **quanto deve essere ampia la visibilità** del modello (tutti i progetti vs. un progetto solo).
## Credenziali nel backend container
In produzione configurare una sola sorgente generica, `THT_MODEL_API_KEY_FILE`, come secret file
assoluto e non il valore della chiave. `PiProcessManager` rilegge e valida il file per ogni processo,
normalizza il provider selezionato e passa al solo child Pi la variabile nativa appropriata
(`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `ZAI_API_KEY`, ecc.). Il percorso generico,
le chiavi di provider non selezionati e il vecchio `PI_PROVIDER_API_KEY` vengono rimossi dall'ambiente
del child. Provider locali come `ollama`, `lmstudio` e `aritmolab` continuano senza chiave; un provider
hosted non mappato o un secret mancante/non sicuro fallisce prima dello spawn con errore sanitizzato.
La sorgente generica supporta soltanto provider con una singola chiave: `ant-ling`, `anthropic`,
`cerebras`, `deepseek`, `fireworks`, `github-copilot`, `google` (anche tramite alias `gemini`),
`google-vertex` in modalità API key, `groq`, `huggingface`, `kimi-coding`, `minimax`, `minimax-cn`,
`mistral`, `moonshotai`, `moonshotai-cn`, `nvidia`, `openai`, `opencode`, `opencode-go`,
`openrouter`, `together`, `vercel-ai-gateway`, `xai`, i quattro provider `xiaomi*`, `zai` e
`zai-coding-cn`.
I provider composti `amazon-bedrock`, `azure-openai-responses`, `cloudflare-workers-ai` e
`cloudflare-ai-gateway` non sono rappresentabili da un solo file. La selezione fallisce prima
dello spawn (anche durante l'elenco modelli); tutte le credenziali ambientali AWS, Azure e
Cloudflare restano comunque rimosse. Servirà una futura configurazione dedicata per provider per
supportare questi bundle senza ambiguità.
## I tre livelli di provenienza di un modello
### 1. Built-in (compilato dentro Pi)
+4
View File
@@ -8,6 +8,10 @@ La documentazione è divisa in due aree:
Come funziona il sistema: architettura, specifiche di design delle singole funzionalità, piani di implementazione, report di test. Parte da qui: [Panoramica dell'architettura](architecture/overview.md).
Per installare l'applicazione in Docker nei quattro contesti operativi, partendo dal comando
predefinito `docker compose up --build -d` e dal bundle unico dei secret:
[Installazione Docker nei quattro contesti](installazione-docker-4-contesti.md).
## Considerazioni Generali
Note operative e di configurazione che non sono specifiche del dominio ThothII ma riguardano l'ambiente di sviluppo condiviso con altri progetti — ad esempio come Pi (il coding agent) risolve i modelli a livello built-in, utente e progetto. Parte da qui: [Configurazione dei modelli in Pi](general/pi-configuration.md).
+234
View File
@@ -0,0 +1,234 @@
# Installazione Docker nei quattro contesti operativi
ThothII viene distribuito con due immagini applicative:
- `thothii-core`: backend Fastify, harness `tht` e Pi;
- `thothii-frontend`: frontend React servito da nginx.
PostgreSQL/pgvector, DWH ed Evidence restano esterni nel profilo predefinito. Il profilo opzionale `local-vector` avvia PostgreSQL/pgvector nel progetto Compose.
## Installazione comune (il comando standard)
Servono Docker Engine/Compose v2 su Linux oppure Docker Desktop su macOS/Windows. Dalla directory in cui si vuole conservare il clone:
```sh
git clone <URL-REPOSITORY> ThothII
cd ThothII
cp .env.example .env
mkdir -p deploy/secrets deploy/workspaces
cp deploy/secrets/thothii.secrets.example deploy/secrets/thothii.secrets
chmod 600 deploy/secrets/thothii.secrets
```
Modificare **solo** questi file interni al clone:
| File | Cosa contiene |
|---|---|
| `.env` | endpoint, database, provider, `COMPOSE_FILE` e `COMPOSE_PROFILES`; mai password/token |
| `deploy/secrets/thothii.secrets` | un bundle `NOME=VALORE`, mode host `0600` o `0400` |
| `deploy/workspaces/<nome>.yaml` | adapter, endpoint non riservati, `roots` ed Evidence |
Il file `.env` viene caricato automaticamente da Docker Compose perché è nella radice del progetto. Il valore predefinito è `COMPOSE_FILE=compose.yaml`, con profili vuoti e `THT_SECRETS_FILE=deploy/secrets/thothii.secrets`. Perciò, dopo aver compilato `.env`, il bundle e almeno il workspace, l'avvio normale è sempre:
```sh
docker compose up --build -d
```
Non occorre usare `--env-file`, `-f` o `--profile` per questa installazione. Verificare lo stato con `docker compose ps` e aprire <http://127.0.0.1:8080>. `docker compose down` conserva il volume `thoth_data`; usare `down --volumes` solo per un ambiente effimero.
### Formato del bundle unico
`deploy/secrets/thothii.secrets` è un file di testo locale, non uno script shell. Sono ammessi commenti e righe vuote; ogni altra riga deve essere una sola assegnazione senza spazi:
```dotenv
THT_MODEL_API_KEY=...
THT_DWH_API_KEY=...
THT_VEC_API_KEY=...
THT_VEC_WRITE_API_KEY=...
THT_VECTOR_BOOTSTRAP_PASSWORD=...
THT_VECTOR_MIGRATOR_PASSWORD=...
THT_VECTOR_READER_PASSWORD=...
THT_VECTOR_WRITER_PASSWORD=...
```
Inserire solo le chiavi necessarie al profilo scelto. Il bundle viene montato in sola lettura nel container come `/run/secrets/thothii.secrets`; il parser rifiuta duplicati, chiavi sconosciute, valori vuoti, symlink e permessi host troppo aperti. Non inserire secret in `.env`, nei workspace, negli URL o nell'output di `docker compose config`.
Una catena CA PEM **non può essere inserita nel bundle**: contiene whitespace e viene rifiutata dal parser. Se un endpoint usa una CA privata, conservarla nel secret manager/host e aggiungere un override Compose revisionato che monti il file in `/run/secrets/ca-chain.pem` e imposti `THT_SSL_CA` (o il parametro dell'adapter). Il clone base non crea quel mount: questa è una limitazione intenzionale da considerare in fase di deployment.
### Overlay opzionali tramite `.env`
Gli overlay non cambiano il comando operativo. Impostare in `.env`:
```dotenv
# DWH/vector/embedding remoti (server applicativo o server con i DB):
COMPOSE_FILE=compose.yaml:deploy/compose.production.yaml
COMPOSE_PROFILES=
# pgvector locale (Mac, Windows o server autonomo):
COMPOSE_FILE=compose.yaml:deploy/compose.local-vector.yaml
COMPOSE_PROFILES=local-vector
```
Su Windows usare `;` come separatore di `COMPOSE_FILE`. Per il preprocessing locale aggiungere `deploy/compose.preprocess.yaml:deploy/compose.preprocess-local-vector.yaml` e impostare `COMPOSE_PROFILES=local-vector,preprocess`; poi usare `docker compose run --rm preprocess-evidence` oppure `docker compose run --rm preprocess-dwh`.
## Workspace, adapter e Evidence
Il workspace YAML seleziona il trasporto disponibile. Esempio DWH REST e vector DB HTTP:
```yaml
language: en
dwh:
type: thoth_rest
database: {database: warehouse, schema: datawarehouse}
endpoint: {base_url: https://dwh.example.test}
vectors:
type: thoth_vector_http
reader: {base_url: https://vectors.example.test}
writer: {base_url: https://vectors.example.test}
roots: {artifacts: artifacts, indexes: indexes, sessions: sessions}
evidence: {source_root: /data/source, evidence_dir: evidence}
embeddings: {base_url: https://embeddings.example.test, model: nomodel, dim: 768}
```
Esempio con accesso diretto a PostgreSQL e pgvector:
```yaml
language: en
dwh:
type: postgres_direct
connection: {host: dwh.internal, database: warehouse, schema: public,
user: thoth_reader, password_file: /run/secrets/dwh_password}
vectors:
type: pgvector_direct
reader: {host: vector.internal, database: thoth, schema: vectors,
user: thoth_vector_reader, password_file: /run/secrets/vector_reader_password}
writer: {host: vector.internal, database: thoth, schema: vectors,
user: thoth_vector_writer, password_file: /run/secrets/vector_writer_password}
roots: {artifacts: artifacts, indexes: indexes, sessions: sessions}
```
Questo esempio mostra il contratto dell'adapter: i file indicati da `password_file` devono
essere montati da un override Compose approvato. Il profilo base monta soltanto il bundle unico;
per un DWH diretto occorre quindi materializzare il file password dal secret manager e aggiungere
il bind mount/runtime adapter corrispondente. Non inserire la password nel workspace o nell'URL.
`roots` sono relativi e vengono risolti sotto `/data/workspaces/<workspace>` nel volume Docker; non inserire path host come `/Users/...` o `C:\\...`. Per Evidence usare una radice filesystem montata in sola lettura oppure l'adapter HTTP/S3 previsto dal workspace. Per HTTP/S3 definire allowlist, limiti di dimensione/paginazione e una politica egress; non mettere token nelle URI.
## 1. Server remoto insieme ai database e al vector DB
Usare quando il server Docker è nella stessa rete del DWH e del vector DB (containerizzati o meno). Il file `.env` può restare sul default, senza profili, impostando gli endpoint raggiungibili localmente:
```dotenv
COMPOSE_FILE=compose.yaml
COMPOSE_PROFILES=
THT_DB_NAME=warehouse
THT_DWH_REST_URL=https://dwh.internal.example
THT_VEC_REST_URL=https://vectors.internal.example
THT_OLLAMA_URL=https://embeddings.internal.example
AUTH_MODE=none
THOTH_PUBLIC_EXPOSURE=false
```
Riempire nel bundle le chiavi DWH/vector/model necessarie e avviare:
```sh
docker compose up --build -d
docker compose exec core /opt/venv/bin/tht doctor --json
```
Se si abilita l'overlay production, il proxy autenticato TLS deve essere l'unico listener pubblico
e deve iniettare `X-Authenticated-User`; non esporre direttamente la porta pubblicata da nginx.
Se il server deve essere raggiungibile da altri host, sostituire `COMPOSE_FILE` con
`compose.yaml:deploy/compose.production.yaml`, configurare il proxy autenticato e impostare
`AUTH_MODE=upstream`/`THOTH_PUBLIC_EXPOSURE=true` come descritto nella sezione di trust boundary.
## 2. Mac locale
Installare Docker Desktop e, se usato, Ollama sul Mac. Nel `.env` selezionare il profilo locale:
```dotenv
COMPOSE_FILE=compose.yaml:deploy/compose.local-vector.yaml
COMPOSE_PROFILES=local-vector
THT_DB_NAME=warehouse
THT_DWH_REST_URL=https://dwh.example.test
THT_OLLAMA_URL=http://host.docker.internal:11434
THT_DOCS_ROOT=/data/source/evidence
```
Nel bundle aggiungere quattro password generate localmente:
```dotenv
THT_VECTOR_BOOTSTRAP_PASSWORD=<valore casuale>
THT_VECTOR_MIGRATOR_PASSWORD=<valore casuale>
THT_VECTOR_READER_PASSWORD=<valore casuale>
THT_VECTOR_WRITER_PASSWORD=<valore casuale>
```
Poi eseguire il comando standard `docker compose up --build -d`. Il primo avvio esegue reconciliation dei ruoli e migrazione pgvector. Per preprocessing, impostare il preset indicato sopra e usare `docker compose run --rm preprocess-evidence`/`preprocess-dwh`.
## 3. PC Windows locale
Usare Docker Desktop con backend WSL2 e abilitare la condivisione della directory del clone. Modificare `.env` con il separatore Windows:
```dotenv
COMPOSE_FILE=compose.yaml;deploy/compose.local-vector.yaml
COMPOSE_PROFILES=local-vector
THT_DB_NAME=warehouse
THT_DWH_REST_URL=https://dwh.example.test
THT_OLLAMA_URL=http://host.docker.internal:11434
THT_DOCS_ROOT=/data/source/evidence
```
Creare `deploy/secrets/thothii.secrets` con un editor locale protetto (ACL leggibile solo dall'utente Docker) e le stesse quattro chiavi pgvector del profilo Mac. Non usare `ConvertFrom-SecureString`: il bundle deve contenere il valore in chiaro per il servizio, con accesso limitato al file. Da PowerShell, dalla radice del clone, eseguire:
```powershell
docker compose up --build -d
docker compose ps
```
Se un bind mount viene rifiutato, aggiungere la cartella del repository a Docker Desktop → Settings → Resources → File Sharing. Per Ollama eseguito in WSL2 usare l'indirizzo raggiungibile dalla rete Docker invece di assumere `localhost`.
## 4. Server applicativo distinto da DB ed Evidence
Usare il profilo production e consentire dal firewall solo le destinazioni necessarie:
```dotenv
COMPOSE_FILE=compose.yaml:deploy/compose.production.yaml
COMPOSE_PROFILES=
THT_DB_NAME=warehouse
THT_DWH_REST_URL=https://dwh.example.test
THT_VEC_REST_URL=https://vectors.example.test
THT_OLLAMA_URL=https://embeddings.example.test
```
Il DWH e il vector DB possono essere REST/HTTP oppure adapter diretti (`postgres_direct`, `pgvector_direct`) se il server ha connettività TCP. Le Evidence possono essere:
- filesystem NFS/SMB montato sul server e presentato come root read-only;
- endpoint HTTPS, con allowlist e limiti SSRF;
- bucket S3 con secret references e endpoint custom esplicitamente autorizzati.
Il preprocessing può girare sul server applicativo usando il volume `/data`; mantenere separati workspace, lock e artefatti dei job. Avviare con il comando standard e verificare `tht doctor`.
## Migrazione da installazioni con secret separati
Le variabili `THT_*_SECRET_FILE` e i file `dwh-api-key`, `vector-reader-api-key`, `vector-writer-api-key`, `model-api-key` e `vector_*_password` appartengono al layout precedente. Non vengono importati automaticamente dal bundle. Per migrare:
1. creare `deploy/secrets/thothii.secrets` mode `0600`;
2. copiare ogni valore nel nome chiave corrispondente (`THT_DWH_API_KEY`, `THT_VEC_API_KEY`, `THT_VEC_WRITE_API_KEY`, `THT_MODEL_API_KEY` o `THT_VECTOR_*_PASSWORD`), senza virgolette né newline;
3. rimuovere dal `.env` le variabili `_SECRET_FILE` e impostare `THT_SECRETS_FILE` al percorso del bundle (il default relativo è già corretto);
4. eseguire `docker compose config --quiet` e poi `docker compose up --build -d`;
5. solo dopo la verifica, cancellare i vecchi file separati.
Una CA PEM resta un'eccezione esterna come descritto sopra. Provider Pi con credenziali composte (Bedrock, Azure OpenAI Responses, Cloudflare Workers AI/Gateway) restano rifiutati finché non viene implementato un adapter dedicato.
## Controlli post-installazione
```sh
docker compose config --quiet
docker compose ps
docker compose exec core /opt/venv/bin/tht doctor --json
./scripts/docker-smoke.sh
```
Per il profilo locale usare anche `./scripts/local-vector-smoke.sh`; per il preprocessing `./scripts/preprocess-smoke.sh`. Non pubblicare `.env` o `deploy/secrets/thothii.secrets` nei log, nei backup Git o nei ticket.
@@ -26,7 +26,7 @@
- Test: `harness/tests/test_dwh_port_contract.py`
**Interfaces:**
- Produces: `DwhCapabilities`, `DwhAdapter`, `DwhHealth`, and `UnsupportedCapability`.
- Produces: `DwhCapabilities`, `DwhAdapter`, `DwhHealth`, `DistinctValues`, and `UnsupportedCapability`.
- Consumes: existing catalog models from `tht.db.introspect` and execution result types from `tht.db.execute`.
- [ ] **Step 1: Write the failing protocol-shape test**
@@ -54,16 +54,21 @@ class DwhCapabilities:
sampling: bool = True
distinct_values: bool = True
@dataclass(frozen=True)
class DistinctValues:
values: list[object]
truncated: bool
@runtime_checkable
class DwhAdapter(Protocol):
@property
def capabilities(self) -> DwhCapabilities: ...
def health(self) -> DwhHealth: ...
def introspect(self) -> DatabaseCatalog: ...
def run_query(self, sql: str, *, limit: int | None = None) -> QueryResult: ...
def introspect(self) -> PhysicalSchema: ...
def run_query(self, sql: str, *, limit: int) -> ExecResult: ...
def explain(self, sql: str) -> PlanSummary: ...
def sample_column(self, table: str, column: str, *, limit: int) -> list[object]: ...
def distinct_values(self, table: str, column: str) -> list[object]: ...
def distinct_values(self, table: str, column: str) -> DistinctValues: ...
```
- [ ] **Step 4: Run contract test and type-oriented import smoke test**
@@ -85,8 +90,15 @@ git commit -m "refactor(dwh): define adapter contract"
- Create: `harness/tht/adapters/dwh/postgres.py`
- Create: `harness/tht/adapters/dwh/thoth_rest.py`
- Test: `harness/tests/test_dwh_adapters.py`
- Test: `harness/tests/test_dwh_port_contract.py`
- Test: `harness/tests/l0/test_db_sampling.py`
- Modify: `harness/tht/ports/__init__.py`
- Modify: `harness/tht/ports/dwh.py`
- Modify: `harness/tht/execute/__init__.py`
- Modify: `harness/tht/db/execute.py`
- Modify: `harness/tht/db/sampling.py`
- Modify: `harness/tht/rest/execute.py`
- Modify: `docs/superpowers/plans/2026-07-11-adapter-foundations.md`
**Interfaces:**
- Consumes: `DwhAdapter` from Task 1; existing `DatabaseConfig`, `RestConfig`, catalog, sampling, execute, and explain functions.
@@ -97,8 +109,8 @@ git commit -m "refactor(dwh): define adapter contract"
```python
@pytest.mark.parametrize("factory", [postgres_factory, rest_factory])
def test_adapter_rejects_write_sql(factory):
with pytest.raises(ReadOnlyViolation):
factory().run_query("delete from fact_sales")
with pytest.raises(ExecutionError):
factory().run_query("delete from fact_sales", limit=10)
```
- [ ] **Step 2: Verify failure**
@@ -111,12 +123,18 @@ Expected: FAIL because the adapter classes are absent.
```python
class PostgresDwhAdapter:
capabilities = DwhCapabilities()
def __init__(self, config: DatabaseConfig): self._config = config
def run_query(self, sql: str, *, limit: int | None = None) -> QueryResult:
return run_query(self._config, sql, limit=limit)
def __init__(self, config: DatabaseConfig):
self._config = config
self._engine = make_engine(config)
def run_query(self, sql: str, *, limit: int) -> ExecResult:
return run_query(self._engine, sql, limit=limit)
```
Implement the analogous REST wrapper by delegating to `tht.rest.*`; translate transport-specific errors only at the adapter boundary.
Implement the analogous REST wrapper by delegating to `tht.rest.*`; translate transport-specific
errors only at the adapter boundary. Both wrappers delegate frequency-ranked, distinct sampling to
the paired implementations in `tht.db.sampling`. Query and sampling limits must be runtime-positive
integers (booleans and floats are rejected), and `distinct_values` reports any cap through
`DistinctValues.truncated`.
- [ ] **Step 4: Run adapter, read-only, sampling, and REST tests**
@@ -126,7 +144,11 @@ Expected: PASS; L0 may deselect when Docker is unavailable.
- [ ] **Step 5: Commit**
```bash
git add harness/tht/adapters harness/tht/db/execute.py harness/tht/rest/execute.py harness/tests/test_dwh_adapters.py
git add docs/superpowers/plans/2026-07-11-adapter-foundations.md \
harness/tht/ports harness/tht/adapters/dwh harness/tht/execute/__init__.py \
harness/tht/db/execute.py harness/tht/db/sampling.py harness/tht/rest/execute.py \
harness/tests/test_dwh_port_contract.py harness/tests/test_dwh_adapters.py \
harness/tests/l0/test_db_sampling.py
git commit -m "refactor(dwh): adapt direct and REST transports"
```
@@ -141,7 +163,8 @@ git commit -m "refactor(dwh): adapt direct and REST transports"
- Modify: `harness/tht/vectorstore/reader.py`
**Interfaces:**
- Produces: `VectorStore`, `VectorCapabilities`, `VectorHealth`, `VectorRecord`, `VectorHit`, `ThothHttpVectorStore`.
- Produces: `VectorStore`, `VectorCapabilities`, `VectorHealth`, `VectorRecord`,
`VectorWriteRecord`, `VectorHit`, `ThothHttpVectorStore`.
- Preserves: current `VectorRestClient`, `DirectSearcher`, and `RestSearcher` behavior behind wrappers.
- [ ] **Step 1: Write read/write capability and dual-credential tests**
@@ -171,9 +194,13 @@ class VectorStore(Protocol):
def search(self, collections: list[str], embedding: list[float], *, limit: int,
kinds: list[str] | None = None) -> list[VectorHit]: ...
def existing_hashes(self, collection: str, kinds: list[str]) -> dict[str, str]: ...
def upsert(self, collection: str, records: list[VectorRecord]) -> int: ...
def upsert(self, collection: str, records: list[VectorWriteRecord]) -> int: ...
```
`VectorWriteRecord` is the transport-neutral write envelope: it contains the canonical
`VectorRecord`, a precomputed embedding, and a content hash. Adapters must preserve
`VectorRecord.metadata` unchanged, including semantic keys named `embedding` or `content_hash`.
- [ ] **Step 4: Run vector regression tests**
Run: `cd harness && .venv/bin/pytest tests/test_vector_port_contract.py tests/test_vector_dual_key.py tests/test_search_similar_kinds.py tests/test_memory_save_one.py tests/test_solved_question.py -q`
@@ -262,6 +289,18 @@ git commit -m "feat(config): add typed resource schema"
- Produces: `build_dwh(cfg: Config) -> DwhAdapter` and `build_vector_store(cfg: Config, *, require_write: bool = False) -> VectorStore`.
- Consumes: resource configs from Task 4 and wrappers from Tasks 2-3.
Correction: `DwhAdapter.distinct_values(table, column, *, limit)` requires an explicit
positive limit, and direct DWH construction injects `cfg.execution.statement_timeout_ms`.
Targeted vector writes consume the factory-returned `VectorStore` and pass
`VectorWriteRecord` objects to `upsert`.
Transitional exception: `build_vector_loader` remains solely for bulk collection sync
(`vector init`/rebuild/index flows). It may still construct the legacy table-scoped writer
directly until `docs/superpowers/plans/2026-07-11-local-pgvector-profile.md` migrates the
local pgvector/vector schema and bulk-sync path. Interactive and targeted writes
(`memory save-one` and solved-question indexing) are not covered by this exception and must
continue through `build_vector_store(..., require_write=True)` and the public vector port.
- [ ] **Step 1: Write exact factory selection and missing-writer tests**
```python
@@ -236,6 +236,13 @@ git commit -m "build(docker): add runtime-configured frontend image"
### Task 5: Compose external profile and end-to-end smoke gate
> **Final-review security amendment (2026-07-12):** the frontend port binds to `127.0.0.1` by
> default. Public deployment uses an authenticated upstream proxy with `AUTH_MODE=upstream`;
> `THOTH_PUBLIC_EXPOSURE=true` plus `AUTH_MODE=none` is invalid. Local env files are development
> only; production uses read-only Compose secrets. Image gates pin exact tags and multi-platform
> digests and verify both linux/amd64 and linux/arm64 using the shared container verification
> script.
**Files:**
- Create: `compose.yaml`
- Create: `deploy/env.example`
@@ -0,0 +1,70 @@
# Local and Server Docker Deployment Implementation Plan
> **For Codex:** execute this plan in the current isolated worktree; keep runtime credentials out of Git.
**Goal:** Configure and verify a Docker Desktop deployment using GLM 5.2 and the existing PSD workspace, while retaining a portable server deployment contract.
**Architecture:** The base Compose file builds two applications and consumes only generic environment values and a Docker secret bundle. A tracked GLM Pi registry is mounted read-only in the core container. A Git-ignored local override supplies Mac-specific PSD workspace and CA mounts; server operators supply equivalent server runtime values separately.
**Tech Stack:** Docker Compose v2, Node 22, Python 3.12, Pi RPC, Fastify, nginx.
---
### Task 1: Add the non-secret GLM Pi registry
**Files:**
- Create: `deploy/pi/models.json`
- Modify: `docker/core.Dockerfile`
- Modify: `compose.yaml`
- Test: Compose configuration and Pi model discovery
1. Define the `zai/glm-5.2` OpenAI-compatible model registry without a credential.
2. Create the Pi user configuration directory in the core image and mount the registry read-only.
3. Verify that `get_available_models` returns `zai/glm-5.2` when the bundle supplies the model key.
### Task 2: Add generic PSD-compatible runtime templates
**Files:**
- Create: `deploy/workspaces/psd.yaml.example`
- Create: `deploy/compose.psd-local.yaml.example`
- Modify: `deploy/env.example`
- Modify: `README.md`
1. Define a relative `/data/workspaces/psd` workspace configuration with external REST DWH/vector adapters.
2. Document required non-secret environment values and the local/server boundary.
3. Keep host paths and credential values out of all tracked files.
### Task 3: Materialize local runtime configuration securely
**Files (ignored):**
- Create: `.env`
- Create: `deploy/secrets/thothii.secrets`
- Create: `deploy/compose.psd-local.yaml`
- Create: `deploy/workspaces/psd.yaml`
1. Transfer only required values from the existing local configuration without writing them to logs.
2. Set `PI_PROVIDER=zai`, `PI_MODEL=glm-5.2`, and the Docker Desktop host gateway for Ollama.
3. Bind-mount the PSD workspace and private CA read-only where appropriate; sessions remain writable.
4. Enforce restricted modes on the secret bundle.
### Task 4: Build and verify the Docker deployment
**Commands:**
- `docker compose config --quiet`
- `docker compose build`
- `docker compose up -d`
- health/API/model/session smoke checks
1. Validate rendered Compose configuration without exposing secrets.
2. Build the core and frontend images.
3. Verify secret mount, core and frontend health, and model listing.
4. Start a PSD session using GLM 5.2 and verify Pi emits a workflow event or gate.
5. Capture sanitized diagnostics and stop only disposable test resources; leave the validated local stack running unless it fails.
### Task 5: Record the deployment result
**Files:**
- Modify: `README.md` or deployment documentation
1. Record the exact local startup command and server-equivalent configuration steps.
2. State verified endpoints, model, and session-start result without secret values.
@@ -0,0 +1,156 @@
# Simple Docker Configuration Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task with review checkpoints.
**Goal:** Make a fresh ThothII clone runnable with `docker compose up --build -d`, using one
`deploy/secrets/thothii.secrets` bundle while preserving a tested legacy fallback.
**Architecture:** A strict Python secret-bundle loader becomes the single in-process source of
secret values. Compose mounts the one bundle only where needed; the core converts values to
provider/database runtime interfaces without logging or placing them in argv. The root `.env`
is the default Compose interpolation file and selects the appropriate overlay through
`COMPOSE_FILE`/`COMPOSE_PROFILES`; legacy `THT_*_SECRET_FILE` installations remain supported.
**Tech Stack:** Docker Compose v2, YAML, Python 3.12/Pydantic, Fastify/TypeScript, shell smoke
tests, pytest, Vitest.
## Global Constraints
- The normal command must be exactly `docker compose up --build -d` from `ThothII/`.
- The canonical secret bundle is `deploy/secrets/thothii.secrets`, key/value syntax, mode `0600`,
ignored by Git and excluded from image build contexts.
- Secret values must never appear in Compose config output, logs, argv, settings, health, or
committed workspace files.
- Existing `THT_*_SECRET_FILE` variables remain a documented compatibility path until removed by
a later migration.
- External, local-vector, and preprocess overlays must remain independently renderable.
- Provider compound credentials remain fail-closed; only supported single-key providers are
restored from the bundle.
- Every task starts with a failing regression test and ends with focused tests, diff checks, and
a small commit.
---
### Task 1: Add the strict secret-bundle loader and compatibility adapter
**Files:**
- Create: `backend/src/config/secret-bundle.ts`
- Modify: `backend/src/config.ts`
- Modify: `backend/src/pi/provider-credentials.ts`
- Modify: `backend/src/pi/pi-process-manager.ts`
- Modify: `backend/src/pi/list-models.ts`
- Create: `backend/test/secret-bundle.test.ts`
- Modify: `backend/test/provider-credentials.test.ts`
- Modify: `backend/test/pi-process-manager.test.ts`
- Modify: `backend/test/list-models.test.ts`
**Interfaces:**
- `loadSecretBundle(file: string): ReadonlyMap<string, string>` validates `NAME=VALUE` lines,
duplicate/unknown/empty keys, `lstat`/`open(O_NOFOLLOW)`/`fstat` identity, owner and mode.
- `secretValue(config, key)` first reads `THT_SECRETS_FILE`, then falls back to the existing
`THT_*_SECRET_FILE` variable for compatibility.
- The existing provider environment builder consumes a value map, so session and model-listing
children share identical scrubbing and canonical-provider mapping.
- [ ] **Step 1: Write failing tests** for valid bundle parsing, comments/blank lines, duplicate
keys, unknown keys, missing file, mode/owner failure, inode replacement, and secret redaction.
- [ ] **Step 2: Run** `cd backend && npx vitest run test/secret-bundle.test.ts`; expected failure
because the loader does not exist.
- [ ] **Step 3: Implement** the loader with bounded line lengths, strict key allowlist, no shell
evaluation, sanitized errors, and legacy adapter lookup.
- [ ] **Step 4: Add tests** proving session spawn and model listing use the same bundle values and
do not inherit bundle path or unselected provider credentials.
- [ ] **Step 5: Run** `cd backend && npm run build && npx tsc --noEmit -p . && npx vitest run`;
expected all backend tests pass.
- [ ] **Step 6: Commit** `git commit -m "feat(config): load one validated secret bundle"`.
### Task 2: Make the root Compose command the default
**Files:**
- Create: `.env.example`
- Modify: `.gitignore`
- Modify: `compose.yaml`
- Modify: `deploy/compose.production.yaml`
- Modify: `deploy/compose.local.yaml`
- Modify: `deploy/env.example`
- Create: `deploy/secrets/thothii.secrets.example`
- Create: `scripts/test-default-compose.sh`
- Modify: `scripts/test-container-deployment.sh`
**Interfaces:**
- Root `.env` is Compose's automatic interpolation file; `.env.example` contains relative
`THT_SECRETS_FILE=deploy/secrets/thothii.secrets`, default `COMPOSE_FILE=compose.yaml`, and
the selected overlay/profile values.
- `compose.yaml` starts `core` and `frontend` without requiring a profile; overlays extend it.
- Core receives one `/run/secrets/thothii.secrets` mount and `THT_SECRETS_FILE` path.
- [ ] **Step 1: Write failing static tests** that run `docker compose config --quiet` from a
temporary clone with `.env` and assert the default services are `core` and `frontend`, one
bundle is declared, and no legacy secret file is required.
- [ ] **Step 2: Run** `./scripts/test-default-compose.sh`; expected failure because root defaults
still require profiles/separate secret files.
- [ ] **Step 3: Implement** `.env.example`, `.gitignore`, Compose defaults and one secret mount.
Preserve `deploy/compose.production.yaml` as an optional authenticated production override.
- [ ] **Step 4: Run** `docker compose --env-file .env.example config --quiet` and the existing
deployment/security scripts; expected no secret values in rendered YAML.
- [ ] **Step 5: Commit** `git commit -m "build(compose): make root startup the default"`.
### Task 3: Convert local-vector and preprocess services to the bundle
**Files:**
- Modify: `deploy/compose.local-vector.yaml`
- Modify: `deploy/compose.preprocess-local-vector.yaml`
- Modify: `deploy/compose.preprocess.yaml`
- Modify: `deploy/workspaces/local-vector.yaml`
- Modify: `deploy/workspaces/preprocess-evidence.yaml`
- Modify: `deploy/workspaces/preprocess-dwh.yaml`
- Modify: `scripts/local-vector-smoke.sh`
- Modify: `scripts/preprocess-smoke.sh`
- Modify: `scripts/test-preprocess-compose-config.sh`
- Modify: `scripts/test-vector-backup-restore-safety.sh`
**Interfaces:**
- Every local-vector/preprocess service reads the same mounted bundle path and selects only the
named value through the shared loader/helper.
- No service declares four file-backed Compose secrets after this task.
- [ ] **Step 1: Add failing tests** asserting one bundle mount, no `vector_*_password` secret
declarations, and valid local-vector workspace resolution.
- [ ] **Step 2: Run** focused Compose config and smoke tests; expected failure with current
separate-secret declarations.
- [ ] **Step 3: Implement** bundle mounts and helper invocations for bootstrap/migrator/reader/
writer operations, keeping passwords out of URLs and shell logs.
- [ ] **Step 4: Run** `./scripts/test-preprocess-compose-config.sh`, local-vector smoke and
preprocess smoke with a clean generated project; expected all pass.
- [ ] **Step 5: Commit** `git commit -m "feat(compose): use one secret bundle for local services"`.
### Task 4: Finish documentation and end-to-end default verification
**Files:**
- Modify: `README.md`
- Modify: `docs/installazione-docker-4-contesti.md`
- Modify: `docs/index.md`
- Modify: `deploy/secrets/README.md`
- Modify: `scripts/docker-smoke.sh`
- Modify: `scripts/test-default-compose.sh`
**Interfaces:**
- Installation docs show only `cp .env.example .env`, create/fill one bundle, then
`docker compose up --build -d`.
- Advanced overlays are shown as optional `.env` presets, not mandatory command-line flags.
- [ ] **Step 1: Add failing documentation/smoke assertions** for the exact command and default
files.
- [ ] **Step 2: Implement** concise context-specific instructions and migration notes for old
separate secret files.
- [ ] **Step 3: Run** all shell syntax/config gates, backend/frontend builds/tests, full harness,
default Docker smoke, local-vector smoke, preprocess smoke and `git diff --check`.
- [ ] **Step 4: Commit** `git commit -m "docs: document one-command Docker installation"`.
### Task 5: Whole-plan review and handoff
- [ ] Review `a6b195b..HEAD` against this plan and confirm no secret leakage, profile regression,
or legacy fallback bypass.
- [ ] Run the complete verification matrix and report exact counts, skipped L2 tests, and any
unavailable Docker/registry prerequisites.
- [ ] Keep the branch/worktree intact for the user's integration choice.
@@ -36,6 +36,7 @@ Non sono presenti Dockerfile o file Compose. Il processo di sviluppo assume Pi e
6. **Configurazione dichiarativa e validata.** I workspace contengono riferimenti logici e configurazioni non segrete; i segreti sono in environment variables o secret store.
7. **Capability esplicite.** Un adapter dichiara ciò che supporta. Le funzioni mancanti producono degradazione o blocco comprensibile, non emulazioni implicite.
8. **Read-only by construction sul DWH.** Credenziali, API e guard client-side mantengono la separazione dall'autorità di scrittura.
9. **Esposizione sicura per default.** La porta applicativa pubblicata è vincolata a loopback. Un'esposizione pubblica richiede un reverse proxy autenticante esterno e `AUTH_MODE=upstream`; la combinazione pubblico + `none` viene rifiutata all'avvio. OIDC interno non fa parte di questa fase.
## 4. Packaging e runtime
@@ -143,6 +144,11 @@ La configurazione si divide in:
- **workspace:** lingua, adapter, namespace, collezioni e policy di preprocessing;
- **segreti:** password, token, certificati e chiavi reader/writer.
Nel profilo locale i segreti possono provenire da un env-file non versionato. In produzione sono
file read-only sotto `/run/secrets`, leggibili dall'UID 10001. Il reverse proxy autenticante è un
confine fidato: rimuove header identità forniti dal client e inserisce
`X-Authenticated-User` soltanto dopo autenticazione.
Deve esistere un comando di diagnostica che produca sia output umano sia JSON pristino, rispettando il contratto CLI corrente.
## 7. Pipeline di preprocessing
@@ -0,0 +1,34 @@
# Local and Server Docker Deployment Design
## Goal
Run ThothII locally in Docker against the existing PSD services, while keeping the
same tracked Docker package deployable on a server hosting the DWH and pgvector.
## Decisions
- `compose.yaml` remains portable and contains no customer paths, credentials, or
private certificate material.
- The GLM registry is a tracked, non-secret Pi configuration. The provider key is
supplied only through the existing Docker secret bundle.
- A local-only Compose override binds the existing PSD workspace and CA material
from the developer machine. It is ignored by Git and exists solely to validate
Docker Desktop against the real workflow.
- A server profile consumes its own workspace mount, secret bundle, and service
endpoints. It never relies on macOS paths or a developer's `~/.pi` directory.
- The verification scope is a live session start through Pi using `zai/glm-5.2`;
it stops at the first human-review gate and does not finalize a datamart.
## Configuration Boundaries
Tracked files define images, Compose service contracts, templates, validation, and
documentation. Ignored runtime files hold the selected endpoint values, secret
bundle, certificate mount source, and local workspace path. The server receives
only the tracked package; its operator materializes equivalent runtime files with
server-specific values.
## Validation
The local run must validate Compose syntax, image builds, secret mount readability,
core/frontend health endpoints, model discovery, session creation, and receipt of a
Pi workflow event. Failure diagnostics must omit secret values.
@@ -0,0 +1,98 @@
# Design: configurazione Docker semplificata
## Obiettivo
Ridurre l'installazione a un file di configurazione `.env` interno al clone e a un solo file
contenente tutti i secret, mantenendo il comando operativo standard:
```sh
docker compose up --build -d
```
Il comportamento di default deve essere determinato dai file presenti nella directory radice
`ThothII/`, senza obbligare l'operatore a ricordare `-f`, `--env-file` o profili Compose.
## Struttura installativa
```text
ThothII/
├── .env # configurazione non segreta e default Compose
├── .env.example # template versionato
├── compose.yaml # file Compose principale, usabile senza -f
├── deploy/
│ ├── secrets/thothii.secrets # unico file secret, escluso da Git
│ └── workspaces/ # workspace YAML versionati
└── data/ # dati persistenti solo se bind mount esplicito
```
`.env` contiene host, endpoint, profilo scelto, `COMPOSE_FILE`, `COMPOSE_PROFILES` e il percorso
del bundle secret. Non contiene valori secret. Il file viene creato copiando `.env.example` e
rimane nella directory `ThothII/`.
Il bundle `deploy/secrets/thothii.secrets` usa righe `NOME=VALORE`, con nomi documentati e
validazione rigorosa. Non sono ammesse espansioni shell, comandi, URL con credenziali o righe
duplicate. Il file deve essere `0600` sull'host e viene montato read-only nei soli servizi che
ne hanno bisogno.
## Default Compose
`compose.yaml` diventa il file principale per il profilo applicativo esterno: `core` e
`frontend` non sono nascosti dietro un profilo obbligatorio. Il `.env` seleziona eventuali
overlay tramite la variabile Compose standard `COMPOSE_FILE` e il profilo tramite
`COMPOSE_PROFILES`.
Esempi:
- server con DWH/vector esterni: `COMPOSE_FILE=compose.yaml:deploy/compose.production.yaml`;
- Mac/Windows con pgvector locale: `COMPOSE_FILE=compose.yaml:deploy/compose.local-vector.yaml` e
`COMPOSE_PROFILES=local-vector`;
- preprocessing locale: aggiunta dell'overlay preprocess nel valore `COMPOSE_FILE`.
Quando `.env` è configurato, il comando non cambia tra i contesti:
```sh
docker compose up --build -d
```
I comandi con `-f` e `--env-file` restano documentati solo come override diagnostico, non come
percorso normale di installazione.
## Bundle secret e runtime
Il core riceve `THT_SECRETS_FILE=/run/secrets/thothii.secrets`. Un loader comune:
1. apre il bundle con `O_NOFOLLOW`, verifica owner, permessi e inode;
2. rifiuta chiavi sconosciute, duplicate, vuote o provider composti non supportati;
3. espone i singoli valori solo in memoria al componente che ne ha bisogno;
4. non stampa il bundle, non lo inserisce in `settings.json`, argv, health o log.
Per pgvector locale, il servizio di inizializzazione e le migrazioni usano lo stesso loader; non
si creano più file `bootstrap`, `reader`, `writer` e `migrator`. Le password non vengono passate
come argomenti URL. I workspace ricevono riferimenti logici al secret bundle, mai valori.
La compatibilità temporanea con le variabili `THT_*_SECRET_FILE` viene mantenuta come fallback
esplicito per installazioni già esistenti, ma il template e la documentazione nuovi usano solo
`THT_SECRETS_FILE`.
## Compatibilità e sicurezza
- `docker compose config --quiet` deve funzionare dalla radice senza opzioni aggiuntive;
- il default non deve avviare pgvector locale se il `.env` seleziona servizi esterni;
- i profili local-vector e preprocess devono aggiungere solo i servizi necessari;
- il bundle secret deve essere escluso da `.gitignore` e dai build context Docker;
- errori di secret mancanti o non validi devono terminare prima dell'avvio applicativo, con messaggi
sanitizzati;
- i test devono coprire sia il percorso standard `docker compose up --build -d` sia gli override
legacy con file secret separati.
## Verifica prevista
La verifica finale comprende:
1. rendering Compose del default e dei quattro preset `.env.example`;
2. test unitari del parser bundle e della compatibilità legacy;
3. build delle immagini core/frontend;
4. smoke health/SSE/persistenza;
5. smoke local-vector con un solo bundle e migrazioni;
6. smoke preprocess con il default selezionato dal `.env`;
7. controllo che nessun secret compaia in `docker compose config`, log, argv o immagini.