mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 01:27:09 +01:00
Keep native sandbox workspace files independent of the webserver disk. Use encrypted saved versions and Postgres ownership so a replacement server can recover files and continue work. Enable the path by default for new eligible workspaces when durable storage is configured. Preserve explicit opt-outs and existing host workspaces. Include safe snapshot repair, Files access, import, migration and retention. Bound saved-Files resources, preserve imported Git recovery, reject unfinished Git operations, publish immutable concurrent captures, and verify permanent object-version deletion. Validated 471 targeted local controls, build/typechecks, all 40 CI partitions, fresh Greptile 5/5 and security review. The PR records original failed attempts and scoped retries. Co-Authored-By: Paperclip <noreply@paperclip.ing>
59 lines
2.6 KiB
TypeScript
59 lines
2.6 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const repository = z.object({
|
|
id: z.string().min(1),
|
|
relativePath: z.string().min(1),
|
|
repoUrl: z.string().min(1),
|
|
ref: z.string().nullable(),
|
|
pinnedCommit: z.string().optional(),
|
|
readOnly: z.boolean().optional(),
|
|
projectId: z.string().uuid().nullable().optional(),
|
|
projectWorkspaceId: z.string().uuid().nullable().optional(),
|
|
}).strict();
|
|
|
|
export const remoteWorkspaceSourceSchema = z.discriminatedUnion("kind", [
|
|
z.object({ kind: z.literal("empty"), repositories: z.array(repository).optional() }).strict(),
|
|
z.object({
|
|
kind: z.literal("git"), repoUrl: z.string().min(1), ref: z.string().nullable(),
|
|
branchName: z.string().nullable().optional(),
|
|
// Older recipes create a task branch from ref when branchName is present.
|
|
branchMode: z.enum(["create", "existing"]).optional(),
|
|
repositories: z.array(repository).optional(),
|
|
}).strict(),
|
|
]);
|
|
export type RemoteWorkspaceSource = z.infer<typeof remoteWorkspaceSourceSchema>;
|
|
|
|
/** Paths in this record belong to the named remote allocation, never the controller. */
|
|
export const remoteWorkspaceAuthoritySchema = z.object({
|
|
schema: z.literal("paperclip.remote-workspace.v1"),
|
|
epoch: z.number().int().positive(),
|
|
snapshotId: z.string().uuid().nullable(),
|
|
source: remoteWorkspaceSourceSchema,
|
|
location: z.object({
|
|
environmentId: z.string().uuid(), leaseId: z.string().uuid(),
|
|
providerLeaseId: z.string().min(1), remoteCwd: z.string().min(1),
|
|
}).strict().nullable(),
|
|
writerRunId: z.string().uuid().nullable(),
|
|
dirty: z.boolean(),
|
|
}).strict();
|
|
export type RemoteWorkspaceAuthority = z.infer<typeof remoteWorkspaceAuthoritySchema>;
|
|
|
|
export const remoteWorkspaceRunReferenceSchema = z.object({
|
|
schema: z.literal("paperclip.remote-workspace-run.v1"),
|
|
workspaceId: z.string().uuid(), leaseId: z.string().uuid(), providerLeaseId: z.string().min(1),
|
|
remoteCwd: z.string().min(1), epoch: z.number().int().positive(),
|
|
state: z.enum(["prepared", "saved"]), snapshotId: z.string().uuid().nullable(),
|
|
}).strict();
|
|
export type RemoteWorkspaceRunReference = z.infer<typeof remoteWorkspaceRunReferenceSchema>;
|
|
|
|
export function readRemoteWorkspaceRunReference(value: unknown): RemoteWorkspaceRunReference | null {
|
|
if (value === undefined || value === null) return null;
|
|
return remoteWorkspaceRunReferenceSchema.parse(value);
|
|
}
|
|
|
|
export function readRemoteWorkspaceAuthority(metadata: Record<string, unknown> | null | undefined): RemoteWorkspaceAuthority | null {
|
|
const value = metadata?.remoteWorkspace;
|
|
if (value === undefined || value === null) return null;
|
|
return remoteWorkspaceAuthoritySchema.parse(value);
|
|
}
|