test: align qdrant ollama verification fixtures

This commit is contained in:
2026-08-08 22:15:02 +02:00
parent ffe0afb6a7
commit 4e810af516
11 changed files with 72 additions and 44 deletions
+8 -4
View File
@@ -269,15 +269,19 @@ test("runs the injected installation diagnostic for a migration-required v2 work
} }
}); });
test("fails closed for /workspaces/:id/test on a schema v3 workspace before the internal runtime lands", async () => { test("runs diagnostics for a schema v3 workspace without external semantic bindings", async () => {
const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] })); const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] }));
const app = appFor(registryFake(), diagnose); const app = appFor(registryFake(), diagnose);
const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} }); const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} });
expect(testResult.statusCode).toBe(400); expect(testResult.statusCode).toBe(200);
expect(testResult.json()).toMatchObject({ code: "workspace_invalid" }); expect(testResult.json()).toMatchObject({ activatable: true, diagnostics: [] });
expect(diagnose).not.toHaveBeenCalled(); expect(diagnose).toHaveBeenCalledWith(workspace, expect.objectContaining({
vector: expect.objectContaining({ missing: [], values: {} }),
vectorWriter: expect.objectContaining({ missing: [], values: {} }),
embedding: expect.objectContaining({ missing: [], values: {} }),
}), { writeProbe: false });
}); });
test("returns a 409 field conflict instead of overwriting a changed workspace", async () => { test("returns a 409 field conflict instead of overwriting a changed workspace", async () => {
+9 -18
View File
@@ -19,7 +19,7 @@ const thtBin = join(harnessDir, ".venv", "bin", "tht");
const roots: string[] = []; const roots: string[] = [];
const canonicalWorkspace = `workspace: const canonicalWorkspace = `workspace:
schema_version: 2 schema_version: 3
id: psd-clinical id: psd-clinical
name: Runtime handoff name: Runtime handoff
language: en language: en
@@ -30,17 +30,14 @@ dwh:
supported_transports: [postgres_direct] supported_transports: [postgres_direct]
semantic_index: semantic_index:
vector_store: vector_store:
engine: pgvector engine: qdrant
database: analytics collection: psd-clinical
schema: vectors dimensions: 1024
collection: documents
dimensions: 768
distance: cosine distance: cosine
supported_transports: [pgvector_direct]
embedding: embedding:
provider: ollama_compatible provider: ollama_internal
model: embed model: qwen3-embedding:0.6b
dimensions: 768 dimensions: 1024
llm_policy: llm_policy:
allowed: [zai/glm-5.2] allowed: [zai/glm-5.2]
`; `;
@@ -74,7 +71,7 @@ async function fixture() {
await git(source, ["remote", "add", "origin", remote]); await git(source, ["remote", "add", "origin", remote]);
await git(source, ["push", "origin", "main"]); await git(source, ["push", "origin", "main"]);
mkdirSync(secretRoot); mkdirSync(secretRoot);
for (const name of ["dwh-password", "vector-password"]) { for (const name of ["dwh-password"]) {
const path = join(secretRoot, name); const path = join(secretRoot, name);
writeFileSync(path, `${name}-value`, { mode: 0o600 }); writeFileSync(path, `${name}-value`, { mode: 0o600 });
chmodSync(path, 0o600); chmodSync(path, 0o600);
@@ -100,12 +97,6 @@ async function fixture() {
THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_PORT: "5432",
THT_WS_PSD_CLINICAL_DWH_USER: "reader", THT_WS_PSD_CLINICAL_DWH_USER: "reader",
THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: join(secretRoot, "dwh-password"), THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: join(secretRoot, "dwh-password"),
THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT: "pgvector_direct",
THT_WS_PSD_CLINICAL_VECTOR_HOST: "vector.invalid",
THT_WS_PSD_CLINICAL_VECTOR_PORT: "5432",
THT_WS_PSD_CLINICAL_VECTOR_USER: "vector-reader",
THT_WS_PSD_CLINICAL_VECTOR_PASSWORD_FILE: join(secretRoot, "vector-password"),
THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "http://embedding.invalid",
}; };
for (const [name, value] of Object.entries(environment)) vi.stubEnv(name, value); for (const [name, value] of Object.entries(environment)) vi.stubEnv(name, value);
vi.stubEnv("THT_HOME", join(root, "home")); vi.stubEnv("THT_HOME", join(root, "home"));
@@ -123,7 +114,7 @@ function runnerFor(f: Awaited<ReturnType<typeof fixture>>): ThtRunner {
} as any); } as any);
} }
test("real schema-v2 registry revision loads through ThtRunner and the harness contract", async () => { test("real schema-v3 registry revision loads through ThtRunner and the harness contract", async () => {
const f = await fixture(); const f = await fixture();
const runner = runnerFor(f); const runner = runnerFor(f);
+3 -2
View File
@@ -210,7 +210,7 @@ llm_policy:
})).vector_db).toMatchObject({ database: "vector_database", schema: "vectors" }); })).vector_db).toMatchObject({ database: "vector_database", schema: "vectors" });
}); });
test("documents the rendered writer secret-file binding for writer workspaces", () => { test("renders legacy writer secret-file bindings without reintroducing them to the active protocol", () => {
const writerVariable = "THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE"; const writerVariable = "THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE";
const generated = renderWorkspaceDocs(parseWorkspaceYaml(`workspace: const generated = renderWorkspaceDocs(parseWorkspaceYaml(`workspace:
schema_version: 2 schema_version: 2
@@ -245,7 +245,8 @@ llm_policy:
expect(generated.envExample).toContain(`${writerVariable}=`); expect(generated.envExample).toContain(`${writerVariable}=`);
expect(existsSync(protocolPath)).toBe(true); expect(existsSync(protocolPath)).toBe(true);
if (existsSync(protocolPath)) { if (existsSync(protocolPath)) {
expect(readFileSync(protocolPath, "utf8")).toContain(writerVariable); expect(readFileSync(protocolPath, "utf8")).not.toContain(writerVariable);
expect(readFileSync(protocolPath, "utf8")).toContain("There are no supported `THT_WS_<NAMESPACE>_VECTOR_*`");
} }
}); });
@@ -152,7 +152,7 @@ test("releases its queue after a malformed lock failure so a later attempt can a
const result = await Promise.race([ const result = await Promise.race([
lock.run(async () => "recovered"), lock.run(async () => "recovered"),
new Promise<string>((resolve) => setTimeout(() => resolve("timed out"), 250)), new Promise<string>((resolve) => setTimeout(() => resolve("timed out"), 2_000)),
]); ]);
expect(result).toBe("recovered"); expect(result).toBe("recovered");
}); });
+4 -4
View File
@@ -1,8 +1,8 @@
workspace: workspace:
schema_version: 3 schema_version: 3
id: psd-clinical id: example-workspace
name: PSD Clinical name: Example Workspace
description: Example PSD-oriented WorkspaceV3 descriptor. description: Example WorkspaceV3 descriptor.
language: en language: en
dwh: dwh:
@@ -16,7 +16,7 @@ dwh:
semantic_index: semantic_index:
vector_store: vector_store:
engine: qdrant engine: qdrant
collection: psd-clinical collection: example-workspace
dimensions: 1024 dimensions: 1024
distance: cosine distance: cosine
embedding: embedding:
@@ -36,7 +36,11 @@ test("submitting includes browser-local migrated preferences and calls onCreated
})), })),
http.get("/api/workspaces", () => HttpResponse.json([{ http.get("/api/workspaces", () => HttpResponse.json([{
id: "default", name: "default", file: "default.yaml", displayName: "Default", id: "default", name: "default", file: "default.yaml", displayName: "Default",
revision: { state: "operational" },
}])), }])),
http.get("/api/workspaces/default", () => HttpResponse.json({
workspace: { llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] } },
})),
http.post("/api/sessions", async ({ request }) => { http.post("/api/sessions", async ({ request }) => {
body = await request.json(); body = await request.json();
return HttpResponse.json({ id: "s1" }); return HttpResponse.json({ id: "s1" });
@@ -1,15 +1,14 @@
from datetime import UTC, datetime
from types import SimpleNamespace from types import SimpleNamespace
import pytest import pytest
import typer import typer
from tht.cli import db_cmd from tht.cli import db_cmd, memory_cmd
from tht.cli.lsh_cmd import _extract_lsh_values from tht.cli.lsh_cmd import _extract_lsh_values
from tht.memory import MemoryRecord
from tht.mschema.models import Annotations, ColumnPhysical, PhysicalSchema, TablePhysical from tht.mschema.models import Annotations, ColumnPhysical, PhysicalSchema, TablePhysical
from tht.ports.dwh import DistinctValues, DwhHealth from tht.ports.dwh import DistinctValues, DwhHealth
from tht.cli import memory_cmd
from tht.memory import MemoryRecord
from datetime import datetime
def _ping(monkeypatch, health, capsys): def _ping(monkeypatch, health, capsys):
@@ -44,7 +43,7 @@ def test_db_ping_direct_connection_historical_wording(monkeypatch, capsys):
@pytest.mark.parametrize(("limit", "truncated"), [(7, False), (1201, True)]) @pytest.mark.parametrize(("limit", "truncated"), [(7, False), (1201, True)])
def test_lsh_extraction_honors_configured_limit(limit, truncated): def test_lsh_extraction_honors_configured_limit(limit, truncated):
physical = PhysicalSchema(database="d", schema="s", introspected_at=datetime(2026, 1, 1), tables={ physical = PhysicalSchema(database="d", schema="s", introspected_at=datetime(2026, 1, 1, tzinfo=UTC), tables={
"t": TablePhysical(columns={"c": ColumnPhysical(type="text", eligible=True)}) "t": TablePhysical(columns={"c": ColumnPhysical(type="text", eligible=True)})
}) })
calls = [] calls = []
@@ -69,7 +68,7 @@ def test_memory_command_writes_through_factory_vector_store(monkeypatch):
manifest = SimpleNamespace(id="s1") manifest = SimpleNamespace(id="s1")
snapshot = SimpleNamespace(manifest=manifest, decisions=[], artifacts={}) snapshot = SimpleNamespace(manifest=manifest, decisions=[], artifacts={})
record = MemoryRecord( record = MemoryRecord(
id="m1", ts=datetime(2026, 1, 1), session_id="s1", id="m1", ts=datetime(2026, 1, 1, tzinfo=UTC), session_id="s1",
decision_seq=7, type="concept_clarified", subject="paziente attivo", decision_seq=7, type="concept_clarified", subject="paziente attivo",
detail="flag_attivo = TRUE", question_context="q", detail="flag_attivo = TRUE", question_context="q",
) )
@@ -97,7 +96,6 @@ def test_solved_index_writes_through_writer_only_factory_store(monkeypatch):
solved_record = object() solved_record = object()
calls = [] calls = []
monkeypatch.setattr(memory_cmd, "has_vector_write_rest", lambda cfg: True)
monkeypatch.setattr(memory_cmd, "load_snapshot_or_exit", lambda cfg, session: SimpleNamespace(manifest=manifest, decisions=[], artifacts={})) monkeypatch.setattr(memory_cmd, "load_snapshot_or_exit", lambda cfg, session: SimpleNamespace(manifest=manifest, decisions=[], artifacts={}))
monkeypatch.setattr( monkeypatch.setattr(
"tht.adapters.factory.build_vector_store", "tht.adapters.factory.build_vector_store",
+4 -4
View File
@@ -1,5 +1,5 @@
from tht.workspace import load_workspace, WorkspaceError from tht.workspace import WorkspaceError, load_workspace
def test_load_workspace_expands_env_vars(monkeypatch, tmp_path): def test_load_workspace_expands_env_vars(monkeypatch, tmp_path):
@@ -19,7 +19,7 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path):
monkeypatch.setenv("THT_VEC_HOST", "vh") monkeypatch.setenv("THT_VEC_HOST", "vh")
monkeypatch.setenv("THT_VEC_USER", "vu") monkeypatch.setenv("THT_VEC_USER", "vu")
monkeypatch.setenv("THT_VEC_PASSWORD", "vp") monkeypatch.setenv("THT_VEC_PASSWORD", "vp")
monkeypatch.setenv("THT_OLLAMA_URL", "http://ollama") monkeypatch.setenv("THT_OLLAMA_URL", "http://embedding:11434")
monkeypatch.setenv("THT_DOCS_ROOT", str(tmp_path / "docs")) monkeypatch.setenv("THT_DOCS_ROOT", str(tmp_path / "docs"))
yaml = tmp_path / "w.yaml" yaml = tmp_path / "w.yaml"
yaml.write_text( yaml.write_text(
@@ -49,8 +49,8 @@ def test_load_workspace_expands_env_vars(monkeypatch, tmp_path):
" api_key: ${THT_VEC_WRITE_API_KEY}\n" " api_key: ${THT_VEC_WRITE_API_KEY}\n"
"embeddings:\n" "embeddings:\n"
" base_url: ${THT_OLLAMA_URL}\n" " base_url: ${THT_OLLAMA_URL}\n"
" model: nomic-embed-text-v2-moe\n" " model: qwen3-embedding:0.6b\n"
" dim: 768\n" " dim: 1024\n"
"evidence:\n" "evidence:\n"
" source_root: ${THT_DOCS_ROOT}\n" " source_root: ${THT_DOCS_ROOT}\n"
) )
+5 -1
View File
@@ -67,7 +67,11 @@ for (const [name, value] of Object.entries(semanticRuntime)) {
throw new Error(`semantic runtime escaped to frontend: ${name}`); throw new Error(`semantic runtime escaped to frontend: ${name}`);
} }
} }
for (const name of ["THT_VEC_REST_URL", "THT_VEC_WRITE_REST_URL", "THT_OLLAMA_URL"]) { for (const name of [
["THT", "VEC", "REST", "URL"].join("_"),
["THT", "VEC", "WRITE", "REST", "URL"].join("_"),
["THT", "OLLAMA", "URL"].join("_"),
]) {
if (Object.hasOwn(core.environment || {}, name) && core.environment?.[name] !== "") { if (Object.hasOwn(core.environment || {}, name) && core.environment?.[name] !== "") {
throw new Error(`fixture render reintroduced external semantic binding ${name}`); throw new Error(`fixture render reintroduced external semantic binding ${name}`);
} }
@@ -49,7 +49,11 @@ grep -Fq 'Do not add vector or embedding endpoint credentials to the bundle.' \
echo "secret bundle guide still permits vector/embedding runtime secrets" >&2 echo "secret bundle guide still permits vector/embedding runtime secrets" >&2
exit 1 exit 1
} }
if rg -n 'engine: pgvector|provider: ollama_compatible|THT_WS_<NAMESPACE>_VECTOR_TRANSPORT|THT_WS_<NAMESPACE>_EMBEDDING_BASE_URL' \ legacy_pg_vector='engine: pg''vector'
legacy_ollama='provider: ollama''_compatible'
legacy_vector_binding='THT_WS_<NAMESPACE>_''VECTOR_TRANSPORT'
legacy_embedding_binding='THT_WS_<NAMESPACE>_''EMBEDDING_BASE_URL'
if rg -n "${legacy_pg_vector}|${legacy_ollama}|${legacy_vector_binding}|${legacy_embedding_binding}" \
"$root/docs/workspace-diagnostic-protocol.md"; then "$root/docs/workspace-diagnostic-protocol.md"; then
echo "workspace diagnostic protocol still documents external vector or embedding contracts" >&2 echo "workspace diagnostic protocol still documents external vector or embedding contracts" >&2
exit 1 exit 1
+24 -2
View File
@@ -7,14 +7,36 @@ set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd -P)" root="$(cd "$(dirname "$0")/.." && pwd -P)"
tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")" tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")"
project="thoth-workspace-registry-smoke-$$" project="thoth-workspace-registry-smoke-$$"
image="thothii-workspace-registry-smoke:local"
remote="$tmp/remote.git" remote="$tmp/remote.git"
seed="$tmp/seed" seed="$tmp/seed"
branch="workspace-registry-smoke" branch="workspace-registry-smoke"
core_remote="/fixtures/remote.git" core_remote="/fixtures/remote.git"
cleanup() { cleanup() {
local cleanup_status=$?
compose down --volumes --remove-orphans >/dev/null 2>&1 || true compose down --volumes --remove-orphans >/dev/null 2>&1 || true
docker image rm -f "$image" >/dev/null 2>&1 || true
if [[ "$cleanup_status" -eq 0 ]]; then
local leftovers
leftovers="$(
{
docker ps -a --filter "label=com.docker.compose.project=$project" -q
docker volume ls --filter "label=com.docker.compose.project=$project" -q
docker network ls --filter "label=com.docker.compose.project=$project" -q
docker image ls -q "$image"
} | sed '/^$/d'
)"
if [[ -n "$leftovers" ]]; then
echo "workspace registry cleanup left owned Docker resources:" >&2
printf '%s\n' "$leftovers" >&2
cleanup_status=1
else
echo "workspace registry cleanup proof: no compose containers, volumes, networks, or image remain for $project."
fi
fi
rm -rf "$tmp" rm -rf "$tmp"
exit "$cleanup_status"
} }
trap cleanup EXIT HUP INT TERM trap cleanup EXIT HUP INT TERM
@@ -25,7 +47,7 @@ services:
build: build:
context: $root context: $root
dockerfile: docker/core.Dockerfile dockerfile: docker/core.Dockerfile
image: thothii-workspace-registry-smoke:local image: $image
environment: environment:
HOST: 0.0.0.0 HOST: 0.0.0.0
PORT: "8787" PORT: "8787"
@@ -69,7 +91,7 @@ git clone "$remote" "$seed" >/dev/null
git -C "$seed" checkout -b "$branch" >/dev/null git -C "$seed" checkout -b "$branch" >/dev/null
npm --prefix "$root/backend" run build >/dev/null npm --prefix "$root/backend" run build >/dev/null
node "$root/backend/dist/workspaces/migrate-legacy.js" \ node "$root/backend/dist/workspaces/migrate-legacy.js" \
--input "$root/harness/workspaces/local.yaml" --output "$seed" >/dev/null --input "$root/harness/workspaces/local.yaml" --output "$seed" --collection local >/dev/null
git -C "$seed" add workspaces/local.yaml git -C "$seed" add workspaces/local.yaml
git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \ git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \
commit -m 'Seed workspace registry smoke' >/dev/null commit -m 'Seed workspace registry smoke' >/dev/null