Files
ThothII/docs/workspace-diagnostic-protocol.md
T

220 lines
11 KiB
Markdown

# Workspace diagnostic protocol
This is the operator contract for testing a workspace on one ThothII installation. The
Git-shared descriptor declares *what* can be checked; the installation supplies the selected
transport and the local bindings. No secret value, certificate content, SSH key, or response body
belongs in the descriptor, this document, a generated `.env.example`, or diagnostic output.
## Scope and safety rules
- The descriptor is schema version 2. Its vector `database` and `schema` are required identity
fields; they are not copied from the DWH, even when both services share PostgreSQL.
- Version 1 descriptors are readable only and have `migration_required` status. An explicit
migration supplies `semantic_index.vector_store.database` and `.schema`, writes version 2, and
must never infer either from `dwh`.
- Each diagnostic is bounded by the configured workspace diagnostic timeout. Redirects are
rejected, response bodies stay inside the adapter, and browser-visible errors are limited to
`binding_missing`, `connector_unavailable`, and `semantic_index_incompatible`.
- A REST path is descriptor-declared, origin-relative, starts with one `/`, and has no query or
fragment. The client may use only the declared method, path, auth mode, and response-field names.
- `auth: none` sends no credential; `auth: bearer` reads a local file and sends
`Authorization: Bearer <file-content>`; `auth: x-api-key` sends `x-api-key: <file-content>`.
The file content is never logged or returned.
## Canonical descriptor additions
```yaml
semantic_index:
vector_store:
engine: pgvector
database: vector_database
schema: vectors
collection: clinical_documents
dimensions: 768
distance: cosine
supported_transports: [pgvector_direct, rest_api, ssh_tunnel]
vector_writer: {} # optional: declares a separately bound writer capability
embedding:
provider: ollama_compatible
model: nomic-embed-text-v2-moe
dimensions: 768
diagnostics:
dwh_rest:
method: POST
path: /rpc/ping
auth: bearer
response: { database: database, schema: schema }
vector_rest:
metadata:
method: GET
path: /vector/metadata
auth: bearer
response: { collection: collection, dimensions: dimensions, distance: distance }
reversible_probe:
method: POST
path: /vector/diagnostic-probe
auth: bearer
embedding:
method: GET
path: /models
auth: none
response: { model: model, dimensions: dimensions }
```
`diagnostics.dwh_rest` requires DWH `rest_api`; `diagnostics.vector_rest` requires vector
`rest_api`. `reversible_probe` is optional, but when present it must be `POST`. Response-map
values are JSON object field names, not values to be put in Git.
## Installation-local variable contract
Replace `<NAMESPACE>` with the immutable workspace ID converted to upper case with hyphens changed
to underscores. For example, `psd-clinical` becomes `PSD_CLINICAL`. Set only the variables for the
selected transport. Every `*_FILE` value is an absolute path to a regular, readable file inside an
approved local secret root; it is never the secret itself.
| Connector and transport | Required local variables |
| --- | --- |
| DWH selection | `THT_WS_<NAMESPACE>_DWH_TRANSPORT` |
| DWH `postgres_direct` | `THT_WS_<NAMESPACE>_DWH_HOST`, `THT_WS_<NAMESPACE>_DWH_PORT`, `THT_WS_<NAMESPACE>_DWH_USER`, `THT_WS_<NAMESPACE>_DWH_PASSWORD_FILE`; optional `THT_WS_<NAMESPACE>_DWH_TLS_CA_FILE` |
| DWH `rest_api` | `THT_WS_<NAMESPACE>_DWH_BASE_URL`, `THT_WS_<NAMESPACE>_DWH_API_KEY_FILE`; optional `THT_WS_<NAMESPACE>_DWH_TLS_CA_FILE` |
| DWH `ssh_tunnel` | `THT_WS_<NAMESPACE>_DWH_USER`, `THT_WS_<NAMESPACE>_DWH_PASSWORD_FILE`, `THT_WS_<NAMESPACE>_DWH_SSH_HOST`, `THT_WS_<NAMESPACE>_DWH_SSH_PORT`, `THT_WS_<NAMESPACE>_DWH_SSH_USER`, `THT_WS_<NAMESPACE>_DWH_SSH_PRIVATE_KEY_FILE`, `THT_WS_<NAMESPACE>_DWH_SSH_KNOWN_HOSTS_FILE`, `THT_WS_<NAMESPACE>_DWH_SSH_TARGET_HOST`, `THT_WS_<NAMESPACE>_DWH_SSH_TARGET_PORT`; optional `THT_WS_<NAMESPACE>_DWH_TLS_CA_FILE` |
| Vector selection | `THT_WS_<NAMESPACE>_VECTOR_TRANSPORT` |
| Vector `pgvector_direct` | `THT_WS_<NAMESPACE>_VECTOR_HOST`, `THT_WS_<NAMESPACE>_VECTOR_PORT`, `THT_WS_<NAMESPACE>_VECTOR_USER`, `THT_WS_<NAMESPACE>_VECTOR_PASSWORD_FILE`; optional `THT_WS_<NAMESPACE>_VECTOR_TLS_CA_FILE` |
| Vector `rest_api` | `THT_WS_<NAMESPACE>_VECTOR_BASE_URL`, `THT_WS_<NAMESPACE>_VECTOR_API_KEY_FILE`; optional `THT_WS_<NAMESPACE>_VECTOR_TLS_CA_FILE` |
| Vector `ssh_tunnel` | `THT_WS_<NAMESPACE>_VECTOR_USER`, `THT_WS_<NAMESPACE>_VECTOR_PASSWORD_FILE`, `THT_WS_<NAMESPACE>_VECTOR_SSH_HOST`, `THT_WS_<NAMESPACE>_VECTOR_SSH_PORT`, `THT_WS_<NAMESPACE>_VECTOR_SSH_USER`, `THT_WS_<NAMESPACE>_VECTOR_SSH_PRIVATE_KEY_FILE`, `THT_WS_<NAMESPACE>_VECTOR_SSH_KNOWN_HOSTS_FILE`, `THT_WS_<NAMESPACE>_VECTOR_SSH_TARGET_HOST`, `THT_WS_<NAMESPACE>_VECTOR_SSH_TARGET_PORT`; optional `THT_WS_<NAMESPACE>_VECTOR_TLS_CA_FILE` |
| Optional vector writer | `THT_WS_<NAMESPACE>_VECTOR_WRITER_API_KEY_FILE` |
| Embedding service | `THT_WS_<NAMESPACE>_EMBEDDING_BASE_URL`; optional `THT_WS_<NAMESPACE>_EMBEDDING_API_KEY_FILE`, `THT_WS_<NAMESPACE>_EMBEDDING_TLS_CA_FILE` |
For the example workspace, the optional writer name is exactly
`THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE`. It must resolve to a different local file from
`THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE`; a reader key is never substituted for a writer key.
### Private-CA REST limitation
The current REST diagnostic adapters use the platform `fetch` implementation and cannot load a
per-request private CA. Therefore a REST diagnostic with any `*_TLS_CA_FILE` binding is refused
rather than silently disabling certificate verification. Use an HTTPS endpoint trusted by the
runtime trust store, use direct or SSH transport where the native PostgreSQL client can validate
the local CA file, or arrange TLS termination at a trusted boundary. This limitation applies to
DWH REST, vector metadata/write REST, and embedding REST diagnostics.
## DWH diagnostic
For direct PostgreSQL and SSH-tunnelled PostgreSQL, the diagnostic connects with the declared
`dwh.database`, checks TLS and authentication, then executes exactly:
```sql
SELECT current_database() AS database, current_schema() AS schema
```
Both returned values must equal the descriptor's DWH database and schema.
For REST, the descriptor above declares the exact ping:
```text
POST <THT_WS_<NAMESPACE>_DWH_BASE_URL>/rpc/ping
Authorization: Bearer <content of DWH_API_KEY_FILE>
```
It has no request body. A 2xx response must be a JSON object whose declared `database` and
`schema` fields equal `dwh.database` and `dwh.schema`. For the sample response map, that is:
```json
{ "database": "warehouse", "schema": "datawarehouse" }
```
The values are illustrative resource identities, not credentials. A different response-field map
is valid only when the descriptor declares it.
## Vector metadata diagnostic
Direct and SSH vector checks connect to the *vector* `database` and `schema`, not the DWH
identity. They inspect the declared collection's vector column and index and must find the exact
collection, integer `dimensions`, and `distance` (`cosine`, `l2`, or `inner_product`) declared in
`semantic_index.vector_store`.
For REST, the exact descriptor-declared request is, for example:
```text
GET <THT_WS_<NAMESPACE>_VECTOR_BASE_URL>/vector/metadata
Authorization: Bearer <content of VECTOR_API_KEY_FILE>
```
It has no request body. A 2xx JSON object must supply the declared `collection`, `dimensions`, and
`distance` fields. All three values must exactly match the vector-store contract; an integer
dimension is required. Metadata from a similarly named collection, a different metric, or a
different dimension makes the semantic index incompatible.
## Reversible vector writer probe
Ordinary validation is reader-only. A write probe runs only when all of the following are true:
1. The operator explicitly requests it.
2. The descriptor has `semantic_index.vector_writer: {}`.
3. The descriptor declares `diagnostics.vector_rest.reversible_probe`.
4. The selected vector transport is `rest_api`.
5. `THT_WS_<NAMESPACE>_VECTOR_WRITER_API_KEY_FILE` exists locally and is distinct from the reader
API-key file.
The probe uses the declared `POST` endpoint twice, with the same generated ID and the writer key:
```json
{ "operation": "create", "id": "diagnostic:<random UUID>", "collection": "<declared collection>", "dimensions": 768 }
```
then:
```json
{ "operation": "remove", "id": "diagnostic:<same UUID>", "collection": "<declared collection>" }
```
Both requests require a 2xx response. Cleanup is attempted in `finally`, including after a write
timeout or error. The endpoint must implement both operations as a bounded, reversible diagnostic
operation; an upsert-only endpoint is prohibited. It must not retain, index, or expose diagnostic
records. If the writer capability or its local binding is absent, validation remains reader-only
and no write request is sent.
## Embedding dimensions diagnostic
The embedding request is descriptor-declared, for example:
```text
GET <THT_WS_<NAMESPACE>_EMBEDDING_BASE_URL>/models
```
It has no body and uses the declared authentication mode. A 2xx JSON object must contain the
declared model field equal to `semantic_index.embedding.model` and a declared dimensions field that
is an integer. That integer must equal both `semantic_index.embedding.dimensions` and
`semantic_index.vector_store.dimensions`.
## SSH host verification and tunnel lifecycle
For either DWH or vector `ssh_tunnel`, the known-hosts file is mandatory and is verified before a
connection is accepted. The tunnel is a short-lived loopback forward for the diagnostic only. The
effective OpenSSH constraints are:
```text
-N -v
-o BatchMode=yes
-o ExitOnForwardFailure=yes
-o StrictHostKeyChecking=yes
-o UserKnownHostsFile=<ROLE>_SSH_KNOWN_HOSTS_FILE
-i <ROLE>_SSH_PRIVATE_KEY_FILE
-p <ROLE>_SSH_PORT
-L 127.0.0.1:<ephemeral-port>:<ROLE>_SSH_TARGET_HOST:<ROLE>_SSH_TARGET_PORT
<ROLE>_SSH_USER@<ROLE>_SSH_HOST
```
The local listener is `127.0.0.1` only. The process is terminated in cleanup after the direct
probe, on timeout, or on failure. There is no accept-new mode, no disabled host-key checking, and
no persistent forwarding.
## Reader-only fallback
A workspace may be fully valid in Git but non-activatable locally when a required reader binding,
secret file, host verification, TLS check, or declared diagnostic fails. That state does not alter
the shared descriptor and does not permit a new session on that installation. It may still be
published and activated elsewhere with valid local bindings. Missing optional writer capability is
not a reader failure: it leaves the workspace in reader-only mode and suppresses the writer probe.