From 9d7e9a05b779441e853927f100e5b0cfcd280108 Mon Sep 17 00:00:00 2001 From: mptyl Date: Thu, 13 Aug 2026 20:58:10 +0200 Subject: [PATCH] fix: resolve diagnostic paths under the base path prefix and tolerate health-style ping responses --- backend/src/workspaces/diagnostics.ts | 12 ++++++++++-- backend/src/workspaces/schema.ts | 5 ++++- backend/test/workspaces-diagnostics.test.ts | 9 ++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/backend/src/workspaces/diagnostics.ts b/backend/src/workspaces/diagnostics.ts index bff93020..8534b168 100644 --- a/backend/src/workspaces/diagnostics.ts +++ b/backend/src/workspaces/diagnostics.ts @@ -236,8 +236,16 @@ export function createConcreteDiagnosticAdapters( if ("response" in request.diagnostic) { const payload = await response.json().catch(() => undefined) as Record | undefined; const declared = request.diagnostic.response; - if (!payload || (declared?.database && payload[declared.database] !== request.resource.database) - || (declared?.schema && payload[declared.schema] !== request.resource.schema)) { + // Validate a declared field only when the probe response actually carries it, so a + // health-style ping (2xx + JSON without database/schema identity) still proves a + // reachable, authenticated connector. Declared fields that are present must match. + const databaseMatches = declared?.database === undefined + || payload?.[declared.database] === undefined + || payload[declared.database] === request.resource.database; + const schemaMatches = declared?.schema === undefined + || payload?.[declared.schema] === undefined + || payload[declared.schema] === request.resource.schema; + if (!payload || !databaseMatches || !schemaMatches) { throw new Error("REST probe failed"); } } diff --git a/backend/src/workspaces/schema.ts b/backend/src/workspaces/schema.ts index 4d9369a9..666fae3e 100644 --- a/backend/src/workspaces/schema.ts +++ b/backend/src/workspaces/schema.ts @@ -423,7 +423,10 @@ export function validateOperationalWorkspace(workspace: unknown): WorkspaceV3 { export function resolveDiagnosticUrl(baseUrl: string, path: string): URL { if (!isOriginRelativeDiagnosticPath(path)) throw new Error("Diagnostic path must remain on the configured origin"); const base = new URL(baseUrl); - const resolved = new URL(path, base); + // Resolve the origin-relative path beneath the configured base path (e.g. `/dwh/`), not the + // origin root: a leading slash must append to the base path instead of resetting it. + const basePath = base.pathname.endsWith("/") ? base.pathname : `${base.pathname}/`; + const resolved = new URL(`${basePath}${path.replace(/^\/+/, "")}`, base.origin); if (resolved.origin !== base.origin) throw new Error("Diagnostic URL must remain on the configured origin"); return resolved; } diff --git a/backend/test/workspaces-diagnostics.test.ts b/backend/test/workspaces-diagnostics.test.ts index ac455312..739c45d7 100644 --- a/backend/test/workspaces-diagnostics.test.ts +++ b/backend/test/workspaces-diagnostics.test.ts @@ -9,7 +9,7 @@ import { type DiagnosticAdapters, } from "../src/workspaces/diagnostics.js"; import type { RuntimeBindings } from "../src/workspaces/runtime-renderer.js"; -import { parseWorkspaceYaml } from "../src/workspaces/schema.js"; +import { parseWorkspaceYaml, resolveDiagnosticUrl } from "../src/workspaces/schema.js"; const workspace = parseWorkspaceYaml(`workspace: schema_version: 3 @@ -486,3 +486,10 @@ test("classifies an observed non-cosine Qdrant distance as semantic incompatibil code: "connector_unavailable", })); }); + +test("resolveDiagnosticUrl appends the path to a base URL with a path prefix", () => { + expect(resolveDiagnosticUrl("https://dwh.example.test/dwh/", "/rpc/ping").toString()) + .toBe("https://dwh.example.test/dwh/rpc/ping"); + expect(resolveDiagnosticUrl("https://dwh.example.test/dwh", "/rpc/ping").toString()) + .toBe("https://dwh.example.test/dwh/rpc/ping"); +});