import type { CanonicalDiagnostics, CanonicalWorkspace, RestDiagnosticRequest } from "../api/workspaces"; export { workspacePreferences, type WorkspacePreference } from "./preferences"; export interface WorkspaceDraft { workspaceId: string; baseCommit: string; baseBlob?: string; workspace: CanonicalWorkspace; updatedAt: string; } type PolicySelection = { workspaceId: string; generation: number; state: "pending" | "ready" | "error"; error?: Error; settled: Promise; settle: () => void; superseded: Promise; supersede: () => void; }; let selection: PolicySelection | undefined; let nextGeneration = 0; function pendingSelection(workspaceId: string): PolicySelection { let settle!: () => void; let supersede!: () => void; return { workspaceId, generation: ++nextGeneration, state: "pending", settled: new Promise((resolve) => { settle = resolve; }), settle, superseded: new Promise((resolve) => { supersede = resolve; }), supersede, }; } /** In-memory coordination between the footer's selected policy and session creation. */ export const workspacePolicyGate = { select(workspaceId: string): void { if (selection?.workspaceId === workspaceId) return; selection?.supersede(); selection = pendingSelection(workspaceId); }, clear(): void { selection?.supersede(); selection = undefined; }, resolve(workspaceId: string): void { if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return; selection.state = "ready"; selection.settle(); }, reject(workspaceId: string): void { if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return; selection.state = "error"; selection.error = new Error("Selected workspace policy could not be loaded."); selection.settle(); }, async waitForCurrent(read: () => T): Promise { while (true) { const current = read(); const pending = selection; if (!current.workspaceId || pending?.workspaceId !== current.workspaceId) return current; const generation = pending.generation; if (pending.state === "ready") return read(); if (pending.state === "error") throw pending.error; await Promise.race([pending.settled, pending.superseded]); const completed = selection; if (!completed || completed.generation !== generation || read().workspaceId !== current.workspaceId) continue; if (completed.state === "error") throw completed.error; if (completed.state === "ready") return read(); } }, }; const PREFIX = "thothii.workspace-registry.v1"; const DRAFT_PREFIX = `${PREFIX}.draft.`; function storage(): Storage | undefined { try { return window.localStorage; } catch { return undefined; } } function record(value: unknown): Record | undefined { return value && typeof value === "object" && !Array.isArray(value) ? value as Record : undefined; } function exactRecord(value: unknown, keys: readonly string[]): Record | undefined { const source = record(value); return source && Object.keys(source).every((key) => keys.includes(key)) ? source : undefined; } function text(value: unknown): string | undefined { return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; } function identifier(value: unknown): string | undefined { return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value) ? value : undefined; } function workspaceId(value: unknown): string | undefined { return typeof value === "string" && /^[a-z][a-z0-9-]{2,62}$/.test(value) ? value : undefined; } function modelReference(value: unknown): `${string}/${string}` | undefined { return typeof value === "string" && /^[^/\s]+\/[^/\s]+$/.test(value) ? value as `${string}/${string}` : undefined; } function positiveInteger(value: unknown, max = Number.MAX_SAFE_INTEGER): number | undefined { return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= max ? value : undefined; } function oneOf(value: unknown, choices: readonly T[]): T | undefined { return typeof value === "string" && choices.includes(value as T) ? value as T : undefined; } function uniqueChoices(value: unknown, choices: readonly T[]): T[] | undefined { if (!Array.isArray(value) || value.length === 0) return undefined; const result: T[] = []; for (const item of value) { const choice = oneOf(item, choices); if (!choice || result.includes(choice)) return undefined; result.push(choice); } return result; } function uniqueModels(value: unknown): `${string}/${string}`[] | undefined { if (!Array.isArray(value) || value.length === 0) return undefined; const result: `${string}/${string}`[] = []; for (const item of value) { const model = modelReference(item); if (!model || result.includes(model)) return undefined; result.push(model); } return result; } function originRelativePath(value: unknown): string | undefined { return typeof value === "string" && /^\/(?!\/)[^\\\u0000-\u001F\u007F?#]*$/.test(value) && !/%5c/i.test(value) ? value : undefined; } function copyRequest(value: unknown, extraKeys: readonly string[] = []): RestDiagnosticRequest | undefined { const source = exactRecord(value, ["method", "path", "auth", ...extraKeys]); const method = oneOf(source?.method, ["GET", "POST"] as const); const path = originRelativePath(source?.path); const auth = oneOf(source?.auth, ["none", "bearer", "x-api-key"] as const); return method && path && auth ? { method, path, auth } : undefined; } function copyDiagnostics(value: unknown): CanonicalDiagnostics | undefined { const source = exactRecord(value, ["dwh_rest", "vector_rest", "embedding"]); if (!source) return undefined; const diagnostics: CanonicalDiagnostics = {}; if (source.dwh_rest !== undefined) { const request = copyRequest(source.dwh_rest, ["response"]); const raw = exactRecord(source.dwh_rest, ["method", "path", "auth", "response"]); const response = exactRecord(raw?.response, ["database", "schema"]); const database = identifier(response?.database); const schema = identifier(response?.schema); if (!request || !database || !schema) return undefined; diagnostics.dwh_rest = { ...request, response: { database, schema } }; } if (source.vector_rest !== undefined) { const vector = exactRecord(source.vector_rest, ["metadata", "reversible_probe"]); const request = copyRequest(vector?.metadata, ["response"]); const rawMetadata = exactRecord(vector?.metadata, ["method", "path", "auth", "response"]); const response = exactRecord(rawMetadata?.response, ["collection", "dimensions", "distance"]); const collection = identifier(response?.collection); const dimensions = identifier(response?.dimensions); const distance = identifier(response?.distance); if (!vector || !request || !collection || !dimensions || !distance) return undefined; const metadata = { ...request, response: { collection, dimensions, distance } }; let reversibleProbe: NonNullable["reversible_probe"] | undefined; if (vector.reversible_probe !== undefined) { const probe = copyRequest(vector.reversible_probe, ["response"]); const rawProbe = exactRecord(vector.reversible_probe, ["method", "path", "auth", "response"]); const probeResponse = exactRecord(rawProbe?.response, ["operation"]); const operation = identifier(probeResponse?.operation); if (!probe || probe.method !== "POST" || probe.auth === "none" || !operation) return undefined; reversibleProbe = { method: "POST", path: probe.path, auth: probe.auth as "bearer" | "x-api-key", response: { operation }, }; } diagnostics.vector_rest = { metadata, ...(reversibleProbe ? { reversible_probe: reversibleProbe } : {}) }; } if (source.embedding !== undefined) { const request = copyRequest(source.embedding, ["response"]); const raw = exactRecord(source.embedding, ["method", "path", "auth", "response"]); const response = exactRecord(raw?.response, ["model", "dimensions"]); const model = identifier(response?.model); const dimensions = identifier(response?.dimensions); if (!request || !model || !dimensions) return undefined; diagnostics.embedding = { ...request, response: { model, dimensions } }; } return diagnostics; } function copyWorkspace(value: unknown): CanonicalWorkspace | undefined { const source = exactRecord(value, ["workspace", "dwh", "semantic_index", "llm_policy", "diagnostics"]); const metadata = exactRecord(source?.workspace, ["schema_version", "id", "name", "description", "language"]); const dwh = exactRecord(source?.dwh, ["engine", "database", "schema", "port", "timeout_ms", "supported_transports"]); const semanticIndex = exactRecord(source?.semantic_index, ["vector_store", "vector_writer", "embedding"]); const vectorStore = exactRecord(semanticIndex?.vector_store, ["engine", "database", "schema", "collection", "dimensions", "distance", "port", "timeout_ms", "supported_transports"]); const embedding = exactRecord(semanticIndex?.embedding, ["provider", "model", "dimensions", "timeout_ms"]); const policy = exactRecord(source?.llm_policy, ["default", "allowed"]); const diagnostics = source?.diagnostics === undefined ? undefined : copyDiagnostics(source.diagnostics); if (!metadata || !dwh || !semanticIndex || !vectorStore || !embedding || !policy) return undefined; const id = workspaceId(metadata.id); const name = text(metadata.name); const language = oneOf(metadata.language, ["en", "it"] as const); const description = metadata.description === undefined ? undefined : text(metadata.description); const database = identifier(dwh.database); const schema = identifier(dwh.schema); const dwhPort = dwh.port === undefined ? undefined : positiveInteger(dwh.port, 65_535); const dwhTimeout = dwh.timeout_ms === undefined ? undefined : positiveInteger(dwh.timeout_ms); const dwhTransports = uniqueChoices(dwh.supported_transports, ["postgres_direct", "rest_api", "ssh_tunnel"] as const); const vectorDatabase = identifier(vectorStore.database); const vectorSchema = identifier(vectorStore.schema); const collection = identifier(vectorStore.collection); const vectorDimensions = positiveInteger(vectorStore.dimensions, 32_768); const distance = oneOf(vectorStore.distance, ["cosine", "l2", "inner_product"] as const); const vectorPort = vectorStore.port === undefined ? undefined : positiveInteger(vectorStore.port, 65_535); const vectorTimeout = vectorStore.timeout_ms === undefined ? undefined : positiveInteger(vectorStore.timeout_ms); const vectorTransports = uniqueChoices(vectorStore.supported_transports, ["pgvector_direct", "rest_api", "ssh_tunnel"] as const); const embeddingProvider = oneOf(embedding.provider, ["ollama_compatible", "openai_compatible"] as const); const embeddingModel = text(embedding.model); const embeddingDimensions = positiveInteger(embedding.dimensions, 32_768); const embeddingTimeout = embedding.timeout_ms === undefined ? undefined : positiveInteger(embedding.timeout_ms); const allowedModels = uniqueModels(policy.allowed); const defaultModel = policy.default === undefined ? undefined : modelReference(policy.default); if ( metadata.schema_version !== 2 || !id || !name || !language || (metadata.description !== undefined && !description) || dwh.engine !== "postgres" || !database || !schema || (dwh.port !== undefined && !dwhPort) || (dwh.timeout_ms !== undefined && !dwhTimeout) || !dwhTransports || vectorStore.engine !== "pgvector" || !vectorDatabase || !vectorSchema || !collection || !vectorDimensions || !distance || (vectorStore.port !== undefined && !vectorPort) || (vectorStore.timeout_ms !== undefined && !vectorTimeout) || !vectorTransports || !embeddingProvider || !embeddingModel || !embeddingDimensions || (embedding.timeout_ms !== undefined && !embeddingTimeout) || !allowedModels || (defaultModel !== undefined && !allowedModels.includes(defaultModel)) || vectorDimensions !== embeddingDimensions || (semanticIndex.vector_writer !== undefined && !exactRecord(semanticIndex.vector_writer, [])) ) return undefined; if (source?.diagnostics !== undefined && !diagnostics) return undefined; if (diagnostics?.dwh_rest && !dwhTransports.includes("rest_api")) return undefined; if (diagnostics?.vector_rest && !vectorTransports.includes("rest_api")) return undefined; return { workspace: { schema_version: 2, id, name, ...(description ? { description } : {}), language, }, dwh: { engine: "postgres", database, schema, ...(dwhPort ? { port: dwhPort } : {}), ...(dwhTimeout ? { timeout_ms: dwhTimeout } : {}), supported_transports: dwhTransports, }, semantic_index: { vector_store: { engine: "pgvector", database: vectorDatabase, schema: vectorSchema, collection, dimensions: vectorDimensions, distance, ...(vectorPort ? { port: vectorPort } : {}), ...(vectorTimeout ? { timeout_ms: vectorTimeout } : {}), supported_transports: vectorTransports, }, ...(semanticIndex.vector_writer ? { vector_writer: {} } : {}), embedding: { provider: embeddingProvider, model: embeddingModel, dimensions: embeddingDimensions, ...(embeddingTimeout ? { timeout_ms: embeddingTimeout } : {}), }, }, llm_policy: { ...(defaultModel ? { default: defaultModel } : {}), allowed: allowedModels, }, ...(diagnostics ? { diagnostics } : {}), }; } function normalize(value: unknown): WorkspaceDraft | undefined { const source = exactRecord(value, ["workspaceId", "baseCommit", "baseBlob", "workspace", "updatedAt"]); const workspace = copyWorkspace(source?.workspace); const id = workspaceId(source?.workspaceId); const baseCommit = typeof source?.baseCommit === "string" && /^[0-9a-f]{40}$/.test(source.baseCommit) ? source.baseCommit : undefined; const baseBlob = source?.baseBlob === undefined ? undefined : typeof source.baseBlob === "string" && /^[0-9a-f]{40}$/.test(source.baseBlob) ? source.baseBlob : undefined; const updatedAt = typeof source?.updatedAt === "string" && Number.isFinite(Date.parse(source.updatedAt)) ? source.updatedAt : undefined; if (!source || !workspace || !id || id !== workspace.workspace.id || !baseCommit || (source.baseBlob !== undefined && !baseBlob) || !updatedAt) { return undefined; } return { workspaceId: id, baseCommit, ...(baseBlob ? { baseBlob } : {}), workspace, updatedAt, }; } function key(id: string): string { return `${DRAFT_PREFIX}${encodeURIComponent(id)}`; } /** Browser-only workspace drafts. Saving or editing one never calls the server. */ export const workspaceDrafts = { load(id: string): WorkspaceDraft | undefined { try { const raw = storage()?.getItem(key(id)); return raw ? normalize(JSON.parse(raw)) : undefined; } catch { return undefined; } }, save(draft: WorkspaceDraft): void { const safe = normalize(draft); if (!safe) return; try { storage()?.setItem(key(safe.workspaceId), JSON.stringify(safe)); } catch { /* storage is optional */ } }, discard(id: string): void { try { storage()?.removeItem(key(id)); } catch { /* storage is optional */ } }, };