Files
PaperClipAI/tests/runner-e2e/codex-ci-sandbox.test.ts
T
DottaandClaude Opus 5.5 187c0f6d6e fix(runner-e2e): prepare the artifact oracle for every suite that runs it (#19518)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The Runner Full-Stack E2E workflow runs paid Daytona restart cells
for the control-plane restart chaos campaign
> - Manual run 42243751291 failed
`daytona-restart-continuity.runner-codex.daytona.recover-controller`
before task creation: `docker version` timed out after 10 seconds in the
artifact oracle preflight
> - The everyday flow requires the Python artifact oracle by case alone
(`requiresEverydayArtifactOracle(caseId)`). The workflow step that
starts Docker and pulls the oracle image also filters by suite, so
suites that reuse `recover-controller` skip it
> - On a cold RunsOn host the first `docker version` takes about 20
seconds (the everyday cell in the same run shows this), so the 10-second
preflight fails when the workflow step did not run first
> - This pull request gates the oracle step by case only, which matches
the runtime requirement
> - The benefit is that the Daytona restart cell reaches task creation
instead of failing in setup

## Linked Issues or Issue Description

**What happened?**

Run https://github.com/paperclipai/paperclip/actions/runs/42243751291
job `daytona-restart-continuity.runner-codex.daytona.recover-controller`
failed with `Artifact sandbox qualification failed before task creation:
... docker version ... timed out after 10 seconds`. The job log shows no
"Prepare pinned Python artifact oracle image" step.

**Expected behavior**

Every cell whose flow runs the artifact oracle prepares Docker and the
pinned oracle image before paid execution.

**Steps to reproduce**

Enumerate `runnerMatrix` cells where
`requiresEverydayArtifactOracle(task.id)` is true. The cells in
`daytona-restart-continuity`, `rolling-web-deployment`,
`rolling-web-deployment-warm` and `remote-workspace-storage` do not
match the old workflow condition.

## What Changed

- `.github/workflows/runner-full-stack-e2e.yml`: the "Prepare pinned
Python artifact oracle image" step is now gated by `matrix.caseId` only.
The suite filter is removed.
- `tests/runner-e2e/codex-ci-sandbox.test.ts`: the oracle gate test now
checks every catalog cell, not only `everyday-workflows` cells, and
rejects a suite filter.
- `tests/runner-e2e/workflow-security.test.ts`: the pinned condition
string follows the new gate.

## Verification

- Red: with only the test change, `npx vitest run --config
tests/runner-e2e/vitest.config.ts
tests/runner-e2e/codex-ci-sandbox.test.ts` fails on `prepares the
artifact oracle for exactly the stories that execute downloads`.
- Green: with the workflow change, the same command passes (13/13).
- `tests/runner-e2e/workflow-security.test.ts` locally shows the same 5
failures before and after this change (`pr-trusted.yml` step counts and
`chat-catalog-testops-case.yml` credential checks). They do not involve
this step.
- Live proof: a narrow manual dispatch after merge.

## Risks

- Low risk. Cells in four more suites now run a Docker version check, a
pinned image pull and an inspect before paid execution. This adds about
20 to 30 seconds per cell. The image is pinned by digest. The step has
no secrets.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, GitHub CLI, file edit).

