mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-06 14:02:36 +01:00
Qualify Claude defaults against pinned Daytona snapshot identities
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
2e107b341f
commit
7bd7699c9c
18 files changed
+325
-31
No files matched your search
@@ -50,6 +50,35 @@ describe("probeEnvironment", () => {
|
||||
mockEnvironmentRuntimeService.mockClear();
|
||||
});
|
||||
|
||||
it("qualifies a named Daytona snapshot only after an ID-pinned lease and owned cleanup", async () => {
|
||||
const environment = { id: "env", driver: "sandbox", status: "active", config: { provider: "daytona", snapshot: "managed-snapshot" }, metadata: null } as any;
|
||||
const identity = { provider: "daytona", requestedRef: "managed-snapshot", id: "a6b9e7d0-f886-471a-b461-640dd03e8d0f", name: "managed-snapshot", organizationId: "provider-account", createdAt: "2026-10-07T23:19:57.133Z" };
|
||||
vi.spyOn(configRuntime, "resolveEnvironmentDriverConfigForRuntime").mockResolvedValue({ driver: "sandbox", config: environment.config });
|
||||
mockRuntimeAcquireRunLease.mockResolvedValue({ lease: { id: "lease", provider: "daytona", metadata: { snapshot: "managed-snapshot", snapshotIdentity: identity } } });
|
||||
mockRuntimeExecute.mockResolvedValue({ exitCode: 0, stdout: "Linux\nx86_64\nglibc\nglibc 2.39\n" });
|
||||
const result = await probeEnvironment({} as any, environment, { companyId: "company", acquireSandboxRuntimeLease: true });
|
||||
expect(result.details?.platformQualificationRecorded).toBe(true);
|
||||
expect(mockRuntimeAcquireRunLease).toHaveBeenCalledWith(expect.objectContaining({ resolveSnapshotIdentity: true }));
|
||||
const proof = mockRecordPlatformProbe.mock.calls[1]![0].proof;
|
||||
expect(proof).toMatchObject({ v: 2, snapshotIdentity: identity });
|
||||
environment.metadata = { [SANDBOX_PLATFORM_PROBE_METADATA_KEY]: { company: proof } };
|
||||
expect(await readSandboxPlatformQualification({} as any, environment, "company")).toMatchObject({ platform: "linux", architecture: "x64", glibcVersion: "glibc 2.39" });
|
||||
expect(await readSandboxPlatformQualification({} as any, environment, "foreign-company")).toBeNull();
|
||||
expect(mockRuntimeAcquireRunLease).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["missing", "wrong-reference", "missing-owner", "cleanup"])("does not qualify a named snapshot after %s", async failure => {
|
||||
const environment = { id: "env", driver: "sandbox", status: "active", config: { provider: "daytona", snapshot: "managed-snapshot" } } as any;
|
||||
const identity = { provider: "daytona", requestedRef: failure === "wrong-reference" ? "other" : "managed-snapshot", id: "a6b9e7d0-f886-471a-b461-640dd03e8d0f", name: "managed-snapshot", organizationId: failure === "missing-owner" ? "" : "provider-account", createdAt: "2026-10-07T23:19:57.133Z" };
|
||||
vi.spyOn(configRuntime, "resolveEnvironmentDriverConfigForRuntime").mockResolvedValue({ driver: "sandbox", config: environment.config });
|
||||
mockRuntimeAcquireRunLease.mockResolvedValue({ lease: { id: "lease", provider: "daytona", metadata: { snapshot: "managed-snapshot", ...(failure === "missing" ? {} : { snapshotIdentity: identity }) } } });
|
||||
mockRuntimeExecute.mockResolvedValue({ exitCode: 0, stdout: "Linux\nx86_64\nglibc\nglibc 2.39\n" });
|
||||
if (failure === "cleanup") mockRuntimeReleaseRunLease.mockResolvedValue({ id: "lease", companyId: "company", environmentId: "env", status: "released", cleanupStatus: "failed" });
|
||||
const result = await probeEnvironment({} as any, environment, { companyId: "company", acquireSandboxRuntimeLease: true });
|
||||
expect(result.details?.platformQualificationRecorded).toBe(false);
|
||||
expect(mockRecordPlatformProbe).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each(["2.38", "2.39", "musl"])("records actual immutable-image facts for %s and reads them without another lease", async libc => {
|
||||
const image = `registry.example.test/runtime@sha256:${"a".repeat(64)}`;
|
||||
const environment = { id: "env", driver: "sandbox", status: "active", config: { provider: "daytona", image, reuseLease: false }, metadata: null } as any;
|
||||
|
||||
@@ -322,6 +322,37 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
} as unknown as PluginWorkerManager;
|
||||
}
|
||||
|
||||
it.each(["paperclip_runner", "claude_local", null])("pins qualified snapshots only for native execution (%s)", async adapterType => {
|
||||
const pluginId = randomUUID();
|
||||
const { companyId, environment: baseEnvironment, runId } = await seedEnvironment();
|
||||
const config = { provider: "daytona", snapshot: "managed-snapshot", reuseLease: false, timeoutMs: 1234 };
|
||||
await insertSandboxProviderPlugin(pluginId, { environmentDrivers: [{ driverKey: "daytona", kind: "sandbox_provider", displayName: "Daytona", configSchema: { type: "object" } }] });
|
||||
await environmentService(db).update(baseEnvironment.id, { driver: "sandbox", config });
|
||||
const environment = { ...baseEnvironment, driver: "sandbox", config };
|
||||
const parsed = await resolveEnvironmentDriverConfigForRuntime(db, companyId, environment);
|
||||
const snapshotId = "a6b9e7d0-f886-471a-b461-640dd03e8d0f";
|
||||
environment.metadata = { paperclipSandboxPlatformProbes: { [companyId]: {
|
||||
v: 2, companyId, snapshot: config.snapshot, template: null,
|
||||
baseConfigFingerprint: createHash("sha256").update(stableStringify(config)).digest("hex"),
|
||||
runtimeConfigFingerprint: createHash("sha256").update(stableStringify(parsed.config)).digest("hex"),
|
||||
snapshotIdentity: { provider: "daytona", requestedRef: config.snapshot, id: snapshotId, name: config.snapshot, organizationId: "provider-account", createdAt: "2026-10-07T23:19:57.133Z" },
|
||||
} } };
|
||||
const worker = createFreshPluginLeaseWorker(pluginId);
|
||||
vi.mocked(worker.call).mockImplementation(async (_id, method, params: any) => {
|
||||
if (method !== "environmentAcquireLease") throw new Error(`Unexpected ${method}`);
|
||||
return { providerLeaseId: "snapshot-lease", metadata: { provider: "daytona", snapshot: params.config.snapshot, remoteCwd: "/workspace" } };
|
||||
});
|
||||
const service = environmentRuntimeService(db, { pluginWorkerManager: worker });
|
||||
const acquired = await service.acquireRunLease({ companyId, environment, issueId: null, heartbeatRunId: runId,
|
||||
persistedExecutionWorkspace: null, adapterType, ...(adapterType === null ? { resolveSnapshotIdentity: true } : {}) });
|
||||
const expectedSnapshot = adapterType === "paperclip_runner" ? snapshotId : config.snapshot;
|
||||
expect(worker.call).toHaveBeenCalledWith(pluginId, "environmentAcquireLease", expect.objectContaining({
|
||||
config: expect.objectContaining({ snapshot: expectedSnapshot }),
|
||||
...(adapterType === null ? { resolveSnapshotIdentity: true } : {}),
|
||||
}), expect.any(Number));
|
||||
expect(acquired.lease.metadata?.snapshot).toBe(expectedSnapshot);
|
||||
});
|
||||
|
||||
function insertReusableExecutionWorkspace(executionWorkspaceId: string, companyId: string, projectId: string) {
|
||||
return db.insert(executionWorkspaces).values({
|
||||
id: executionWorkspaceId,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Environment, SandboxEnvironmentConfig } from "@paperclipai/shared";
|
||||
import { fingerprintEnvironmentSandboxProviderConfig as fingerprint, SANDBOX_PLATFORM_PROBE_METADATA_KEY } from "../services/environment-custom-image-runtime.js";
|
||||
import { qualifiedSandboxSnapshotId, type SandboxTemplateIdentity } from "../services/sandbox-snapshot-qualification.js";
|
||||
|
||||
function fixture() {
|
||||
const config = { provider: "daytona", snapshot: "managed-snapshot", apiKey: "fixture-account" } as SandboxEnvironmentConfig;
|
||||
const template: SandboxTemplateIdentity = { id: "template", capturedAt: "2026-10-15T00:00:00Z", templateKind: "snapshot", bindingField: "snapshot" };
|
||||
const identity = { provider: "daytona", requestedRef: config.snapshot, id: "a6b9e7d0-f886-471a-b461-640dd03e8d0f", name: config.snapshot, organizationId: "provider-account", createdAt: "2026-10-07T23:19:57.133Z" };
|
||||
const proof = { v: 2, companyId: "company", snapshot: config.snapshot, template,
|
||||
baseConfigFingerprint: fingerprint(config), runtimeConfigFingerprint: fingerprint(config), snapshotIdentity: identity };
|
||||
const environment = { config, metadata: { [SANDBOX_PLATFORM_PROBE_METADATA_KEY]: { company: proof } } } as unknown as Environment;
|
||||
return { config, template, identity, proof, environment };
|
||||
}
|
||||
|
||||
describe("qualified snapshot execution binding", () => {
|
||||
it("pins the measured provider ID without a network lookup", () => {
|
||||
const f = fixture();
|
||||
expect(qualifiedSandboxSnapshotId(f.environment, "company", f.config, f.template)).toBe(f.identity.id);
|
||||
});
|
||||
it.each(["company", "base-config", "account", "template", "snapshot", "image", "provider", "identity"])("rejects changed %s", change => {
|
||||
const f = fixture();
|
||||
let company = "company";
|
||||
if (change === "company") company = "foreign-company";
|
||||
if (change === "base-config") f.environment.config = { ...f.config, target: "different" };
|
||||
if (change === "account") f.config = { ...f.config, apiKey: "different-account" };
|
||||
if (change === "template") f.template = { ...f.template!, capturedAt: "2026-10-16T00:00:00Z" };
|
||||
if (change === "snapshot") f.config = { ...f.config, snapshot: "new-snapshot" };
|
||||
if (change === "image") f.config = { ...f.config, image: "other-image" };
|
||||
if (change === "provider") f.config = { ...f.config, provider: "other-provider" };
|
||||
if (change === "identity") f.identity.id = "mutable-name";
|
||||
expect(qualifiedSandboxSnapshotId(f.environment, company, f.config, f.template)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import { environmentRuntimeService } from "./environment-runtime.js";
|
||||
import { environmentService } from "./environments.js";
|
||||
import { fingerprintEnvironmentSandboxProviderConfig, SANDBOX_PLATFORM_PROBE_METADATA_KEY } from "./environment-custom-image-runtime.js";
|
||||
import type { SandboxEnvironmentConfig } from "@paperclipai/shared";
|
||||
import { isQualifiableDaytonaSnapshot, qualifiedSandboxSnapshotId, readSnapshotIdentity, type SandboxTemplateIdentity } from "./sandbox-snapshot-qualification.js";
|
||||
|
||||
export const SANDBOX_PLATFORM_PROBE_COMMAND = "uname -s; uname -m; if version=$(getconf GNU_LIBC_VERSION 2>/dev/null) && [ -n \"$version\" ]; then printf 'glibc\\n%s\\n' \"$version\"; else case \"$(ldd --version 2>&1)\" in *musl*) printf 'musl\\n' ;; *) printf 'unknown\\n' ;; esac; fi";
|
||||
type PlatformFacts = { platform: string; architecture: string; libc: "glibc" | "musl" | "unknown"; glibcVersion?: string };
|
||||
@@ -34,7 +35,7 @@ export function parseSandboxPlatformFacts(output: string): PlatformFacts {
|
||||
...(libc === "glibc" ? { glibcVersion: version?.trim() ?? "unknown" } : {}) };
|
||||
}
|
||||
async function sandboxBinding(db: Db, environment: Environment, companyId: string) {
|
||||
const selectedTemplate: { value: { id: string; capturedAt: string | null; templateKind: string; bindingField: string } | null } = { value: null };
|
||||
const selectedTemplate: { value: SandboxTemplateIdentity } = { value: null };
|
||||
const parsed = await resolveEnvironmentDriverConfigForRuntime(db, companyId, environment, {
|
||||
applyCustomImageTemplate: true, readOnlyCustomImageTemplate: true,
|
||||
onResolvedCustomImageTemplate: identity => { selectedTemplate.value = identity; },
|
||||
@@ -42,6 +43,10 @@ async function sandboxBinding(db: Db, environment: Environment, companyId: strin
|
||||
if (parsed.driver !== "sandbox") return null;
|
||||
const image = parsed.config.image;
|
||||
const template = selectedTemplate.value;
|
||||
if (isQualifiableDaytonaSnapshot(parsed.config, template)) {
|
||||
return { baseConfigFingerprint: fingerprint(environment.config), runtimeConfigFingerprint: fingerprint(parsed.config),
|
||||
snapshot: parsed.config.snapshot, template, qualifiedSnapshotId: qualifiedSandboxSnapshotId(environment, companyId, parsed.config, template) };
|
||||
}
|
||||
if (!isImmutableImage(image) || ["snapshot", "template", "templateRef"].some(key => record(parsed.config)[key])
|
||||
|| (template && (template.templateKind !== "image" || template.bindingField !== "image"))) return null;
|
||||
return { baseConfigFingerprint: fingerprint(environment.config), runtimeConfigFingerprint: fingerprint(parsed.config as unknown as Record<string, unknown>), image, template };
|
||||
@@ -49,9 +54,11 @@ async function sandboxBinding(db: Db, environment: Environment, companyId: strin
|
||||
|
||||
export async function readSandboxPlatformQualification(db: Db, environment: Environment, companyId: string): Promise<PlatformFacts | null> {
|
||||
const proof = record(record(environment.metadata?.[SANDBOX_PLATFORM_PROBE_METADATA_KEY])[companyId]);
|
||||
if (proof.v !== 1 || proof.companyId !== companyId || proof.baseConfigFingerprint !== fingerprint(environment.config)) return null;
|
||||
if (![1, 2].includes(Number(proof.v)) || proof.companyId !== companyId || proof.baseConfigFingerprint !== fingerprint(environment.config)) return null;
|
||||
const binding = await sandboxBinding(db, environment, companyId);
|
||||
if (!binding || proof.runtimeConfigFingerprint !== binding.runtimeConfigFingerprint || proof.image !== binding.image
|
||||
if (!binding || (proof.v === 2 ? !("qualifiedSnapshotId" in binding && binding.qualifiedSnapshotId)
|
||||
: !("image" in binding) || proof.image !== binding.image)
|
||||
|| proof.runtimeConfigFingerprint !== binding.runtimeConfigFingerprint
|
||||
|| JSON.stringify(proof.template) !== JSON.stringify(binding.template)) return null;
|
||||
const facts = record(proof.facts);
|
||||
if (typeof facts.platform !== "string" || typeof facts.architecture !== "string"
|
||||
@@ -140,6 +147,7 @@ export async function probeEnvironment(
|
||||
heartbeatRunId: null,
|
||||
persistedExecutionWorkspace: null,
|
||||
adapterType: null,
|
||||
resolveSnapshotIdentity: true,
|
||||
applyCustomImageTemplate: options.applyCustomImageTemplate === true,
|
||||
});
|
||||
const metadata = leaseRecord.lease.metadata ?? {};
|
||||
@@ -149,11 +157,13 @@ export async function probeEnvironment(
|
||||
: null;
|
||||
details = { provider, providerLeaseId: leaseRecord.lease.providerLeaseId, leaseId: leaseRecord.lease.id,
|
||||
leasePolicy: leaseRecord.lease.leasePolicy, metadata, platformQualificationRecorded: false,
|
||||
platformQualificationDiagnostic: "An immutable OCI image digest is required; this connection probe does not qualify a mutable image or snapshot name." };
|
||||
if (isImmutableImage(metadata.image) && !metadata.snapshot) {
|
||||
platformQualificationDiagnostic: "Platform qualification requires an immutable OCI image or a Daytona snapshot with a verified provider ID. Update the provider plugin and probe again, or select Legacy runner." };
|
||||
if ((isImmutableImage(metadata.image) && !metadata.snapshot) || metadata.snapshotIdentity) {
|
||||
try {
|
||||
const binding = await sandboxBinding(db, environment, resolvedCompanyId);
|
||||
if (binding && metadata.image === binding.image) {
|
||||
const snapshotIdentity = binding && "snapshot" in binding ? readSnapshotIdentity(metadata.snapshotIdentity, binding.snapshot) : null;
|
||||
if (binding && (("image" in binding && metadata.image === binding.image && !metadata.snapshot)
|
||||
|| (snapshotIdentity && metadata.snapshot === binding.snapshot && provider === "daytona"))) {
|
||||
const platform = await runtime.execute({ environment: probeEnvironmentConfig, lease: leaseRecord.lease,
|
||||
command: "sh", args: ["-c", SANDBOX_PLATFORM_PROBE_COMMAND],
|
||||
bypassSession: true, timeoutMs: 10_000, commandTimeoutMs: 10_000 });
|
||||
@@ -163,10 +173,12 @@ export async function probeEnvironment(
|
||||
if (facts.platform !== "unknown" && facts.architecture !== "unknown"
|
||||
&& (facts.platform !== "linux" || facts.libc !== "unknown")
|
||||
&& (facts.libc !== "glibc" || /^(?:glibc\s+)?\d+\.\d+(?:\.\d+)?$/.test(facts.glibcVersion ?? ""))) {
|
||||
qualification = { v: 1, companyId: resolvedCompanyId, ...binding, facts, probedAt: new Date().toISOString() };
|
||||
const { qualifiedSnapshotId: _qualifiedSnapshotId, ...recordedBinding } = { qualifiedSnapshotId: null, ...binding };
|
||||
qualification = { v: snapshotIdentity ? 2 : 1, companyId: resolvedCompanyId, ...recordedBinding,
|
||||
...(snapshotIdentity ? { snapshotIdentity } : {}), facts, probedAt: new Date().toISOString() };
|
||||
delete details.platformQualificationDiagnostic;
|
||||
} else details.platformQualificationDiagnostic = "The image's OS, architecture or libc could not be identified. Legacy remains the creation default.";
|
||||
} else details.platformQualificationDiagnostic = "The live lease does not match the selected immutable image configuration. Probe again.";
|
||||
} else details.platformQualificationDiagnostic = "The sandbox OS, architecture or libc could not be identified. Legacy remains the creation default.";
|
||||
} else details.platformQualificationDiagnostic = "The live lease does not match the selected image or snapshot identity. Probe again.";
|
||||
} catch {
|
||||
details.platformQualificationDiagnostic = "Platform qualification could not complete; connection readiness does not establish native compatibility. Check the image and probe again.";
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
} from "@paperclipai/adapter-utils/acpx-engine/startup-timing";
|
||||
import { environmentService } from "./environments.js";
|
||||
import { instanceSettingsService } from "./instance-settings.js";
|
||||
import { qualifiedSandboxSnapshotId, type SandboxTemplateIdentity } from "./sandbox-snapshot-qualification.js";
|
||||
import { verifyNativeHarnessBackupStamp } from "./native-runtime/native-harness-backup-stamp.js";
|
||||
import {
|
||||
collectEnvironmentSecretRefs,
|
||||
@@ -454,6 +455,8 @@ export interface EnvironmentDriverAcquireInput {
|
||||
* configured default adapter.
|
||||
*/
|
||||
adapterType: string | null;
|
||||
/** Resolve an immutable provider snapshot identity for an explicit platform probe. */
|
||||
resolveSnapshotIdentity?: boolean;
|
||||
/**
|
||||
* Force applying the active custom-image template even when issueId and
|
||||
* heartbeatRunId are null. Operator-initiated `Test` probes set this so the
|
||||
@@ -1844,14 +1847,19 @@ function createSandboxEnvironmentDriver(
|
||||
|
||||
async acquireRunLease(input) {
|
||||
const storedParsed = parseEnvironmentDriverConfig(input.environment);
|
||||
const selectedTemplate: { value: SandboxTemplateIdentity } = { value: null };
|
||||
const parsed = await resolveEnvironmentDriverConfigForRuntime(db, input.companyId, input.environment, {
|
||||
issueId: input.issueId,
|
||||
heartbeatRunId: input.heartbeatRunId,
|
||||
applyCustomImageTemplate: input.applyCustomImageTemplate ?? false,
|
||||
onResolvedCustomImageTemplate: identity => { selectedTemplate.value = identity; },
|
||||
});
|
||||
if (parsed.driver !== "sandbox" || storedParsed.driver !== "sandbox") {
|
||||
throw new Error(`Expected sandbox environment config for driver "${input.environment.driver}".`);
|
||||
}
|
||||
const pinnedSnapshotId = input.adapterType === "paperclip_runner"
|
||||
? qualifiedSandboxSnapshotId(input.environment, input.companyId, parsed.config, selectedTemplate.value) : null;
|
||||
if (pinnedSnapshotId) parsed.config = { ...parsed.config, snapshot: pinnedSnapshotId };
|
||||
|
||||
// Check if this provider should be handled by a plugin.
|
||||
if (!isBuiltinSandboxProvider(parsed.config.provider)) {
|
||||
@@ -1882,7 +1890,8 @@ function createSandboxEnvironmentDriver(
|
||||
}
|
||||
|
||||
const workerConfig = stripSandboxProviderEnvelope(parsed.config);
|
||||
const storedConfig = storedParsed.config;
|
||||
const storedConfig = pinnedSnapshotId ? { ...storedParsed.config, snapshot: pinnedSnapshotId } : storedParsed.config;
|
||||
if (pinnedSnapshotId) delete storedConfig.image;
|
||||
const providerConfigForLease = sandboxConfigForLeaseMetadata(storedConfig);
|
||||
// Require the reusable-lease capability AND a worker that verifies the
|
||||
// reuse methods. The provider must first opt in through the declaration:
|
||||
@@ -2122,6 +2131,7 @@ function createSandboxEnvironmentDriver(
|
||||
// environment's default adapter image (a pi agent then runs in the
|
||||
// opencode image and the harness binary is missing at exec time).
|
||||
adapterType: input.adapterType ?? undefined,
|
||||
...(input.resolveSnapshotIdentity ? { resolveSnapshotIdentity: true } : {}),
|
||||
// Forward a caller deadline so the provider configures a provider-side
|
||||
// expiry at or before it and returns the real provider expiry.
|
||||
...(requestedExpiresAtParam(input.requestedExpiresAt) !== undefined
|
||||
@@ -4000,6 +4010,7 @@ export function environmentRuntimeService(
|
||||
executionWorkspaceSettings?: IssueExecutionWorkspaceSettings | null;
|
||||
/** The agent's adapter type for this run (mixed-harness environments). */
|
||||
adapterType?: string | null;
|
||||
resolveSnapshotIdentity?: boolean;
|
||||
/**
|
||||
* Force applying the active custom-image template even for ad-hoc (no
|
||||
* issue/run) invocations. Operator `Test` probes set this so the runtime
|
||||
@@ -4037,6 +4048,7 @@ export function environmentRuntimeService(
|
||||
executionWorkspaceMode: leaseContext.executionWorkspaceMode,
|
||||
executionWorkspaceSettings: input.executionWorkspaceSettings ?? null,
|
||||
adapterType: input.adapterType ?? null,
|
||||
resolveSnapshotIdentity: input.resolveSnapshotIdentity,
|
||||
applyCustomImageTemplate: input.applyCustomImageTemplate ?? false,
|
||||
requestedExpiresAt: input.requestedExpiresAt ?? null,
|
||||
assertCompanyBinding: input.assertCompanyBinding,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { Environment, SandboxEnvironmentConfig } from "@paperclipai/shared";
|
||||
import { fingerprintEnvironmentSandboxProviderConfig, SANDBOX_PLATFORM_PROBE_METADATA_KEY } from "./environment-custom-image-runtime.js";
|
||||
|
||||
export type SandboxTemplateIdentity = { id: string; capturedAt: string | null; templateKind: string; bindingField: string } | null;
|
||||
export type SandboxSnapshotIdentity = {
|
||||
provider: "daytona";
|
||||
requestedRef: string;
|
||||
id: string;
|
||||
name: string;
|
||||
organizationId: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export function readSnapshotIdentity(value: unknown, reference: unknown): SandboxSnapshotIdentity | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const identity = value as Record<string, unknown>;
|
||||
if (identity.provider !== "daytona" || identity.requestedRef !== reference
|
||||
|| typeof identity.id !== "string" || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(identity.id)
|
||||
|| typeof identity.name !== "string" || !identity.name.trim()
|
||||
|| (reference !== identity.id && reference !== identity.name)
|
||||
|| typeof identity.organizationId !== "string" || !identity.organizationId.trim()
|
||||
|| typeof identity.createdAt !== "string" || !Number.isFinite(Date.parse(identity.createdAt))) return null;
|
||||
return identity as SandboxSnapshotIdentity;
|
||||
}
|
||||
|
||||
export function isQualifiableDaytonaSnapshot(
|
||||
input: SandboxEnvironmentConfig, template: SandboxTemplateIdentity,
|
||||
): input is SandboxEnvironmentConfig & Record<string, unknown> & { snapshot: string } {
|
||||
const config = input as unknown as Record<string, unknown>;
|
||||
return config.provider === "daytona" && typeof config.snapshot === "string" && !!config.snapshot.trim()
|
||||
&& !config.image && !config.template && !config.templateRef
|
||||
&& (!template || (template.templateKind === "snapshot" && template.bindingField === "snapshot"));
|
||||
}
|
||||
|
||||
/** Discovery reads only saved, company-scoped proof. Execution pins the same
|
||||
* provider ID rather than resolving the mutable name again. A new config or
|
||||
* captured template needs a new probe; deleting the pinned snapshot fails at
|
||||
* the provider instead of silently selecting its replacement. */
|
||||
export function qualifiedSandboxSnapshotId(
|
||||
environment: Environment, companyId: string, config: SandboxEnvironmentConfig, template: SandboxTemplateIdentity,
|
||||
): string | null {
|
||||
if (!isQualifiableDaytonaSnapshot(config, template)) return null;
|
||||
const proofs = environment.metadata?.[SANDBOX_PLATFORM_PROBE_METADATA_KEY];
|
||||
if (!proofs || typeof proofs !== "object" || Array.isArray(proofs)) return null;
|
||||
const proof = (proofs as Record<string, unknown>)[companyId];
|
||||
if (!proof || typeof proof !== "object" || Array.isArray(proof)) return null;
|
||||
const value = proof as Record<string, unknown>;
|
||||
if (value.v !== 2 || value.companyId !== companyId
|
||||
|| value.baseConfigFingerprint !== fingerprintEnvironmentSandboxProviderConfig(environment.config as SandboxEnvironmentConfig)
|
||||
|| value.runtimeConfigFingerprint !== fingerprintEnvironmentSandboxProviderConfig(config)
|
||||
|| value.snapshot !== config.snapshot || JSON.stringify(value.template) !== JSON.stringify(template)) return null;
|
||||
return readSnapshotIdentity(value.snapshotIdentity, config.snapshot)?.id ?? null;
|
||||
}
|
||||
Reference in new issue
Block a user