Files
ThothII/frontend/src/workspaces/drafts.ts
T

458 lines
18 KiB
TypeScript

import type {
CanonicalDiagnostics, CanonicalWorkspace, EvidencePolicy, EvidenceSource,
RestDiagnosticRequest, WorkspaceEvidence,
} from "../api/workspaces";
export { workspacePreferences, type WorkspacePreference } from "./preferences";
export const WORKSPACE_SUMMARY_ERROR = "Could not load workspace registry. Please retry.";
export const WORKSPACE_POLICY_ERROR = "Could not load selected workspace policy. Please retry.";
export class WorkspaceSelectionError extends Error {}
type PolicySelection = {
workspaceId: string;
generation: number;
state: "summary" | "pending" | "ready" | "error";
error?: Error;
settled: Promise<void>;
settle: () => void;
superseded: Promise<void>;
supersede: () => void;
};
let selection: PolicySelection | undefined;
let nextGeneration = 0;
function pendingSelection(workspaceId: string, state: "summary" | "pending"): PolicySelection {
let settle!: () => void;
let supersede!: () => void;
return {
workspaceId,
generation: ++nextGeneration,
state,
settled: new Promise<void>((resolve) => { settle = resolve; }),
settle,
superseded: new Promise<void>((resolve) => { supersede = resolve; }),
supersede,
};
}
/** In-memory coordination between the footer's selected policy and session creation. */
export const workspacePolicyGate = {
beginSummary(workspaceId: string): void {
if (selection?.workspaceId === workspaceId && selection.state === "summary") return;
selection?.supersede();
selection = pendingSelection(workspaceId, "summary");
},
select(workspaceId: string): void {
if (selection?.workspaceId === workspaceId) {
if (selection.state === "summary") selection.state = "pending";
if (selection.state === "pending") return;
}
selection?.supersede();
selection = pendingSelection(workspaceId, "pending");
},
allowLegacy(workspaceId: string): void {
if (selection?.workspaceId !== workspaceId) {
this.clear();
return;
}
if (selection.state === "summary") {
selection.state = "ready";
selection.settle();
}
},
rejectSummary(workspaceId: string): void {
if (!selection || selection.workspaceId !== workspaceId || selection.state !== "summary") return;
selection.state = "error";
selection.error = new WorkspaceSelectionError(WORKSPACE_SUMMARY_ERROR);
selection.settle();
},
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 WorkspaceSelectionError(WORKSPACE_POLICY_ERROR);
selection.settle();
},
async waitForCurrent<T extends { workspaceId?: string }>(read: () => T): Promise<T> {
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();
}
},
};
function record(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: undefined;
}
function exactRecord(value: unknown, keys: readonly string[]): Record<string, unknown> | 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.isSafeInteger(value) && value > 0 && value <= max ? value : undefined;
}
function nonnegativeInteger(value: unknown): number | undefined {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
}
function isSafeEvidencePattern(value: string): boolean {
const parts = value.split("/");
return value.length > 0
&& !value.startsWith("/")
&& !value.includes("\\")
&& !/[\u0000-\u001f\u007f]/u.test(value)
&& parts.every((part) => part !== "" && part !== "." && part !== "..");
}
function parsePublicHttpUri(value: string): URL | undefined {
if (value.trim() !== value || /[\u0000-\u001f\u007f\\]/u.test(value)) return undefined;
try {
const parsed = new URL(value);
if (
!["http:", "https:"].includes(parsed.protocol)
|| parsed.hostname.length === 0
|| parsed.username !== ""
|| parsed.password !== ""
|| parsed.search !== ""
|| parsed.hash !== ""
) return undefined;
return parsed;
} catch {
return undefined;
}
}
function isSafeS3Uri(value: string): boolean {
if (value.trim() !== value || /[\u0000-\u001f\u007f\\]/u.test(value)) return false;
try {
const parsed = new URL(value);
const bucket = parsed.hostname;
const validBucket = /^(?=.{3,63}$)(?!-)(?!.*\.\.)(?!.*\.-)(?!.*-\.)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(bucket)
&& !/^\d{1,3}(?:\.\d{1,3}){3}$/.test(bucket);
return parsed.protocol === "s3:"
&& validBucket
&& parsed.port === ""
&& parsed.username === ""
&& parsed.password === ""
&& parsed.search === ""
&& parsed.hash === ""
&& parsed.href === value;
} catch {
return false;
}
}
function isSafeS3Endpoint(value: string): boolean {
const parsed = parsePublicHttpUri(value);
return parsed !== undefined && (parsed.pathname === "/" || parsed.pathname === "");
}
function copyEvidencePolicy(value: unknown): EvidencePolicy | undefined {
const source = exactRecord(value, ["max_chunk_chars", "retain_published_generations"]);
const maxChunkChars = positiveInteger(source?.max_chunk_chars);
const retainedGenerations = positiveInteger(source?.retain_published_generations);
return source && maxChunkChars && retainedGenerations
? { max_chunk_chars: maxChunkChars, retain_published_generations: retainedGenerations }
: undefined;
}
function copyFilesystemEvidence(value: unknown, id: string): EvidenceSource | undefined {
const source = exactRecord(value, ["type", "uri", "patterns", "max_bytes"]);
const uri = typeof source?.uri === "string" ? source.uri : undefined;
const patterns = source?.patterns;
const maxBytes = positiveInteger(source?.max_bytes);
if (
source?.type !== "filesystem"
|| uri !== `${id}/evidence`
|| !Array.isArray(patterns)
|| patterns.length === 0
|| !patterns.every((pattern) => typeof pattern === "string" && isSafeEvidencePattern(pattern))
|| new Set(patterns).size !== patterns.length
|| !maxBytes
) return undefined;
return { type: "filesystem", uri, patterns: [...patterns] as string[], max_bytes: maxBytes };
}
function copyHttpEvidence(value: unknown): EvidenceSource | undefined {
const source = exactRecord(value, [
"type", "uris", "authentication", "connect_timeout_ms", "read_timeout_ms", "max_bytes",
"max_redirects", "allow_private_hosts", "max_cache_bytes",
]);
const uris = source?.uris;
const authentication = oneOf(source?.authentication, ["none", "signed_urls_file"] as const);
const connectTimeout = positiveInteger(source?.connect_timeout_ms);
const readTimeout = positiveInteger(source?.read_timeout_ms);
const maxBytes = positiveInteger(source?.max_bytes);
const maxRedirects = nonnegativeInteger(source?.max_redirects);
const maxCacheBytes = positiveInteger(source?.max_cache_bytes);
if (
source?.type !== "http"
|| !Array.isArray(uris)
|| uris.length === 0
|| !uris.every((uri) => typeof uri === "string" && parsePublicHttpUri(uri) !== undefined)
|| new Set(uris.map((uri) => parsePublicHttpUri(uri as string)?.href)).size !== uris.length
|| !authentication
|| !connectTimeout
|| !readTimeout
|| !maxBytes
|| maxRedirects === undefined
|| typeof source.allow_private_hosts !== "boolean"
|| !maxCacheBytes
) return undefined;
return {
type: "http",
uris: [...uris] as string[],
authentication,
connect_timeout_ms: connectTimeout,
read_timeout_ms: readTimeout,
max_bytes: maxBytes,
max_redirects: maxRedirects,
allow_private_hosts: source.allow_private_hosts,
max_cache_bytes: maxCacheBytes,
};
}
function copyS3Evidence(value: unknown): EvidenceSource | undefined {
const source = exactRecord(value, [
"type", "uri", "endpoint_url", "region", "credentials", "trusted_endpoint",
"allow_private_endpoint", "allow_insecure_endpoint", "max_bytes", "max_objects",
"max_pages", "page_size",
]);
const uri = typeof source?.uri === "string" && isSafeS3Uri(source.uri) ? source.uri : undefined;
const endpoint = source?.endpoint_url === undefined
? undefined
: typeof source.endpoint_url === "string" && isSafeS3Endpoint(source.endpoint_url)
? source.endpoint_url
: null;
const region = source?.region === undefined ? undefined : text(source.region);
const credentials = oneOf(source?.credentials, ["ambient", "static_files"] as const);
const maxBytes = positiveInteger(source?.max_bytes);
const maxObjects = positiveInteger(source?.max_objects);
const maxPages = positiveInteger(source?.max_pages);
const pageSize = positiveInteger(source?.page_size, 1_000);
if (
source?.type !== "s3"
|| !uri
|| endpoint === null
|| (source.region !== undefined && !region)
|| !credentials
|| typeof source.trusted_endpoint !== "boolean"
|| typeof source.allow_private_endpoint !== "boolean"
|| typeof source.allow_insecure_endpoint !== "boolean"
|| !maxBytes
|| !maxObjects
|| !maxPages
|| !pageSize
|| (endpoint === undefined && (
source.trusted_endpoint || source.allow_private_endpoint || source.allow_insecure_endpoint
))
|| (endpoint !== undefined && !source.trusted_endpoint)
|| (endpoint !== undefined && parsePublicHttpUri(endpoint)?.protocol === "http:" && !source.allow_insecure_endpoint)
) return undefined;
return {
type: "s3",
uri,
...(endpoint === undefined ? {} : { endpoint_url: endpoint }),
...(region === undefined ? {} : { region }),
credentials,
trusted_endpoint: source.trusted_endpoint,
allow_private_endpoint: source.allow_private_endpoint,
allow_insecure_endpoint: source.allow_insecure_endpoint,
max_bytes: maxBytes,
max_objects: maxObjects,
max_pages: maxPages,
page_size: pageSize,
};
}
function copyEvidence(value: unknown, id: string): WorkspaceEvidence | undefined {
const source = exactRecord(value, ["source", "policy"]);
if (!source) return undefined;
const type = record(source.source)?.type;
const evidenceSource = type === "filesystem"
? copyFilesystemEvidence(source.source, id)
: type === "http"
? copyHttpEvidence(source.source)
: type === "s3"
? copyS3Evidence(source.source)
: undefined;
const policy = copyEvidencePolicy(source.policy);
return evidenceSource && policy ? { source: evidenceSource, policy } : undefined;
}
function oneOf<T extends string>(value: unknown, choices: readonly T[]): T | undefined {
return typeof value === "string" && choices.includes(value as T) ? value as T : undefined;
}
function uniqueChoices<T extends string>(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"]);
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 } };
}
return diagnostics;
}
/** Drops unknown fields before a server response can become a browser draft or conflict view. */
export function sanitizeCanonicalWorkspace(value: unknown): CanonicalWorkspace | undefined {
const source = exactRecord(value, [
"workspace", "dwh", "semantic_index", "llm_policy", "diagnostics", "evidence",
]);
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", "embedding"]);
const vectorStore = exactRecord(semanticIndex?.vector_store, ["engine", "collection", "dimensions", "distance"]);
const embedding = exactRecord(semanticIndex?.embedding, ["provider", "model", "dimensions"]);
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 evidence = id && source?.evidence !== undefined ? copyEvidence(source.evidence, id) : undefined;
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 collection = workspaceId(vectorStore.collection);
const vectorDimensions = positiveInteger(vectorStore.dimensions, 32_768);
const distance = oneOf(vectorStore.distance, ["cosine"] as const);
const embeddingProvider = oneOf(embedding.provider, ["ollama_internal"] as const);
const embeddingModel = text(embedding.model);
const embeddingDimensions = positiveInteger(embedding.dimensions, 32_768);
const allowedModels = uniqueModels(policy.allowed);
const defaultModel = policy.default === undefined ? undefined : modelReference(policy.default);
if (
metadata.schema_version !== 3 || !id || !name || !language || (metadata.description !== undefined && !description)
|| dwh.engine !== "postgres" || !database || !schema || (dwh.port !== undefined && !dwhPort) || (dwh.timeout_ms !== undefined && !dwhTimeout) || !dwhTransports
|| vectorStore.engine !== "qdrant" || !collection || !vectorDimensions || !distance
|| !embeddingProvider || !embeddingModel || !embeddingDimensions || !allowedModels
|| (defaultModel !== undefined && !allowedModels.includes(defaultModel)) || vectorDimensions !== embeddingDimensions
|| vectorDimensions !== 1024 || embeddingDimensions !== 1024
|| embeddingModel !== "qwen3-embedding:0.6b"
) return undefined;
if (source?.diagnostics !== undefined && !diagnostics) return undefined;
if (source?.evidence !== undefined && !evidence) return undefined;
if (diagnostics?.dwh_rest && !dwhTransports.includes("rest_api")) return undefined;
return {
workspace: {
schema_version: 3,
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: "qdrant", collection, dimensions: 1024, distance: "cosine",
},
embedding: {
provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024,
},
},
llm_policy: {
...(defaultModel ? { default: defaultModel } : {}), allowed: allowedModels,
},
...(diagnostics ? { diagnostics } : {}),
...(evidence ? { evidence } : {}),
};
}