mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
test(evals): use ordinary embedded Postgres on AWS chat
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e68d1a8044
commit
78de49f09d
5 files changed
+69
-106
No files matched your search
@@ -32,6 +32,25 @@ describe("remote Actions cell import", () => {
|
||||
["wrong scenario", (f: ReturnType<typeof fixture>) => { f.definition.scenarioId = "tasks.other"; }],
|
||||
["missing accounting", (f: ReturnType<typeof fixture>) => { delete f.result.accountingIdentity; }],
|
||||
] as const)("rejects %s without recording a measurement", (_name, mutate) => { const f = fixture(); mutate(f); expect(() => validateRemoteCell(f.input())).toThrow("Remote import:"); });
|
||||
it("imports the ordinary embedded lane only under an explicit matching intent", () => {
|
||||
const f = fixture();
|
||||
delete f.result.databaseIdentity; delete f.host.postgres; delete f.host.dockerEngineSha256;
|
||||
f.host.databaseLane = "embedded-postgres";
|
||||
expect(() => validateRemoteCell(f.input())).toThrow("host database lane mismatch");
|
||||
const input = f.input();
|
||||
expect(validateRemoteCell({ ...input, intent: { ...input.intent, databaseLane: { lane: "embedded-postgres" } } }).databaseLane).toEqual({ lane: "embedded-postgres" });
|
||||
});
|
||||
it.each(["host lane", "Docker receipt", "Docker engine", "Docker artifact"])("rejects embedded/Docker mixed evidence: %s", variant => {
|
||||
const f = fixture(); const docker = f.result.databaseIdentity;
|
||||
delete f.result.databaseIdentity; delete f.host.postgres; delete f.host.dockerEngineSha256;
|
||||
f.host.databaseLane = "embedded-postgres";
|
||||
if (variant === "host lane") f.host.databaseLane = "owned-docker-postgres";
|
||||
if (variant === "Docker receipt") f.result.databaseIdentity = docker;
|
||||
if (variant === "Docker engine") f.host.dockerEngineSha256 = hash;
|
||||
if (variant === "Docker artifact") f.host.postgres = {};
|
||||
const input = f.input();
|
||||
expect(() => validateRemoteCell({ ...input, intent: { ...input.intent, databaseLane: { lane: "embedded-postgres" } } })).toThrow("database");
|
||||
});
|
||||
it("rejects changed bytes, foreign result paths and unreviewed Actions reruns", () => {
|
||||
const f = fixture(), changed = f.input(); changed.result.bytes += " "; expect(() => validateRemoteCell(changed)).toThrow("bytes changed");
|
||||
const foreign = f.input(); foreign.result.path = "../result.json"; expect(() => validateRemoteCell(foreign)).toThrow("artifact path");
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface RemoteCellIntent {
|
||||
campaignId: string; sourceRevision: string;
|
||||
/** Absent on preserved strict-Spot intents. Only new explicit user-authorized sources opt in. */
|
||||
hostPolicy?: "aws-fleet-spot-or-on-demand";
|
||||
/** Explicit new lane selection; absent preserves historical Docker-only intents. */
|
||||
databaseLane?: DatabaseFingerprintIdentity;
|
||||
selected: Array<{ scenarioId: string; executionId: string; remoteOrdinal: 1 | 2 }>;
|
||||
}
|
||||
/** Obtained by the coordinator through authenticated Actions run/artifact APIs, not from
|
||||
@@ -66,10 +68,19 @@ export function validateRemoteCell(input: {
|
||||
&& allowedHost && host.architecture === "x86_64" && host.region === "us-east-1", "host not qualified for explicit intent policy");
|
||||
requireProof(host.workflowRef === "paperclipai/paperclip/.github/workflows/runner-full-stack-e2e.yml@refs/heads/master"
|
||||
&& host.workflowRunId === p.runId && host.workflowAttempt === p.runAttempt && host.sourceRevision === intent.sourceRevision
|
||||
&& Array.isArray(host.executionIds) && host.executionIds.includes(result.executionId) && host.automaticRetries === 0 && hex(host.dockerEngineSha256), "host/result provenance mismatch");
|
||||
requireProof(host.postgres?.image === POSTGRES_AMD64_IMAGE && POSTGRES_AMD64_IMAGE_IDS.includes(host.postgres?.imageId) && host.postgres.architecture === "amd64" && host.postgres.version === POSTGRES_VERSION, "foreign database artifact");
|
||||
const databaseLane = { lane: "owned-docker-postgres" as const, image: host.postgres.image, imageId: host.postgres.imageId, version: host.postgres.version, architecture: "amd64" as const };
|
||||
requireProof(databaseIdentityMatches(databaseLane, result.databaseIdentity) && result.databaseIdentity?.engineSha256 === host.dockerEngineSha256, "cell database identity mismatch");
|
||||
&& Array.isArray(host.executionIds) && host.executionIds.includes(result.executionId) && host.automaticRetries === 0, "host/result provenance mismatch");
|
||||
let databaseLane: DatabaseFingerprintIdentity;
|
||||
if (intent.databaseLane?.lane === "embedded-postgres") {
|
||||
requireProof(host.databaseLane === "embedded-postgres" && host.postgres === undefined && host.dockerEngineSha256 === undefined, "host database lane mismatch");
|
||||
databaseLane = intent.databaseLane;
|
||||
requireProof(databaseIdentityMatches(databaseLane, result.databaseIdentity), "cell database identity mismatch");
|
||||
} else {
|
||||
requireProof(host.databaseLane === undefined || host.databaseLane === "owned-docker-postgres", "host database lane mismatch");
|
||||
requireProof(hex(host.dockerEngineSha256) && host.postgres?.image === POSTGRES_AMD64_IMAGE && POSTGRES_AMD64_IMAGE_IDS.includes(host.postgres?.imageId) && host.postgres.architecture === "amd64" && host.postgres.version === POSTGRES_VERSION, "foreign database artifact");
|
||||
databaseLane = { lane: "owned-docker-postgres", image: host.postgres.image, imageId: host.postgres.imageId, version: host.postgres.version, architecture: "amd64" };
|
||||
requireProof(!intent.databaseLane || databaseIdentityMatches(intent.databaseLane, result.databaseIdentity), "intent database lane mismatch");
|
||||
requireProof(databaseIdentityMatches(databaseLane, result.databaseIdentity) && result.databaseIdentity?.engineSha256 === host.dockerEngineSha256, "cell database identity mismatch");
|
||||
}
|
||||
requireProof(definition.scenarioId === selection.scenarioId && hex(definition.definitionDigest) && hex(definition.graderDigest)
|
||||
&& definition.suiteDefinitionHash === result.suiteDefinitionHash && hex(result.suiteDefinitionHash), "definition/result mismatch");
|
||||
return { schema: "paperclip.chat-remote-cell-import/v1", intentCampaignId: intent.campaignId, importKey: `${p.runId}/${p.runAttempt}/${result.executionId}`,
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 { dockerCommandDiagnostic, DockerCommandError, 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 }))); });
|
||||
@@ -19,35 +18,29 @@ async function fixture(lifecycle = "spot") {
|
||||
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,
|
||||
return { executions: [execution], env, repositoryRoot: root, outputDirectory: root, dependencies: { fetch: fetch as typeof globalThis.fetch, platform: "linux", arch: "x64", sourceRevision: async () => "a".repeat(40) }, fetch,
|
||||
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 () => {
|
||||
it("requires real Spot metadata 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(policy).toMatchObject({ databaseLane: "embedded-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]!);
|
||||
expect(receipt.databaseLane).toBe("embedded-postgres");
|
||||
expect(receipt).not.toHaveProperty("postgres");
|
||||
expect(receipt).not.toHaveProperty("dockerEngineSha256");
|
||||
});
|
||||
it("accepts the user-authorized fleet fallback while truthfully recording On-Demand", async () => {
|
||||
const f = await fixture("on-demand"); await prepareChatRemoteHost(f);
|
||||
expect(await f.receipt()).toMatchObject({ lifecycle: "on-demand", status: "qualified-host-only", spotVerified: false, hostPolicy: "aws-fleet-spot-or-on-demand", evaluationAdmitted: false });
|
||||
expect(f.docker).toHaveBeenCalled();
|
||||
});
|
||||
it.each(["", "unknown", "preemptible"])("refuses unproven host lifecycle %s before Docker", async lifecycle => {
|
||||
it.each(["", "unknown", "preemptible"])("refuses unproven host lifecycle %s", async lifecycle => {
|
||||
const f = await fixture(lifecycle); await expect(prepareChatRemoteHost(f)).rejects.toThrow("unknown-host-lifecycle-refused");
|
||||
expect(f.docker).not.toHaveBeenCalled(); expect(await f.receipt()).toMatchObject({ lifecycle, status: "blocked", evaluationAdmitted: false });
|
||||
expect(await f.receipt()).toMatchObject({ lifecycle, 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 => {
|
||||
it.each(["local", "branch-workflow", "wrong-source", "wrong-model", "automatic-retry", "foreign-repository"])("fails closed on %s before metadata", 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";
|
||||
@@ -55,73 +48,32 @@ describe("remote-only chat host qualification without native/provider calls", ()
|
||||
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();
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("trusted-github-linux-source-required"); expect(f.fetch).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);
|
||||
expect(await f.receipt()).toMatchObject({ qualificationStage: "postgres-image-validate", imageChecks: { imageId: false, amd64: false, repositoryDigest: false }, observedImage: { imageId: null, architecture: "arm64" } });
|
||||
it("records missing actual AWS identity without admitting an evaluation", async () => {
|
||||
const f = await fixture(); f.dependencies.fetch = vi.fn(async () => new Response("", { status: 403 }));
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("aws-instance-identity-unavailable");
|
||||
expect(await f.receipt()).toMatchObject({ status: "blocked", evaluationAdmitted: false, databaseLane: "embedded-postgres" });
|
||||
});
|
||||
it.each([
|
||||
["context", "docker-engine", "Cannot connect to the Docker daemon", "docker-engine-unavailable"],
|
||||
["pull", "postgres-image-pull", "toomanyrequests: registry rate limit", "registry-rate-limit"],
|
||||
["image", "postgres-image-inspect", "unclassified synthetic-private-canary", "unknown-docker-command-failure"],
|
||||
])("retains safe actual %s failure stage without raw Docker/private output", async (command, stage, stderr, category) => {
|
||||
const f = await fixture(), original = f.dependencies.docker;
|
||||
f.dependencies.docker = vi.fn(async args => {
|
||||
if (args.includes(command)) throw new DockerCommandError({ args, stdout: "synthetic-imds-token", stderr: `${stderr} synthetic-private-canary`, exitCode: 1, signal: null });
|
||||
return original(args);
|
||||
it.each(["architecture", "region", "instanceId"])("rejects wrong AWS %s", async field => {
|
||||
const f = await fixture(), original = f.dependencies.fetch;
|
||||
f.dependencies.fetch = vi.fn(async (url, init) => {
|
||||
const response = await original(url, init);
|
||||
if (!String(url).endsWith("/document")) return response;
|
||||
const identity = await response.json(); identity[field] = "wrong"; return Response.json(identity);
|
||||
});
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("owned-linux-amd64-postgres-image-unqualified");
|
||||
const receipt = await f.receipt();
|
||||
expect(receipt).toMatchObject({ evaluationAdmitted: false, qualificationStage: stage, failureDiagnostic: { category, exitCode: 1, signal: null } });
|
||||
expect(JSON.stringify(receipt)).not.toMatch(/synthetic-private-canary|synthetic-imds-token|stderr|stdout/);
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("aws-instance-identity-unavailable");
|
||||
});
|
||||
it("records pull timeout without relaxing or retrying image qualification", async () => {
|
||||
const f = await fixture(), original = f.dependencies.docker;
|
||||
f.dependencies.docker = vi.fn(async args => { if (args.includes("pull")) throw new DockerCommandError({ args, stdout: "", stderr: "", exitCode: null, signal: "SIGTERM" }); return original(args); });
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("owned-linux-amd64-postgres-image-unqualified");
|
||||
expect(await f.receipt()).toMatchObject({ qualificationStage: "postgres-image-pull", failureDiagnostic: { category: "command-timeout-or-termination" } });
|
||||
expect(f.dependencies.docker.mock.calls.filter(([args]) => args.includes("pull"))).toHaveLength(1);
|
||||
});
|
||||
it.each([
|
||||
["ENOENT", "", "docker-command-missing"],
|
||||
[undefined, "denied: unauthorized", "registry-access-denied"],
|
||||
[undefined, "dial tcp: lookup registry: no such host", "registry-network-or-tls-failure"],
|
||||
[undefined, "manifest unknown", "image-manifest-unavailable"],
|
||||
[undefined, "no matching manifest for linux/amd64", "image-platform-unavailable"],
|
||||
[undefined, "permission denied while connecting to docker.sock", "docker-engine-unavailable"],
|
||||
])("projects only closed Docker diagnosis %s/%s", (spawnCode, stderr, category) => {
|
||||
expect(dockerCommandDiagnostic(new DockerCommandError({ args: ["private-arg"], stdout: "", stderr, spawnCode, exitCode: null, signal: null }))).toEqual({ category, exitCode: null, signal: null });
|
||||
});
|
||||
it.each(["singleImage", "imageId", "linux", "amd64", "repositoryDigest", "postgresVersion"])("retains exact failed inspect predicate %s", async predicate => {
|
||||
const f = await fixture(), original = f.dependencies.docker;
|
||||
f.dependencies.docker = vi.fn(async args => {
|
||||
const output = await original(args);
|
||||
if (!args.includes("image")) return output;
|
||||
const rows = JSON.parse(output), image = rows[0];
|
||||
if (predicate === "singleImage") rows.push(image);
|
||||
if (predicate === "imageId") image.Id = `sha256:${"b".repeat(64)}`;
|
||||
if (predicate === "linux") image.Os = "windows";
|
||||
if (predicate === "amd64") image.Architecture = "arm64";
|
||||
if (predicate === "repositoryDigest") image.RepoDigests = [];
|
||||
if (predicate === "postgresVersion") image.Config.Env = ["PG_VERSION=18.5"];
|
||||
return JSON.stringify(rows);
|
||||
});
|
||||
await expect(prepareChatRemoteHost(f)).rejects.toThrow("owned-linux-amd64-postgres-image-unqualified");
|
||||
expect(await f.receipt()).toMatchObject({ qualificationStage: "postgres-image-validate", evaluationAdmitted: false, imageChecks: { [predicate]: false } });
|
||||
});
|
||||
it("keeps the one exact health cell on the same AWS/Docker policy", async () => {
|
||||
it("keeps the one exact health cell on the same AWS/ordinary embedded database policy", async () => {
|
||||
const f = await fixture("on-demand"); f.executions[0]!.id = CHAT_SETUP_HEALTH_ID; f.executions[0]!.suite.id = CHAT_SETUP_HEALTH_SUITE;
|
||||
expect(selectsRemoteChat(f.executions)).toBe(true);
|
||||
expect(await prepareChatRemoteHost(f)).toMatchObject({ databaseLane: "owned-docker-postgres", maxAutomaticRetries: 0, maxParallel: 2 });
|
||||
expect(f.docker).toHaveBeenCalled();
|
||||
expect(await prepareChatRemoteHost(f)).toMatchObject({ databaseLane: "embedded-postgres", maxAutomaticRetries: 0, maxParallel: 2 });
|
||||
expect(await f.receipt()).not.toHaveProperty("dockerEngineSha256");
|
||||
f.executions[0]!.id = "chat-setup-health.runner-opencode.local.wrong";
|
||||
expect(selectsRemoteChat(f.executions)).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();
|
||||
expect(selectsRemoteChat(f.executions)).toBe(false); expect(await prepareChatRemoteHost(f)).toBeUndefined(); expect(f.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,34 +1,31 @@
|
||||
import { CHAT_SETUP_HEALTH_ID, CHAT_SETUP_HEALTH_SUITE } from "./chat-setup-health.js";
|
||||
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, dockerCommandDiagnostic, 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;
|
||||
fetch: typeof fetch; platform: string; arch: string;
|
||||
sourceRevision: (root: string) => Promise<string>;
|
||||
}
|
||||
const defaults: RemoteHostDependencies = { fetch: globalThis.fetch, docker: runDocker, platform: process.platform, arch: process.arch,
|
||||
const defaults: RemoteHostDependencies = { fetch: globalThis.fetch, 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.id === CHAT_SETUP_HEALTH_ID) && 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. */
|
||||
/** Qualifies only the actual trusted GitHub AWS host. The ordinary isolated
|
||||
* embedded PostgreSQL bootstrap, migrations and cleanup remain owned by the launcher. */
|
||||
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, hostPolicy: "aws-fleet-spot-or-on-demand", databaseLane: "owned-docker-postgres", automaticRetries: 0, maximumParallel: 2,
|
||||
allocationSpotOnlyClaimed: false, hostPolicy: "aws-fleet-spot-or-on-demand", databaseLane: "embedded-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 });
|
||||
@@ -55,26 +52,10 @@ export async function prepareChatRemoteHost(input: { executions: readonly Select
|
||||
Object.assign(receipt, { instanceId: identity.instanceId, imageId: identity.imageId, region: identity.region, architecture: identity.architecture, lifecycle });
|
||||
reason = "unknown-host-lifecycle-refused-before-evaluation";
|
||||
if (lifecycle !== "spot" && lifecycle !== "on-demand") throw new Error(reason);
|
||||
reason = "owned-linux-amd64-postgres-image-unqualified";
|
||||
receipt.qualificationStage = "docker-engine";
|
||||
const engine = await currentDockerEngine(deps.docker, env);
|
||||
receipt.qualificationStage = "postgres-image-pull";
|
||||
await deps.docker(["--context", engine.context, "pull", "--platform", "linux/amd64", POSTGRES_AMD64_IMAGE], 120000);
|
||||
receipt.qualificationStage = "postgres-image-inspect";
|
||||
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);
|
||||
receipt.qualificationStage = "postgres-image-validate";
|
||||
const checks = { singleImage: images.length === 1, imageId: POSTGRES_AMD64_IMAGE_IDS.some(id => id === image?.Id), linux: image?.Os === "linux", amd64: image?.Architecture === "amd64", repositoryDigest: image?.RepoDigests?.includes(POSTGRES_AMD64_IMAGE) === true, postgresVersion: pgVersion === POSTGRES_VERSION };
|
||||
receipt.imageChecks = checks;
|
||||
receipt.observedImage = { imageId: /^sha256:[a-f0-9]{64}$/.test(image?.Id ?? "") ? image.Id : null,
|
||||
os: ["linux", "windows"].includes(image?.Os) ? image.Os : null, architecture: ["amd64", "arm64"].includes(image?.Architecture) ? image.Architecture : null,
|
||||
version: typeof pgVersion === "string" && /^[0-9][a-zA-Z0-9.+~-]{0,79}$/.test(pgVersion) ? pgVersion : null };
|
||||
if (Object.values(checks).some(value => !value)) throw new Error(reason);
|
||||
Object.assign(receipt, { status: "qualified-host-only", spotVerified: lifecycle === "spot", postgres: { image: POSTGRES_AMD64_IMAGE, imageId: image.Id, architecture: image.Architecture, version: pgVersion }, dockerEngineSha256: hash(engine.engineId) });
|
||||
Object.assign(receipt, { status: "qualified-host-only", spotVerified: lifecycle === "spot" });
|
||||
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 (error) {
|
||||
if (receipt.qualificationStage) receipt.failureDiagnostic = dockerCommandDiagnostic(error);
|
||||
return { receiptPath: filename, databaseLane: "embedded-postgres" as const, maxAutomaticRetries: 0, maxParallel: 2 };
|
||||
} catch {
|
||||
receipt.reason = reason; await persist();
|
||||
throw new Error(`Remote chat preflight blocked: ${reason}; inspect chat-remote-host.json. No evaluation admitted.`);
|
||||
}
|
||||
|
||||
@@ -1200,8 +1200,8 @@ async function main() {
|
||||
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;
|
||||
delete process.env[POSTGRES_ARTIFACT_ENV];
|
||||
delete process.env[POSTGRES_IMAGE_ID_ENV];
|
||||
options.maxAutomaticRetries = remoteChat.maxAutomaticRetries;
|
||||
options.maxParallel = Math.min(options.maxParallel, remoteChat.maxParallel);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user