## 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 `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [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
- [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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:40:12 -06:00

111 lines
5.2 KiB
TypeScript

import { readFile } from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { runnerMatrix } from "./catalog.js";
import { requiresEverydayArtifactOracle } from "./everyday-cases.js";
import { requiresCodexCiSandbox } from "./codex-ci-sandbox.js";
const root = path.resolve(import.meta.dirname, "../..");
describe("Codex CI sandbox trust boundary", () => {
it.each([
["everyday-workflows.runner-codex.local.build-revise", true],
["everyday-workflows.runner-codex-mini.local.build-revise", true],
["everyday-workflows.runner-acpx-claude.local.build-revise", false],
["everyday-workflows.runner-codex.daytona.build-revise", false],
["core-compatibility.legacy-codex.local.message-marker", false],
["core-compatibility.runner-acpx-codex.local.message-marker", true],
["case-handoffs.runner-codex-subscription.local.same-task-roundtrip", true],
["case-handoffs.runner-codex-subscription.local.receipt-smoke", true],
["case-handoffs.runner-codex.local.receipt-smoke", true],
] as const)("qualifies the native local Codex sandbox for %s: %s", (id, expected) => {
const execution = runnerMatrix.find((cell) => cell.id === id);
expect(execution, id).toBeDefined();
expect(requiresCodexCiSandbox(execution!)).toBe(expected);
});
it("keeps trusted CI provisioning in sync with every catalog cell", async () => {
const workflow = await readFile(
path.join(root, ".github/workflows/runner-full-stack-e2e.yml"),
"utf8",
);
const condition = workflow.match(
/- name: Provision Codex sandbox on the disposable trusted runner\n\s+if: ([^\n]+)/,
)?.[1];
expect(condition).toMatch(
/^matrix\.environmentId == 'local' && \(needs\.catalog\.outputs\.operations_cadence == 'weekly' \|\| matrix\.profileId == '[^']+'(?: \|\| matrix\.profileId == '[^']+')*\)$/,
);
const provisionedProfiles = new Set(
[...condition!.matchAll(/matrix\.profileId == '([^']+)'/g)].map((match) => match[1]),
);
expect([...provisionedProfiles].sort()).toEqual(
[...new Set(runnerMatrix.filter(cell => cell.task.flow !== "provider_connection").filter(requiresCodexCiSandbox).map((cell) => cell.profile.id))].sort(),
);
for (const cell of runnerMatrix.filter(cell => cell.task.flow !== "provider_connection")) {
expect(
cell.environment.id === "local" && provisionedProfiles.has(cell.profile.id),
cell.id,
).toBe(requiresCodexCiSandbox(cell));
}
});
it("keeps privileged policy changes out of target-controlled tests", async () => {
const source = await readFile(
path.join(root, "tests/runner-e2e/codex-ci-sandbox.ts"),
"utf8",
);
expect(source).not.toMatch(
/execFileSync\(["']sudo|apparmor_parser|flags=\(unconfined\)/,
);
expect(source).toMatch(/"sandbox",\s*"--permission-profile",\s*"paperclip-e2e-probe"/);
expect(source).toContain(
"permissions.paperclip-e2e-probe.network.enabled=false",
);
expect(source).toContain(
'permissions.paperclip-e2e-probe.filesystem={":root"="read"}',
);
expect(source).toContain("Codex sandbox preflight failed");
expect(source).not.toContain("...process.env");
});
it("provisions the exact executable in trusted CI before provider credentials", async () => {
const workflow = await readFile(
path.join(root, ".github/workflows/runner-full-stack-e2e.yml"),
"utf8",
);
const setup = workflow.indexOf(
"- name: Provision Codex sandbox on the disposable trusted runner",
);
const paid = workflow.indexOf("- name: Run bounded campaign and retain feedback review");
expect(setup).toBeGreaterThan(
workflow.indexOf(
"- name: Reauthorize paid execution before provider access",
),
);
expect(paid).toBeGreaterThan(setup);
const step = workflow.slice(setup, paid);
expect(step).toContain("matrix.profileId == 'runner-codex-mini'");
expect(step).toContain('binary.startsWith(root + "/node_modules/.pnpm/")');
expect(step).toContain("binary.endsWith(suffix)");
expect(step).toContain('"-n", "apparmor_parser", "-r", profilePath');
expect(step).toContain('flag:"wx"');
expect(step).not.toContain("secrets.");
expect(step).not.toContain("node scripts/");
expect(step).not.toContain("sysctl -w");
});
});
it("prepares the artifact oracle for exactly the stories that execute downloads", async () => {
const workflow = await readFile(path.join(root, ".github/workflows/runner-full-stack-e2e.yml"), "utf8");
const condition = workflow.match(/- name: Prepare pinned Python artifact oracle image\n\s+if: ([^\n]+)/)?.[1];
expect(condition).not.toContain("matrix.suiteId");
const cases = new Set([...condition!.matchAll(/matrix\.caseId == '([^']+)'/g)].map((match) => match[1]));
for (const cell of runnerMatrix) {
expect(cases.has(cell.task.id), cell.id).toBe(requiresEverydayArtifactOracle(cell.task.id));
}
expect(runnerMatrix.some((cell) => cell.id === "daytona-restart-continuity.runner-codex.daytona.recover-controller")).toBe(true);
expect(requiresEverydayArtifactOracle("agent-review-handoff")).toBe(true);
expect(requiresEverydayArtifactOracle("create-skill-studio")).toBe(false);
});