mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
feat(evals): gate remote chat on verified Spot and amd64 PostgreSQL
This commit is contained in:
1 parent
279af36535
commit
e439748839
5 files changed
+202
-15
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { MatrixExecution } from "./types.js";
|
||||
import { prepareChatRemoteHost, selectsRemoteChat } from "./chat-remote-host.js";
|
||||
import { POSTGRES_AMD64_IMAGE, POSTGRES_AMD64_IMAGE_IDS, POSTGRES_VERSION } from "./docker-postgres.js";
|
||||
import { CHAT_OPENCODE_MODEL } from "./chat-runtime-profile.js";
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
|
||||
async function fixture(lifecycle = "spot") {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "remote-host-offline-")); roots.push(root);
|
||||
const execution = { id: "chat-features.runner-opencode.local.create-backlog", suite: { id: "chat-features" }, profile: { id: "runner-opencode", provider: "opencode", model: CHAT_OPENCODE_MODEL, adapterType: "paperclip_runner", credential: "OPENROUTER_API_KEY" }, environment: { id: "local" }, task: { automaticRetryPolicy: "single_attempt" } } as unknown as MatrixExecution;
|
||||
const env = { GITHUB_ACTIONS: "true", GITHUB_REPOSITORY: "paperclipai/paperclip", GITHUB_REF: "refs/heads/master", GITHUB_WORKFLOW_REF: "paperclipai/paperclip/.github/workflows/runner-full-stack-e2e.yml@refs/heads/master", GITHUB_RUN_ID: "123", GITHUB_RUN_ATTEMPT: "1", PAPERCLIP_RUNNER_E2E_SOURCE_SHA: "a".repeat(40), OPENROUTER_API_KEY: "synthetic-private-canary" };
|
||||
const fetch = vi.fn(async (url: string | URL | Request) => {
|
||||
const u = String(url);
|
||||
if (u.endsWith("/api/token")) return new Response("synthetic-imds-token");
|
||||
if (u.endsWith("/instance-life-cycle")) return new Response(lifecycle);
|
||||
return Response.json({ instanceId: "i-1234567890abcdef0", imageId: "ami-1234567890abcdef0", region: "us-east-1", architecture: "x86_64", privateIp: "not-retained" });
|
||||
});
|
||||
const docker = vi.fn(async (args: string[]) => {
|
||||
if (args[0] === "context" && args[1] === "show") return "default";
|
||||
if (args[0] === "context") return JSON.stringify([{ Endpoints: { docker: { Host: "unix:///var/run/docker.sock" } } }]);
|
||||
if (args.includes("info")) return JSON.stringify({ id: "engine", os: "linux", operatingSystem: "Linux" });
|
||||
if (args.includes("pull")) return "downloaded";
|
||||
return JSON.stringify([{ Id: POSTGRES_AMD64_IMAGE_IDS[0], RepoDigests: [POSTGRES_AMD64_IMAGE], Os: "linux", Architecture: "amd64", Config: { Env: [`PG_VERSION=${POSTGRES_VERSION}`] } }]);
|
||||
});
|
||||
return { executions: [execution], env, repositoryRoot: root, outputDirectory: root, dependencies: { fetch: fetch as typeof globalThis.fetch, docker, platform: "linux", arch: "x64", sourceRevision: async () => "a".repeat(40) }, fetch, docker,
|
||||
receipt: async () => JSON.parse(await readFile(path.join(root, "chat-remote-host.json"), "utf8")) };
|
||||
}
|
||||
describe("remote-only chat host qualification without native/provider calls", () => {
|
||||
it("requires real Spot metadata before Docker and retains only closed source-bound host proof", async () => {
|
||||
const f = await fixture(); const policy = await prepareChatRemoteHost(f); const receipt = await f.receipt();
|
||||
expect(policy).toMatchObject({ databaseLane: "owned-docker-postgres", maxAutomaticRetries: 0, maxParallel: 2 });
|
||||
expect(receipt).toMatchObject({ status: "qualified-host-only", spotVerified: true, evaluationAdmitted: false, allocationSpotOnlyClaimed: false, sourceRevision: "a".repeat(40) });
|
||||
expect(JSON.stringify(receipt)).not.toMatch(/synthetic-private-canary|synthetic-imds-token|privateIp|not-retained/);
|
||||
expect(f.fetch.mock.calls.every(([url]) => String(url).startsWith("http://169.254.169.254/latest/"))).toBe(true);
|
||||
expect(f.fetch.mock.invocationCallOrder.at(-1)!).toBeLessThan(f.docker.mock.invocationCallOrder[0]!);
|
||||
});
|
||||
it("refuses On-Demand fallback before any Docker/native/provider/DB action", async () => {
|
||||
const f = await fixture("on-demand"); await expect(prepareChatRemoteHost(f)).rejects.toThrow("on-demand-or-unknown-host-refused");
|
||||
expect(f.docker).not.toHaveBeenCalled(); expect(await f.receipt()).toMatchObject({ lifecycle: "on-demand", status: "blocked", evaluationAdmitted: false });
|
||||
});
|
||||
it.each(["local", "branch-workflow", "wrong-source", "wrong-model", "automatic-retry", "foreign-repository"])("fails closed on %s before metadata or Docker", async kind => {
|
||||
const f = await fixture();
|
||||
if (kind === "local") f.dependencies.platform = "darwin";
|
||||
if (kind === "branch-workflow") f.env.GITHUB_WORKFLOW_REF = "untrusted@refs/heads/branch";
|
||||
if (kind === "wrong-source") f.env.PAPERCLIP_RUNNER_E2E_SOURCE_SHA = "b".repeat(40);
|
||||
if (kind === "wrong-model") f.executions[0]!.profile.model = "other";
|
||||
if (kind === "automatic-retry") f.executions[0]!.task.automaticRetryPolicy = undefined;
|
||||
if (kind === "foreign-repository") f.env.GITHUB_REPOSITORY = "foreign/repository";
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("trusted-github-linux-source-required"); expect(f.fetch).not.toHaveBeenCalled(); expect(f.docker).not.toHaveBeenCalled();
|
||||
});
|
||||
it("does not silently accept an incompatible PostgreSQL image/platform", async () => {
|
||||
const f = await fixture(); const original = f.dependencies.docker;
|
||||
f.dependencies.docker = vi.fn(async args => args.includes("image") ? JSON.stringify([{ Id: "other", Os: "linux", Architecture: "arm64" }]) : original(args));
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("owned-linux-amd64-postgres-image-unqualified"); expect((await f.receipt()).evaluationAdmitted).toBe(false);
|
||||
});
|
||||
it("does not change historical/other profiles or require IMDS for their discovery", async () => {
|
||||
const f = await fixture(); f.executions[0]!.profile.id = "runner-codex-subscription";
|
||||
expect(selectsRemoteChat(f.executions)).toBe(false); expect(await prepareChatRemoteHost(f)).toBeUndefined(); expect(f.fetch).not.toHaveBeenCalled(); expect(f.docker).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { reviewedChatProfile } from "./chat-runtime-profile.js";
|
||||
import type { MatrixExecution } from "./types.js";
|
||||
import { currentDockerEngine, runDocker, POSTGRES_AMD64_IMAGE, POSTGRES_AMD64_IMAGE_IDS, POSTGRES_VERSION, type DockerRun } from "./docker-postgres.js";
|
||||
|
||||
const workflow = "paperclipai/paperclip/.github/workflows/runner-full-stack-e2e.yml@refs/heads/master";
|
||||
const hash = (text: string) => createHash("sha256").update(text).digest("hex");
|
||||
type Selection = Pick<MatrixExecution, "id" | "suite" | "profile" | "environment" | "task">;
|
||||
type Environment = Record<string, string | undefined>;
|
||||
export interface RemoteHostDependencies {
|
||||
fetch: typeof fetch; docker: DockerRun; platform: string; arch: string;
|
||||
sourceRevision: (root: string) => Promise<string>;
|
||||
}
|
||||
const defaults: RemoteHostDependencies = { fetch: globalThis.fetch, docker: runDocker, platform: process.platform, arch: process.arch,
|
||||
sourceRevision: async root => (await promisify(execFile)("git", ["rev-parse", "HEAD"], { cwd: root, timeout: 5000 })).stdout.trim() };
|
||||
/** This new campaign lane is remote-only. Existing profiles/local defaults remain unchanged. */
|
||||
export function selectsRemoteChat(executions: readonly Selection[]) {
|
||||
return executions.some(e => e.suite.id === "chat-features" && e.profile.id === "runner-opencode");
|
||||
}
|
||||
/** No native runner, model, or database workload is started here. IMDS and image acquisition
|
||||
* run only after the actual trusted GitHub AWS host identity has been checked. */
|
||||
export async function prepareChatRemoteHost(input: { executions: readonly Selection[]; env: Environment; repositoryRoot: string; outputDirectory: string; dependencies?: Partial<RemoteHostDependencies> }) {
|
||||
if (!selectsRemoteChat(input.executions)) return undefined;
|
||||
const deps = { ...defaults, ...input.dependencies }, env = input.env;
|
||||
const receipt: Record<string, unknown> = { schema: "paperclip.chat-remote-host/v1", status: "blocked", scope: "before-evaluation", evaluationAdmitted: false,
|
||||
allocationSpotOnlyClaimed: false, databaseLane: "owned-docker-postgres", automaticRetries: 0, maximumParallel: 2,
|
||||
executionIds: input.executions.map(e => e.id) };
|
||||
const filename = path.join(input.outputDirectory, "chat-remote-host.json");
|
||||
await mkdir(input.outputDirectory, { recursive: true });
|
||||
const persist = () => writeFile(filename, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 });
|
||||
let reason = "trusted-github-linux-source-required";
|
||||
try {
|
||||
if (env.GITHUB_ACTIONS !== "true" || env.GITHUB_REPOSITORY !== "paperclipai/paperclip" || env.GITHUB_REF !== "refs/heads/master" || env.GITHUB_WORKFLOW_REF !== workflow
|
||||
|| !/^\d+$/.test(env.GITHUB_RUN_ID ?? "") || !/^\d+$/.test(env.GITHUB_RUN_ATTEMPT ?? "") || deps.platform !== "linux" || deps.arch !== "x64") throw new Error(reason);
|
||||
if (input.executions.some(e => e.suite.id !== "chat-features" || e.profile.id !== "runner-opencode" || !reviewedChatProfile(e.profile) || e.environment.id !== "local" || e.task.automaticRetryPolicy !== "single_attempt")) throw new Error(reason);
|
||||
const source = await deps.sourceRevision(input.repositoryRoot);
|
||||
if (!/^[a-f0-9]{40}$/.test(source) || env.PAPERCLIP_RUNNER_E2E_SOURCE_SHA !== source) throw new Error(reason);
|
||||
Object.assign(receipt, { workflowRef: workflow, workflowRunId: env.GITHUB_RUN_ID, workflowAttempt: Number(env.GITHUB_RUN_ATTEMPT), sourceRevision: source });
|
||||
reason = "aws-instance-identity-unavailable";
|
||||
const tokenResponse = await deps.fetch("http://169.254.169.254/latest/api/token", { method: "PUT", redirect: "error", headers: { "X-aws-ec2-metadata-token-ttl-seconds": "60" }, signal: AbortSignal.timeout(5000) });
|
||||
if (!tokenResponse.ok) throw new Error(reason);
|
||||
const token = await tokenResponse.text(); if (!token || token.length > 4096) throw new Error(reason);
|
||||
const metadata = async (url: string) => {
|
||||
const response = await deps.fetch(url, { redirect: "error", headers: { "X-aws-ec2-metadata-token": token }, signal: AbortSignal.timeout(5000) });
|
||||
if (!response.ok) throw new Error(reason); const text = await response.text(); if (text.length > 8192) throw new Error(reason); return text;
|
||||
};
|
||||
const lifecycle = (await metadata("http://169.254.169.254/latest/meta-data/instance-life-cycle")).trim();
|
||||
const identity = JSON.parse(await metadata("http://169.254.169.254/latest/dynamic/instance-identity/document"));
|
||||
if (!/^i-[a-f0-9]{8,17}$/.test(identity.instanceId ?? "") || !/^ami-[a-f0-9]{8,17}$/.test(identity.imageId ?? "") || identity.region !== "us-east-1" || identity.architecture !== "x86_64") throw new Error(reason);
|
||||
Object.assign(receipt, { instanceId: identity.instanceId, imageId: identity.imageId, region: identity.region, architecture: identity.architecture, lifecycle });
|
||||
reason = "on-demand-or-unknown-host-refused-before-evaluation";
|
||||
if (lifecycle !== "spot") throw new Error(reason);
|
||||
reason = "owned-linux-amd64-postgres-image-unqualified";
|
||||
const engine = await currentDockerEngine(deps.docker, env);
|
||||
await deps.docker(["--context", engine.context, "pull", "--platform", "linux/amd64", POSTGRES_AMD64_IMAGE], 120000);
|
||||
const images = JSON.parse(await deps.docker(["--context", engine.context, "image", "inspect", POSTGRES_AMD64_IMAGE]));
|
||||
const image = images[0], pgVersion = (image?.Config?.Env ?? []).find((entry: string) => entry.startsWith("PG_VERSION="))?.slice(11);
|
||||
if (images.length !== 1 || !POSTGRES_AMD64_IMAGE_IDS.some(id => id === image?.Id) || image.Os !== "linux" || image.Architecture !== "amd64" || !image.RepoDigests?.includes(POSTGRES_AMD64_IMAGE) || pgVersion !== POSTGRES_VERSION) throw new Error(reason);
|
||||
Object.assign(receipt, { status: "qualified-host-only", spotVerified: true, postgres: { image: POSTGRES_AMD64_IMAGE, imageId: image.Id, architecture: image.Architecture, version: pgVersion }, dockerEngineSha256: hash(engine.engineId) });
|
||||
await persist();
|
||||
return { receiptPath: filename, databaseLane: "owned-docker-postgres" as const, maxAutomaticRetries: 0, maxParallel: 2, postgresArtifact: "linux-amd64" as const, postgresImageId: image.Id as typeof POSTGRES_AMD64_IMAGE_IDS[number] };
|
||||
} catch {
|
||||
receipt.reason = reason; await persist();
|
||||
throw new Error(`Remote chat preflight blocked: ${reason}; inspect chat-remote-host.json. No evaluation admitted.`);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { publicDockerDatabaseIdentity, selectedPostgresArtifact, POSTGRES_ARTIFACT_ENV, POSTGRES_IMAGE_ID_ENV, POSTGRES_AMD64_IMAGE, POSTGRES_AMD64_IMAGE_IDS } from "./docker-postgres.js";
|
||||
import { mkdtemp, mkdir, readFile, realpath, rm, writeFile, symlink, chmod } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
@@ -18,6 +19,7 @@ beforeEach(() => { vi.stubEnv("DOCKER_HOST", ""); vi.stubEnv("DOCKER_CONTEXT", "
|
||||
afterEach(async () => { vi.unstubAllEnvs(); for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true }); });
|
||||
async function root() { const p = await realpath(await mkdtemp(path.join(os.tmpdir(), "paperclip-runner-e2e-"))); roots.push(p); return p; }
|
||||
function daemon() {
|
||||
const artifact = selectedPostgresArtifact();
|
||||
const state = { calls: [] as string[][], engine: "engine-one", operatingSystem: "Docker Desktop", endpoint: "unix:///private/offline-docker.sock", container: null as any, volume: null as any, loseVolumeAck: false, loseContainerAck: false, inspectionError: false, volumeInspectionError: false, badImage: false, failStart: false };
|
||||
const missing = (args: string[], name: string) => new DockerCommandError({ args, stdout: "[]", stderr: `Error: No such object: ${name}`, exitCode: 1, signal: null });
|
||||
const run: DockerRun = async args => {
|
||||
@@ -25,7 +27,7 @@ function daemon() {
|
||||
if (cmd.join() === "context,show") return "offline-context";
|
||||
if (cmd[0] === "context") return JSON.stringify([{ Endpoints: { docker: { Host: state.endpoint } } }]);
|
||||
if (cmd[0] === "info") return JSON.stringify({ id: state.engine, os: "linux", operatingSystem: state.operatingSystem });
|
||||
if (cmd[0] === "image") return JSON.stringify([{ Id: state.badImage ? "wrong-image" : POSTGRES_IMAGE_ID, Os: "linux", Architecture: "arm64", RepoDigests: [POSTGRES_IMAGE], Config: { Env: [`PG_VERSION=${POSTGRES_VERSION}`] } }]);
|
||||
if (cmd[0] === "image") return JSON.stringify([{ Id: state.badImage ? "wrong-image" : artifact.imageId, Os: "linux", Architecture: artifact.architecture, RepoDigests: [artifact.image], Config: { Env: [`PG_VERSION=${POSTGRES_VERSION}`] } }]);
|
||||
if (cmd[0] === "volume") {
|
||||
if (cmd[1] === "create") { const labels: Record<string, string> = {}; for (let i = 0; i < cmd.length; i++) if (cmd[i] === "--label") { const [key, value] = cmd[i + 1]!.split("="); labels[key!] = value!; }
|
||||
state.volume = { Name: cmd.at(-1), Driver: "local", Scope: "local", Options: null, Labels: labels };
|
||||
@@ -41,7 +43,7 @@ function daemon() {
|
||||
if (cmd[i] === "--env") env.push(cmd[i + 1]!);
|
||||
if (cmd[i] === "--mount") { const fields = Object.fromEntries(cmd[i + 1]!.split(",").map(p => p.split("="))); mounts.push({ Type: fields.type, ...(fields.type === "volume" ? { Name: fields.src } : { Source: fields.src }), Destination: fields.dst, RW: !cmd[i + 1]!.includes("readonly") }); }
|
||||
}
|
||||
state.container = { Id: "a".repeat(64), Name: `/${cmd[cmd.indexOf("--name") + 1]}`, Image: POSTGRES_IMAGE_ID, Config: { Image: POSTGRES_IMAGE, Env: env, Labels: labels }, HostConfig: { Privileged: false, NetworkMode: "default", PortBindings: { "5432/tcp": [{ HostIp: "127.0.0.1", HostPort: "" }] } }, Mounts: mounts, State: { Running: false }, NetworkSettings: { Ports: {} } };
|
||||
state.container = { Id: "a".repeat(64), Name: `/${cmd[cmd.indexOf("--name") + 1]}`, Image: artifact.imageId, Config: { Image: artifact.image, Env: env, Labels: labels }, HostConfig: { Privileged: false, NetworkMode: "default", PortBindings: { "5432/tcp": [{ HostIp: "127.0.0.1", HostPort: "" }] } }, Mounts: mounts, State: { Running: false }, NetworkSettings: { Ports: {} } };
|
||||
if (state.loseContainerAck) throw new Error("lost container ACK"); return state.container.Id;
|
||||
}
|
||||
if (cmd[0] === "inspect") { if (state.inspectionError) throw new Error("daemon inspection unavailable"); if (!state.container) throw missing(args, cmd.at(-1)!); return JSON.stringify([state.container]); }
|
||||
@@ -233,3 +235,19 @@ it("requires exact current UID, type and private custody modes", () => {
|
||||
expect(() => assertDockerCustodyMetadata({ ...metadata, isSymbolicLink: () => true }, false)).toThrow();
|
||||
expect(() => assertDockerCustodyMetadata(metadata, true)).toThrow();
|
||||
});
|
||||
|
||||
it("keeps historical arm64 identity unchanged and admits only the explicit reviewed amd64 artifact", () => {
|
||||
expect(databaseFingerprintIdentity({ [DATABASE_LANE_ENV]: "owned-docker-postgres" })).toEqual({ lane: "owned-docker-postgres", image: POSTGRES_IMAGE, imageId: POSTGRES_IMAGE_ID, version: POSTGRES_VERSION });
|
||||
const remote = databaseFingerprintIdentity({ [DATABASE_LANE_ENV]: "owned-docker-postgres", [POSTGRES_ARTIFACT_ENV]: "linux-amd64", [POSTGRES_IMAGE_ID_ENV]: POSTGRES_AMD64_IMAGE_IDS[1] });
|
||||
expect(remote).toEqual({ lane: "owned-docker-postgres", image: POSTGRES_AMD64_IMAGE, imageId: POSTGRES_AMD64_IMAGE_IDS[1], version: POSTGRES_VERSION, architecture: "amd64" });
|
||||
expect(() => selectedPostgresArtifact({ [POSTGRES_ARTIFACT_ENV]: "linux-amd64", [POSTGRES_IMAGE_ID_ENV]: "foreign" })).toThrow("Unreviewed");
|
||||
expect(() => selectedPostgresArtifact({ [POSTGRES_IMAGE_ID_ENV]: POSTGRES_AMD64_IMAGE_IDS[0] })).toThrow("Undeclared");
|
||||
});
|
||||
it("creates, verifies and cleans the explicit amd64 receipt using only the owned fake daemon", async () => {
|
||||
vi.stubEnv(POSTGRES_ARTIFACT_ENV, "linux-amd64"); vi.stubEnv(POSTGRES_IMAGE_ID_ENV, POSTGRES_AMD64_IMAGE_IDS[0]);
|
||||
const temp = await root(), { run, state } = daemon(); const owned = await startOwnedDockerDatabase({ temporaryRoot: temp, privateSecrets: [], run });
|
||||
expect(owned.receipt).toMatchObject({ image: POSTGRES_AMD64_IMAGE, imageId: POSTGRES_AMD64_IMAGE_IDS[0], architecture: "amd64", artifactVerified: true });
|
||||
expect(databaseIdentityMatches(databaseFingerprintIdentity({ [DATABASE_LANE_ENV]: "owned-docker-postgres", [POSTGRES_ARTIFACT_ENV]: "linux-amd64" }), publicDockerDatabaseIdentity(owned.receipt))).toBe(true);
|
||||
expect(databaseIdentityMatches(databaseFingerprintIdentity({ [DATABASE_LANE_ENV]: "owned-docker-postgres" }), publicDockerDatabaseIdentity(owned.receipt))).toBe(false);
|
||||
await owned.close(); expect(state.container).toBeNull(); expect(state.volume).toBeNull(); expect(owned.receipt.status).toBe("removed");
|
||||
});
|
||||
@@ -9,6 +9,29 @@ export const DOCKER_DATABASE_ENV = "PAPERCLIP_RUNNER_E2E_DOCKER_DATABASE_RECEIPT
|
||||
export const POSTGRES_IMAGE = "postgres@sha256:74935e72241653ca55e0414067e6d8763aceb8a810eb51b452253ec3dcfc4336";
|
||||
export const POSTGRES_IMAGE_ID = "sha256:74935e72241653ca55e0414067e6d8763aceb8a810eb51b452253ec3dcfc4336";
|
||||
export const POSTGRES_VERSION = "18.6-1.pgdg13+2";
|
||||
// Separate official Linux/amd64 artifact for GitHub AWS execution. Historical
|
||||
// arm64 defaults/receipts keep their exact bytes and identities.
|
||||
export const POSTGRES_ARTIFACT_ENV = "PAPERCLIP_RUNNER_E2E_POSTGRES_ARTIFACT";
|
||||
export const POSTGRES_IMAGE_ID_ENV = "PAPERCLIP_RUNNER_E2E_POSTGRES_IMAGE_ID";
|
||||
export const POSTGRES_AMD64_IMAGE = "postgres@sha256:9770878138a2e83abcb10856d30cc3f24dae0d317bb956db6ab9159de6c3c80d";
|
||||
export const POSTGRES_AMD64_IMAGE_IDS = ["sha256:79061104669bb30919d015b549c764e2d8021a18ee39cb175581a2588fe90dbe", "sha256:9770878138a2e83abcb10856d30cc3f24dae0d317bb956db6ab9159de6c3c80d"] as const;
|
||||
type PostgresImage = typeof POSTGRES_IMAGE | typeof POSTGRES_AMD64_IMAGE;
|
||||
type PostgresImageId = typeof POSTGRES_IMAGE_ID | typeof POSTGRES_AMD64_IMAGE_IDS[number];
|
||||
export function selectedPostgresArtifact(env = process.env): { image: PostgresImage; imageId: PostgresImageId; architecture: "arm64" | "amd64"; version: typeof POSTGRES_VERSION } {
|
||||
if (!env[POSTGRES_ARTIFACT_ENV]) {
|
||||
if (env[POSTGRES_IMAGE_ID_ENV]) throw new Error("Undeclared PostgreSQL image identity override");
|
||||
return { image: POSTGRES_IMAGE, imageId: POSTGRES_IMAGE_ID, architecture: "arm64" as const, version: POSTGRES_VERSION };
|
||||
}
|
||||
if (env[POSTGRES_ARTIFACT_ENV] !== "linux-amd64") throw new Error("Unknown explicit PostgreSQL artifact");
|
||||
const imageId = POSTGRES_AMD64_IMAGE_IDS.find(id => id === (env[POSTGRES_IMAGE_ID_ENV] ?? POSTGRES_AMD64_IMAGE_IDS[0]));
|
||||
if (!imageId) throw new Error("Unreviewed amd64 PostgreSQL image identity");
|
||||
return { image: POSTGRES_AMD64_IMAGE, imageId, architecture: "amd64" as const, version: POSTGRES_VERSION };
|
||||
}
|
||||
function knownPostgresReceipt(receipt: { image: string; imageId: string; architecture?: string }) {
|
||||
return receipt.image === POSTGRES_IMAGE && receipt.imageId === POSTGRES_IMAGE_ID && !receipt.architecture
|
||||
|| receipt.image === POSTGRES_AMD64_IMAGE && POSTGRES_AMD64_IMAGE_IDS.some(id => id === receipt.imageId) && receipt.architecture === "amd64";
|
||||
}
|
||||
|
||||
export type DockerRun = (args: string[], timeout?: number) => Promise<string>;
|
||||
const hash = (value: string) => createHash("sha256").update(value).digest("hex");
|
||||
const fullId = (value: unknown): value is string => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
||||
@@ -18,7 +41,7 @@ export interface DockerEngineIdentity { context: string; endpoint: string; engin
|
||||
export interface DockerDatabaseReceipt {
|
||||
schemaVersion: 1; lane: "owned-docker-postgres"; temporaryRoot: string; nonce: string;
|
||||
engine: DockerEngineIdentity; containerId: string | null; containerName: string; volumeName: string;
|
||||
image: typeof POSTGRES_IMAGE; imageId: typeof POSTGRES_IMAGE_ID; imageVersion: typeof POSTGRES_VERSION;
|
||||
image: PostgresImage; imageId: PostgresImageId; architecture?: "amd64"; imageVersion: typeof POSTGRES_VERSION;
|
||||
username: string; database: string; connectionString?: string; port?: number;
|
||||
status: "creating" | "ready" | "failed" | "removed" | "cleanup-failed";
|
||||
artifactVerified?: boolean;
|
||||
@@ -92,7 +115,7 @@ function exactPasswordBindSource(source: unknown, receipt: DockerDatabaseReceipt
|
||||
export function assertOwnedContainer(value: any, receipt: DockerDatabaseReceipt, running?: boolean) {
|
||||
const mounts = value?.Mounts, data = mounts?.find((m: any) => m.Destination === "/var/lib/postgresql"), password = mounts?.find((m: any) => m.Destination === "/run/secrets/postgres-password");
|
||||
const ports = value?.HostConfig?.PortBindings?.["5432/tcp"], config = value?.Config;
|
||||
if (!fullId(receipt.containerId) || value?.Id !== receipt.containerId || value.Name !== `/${receipt.containerName}` || value.Image !== POSTGRES_IMAGE_ID || config?.Image !== POSTGRES_IMAGE || !hasLabels(config.Labels, receipt)
|
||||
if (!fullId(receipt.containerId) || value?.Id !== receipt.containerId || value.Name !== `/${receipt.containerName}` || !knownPostgresReceipt(receipt) || value.Image !== receipt.imageId || config?.Image !== receipt.image || !hasLabels(config.Labels, receipt)
|
||||
|| value.HostConfig.Privileged !== false || !["default", "bridge"].includes(value.HostConfig.NetworkMode) || !Array.isArray(mounts) || mounts.length !== 2
|
||||
|| data?.Type !== "volume" || data.Name !== receipt.volumeName || data.RW !== true || password?.Type !== "bind" || !exactPasswordBindSource(password.Source, receipt) || password.RW !== false
|
||||
|| !Array.isArray(ports) || ports.length !== 1 || ports[0].HostIp !== "127.0.0.1" || Object.keys(value.HostConfig.PortBindings).join() !== "5432/tcp"
|
||||
@@ -109,7 +132,7 @@ async function privateRoot(root: string) {
|
||||
}
|
||||
function assertReceipt(value: DockerDatabaseReceipt, root: string) {
|
||||
if (value?.schemaVersion !== 1 || value.lane !== "owned-docker-postgres" || value.temporaryRoot !== root || !uuidHex(value.nonce) || value.containerName !== `paperclip-e2e-pg-${value.nonce}` || value.volumeName !== `paperclip-e2e-pg-data-${value.nonce}`
|
||||
|| value.image !== POSTGRES_IMAGE || value.imageId !== POSTGRES_IMAGE_ID || value.imageVersion !== POSTGRES_VERSION || !/^[a-z][a-z0-9_]{1,60}$/.test(value.username) || !/^[a-z][a-z0-9_]{1,60}$/.test(value.database)) throw new Error("Invalid private Docker database receipt");
|
||||
|| !knownPostgresReceipt(value) || value.imageVersion !== POSTGRES_VERSION || !/^[a-z][a-z0-9_]{1,60}$/.test(value.username) || !/^[a-z][a-z0-9_]{1,60}$/.test(value.database)) throw new Error("Invalid private Docker database receipt");
|
||||
}
|
||||
async function readPrivateDockerReceipt(filename: string, root: string) {
|
||||
await privateRoot(root);
|
||||
@@ -132,22 +155,24 @@ export async function readDockerDatabaseReceipt(filename: string, root: string,
|
||||
return receipt;
|
||||
}
|
||||
export interface DockerDatabaseIdentity {
|
||||
lane: "owned-docker-postgres"; image: typeof POSTGRES_IMAGE; imageId: typeof POSTGRES_IMAGE_ID; imageVersion: typeof POSTGRES_VERSION;
|
||||
lane: "owned-docker-postgres"; image: PostgresImage; imageId: PostgresImageId; architecture?: "amd64"; imageVersion: typeof POSTGRES_VERSION;
|
||||
artifactVerified: boolean; contextSha256: string | null; engineSha256: string | null; containerId: string | null;
|
||||
cluster?: { version: string; systemIdentifier: string }; status: DockerDatabaseReceipt["status"] | "startup-unconfirmed";
|
||||
}
|
||||
export function publicDockerDatabaseIdentity(receipt: DockerDatabaseReceipt): DockerDatabaseIdentity { return { lane: receipt.lane, image: receipt.image, imageId: receipt.imageId, imageVersion: receipt.imageVersion, artifactVerified: receipt.artifactVerified === true, contextSha256: hash(`${receipt.engine.context}\n${receipt.engine.endpoint}`), engineSha256: hash(receipt.engine.engineId), containerId: receipt.containerId, cluster: receipt.cluster ? { version: receipt.cluster.version, systemIdentifier: receipt.cluster.systemIdentifier } : undefined, status: receipt.status }; }
|
||||
export function publicDockerDatabaseIdentity(receipt: DockerDatabaseReceipt): DockerDatabaseIdentity { return { lane: receipt.lane, image: receipt.image, imageId: receipt.imageId, imageVersion: receipt.imageVersion, ...(receipt.architecture ? { architecture: receipt.architecture } : {}), artifactVerified: receipt.artifactVerified === true, contextSha256: hash(`${receipt.engine.context}\n${receipt.engine.endpoint}`), engineSha256: hash(receipt.engine.engineId), containerId: receipt.containerId, cluster: receipt.cluster ? { version: receipt.cluster.version, systemIdentifier: receipt.cluster.systemIdentifier } : undefined, status: receipt.status }; }
|
||||
/** Also used when startup throws before returning a ready handle. Never confuse
|
||||
* an unavailable receipt/daemon with proven absence or embedded fallback. */
|
||||
export async function dockerDatabaseLaunchState(lane: string, root: string) {
|
||||
if (lane === "embedded-postgres") return { allowDarwinReap: true, cleanupUnconfirmed: false, identity: undefined };
|
||||
const unknown: DockerDatabaseIdentity = { lane: "owned-docker-postgres", image: POSTGRES_IMAGE, imageId: POSTGRES_IMAGE_ID, imageVersion: POSTGRES_VERSION, artifactVerified: false, contextSha256: null, engineSha256: null, containerId: null, status: "startup-unconfirmed" };
|
||||
const artifact = selectedPostgresArtifact();
|
||||
const unknown: DockerDatabaseIdentity = { lane: "owned-docker-postgres", image: artifact.image, imageId: artifact.imageId, imageVersion: POSTGRES_VERSION, ...(artifact.architecture === "amd64" ? { architecture: artifact.architecture } : {}), artifactVerified: false, contextSha256: null, engineSha256: null, containerId: null, status: "startup-unconfirmed" };
|
||||
try {
|
||||
const receipt = await readPrivateDockerReceipt(path.join(root, "docker-postgres-private", "receipt.json"), root);
|
||||
return { allowDarwinReap: false, cleanupUnconfirmed: receipt.status !== "removed" || !!receipt.cleanupFailures?.length, identity: publicDockerDatabaseIdentity(receipt) };
|
||||
} catch { return { allowDarwinReap: false, cleanupUnconfirmed: true, identity: unknown }; }
|
||||
}
|
||||
export async function startOwnedDockerDatabase(input: { temporaryRoot: string; privateSecrets: string[]; run?: DockerRun }) {
|
||||
const artifact = selectedPostgresArtifact();
|
||||
const rawRun = input.run ?? runDocker, root = input.temporaryRoot; await privateRoot(root);
|
||||
const startupDeadline = Date.now() + 120000; let cleaning = false;
|
||||
const run: DockerRun = (args, timeout = 15000) => {
|
||||
@@ -157,7 +182,7 @@ export async function startOwnedDockerDatabase(input: { temporaryRoot: string; p
|
||||
};
|
||||
const engine = await currentDockerEngine(run), nonce = randomBytes(16).toString("hex"), password = randomBytes(32).toString("hex"), directory = path.join(root, "docker-postgres-private"), filename = path.join(directory, "receipt.json");
|
||||
await mkdir(directory, { mode: 0o700 });
|
||||
const receipt: DockerDatabaseReceipt = { schemaVersion: 1, lane: "owned-docker-postgres", temporaryRoot: root, nonce, engine, containerId: null, containerName: `paperclip-e2e-pg-${nonce}`, volumeName: `paperclip-e2e-pg-data-${nonce}`, image: POSTGRES_IMAGE, imageId: POSTGRES_IMAGE_ID, imageVersion: POSTGRES_VERSION, username: `pc_${nonce.slice(0, 20)}`, database: `pc_${nonce.slice(0, 20)}`, status: "creating", startedAt: new Date().toISOString() };
|
||||
const receipt: DockerDatabaseReceipt = { schemaVersion: 1, lane: "owned-docker-postgres", temporaryRoot: root, nonce, engine, containerId: null, containerName: `paperclip-e2e-pg-${nonce}`, volumeName: `paperclip-e2e-pg-data-${nonce}`, image: artifact.image, imageId: artifact.imageId, imageVersion: POSTGRES_VERSION, ...(artifact.architecture === "amd64" ? { architecture: artifact.architecture } : {}), username: `pc_${nonce.slice(0, 20)}`, database: `pc_${nonce.slice(0, 20)}`, status: "creating", startedAt: new Date().toISOString() };
|
||||
input.privateSecrets.push(password); await writeFile(path.join(directory, "password"), password, { mode: 0o600, flag: "wx" });
|
||||
const persist = () => writeFile(filename, `${JSON.stringify(receipt, null, 2)}\n`, { mode: 0o600 }); await persist();
|
||||
const command = async (args: string[], timeout?: number) => { await pinnedEngine(engine, run); return run(["--context", engine.context, ...args], timeout); };
|
||||
@@ -198,15 +223,15 @@ export async function startOwnedDockerDatabase(input: { temporaryRoot: string; p
|
||||
closed = true; await rm(path.join(directory, "password"), { force: true });
|
||||
};
|
||||
try {
|
||||
const image = JSON.parse(await command(["image", "inspect", POSTGRES_IMAGE]))[0];
|
||||
if (image?.Id !== POSTGRES_IMAGE_ID || image.Os !== "linux" || image.Architecture !== "arm64" || !image.RepoDigests?.includes(POSTGRES_IMAGE) || !image.Config.Env.includes(`PG_VERSION=${POSTGRES_VERSION}`)) throw new Error("Installed official PostgreSQL artifact differs from pinned identity; no pull permitted");
|
||||
const image = JSON.parse(await command(["image", "inspect", artifact.image]))[0];
|
||||
if (image?.Id !== artifact.imageId || image.Os !== "linux" || image.Architecture !== artifact.architecture || !image.RepoDigests?.includes(artifact.image) || !image.Config.Env.includes(`PG_VERSION=${POSTGRES_VERSION}`)) throw new Error("Installed official PostgreSQL artifact differs from pinned identity; no pull permitted");
|
||||
receipt.artifactVerified = true; await persist();
|
||||
const labelArgs = Object.entries(labels(receipt)).flatMap(([k, v]) => ["--label", `${k}=${v}`]);
|
||||
receipt.volumeCreationAttempted = true; await persist();
|
||||
await command(["volume", "create", "--driver", "local", ...labelArgs, receipt.volumeName]);
|
||||
assertOwnedVolume(JSON.parse(await command(["volume", "inspect", receipt.volumeName]))[0], receipt);
|
||||
receipt.containerCreationAttempted = true; await persist();
|
||||
const cid = await command(["create", "--cidfile", path.join(directory, "container.id"), "--pull=never", "--name", receipt.containerName, ...labelArgs, "--publish", "127.0.0.1::5432/tcp", "--memory", "1g", "--cpus", "2", "--pids-limit", "256", "--shm-size", "128m", "--mount", `type=volume,src=${receipt.volumeName},dst=/var/lib/postgresql`, "--mount", `type=bind,src=${path.join(directory, "password")},dst=/run/secrets/postgres-password,readonly`, "--env", "POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password", "--env", `POSTGRES_USER=${receipt.username}`, "--env", `POSTGRES_DB=${receipt.database}`, POSTGRES_IMAGE]);
|
||||
const cid = await command(["create", "--cidfile", path.join(directory, "container.id"), "--pull=never", "--name", receipt.containerName, ...labelArgs, "--publish", "127.0.0.1::5432/tcp", "--memory", "1g", "--cpus", "2", "--pids-limit", "256", "--shm-size", "128m", "--mount", `type=volume,src=${receipt.volumeName},dst=/var/lib/postgresql`, "--mount", `type=bind,src=${path.join(directory, "password")},dst=/run/secrets/postgres-password,readonly`, "--env", "POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password", "--env", `POSTGRES_USER=${receipt.username}`, "--env", `POSTGRES_DB=${receipt.database}`, artifact.image]);
|
||||
if (!fullId(cid)) throw new Error("Docker create returned no exact container ID"); receipt.containerId = cid; await persist();
|
||||
assertOwnedContainer(JSON.parse(await command(["inspect", cid]))[0], receipt, false); await command(["start", cid], 20000);
|
||||
const deadline = Date.now() + 60000; let ready = false;
|
||||
@@ -233,7 +258,7 @@ export async function startOwnedDockerDatabase(input: { temporaryRoot: string; p
|
||||
* before constructing the actual Paperclip/native environment. */
|
||||
export async function dockerDatabaseSupervisorEnvironment(input: { temporaryRoot: string; env: NodeJS.ProcessEnv; run?: DockerRun }) {
|
||||
const lane = selectedDatabaseLane(input.env), source = { ...input.env }, filename = source[DOCKER_DATABASE_ENV];
|
||||
delete source[DOCKER_DATABASE_ENV]; delete source[DATABASE_LANE_ENV];
|
||||
delete source[DOCKER_DATABASE_ENV]; delete source[DATABASE_LANE_ENV]; delete source[POSTGRES_ARTIFACT_ENV]; delete source[POSTGRES_IMAGE_ID_ENV];
|
||||
if (lane === "embedded-postgres") { if (filename) throw new Error("Undeclared Docker database receipt"); return { source, filename: undefined }; }
|
||||
if (!filename) throw new Error("Selected Docker database lane is missing its owned receipt");
|
||||
await readDockerDatabaseReceipt(filename, input.temporaryRoot, input.run);
|
||||
@@ -242,14 +267,15 @@ export async function dockerDatabaseSupervisorEnvironment(input: { temporaryRoot
|
||||
}
|
||||
|
||||
export function databaseFingerprintIdentity(env = process.env) {
|
||||
const artifact = selectedPostgresArtifact(env);
|
||||
return selectedDatabaseLane(env) === "owned-docker-postgres"
|
||||
? { lane: "owned-docker-postgres" as const, image: POSTGRES_IMAGE, imageId: POSTGRES_IMAGE_ID, version: POSTGRES_VERSION }
|
||||
? { lane: "owned-docker-postgres" as const, image: artifact.image, imageId: artifact.imageId, version: POSTGRES_VERSION, ...(artifact.architecture === "amd64" ? { architecture: artifact.architecture } : {}) }
|
||||
: { lane: "embedded-postgres" as const };
|
||||
}
|
||||
export type DatabaseFingerprintIdentity = ReturnType<typeof databaseFingerprintIdentity>;
|
||||
export function databaseIdentityMatches(expected: DatabaseFingerprintIdentity, actual?: DockerDatabaseIdentity) {
|
||||
if (expected.lane === "embedded-postgres") return actual === undefined;
|
||||
return !!actual && actual.lane === expected.lane && actual.image === expected.image && actual.imageId === expected.imageId && actual.imageVersion === expected.version
|
||||
return !!actual && actual.lane === expected.lane && actual.image === expected.image && actual.imageId === expected.imageId && actual.imageVersion === expected.version && knownPostgresReceipt(actual) && (expected.architecture ?? undefined) === actual.architecture
|
||||
&& actual.artifactVerified && !!actual.contextSha256 && !!actual.engineSha256 && fullId(actual.containerId) && !!actual.cluster
|
||||
&& /^18\.6(?:\s|$)/.test(actual.cluster.version) && /^[0-9]+$/.test(actual.cluster.systemIdentifier) && ["ready", "removed"].includes(actual.status);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { prepareChatRemoteHost } from "./chat-remote-host.js";
|
||||
import { POSTGRES_ARTIFACT_ENV, POSTGRES_IMAGE_ID_ENV } from "./docker-postgres.js";
|
||||
import { prepareOwnedPublicOrigin, PublicOriginCleanupError, PUBLIC_ORIGIN_CONTROL_ENV, PUBLIC_ORIGIN_RECEIPT_ENV } from "./chat-public-origin.js";
|
||||
import { DATABASE_LANE_ENV, DOCKER_DATABASE_ENV, dockerDatabaseLaunchState, publicDockerDatabaseIdentity, selectedDatabaseLane, startOwnedDockerDatabase } from "./docker-postgres.js";
|
||||
import { ARCHIVE_CONTROL_ENV, ARCHIVE_SCENARIO, startArchiveFaultControl } from "./chat-archive-fault-control.js";
|
||||
@@ -1193,6 +1195,14 @@ async function main() {
|
||||
);
|
||||
const summaryDir = path.join(resultsRoot, campaignId);
|
||||
await mkdir(summaryDir, { recursive: true });
|
||||
const remoteChat = await prepareChatRemoteHost({ executions, env: process.env, repositoryRoot, outputDirectory: summaryDir });
|
||||
if (remoteChat) {
|
||||
process.env[DATABASE_LANE_ENV] = remoteChat.databaseLane;
|
||||
process.env[POSTGRES_ARTIFACT_ENV] = remoteChat.postgresArtifact;
|
||||
process.env[POSTGRES_IMAGE_ID_ENV] = remoteChat.postgresImageId;
|
||||
options.maxAutomaticRetries = remoteChat.maxAutomaticRetries;
|
||||
options.maxParallel = Math.min(options.maxParallel, remoteChat.maxParallel);
|
||||
}
|
||||
if (executions.some(execution => execution.suite.id === "native-completion")) {
|
||||
process.env[NATIVE_COMPLETION_PREFLIGHT_ENV] = prepareNativeCompletionPreflight(summaryDir);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user