Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/web/e2e/fixture-console.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ const LOCKOUT_SECONDS = 30;

let state;
const hex = (c) => c.repeat(64);
/** The Runtime release this fixture's distribution manifest describes. */
const release = { source_commit: "c0ffee".padEnd(40, "0"), image_id: `sha256:${hex("1")}`, image_manifest_digest: `sha256:${hex("2")}`, microsandbox_ref: `oac-runtime@sha256:${hex("3")}`, runtime_sha256: hex("4"), firmware_sha256: hex("5") };
const manifest = { platform: "linux/amd64", source_commit: release.source_commit, images: { runtime: release.image_id }, image_manifest_digests: { runtime: release.image_manifest_digest }, runtime_ref: release.microsandbox_ref, microsandbox: { runtime_sha256: release.runtime_sha256, firmware_sha256: release.firmware_sha256 }, artifacts: {} };
/** The fixture distribution includes each provider's native identities. */
const manifest = { platform: "linux/amd64", source_commit: "c0ffee".padEnd(40, "0"), images: { runtime: `sha256:${hex("1")}` }, image_manifest_digests: { runtime: `sha256:${hex("2")}` }, runtime_ref: `oac-runtime@sha256:${hex("3")}`, microsandbox: { runtime_sha256: hex("4"), firmware_sha256: hex("5") }, artifacts: {} };
const release = { source_commit: manifest.source_commit, artifacts: { image_id: manifest.images.runtime, image_manifest_digest: manifest.image_manifest_digests.runtime } };

