mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
## Thinking Path > - Paperclip manages work performed by AI agents. > - Its Runner support tests verify workflow authorization and target pinning. > - The workflow now passes two Boolean inputs to its shell guard. > - The target fixture runs that guard with `set -u` but omits those inputs. > - Five cases stop on an unset variable before they reach their assertions. > - This change supplies the workflow's normal false defaults in the fixture. > - The production guard and all target-pinning assertions remain unchanged. ## Linked Issues or Issue Description Refs #19537, #19576. **What happened?** The required Runner support suite fails five `workflow-target.test.ts` cases with `IMAGE_ONLY: unbound variable`. The real workflow supplies `IMAGE_ONLY` and `TESTOPS_REQUESTED`. The test fixture does not supply either value. **Expected behavior** The fixture uses the normal workflow defaults and reaches the target authorization assertions. **Steps to reproduce** Run `pnpm exec vitest run --config tests/runner-e2e/vitest.config.ts workflow-target.test.ts` on master `4938a930d66846094810cbed45d2fd33c0d14c2d`. ## What Changed - Set `IMAGE_ONLY` and `TESTOPS_REQUESTED` to `false` in the target fixture's child environment. ## Verification - RED: the unchanged current-master fixture had five failures and four passes. Bash stopped on `IMAGE_ONLY: unbound variable` before target authorization ran (220ms). - GREEN: all nine cases pass after the one-line fixture repair (168ms). Existing checks still reject a custom bounded target and preserve the queued commit for scheduled and bounded manual runs. - Local evidence uses the actual test and workflow files with installed Vitest 5.0.3 and a temporary focused config. It omits the unrelated provider-bundle global setup. No provider, external service, or paid workflow runs. - The [original required CI failure](https://github.com/paperclipai/paperclip/actions/runs/42288042001/job/128134318743) has the same five Bash failures. Its evidence is retained; no unchanged retry was used. - Whitespace checks pass. Exact head `f1d178ad483a49f8dd087a1c6d22bf43c06a39e4` has 56 successful checks, three expected skips, one neutral advisory status and successful Snyk. No check is pending or failed. The [full Runner support suite](https://github.com/paperclipai/paperclip/actions/runs/42289536842/job/128139308510), typecheck, build and [aggregate verification](https://github.com/paperclipai/paperclip/actions/runs/42289536842/job/128145908574) pass. Greptile and Paperclip Review both rate this exact head 5/5, with no findings or unresolved threads. - Environment: macOS arm64, Node 26.4.0, Vitest 5.0.3. ## Risks Low risk. This changes only fixture inputs for normal target selection. It does not change the production workflow guard, branch policy, credentials, paid execution, timeouts or assertions. Product documentation needs no update for test-only default inputs. ## Model Used OpenAI Codex, GPT-6 family, with repository analysis, code editing and tool execution. The exact runtime model ID and context window are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub references) - [x] My branch name describes the change and contains no internal Paperclip ticket id - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes (not needed: fixture-only default inputs) - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
83 lines
4.7 KiB
TypeScript
83 lines
4.7 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,
|
|
IMAGE_ONLY: "false", TESTOPS_REQUESTED: "false",
|
|
TARGET_BRANCH: branch, DEFAULT_BRANCH: "master", GITHUB_SHA: queuedSha,
|
|
MOCK_BRANCH_SHA: advancedSha, GITHUB_OUTPUT: output, REPOSITORY: "paperclipai/paperclip" },
|
|
});
|
|
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);
|
|
});
|
|
});
|