Files
PaperClipAI/tests/runner-e2e/workflow-target.test.ts
T
PaperclipandClaude Opus 5.5 0048595610 Reject failover backups behind the accepted event cursor
Check supported controller journals before copying a verified backup to a replacement sandbox. Preserve native_event_replay_conflict when a runner exit wraps the error, so stale state does not enter a retry loop. Also supply the normal image-only input in the workflow target fixture used by current master.

Verified 1,171 native session tests and nine workflow target tests. The new stale-cursor and classification cases fail on unchanged master; the workflow fixture reproduces and fixes the unset input error. Broader CI and exact-head Apex review remain required.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:58:01 -08:00

82 lines
4.6 KiB
TypeScript

import { spawnSync } from "node:child_process";
import { mkdtemp, readFile, rm, access } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
const queuedSha = "a".repeat(40);
const advancedSha = "b".repeat(40);
async function resolveTarget(event: string, cadence: string, branch = "master") {
const workflow = await readFile(new URL("../../.github/workflows/runner-full-stack-e2e.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Resolve requested repository branch to an immutable commit\n")[1]!.split("\n - name:")[0]!;
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const directory = await mkdtemp(path.join(os.tmpdir(), "eval-workflow-target-"));
try {
const output = path.join(directory, "output");
const result = spawnSync("bash", ["-c", `gh() { printf '%s\\n' "$MOCK_BRANCH_SHA"; }\n${script}`], {
encoding: "utf8", env: { ...process.env, EVENT_NAME: event, EVAL_CADENCE: cadence,
TARGET_BRANCH: branch, DEFAULT_BRANCH: "master", GITHUB_SHA: queuedSha,
MOCK_BRANCH_SHA: advancedSha, GITHUB_OUTPUT: output, REPOSITORY: "paperclipai/paperclip", IMAGE_ONLY: "false" },
});
return { status: result.status, stderr: result.stderr,
output: await readFile(output, "utf8").catch(() => "") };
} finally { await rm(directory, { recursive: true, force: true }); }
}
describe("paid workflow target authorization", () => {
it.each([["schedule", "manual"], ["workflow_dispatch", "daily"], ["workflow_dispatch", "weekly"]])(
"pins %s/%s to the queued revision when the branch advances", async (event, cadence) => {
const result = await resolveTarget(event, cadence);
expect(result.status, result.stderr).toBe(0);
expect(result.output).toContain(`sha=${queuedSha}\n`);
expect(result.output).not.toContain(advancedSha);
});
it("rejects custom bounded targets before emitting a commit", async () => {
const result = await resolveTarget("workflow_dispatch", "daily", "development");
expect(result.status).toBe(1);
expect(result.output).toBe("");
expect(result.stderr).toContain("default-branch commit");
});
it("still resolves explicit manual targets at authorization time", async () => {
const result = await resolveTarget("workflow_dispatch", "manual", "development");
expect(result.status, result.stderr).toBe(0);
expect(result.output).toContain(`sha=${advancedSha}\n`);
expect(result.output).toContain("ref=refs/heads/development\n");
});
});
describe("qualification image dependency correction", () => {
it.each([true, false])("applies the workflow correction only to its own source: %s", async sameSource => {
const workflow = await readFile(new URL("../../.github/workflows/docker-runner-check.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Apply reviewed qualification lock corrections\n")[1]!.split("\n - ")[0]!;
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const directory = await mkdtemp(path.join(os.tmpdir(), "eval-image-lock-target-"));
try {
const marker = path.join(directory, "called");
const result = spawnSync("bash", ["-c", `node() { touch "$CALLED"; }\n${script}`], {
cwd: directory, encoding: "utf8", env: { ...process.env, GITHUB_SHA: queuedSha,
TARGET_SHA: sameSource ? queuedSha : advancedSha, CALLED: marker },
});
expect(result.status, result.stderr).toBe(0);
expect(await access(marker).then(() => true, () => false)).toBe(sameSource);
} finally { await rm(directory, { recursive: true, force: true }); }
});
});
describe("release image capacity prerequisite", () => {
it.each([34, 35])("checks the unchanged disk threshold for every image route: %s GiB", async available => {
const workflow = await readFile(new URL("../../.github/workflows/docker-runner-check.yml", import.meta.url), "utf8");
const step = workflow.split(" - name: Prepare hosted image build capacity\n")[1]!.split("\n - ")[0]!;
expect(step).toContain("if: inputs.build_eval_image || inputs.publish_eval_image\n");
expect(step).not.toContain("release_run_id");
const script = step.split(" run: |\n")[1]!.replace(/^ {10}/gm, "");
const result = spawnSync("bash", ["-c", `sudo() { test "$*" = "rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc"; }; df() { printf 'Avail\\n%sG\\n' "$AVAILABLE"; }; ${script}`], {
encoding: "utf8", env: { PATH: process.env.PATH, AVAILABLE: String(available) },
});
expect(result.status, result.stderr).toBe(available >= 35 ? 0 : 1);
});
});