/**
* config.json's public_url. "public": an HTTPS address, so applications get an API base URL;
Expand Down
9 changes: 7 additions & 2 deletions apps/web/e2e/nodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ test("preselects microsandbox and asks once before switching to Docker", async (
await docker.click();
await expect(sizeStep).toBeVisible();
await expect(confirm).toHaveCount(0);
await page.getByRole("button", { name: /^Standard/ }).click();
await page.getByRole("button", { name: "Advanced settings", exact: true }).click();
await expect(page.getByLabel("Image ID", { exact: true })).toHaveValue(`sha256:${"1".repeat(64)}`);
await expect(page.getByLabel("Image manifest digest", { exact: true })).toHaveValue(`sha256:${"2".repeat(64)}`);
await expect(page.getByLabel("Firmware SHA-256", { exact: true })).toHaveCount(0);
await expect(page.getByLabel("microsandbox reference", { exact: true })).toHaveCount(0);
});

test("saves E2B without opening Add node, as it has no machines", async ({ page, request }) => {
Expand Down Expand Up @@ -300,8 +306,7 @@ test("shows the retained E2B build while a replacement key is checked", async ({
});

test("edits only the saved backend, preserving a custom size and Runtime", async ({ page, request }) => {
const runtime = { source_commit: "0".repeat(40), image_id: `sha256:${"a".repeat(64)}`, image_manifest_digest: `sha256:${"b".repeat(64)}`,
microsandbox_ref: `oac-runtime@sha256:${"b".repeat(64)}`, runtime_sha256: "c".repeat(64), firmware_sha256: "d".repeat(64) };
const runtime = { source_commit: "0".repeat(40), artifacts: { image_id: `sha256:${"a".repeat(64)}`, image_manifest_digest: `sha256:${"b".repeat(64)}` } };
const current = { resources: { cpus: 7, memory_mib: 8192 }, runtime };
let deployment = { configuration: {}, metadata: {}, credential_configured: false, installation_id: "94be54a1-138c-4f30-bc87-b13686272dbe", provider: "docker", core_url: "https://core.example", reset: null, rollout: { state: "settled", previous_generation_sandboxes: 0, nodes: { ready: 0, preparing: 0, failed: 0, update_required: 0, unknown: 0 } },
owner_epoch: 1, generation: 1, mode: "nodes", resources: { allocations: 0, pending: 0 }, specification: current, specification_digest: "e".repeat(64),
Expand Down
18 changes: 9 additions & 9 deletions apps/web/src/features/sandbox/SandboxSetupWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { formatBytes } from "../../lib/format";
import { installationQuery } from "../../lib/installation";
import type { ParseKeys } from "i18next";
import { sandboxConfigurationRejection, sandboxProviderLabel } from "../../lib/sandbox-labels";
import { defaultSandboxResources, distributionRuntime, isRuntimeRelease, isRuntimeReleaseField, RUNTIME_RELEASE_FIELDS, savedSpecification, validSandboxResources } from "./deployment-specification";
import { defaultSandboxResources, distributionRuntime, isRuntimeRelease, isRuntimeReleaseField, runtimeReleaseFields, savedSpecification, validSandboxResources } from "./deployment-specification";
import { e2bKeyReady, e2bUpdateSelection } from "./sandbox-update";
import { sandboxAdmin } from "./sandbox-queries";
import "./sandbox-wizard.css";
Expand Down Expand Up @@ -81,7 +81,7 @@ function presets(provider: SandboxProvider): Record<Preset, SandboxResources> |
return { small: scale(0.5), standard, large: scale(2) };
}

const releaseLabels: Record<keyof SandboxRuntimeRelease, ParseKeys<"sandbox">> = {
const releaseLabels: Record<string, ParseKeys<"sandbox">> = {
source_commit: "Source commit",
image_id: "Image ID",
image_manifest_digest: "Image manifest digest",
Expand Down Expand Up @@ -195,11 +195,11 @@ export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disab

// A release the administrator entered comes first, then the saved one of the same backend,
// then the one this console distributes (the release its node installer verifies).
const matched = useQuery({ queryKey: ["sandbox-runtime-release"], queryFn: ({ signal }) => distributionRuntime(signal).catch(() => null), staleTime: Infinity, retry: false });
const needsRuntime = policy !== null && Object.keys(policy.artifacts).length > 0;
const matched = useQuery({ queryKey: ["sandbox-runtime-release", provider], queryFn: ({ signal }) => distributionRuntime(provider!, signal).catch(() => null), enabled: needsRuntime, staleTime: Infinity, retry: false });
const release: Partial<SandboxRuntimeRelease> = Object.keys(runtime).length ? runtime : saved?.runtime ?? matched.data ?? {};

const needsRuntime = policy?.runtime ?? false;
const runtimeReady = !needsRuntime || isRuntimeRelease(release);
const runtimeReady = !needsRuntime || (provider !== null && isRuntimeRelease(provider, release));
// Initial setup requires a key; an update may retain the committed key.
const keyReady = e2bKeyReady(Boolean(editing), replacementRequested, apiKey);
const connectionChanged = Boolean(editing && (apiURL.trim() !== (current?.e2bAPIURL || E2B_PRESETS.official.apiURL) || domain.trim() !== (current?.e2bDomain || E2B_PRESETS.official.domain)));
Expand Down Expand Up @@ -437,11 +437,11 @@ export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disab
<fieldset className="wizard-group">
<legend>{t("Runtime release")}<HelpTip>{t("Filled in from this console's distribution when it serves one. Otherwise copy these from the distribution manifest that matches your nodes; image configuration IDs and manifest digests are different values.")}</HelpTip></legend>
{fieldError("runtime") ? <p id={`${id}-runtime-error`} className="field-error" role="alert">{fieldError("runtime")}</p> : null}
{RUNTIME_RELEASE_FIELDS.map((field) => {
const value = release[field] ?? "";
{runtimeReleaseFields(provider!).map((field) => {
const value = (field === "source_commit" ? release.source_commit : release.artifacts?.[field]) ?? "";
return (
<Field key={field} id={`${id}-${field}`} label={t(releaseLabels[field])} error={value && !isRuntimeReleaseField(field, value) ? t("Check this value") : null}>
<input id={`${id}-${field}`} value={value} spellCheck={false} autoComplete="off" aria-invalid={Boolean(fieldError("runtime"))} aria-describedby={fieldError("runtime") ? `${id}-runtime-error` : undefined} onChange={(event) => { setRuntime({ ...release, [field]: event.target.value.trim() }); setFieldRejection(null); }} />
<Field key={field} id={`${id}-${field}`} label={releaseLabels[field] ? t(releaseLabels[field]) : field} error={value && !isRuntimeReleaseField(provider!, field, value) ? t("Check this value") : null}>
<input id={`${id}-${field}`} value={value} spellCheck={false} autoComplete="off" aria-invalid={Boolean(fieldError("runtime"))} aria-describedby={fieldError("runtime") ? `${id}-runtime-error` : undefined} onChange={(event) => { setRuntime(field === "source_commit" ? { ...release, source_commit: event.target.value.trim() } : { ...release, artifacts: { ...release.artifacts, [field]: event.target.value.trim() } }); setFieldRejection(null); }} />
</Field>
);
})}
Expand Down
49 changes: 31 additions & 18 deletions apps/web/src/features/sandbox/deployment-specification.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { defaultSandboxResources, distributionRuntime, isRuntimeReleaseField, sandboxesThatFit, savedSpecification, validSandboxResources } from "./deployment-specification";
import { deploymentContract, type SandboxProvider, type SandboxSpecification } from "@oac/agents-client";
import { defaultSandboxResources, distributionRuntime, isRuntimeRelease, isRuntimeReleaseField, sandboxesThatFit, savedSpecification, validSandboxResources } from "./deployment-specification";
import { deploymentContract, type SandboxProvider, type SandboxRuntimeRelease, type SandboxSpecification } from "@oac/agents-client";

import deploymentContractFixture from "../../../../../services/core/internal/sandbox/testdata/deployment-contract.json";

const manifest = { platform: "linux/amd64", source_commit: "0".repeat(40), images: { runtime: `sha256:${"a".repeat(64)}` },
image_manifest_digests: { runtime: `sha256:${"b".repeat(64)}` }, runtime_ref: `oac-runtime@sha256:${"b".repeat(64)}`,
microsandbox: { runtime_sha256: "c".repeat(64), firmware_sha256: "d".repeat(64) } };
afterEach(() => vi.unstubAllGlobals());

describe("deployment resources and Runtime", () => {
it("maps the exact six identities from one matched distribution", async () => {
const fetcher = vi.fn().mockResolvedValue(new Response(JSON.stringify(manifest)));
it.each(deploymentContractFixture.filter((entry) => entry.provider !== "e2b" && (entry.valid || /release|artifact|newline|crlf/.test(entry.name))))("checks the shared release contract: $name", (entry) => {
expect(isRuntimeRelease(entry.provider as SandboxProvider, entry.specification.runtime as unknown as Partial<SandboxRuntimeRelease>)).toBe(entry.valid);
});
it("maps only each provider's declared identities from one matched distribution", async () => {
const fetcher = vi.fn().mockImplementation(() => Promise.resolve(new Response(JSON.stringify(manifest))));
vi.stubGlobal("fetch", fetcher);
expect(await distributionRuntime(new AbortController().signal)).toEqual({ source_commit: manifest.source_commit,
image_id: manifest.images.runtime, image_manifest_digest: manifest.image_manifest_digests.runtime,
microsandbox_ref: manifest.runtime_ref, runtime_sha256: manifest.microsandbox.runtime_sha256, firmware_sha256: manifest.microsandbox.firmware_sha256 });
expect(fetcher.mock.calls.map((call) => call[0])).toEqual(["/node-install/manifest.json"]);
expect(await distributionRuntime("docker", new AbortController().signal)).toEqual({ source_commit: manifest.source_commit,
artifacts: { image_id: manifest.images.runtime, image_manifest_digest: manifest.image_manifest_digests.runtime } });
expect(await distributionRuntime("microsandbox", new AbortController().signal)).toEqual({ source_commit: manifest.source_commit,
artifacts: { microsandbox_ref: manifest.runtime_ref, runtime_sha256: manifest.microsandbox.runtime_sha256, firmware_sha256: manifest.microsandbox.firmware_sha256 } });
await expect(distributionRuntime("e2b", new AbortController().signal)).rejects.toThrow();
expect(fetcher.mock.calls.every((call) => call[0] === "/node-install/manifest.json")).toBe(true);
});
it("requires only the selected provider's manifest identities", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify({ platform: manifest.platform, source_commit: manifest.source_commit, images: manifest.images, image_manifest_digests: manifest.image_manifest_digests }))));
const release = await distributionRuntime("docker", new AbortController().signal);
expect(isRuntimeRelease("docker", release)).toBe(true);
expect(isRuntimeRelease("microsandbox", release)).toBe(false);
expect(isRuntimeRelease("docker", { ...release, artifacts: { ...release.artifacts, extra: "x" } })).toBe(false);
});
it("preserves saved resources and Runtime only for the same provider", () => {
const current: SandboxSpecification = { resources: { cpus: 7, memory_mib: 8192 }, runtime: { source_commit: manifest.source_commit,
image_id: manifest.images.runtime, image_manifest_digest: manifest.image_manifest_digests.runtime,
microsandbox_ref: manifest.runtime_ref, runtime_sha256: manifest.microsandbox.runtime_sha256, firmware_sha256: manifest.microsandbox.firmware_sha256 } };
artifacts: { image_id: manifest.images.runtime, image_manifest_digest: manifest.image_manifest_digests.runtime } } };
expect(savedSpecification("docker", "docker", current)).toEqual(current);
expect(savedSpecification("docker", "docker", current)).not.toBe(current);
expect(savedSpecification("microsandbox", "docker", current)).toBeNull();
Expand Down Expand Up @@ -49,20 +62,20 @@ describe("deployment resources and Runtime", () => {
const digest = "b".repeat(64);
const runtime_ref = `oac-runtime@sha256:${digest}`;
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify({ ...manifest, runtime_ref }))));
expect((await distributionRuntime(new AbortController().signal)).microsandbox_ref).toBe(runtime_ref);
expect(isRuntimeReleaseField("microsandbox_ref", runtime_ref)).toBe(true);
for (const runtime_ref of [`custom-runtime@sha256:${digest}`, `oac-runtime:${digest}`, `oac-runtime@sha256:${"b".repeat(63)}`, `oac-runtime@sha256:${"B".repeat(64)}`, `@sha256:${digest}`]) {
expect((await distributionRuntime("microsandbox", new AbortController().signal)).artifacts.microsandbox_ref).toBe(runtime_ref);
expect(isRuntimeReleaseField("microsandbox", "microsandbox_ref", runtime_ref)).toBe(true);
for (const runtime_ref of [`custom-runtime@sha256:${digest}`, `oac-runtime:${digest}`, `oac-runtime@sha256:${"b".repeat(63)}`, `oac-runtime@sha256:${"B".repeat(64)}`, `@sha256:${digest}`, `oac-runtime@sha256:${digest}\n`]) {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify({ ...manifest, runtime_ref }))));
await expect(distributionRuntime(new AbortController().signal)).rejects.toThrow();
expect(isRuntimeReleaseField("microsandbox_ref", runtime_ref)).toBe(false);
await expect(distributionRuntime("microsandbox", new AbortController().signal)).rejects.toThrow();
expect(isRuntimeReleaseField("microsandbox", "microsandbox_ref", runtime_ref)).toBe(false);
}
});
it("rejects unavailable, mutable or incomplete release identities", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("{}", { status: 404 })));
await expect(distributionRuntime(new AbortController().signal)).rejects.toThrow();
for (const invalid of [{ ...manifest, platform: "linux/arm64" }, { ...manifest, source_commit: "main" }, { ...manifest, runtime_ref: "oac-runtime:latest" }, { ...manifest, microsandbox: {} }]) {
await expect(distributionRuntime("microsandbox", new AbortController().signal)).rejects.toThrow();
for (const invalid of [{ ...manifest, platform: "linux/arm64" }, { ...manifest, source_commit: "main" }, { ...manifest, source_commit: manifest.source_commit + "\n" }, { ...manifest, runtime_ref: "oac-runtime:latest" }, { ...manifest, microsandbox: {} }]) {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify(invalid))));
await expect(distributionRuntime(new AbortController().signal)).rejects.toThrow();
await expect(distributionRuntime("microsandbox", new AbortController().signal)).rejects.toThrow();
}
});
});
Expand Down
Loading
Loading