fix: preserve workspace evidence in browser drafts

This commit is contained in:
2026-08-09 20:27:20 +02:00
parent ba7596c2dd
commit a580c4ca8a
6 changed files with 637 additions and 12 deletions
+84 -7
View File
@@ -17,6 +17,49 @@ export interface CanonicalDiagnostics {
dwh_rest?: RestDiagnosticRequest & { response: { database: string; schema: string } };
}
export interface EvidencePolicy {
max_chunk_chars: number;
retain_published_generations: number;
}
export type EvidenceSource =
| {
type: "filesystem";
uri: string;
patterns: string[];
max_bytes: number;
}
| {
type: "http";
uris: string[];
authentication: "none" | "signed_urls_file";
connect_timeout_ms: number;
read_timeout_ms: number;
max_bytes: number;
max_redirects: number;
allow_private_hosts: boolean;
max_cache_bytes: number;
}
| {
type: "s3";
uri: string;
endpoint_url?: string;
region?: string;
credentials: "ambient" | "static_files";
trusted_endpoint: boolean;
allow_private_endpoint: boolean;
allow_insecure_endpoint: boolean;
max_bytes: number;
max_objects: number;
max_pages: number;
page_size: number;
};
export interface WorkspaceEvidence {
source: EvidenceSource;
policy: EvidencePolicy;
}
export interface CanonicalWorkspace {
workspace: {
schema_version: 3;
@@ -48,6 +91,7 @@ export interface CanonicalWorkspace {
};
llm_policy: { default?: `${string}/${string}`; allowed: `${string}/${string}`[] };
diagnostics?: CanonicalDiagnostics;
evidence?: WorkspaceEvidence;
}
export interface WorkspaceRevision {
@@ -132,6 +176,16 @@ const conflictFields = new Set([
"diagnostics", "diagnostics.dwh_rest",
"diagnostics.dwh_rest.method", "diagnostics.dwh_rest.path", "diagnostics.dwh_rest.auth",
"diagnostics.dwh_rest.response.database", "diagnostics.dwh_rest.response.schema",
"evidence", "evidence.source", "evidence.source.type", "evidence.source.uri",
"evidence.source.patterns", "evidence.source.max_bytes", "evidence.source.uris",
"evidence.source.authentication", "evidence.source.connect_timeout_ms",
"evidence.source.read_timeout_ms", "evidence.source.max_redirects",
"evidence.source.allow_private_hosts", "evidence.source.max_cache_bytes",
"evidence.source.endpoint_url", "evidence.source.region", "evidence.source.credentials",
"evidence.source.trusted_endpoint", "evidence.source.allow_private_endpoint",
"evidence.source.allow_insecure_endpoint", "evidence.source.max_objects",
"evidence.source.max_pages", "evidence.source.page_size", "evidence.policy",
"evidence.policy.max_chunk_chars", "evidence.policy.retain_published_generations",
]);
const workspaceErrorCodes = new Set<WorkspaceErrorCode>([
@@ -193,20 +247,43 @@ export function asWorkspaceConflict(error: unknown): WorkspaceConflict | undefin
};
}
function requireCanonicalWorkspace(value: unknown): CanonicalWorkspace {
const workspace = sanitizeCanonicalWorkspace(value);
if (!workspace) throw new Error("Workspace API returned an invalid canonical workspace");
return workspace;
}
export const listWorkspaces = () => apiFetch<WorkspaceSummary[]>("/workspaces");
export const getWorkspace = (id: string) => apiFetch<WorkspaceRecord>(`/workspaces/${encodeURIComponent(id)}`);
export const getWorkspace = async (id: string): Promise<WorkspaceRecord> => {
const response = await apiFetch<unknown>(`/workspaces/${encodeURIComponent(id)}`);
const source = object(response);
if (!source) throw new Error("Workspace API returned an invalid workspace record");
return {
workspace: requireCanonicalWorkspace(source.workspace),
revision: source.revision as WorkspaceRevision,
};
};
export const getWorkspaceRegistryStatus = () => apiFetch<WorkspaceRegistryStatus>("/workspace-registry/status");
export const pullWorkspaceRegistry = () => apiFetch<WorkspaceRegistryStatus>("/workspace-registry/pull", { method: "POST" });
export const validateWorkspace = (workspace: CanonicalWorkspace) =>
apiFetch<{ workspace: CanonicalWorkspace; contract: unknown }>("/workspaces/validate", {
method: "POST", body: JSON.stringify({ workspace }),
export const validateWorkspace = async (workspace: CanonicalWorkspace) => {
const safe = requireCanonicalWorkspace(workspace);
const response = await apiFetch<unknown>("/workspaces/validate", {
method: "POST", body: JSON.stringify({ workspace: safe }),
});
const source = object(response);
if (!source) throw new Error("Workspace API returned an invalid validation result");
return { workspace: requireCanonicalWorkspace(source.workspace), contract: source.contract };
};
export const testWorkspace = (id: string) =>
apiFetch<WorkspaceDiagnostics>(`/workspaces/${encodeURIComponent(id)}/test`, { method: "POST" });
export const publishWorkspace = (request: PublishWorkspaceRequest) =>
apiFetch<{ revision: WorkspaceRevision } | undefined>("/workspaces/publish", {
method: "POST", body: JSON.stringify(request),
export const publishWorkspace = (request: PublishWorkspaceRequest) => {
const safeRequest: PublishWorkspaceRequest = request.action === "delete"
? request
: { ...request, workspace: requireCanonicalWorkspace(request.workspace) };
return apiFetch<{ revision: WorkspaceRevision } | undefined>("/workspaces/publish", {
method: "POST", body: JSON.stringify(safeRequest),
});
};
export const exportWorkspace = (id: string) =>
apiFetchBlob(`/workspaces/${encodeURIComponent(id)}/export`);
export const importWorkspace = (bundle: File) => {