diff --git a/.github/workflows/runner-full-stack-e2e.yml b/.github/workflows/runner-full-stack-e2e.yml index 73042a12b5..7ea1660729 100644 --- a/.github/workflows/runner-full-stack-e2e.yml +++ b/.github/workflows/runner-full-stack-e2e.yml @@ -84,7 +84,15 @@ jobs: echo "RUNNER_E2E_ALLOWED_ACTOR_IDS must be a non-empty JSON array of numeric GitHub user IDs." >&2 exit 1 fi - triggering_actor_id="$(gh api "users/$TRIGGERING_ACTOR" --jq .id)" + # Retry transient transport/server failures, but never proceed without + # a successfully resolved identity and the allowlist checks below. + for attempt in 1 2 3; do + if triggering_actor_id="$(timeout 30s gh api "users/$TRIGGERING_ACTOR" --jq .id)"; then + break + fi + if [ "$attempt" = 3 ]; then exit 1; fi + sleep "$((attempt * 2))" + done if [ "$triggering_actor_id" != "$ACTOR_ID" ] && [ "$TRIGGERING_ACTOR" = "$ACTOR" ]; then echo "GitHub actor identity contexts disagree; refusing the paid run." >&2 exit 1 @@ -779,7 +787,15 @@ jobs: set -euo pipefail test "$REF" = "refs/heads/$DEFAULT_BRANCH" jq -e 'type == "array" and length > 0 and all(.[]; type == "number" and . > 0 and floor == .)' <<< "${ALLOWED_ACTOR_IDS:-}" >/dev/null - triggering_actor_id="$(gh api "users/$TRIGGERING_ACTOR" --jq .id)" + # Retry transient transport/server failures, but never proceed without + # a successfully resolved identity and the allowlist checks below. + for attempt in 1 2 3; do + if triggering_actor_id="$(timeout 30s gh api "users/$TRIGGERING_ACTOR" --jq .id)"; then + break + fi + if [ "$attempt" = 3 ]; then exit 1; fi + sleep "$((attempt * 2))" + done jq -e --argjson candidate "$triggering_actor_id" 'index($candidate) != null' <<< "$ALLOWED_ACTOR_IDS" >/dev/null jq -e --argjson candidate "$ACTOR_ID" 'index($candidate) != null' <<< "$ALLOWED_ACTOR_IDS" >/dev/null @@ -976,7 +992,7 @@ jobs: # This definition executes only from the authorized default-branch workflow. # Provision host policy before credentials reach target-controlled tests. - name: Provision Codex sandbox on the disposable trusted runner - if: matrix.environmentId == 'local' && (matrix.profileId == 'runner-codex' || matrix.profileId == 'runner-codex-mini') + if: matrix.environmentId == 'local' && (matrix.profileId == 'runner-codex' || matrix.profileId == 'runner-codex-mini' || matrix.profileId == 'runner-acpx-codex') run: | node --input-type=module <<'NODE' import { execFileSync } from "node:child_process"; @@ -1008,7 +1024,7 @@ jobs: NODE - name: Prepare pinned Python artifact oracle image - if: matrix.suiteId == 'everyday-workflows' && (matrix.caseId == 'build-revise' || matrix.caseId == 'delegate-feedback' || matrix.caseId == 'hire-reuse' || matrix.caseId == 'recover-controller' || matrix.caseId == 'stop-redirect') + if: matrix.suiteId == 'everyday-workflows' && (matrix.caseId == 'build-revise' || matrix.caseId == 'delegate-feedback' || matrix.caseId == 'agent-review-handoff' || matrix.caseId == 'hire-reuse' || matrix.caseId == 'recover-controller' || matrix.caseId == 'stop-redirect') run: | set -euo pipefail oracle_image='python@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285' @@ -1074,6 +1090,9 @@ jobs: node-version: 24 cache: pnpm + # Resolve from this trusted checkout only. Never use the target branch's + # lockfile or install scripts in the publication job. + - run: pnpm install --lockfile-only --ignore-scripts --no-frozen-lockfile - run: pnpm install --frozen-lockfile - name: Resolve workflow job attempts diff --git a/tests/runner-e2e/README.md b/tests/runner-e2e/README.md index 5ef2169bd8..7d54c59de8 100644 --- a/tests/runner-e2e/README.md +++ b/tests/runner-e2e/README.md @@ -771,3 +771,16 @@ confirmed discovery on `2.1.277`. The workflow pin and local qualifier are check together. This change applies to local cells; Daytona images remain separately pinned. Continuation question flows also wait for the submitted interaction's durable `answered` state before considering the next checkpoint ready. + +### Worker prerequisites + +The trusted default-branch workflow provisions the local Codex sandbox for both +native Codex and ACPX Codex. It prepares the pinned Python artifact oracle only +for everyday stories that execute a downloaded ZIP; skill creation and service +questions do not need that oracle. Catalog coverage tests keep this list aligned +with the test flow. Native provider runs do not require an unrelated host +`claude` or `codex` CLI for version probing. + +Changes to privileged worker setup must reach the default branch before a +branch-targeted paid campaign can exercise them. The report job resolves its +lockfile from its own trusted checkout, never from the tested branch. diff --git a/tests/runner-e2e/codex-ci-sandbox.test.ts b/tests/runner-e2e/codex-ci-sandbox.test.ts index 20bd4f63f2..b41dc24d89 100644 --- a/tests/runner-e2e/codex-ci-sandbox.test.ts +++ b/tests/runner-e2e/codex-ci-sandbox.test.ts @@ -3,6 +3,7 @@ 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, "../.."); @@ -14,6 +15,7 @@ describe("Codex CI sandbox trust boundary", () => { ["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], ] 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(); @@ -90,3 +92,15 @@ describe("Codex CI sandbox trust boundary", () => { 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).toContain("matrix.suiteId == 'everyday-workflows'"); + const cases = new Set([...condition!.matchAll(/matrix\.caseId == '([^']+)'/g)].map((match) => match[1])); + for (const cell of runnerMatrix.filter((cell) => cell.suite.id === "everyday-workflows")) { + expect(cases.has(cell.task.id), cell.id).toBe(requiresEverydayArtifactOracle(cell.task.id)); + } + expect(requiresEverydayArtifactOracle("agent-review-handoff")).toBe(true); + expect(requiresEverydayArtifactOracle("create-skill-studio")).toBe(false); +}); diff --git a/tests/runner-e2e/codex-ci-sandbox.ts b/tests/runner-e2e/codex-ci-sandbox.ts index 4e1e245bc6..b0d8faf419 100644 --- a/tests/runner-e2e/codex-ci-sandbox.ts +++ b/tests/runner-e2e/codex-ci-sandbox.ts @@ -7,7 +7,7 @@ import type { MatrixExecution } from "./types.js"; export function requiresCodexCiSandbox(execution: MatrixExecution): boolean { return execution.environment.id === "local" && execution.profile.generation === "native" - && execution.profile.provider === "codex"; + && (execution.profile.provider === "codex" || execution.profile.id === "runner-acpx-codex"); } /** Probe the existing Linux sandbox before spending provider credentials. diff --git a/tests/runner-e2e/everyday-cases.ts b/tests/runner-e2e/everyday-cases.ts index 02d4955c55..11afe75035 100644 --- a/tests/runner-e2e/everyday-cases.ts +++ b/tests/runner-e2e/everyday-cases.ts @@ -110,3 +110,8 @@ export const everydayTasks: readonly RunnerTaskFixture[] = definitions.map( buildMatchers: () => [], // The workflow records independent artifact and lifecycle checks. }), ); + +/** Only these stories execute downloaded Python ZIPs in the pinned oracle. */ +export function requiresEverydayArtifactOracle(caseId: string): boolean { + return ["build-revise", "delegate-feedback", "agent-review-handoff", "hire-reuse", "recover-controller", "stop-redirect"].includes(caseId); +} diff --git a/tests/runner-e2e/everyday-flow.ts b/tests/runner-e2e/everyday-flow.ts index 590c304ae8..6885868d8d 100644 --- a/tests/runner-e2e/everyday-flow.ts +++ b/tests/runner-e2e/everyday-flow.ts @@ -18,7 +18,7 @@ import { StoryDecisionError, type StoryInteraction, } from "./everyday-decisions.js"; -import { LATE_REQUIREMENT, SLUGIFY_REVISION } from "./everyday-cases.js"; +import { LATE_REQUIREMENT, SLUGIFY_REVISION, requiresEverydayArtifactOracle } from "./everyday-cases.js"; import { isActiveStoryRun, isStoryWorkspaceDeferral, @@ -502,11 +502,8 @@ export async function runEverydayFlow(input: Input) { await mkdir(path.join(input.privateDir, "snapshots"), { recursive: true }); const revision = await runCommand("git", ["rev-parse", "HEAD"]); if (revision.code === 0) ev.sourceRevision = revision.stdout.trim(); - const version = await runCommand( - execution.profile.provider === "acpx" ? "claude" : "codex", - ["--version"], - ); - if (version.code === 0) ev.providerVersion = version.stdout.trim(); + // Native providers run the packaged runtime (possibly remotely). A host + // `claude`/`codex` binary is neither required nor its observed version. const harnessFiles = [ "everyday-flow.ts", "everyday-cases.ts", @@ -536,7 +533,7 @@ export async function runEverydayFlow(input: Input) { .join("\n"), ) .digest("hex"); - if (!caseId.startsWith("service-") && !decliningConnection) { + if (requiresEverydayArtifactOracle(caseId)) { try { const sandbox = await runCommand(process.env.PYTHON ?? "python3", [ path.join(import.meta.dirname, "everyday-artifact.py"), "--preflight", diff --git a/tests/runner-e2e/workflow-security.test.ts b/tests/runner-e2e/workflow-security.test.ts index cfaec2ee74..6e80147d17 100644 --- a/tests/runner-e2e/workflow-security.test.ts +++ b/tests/runner-e2e/workflow-security.test.ts @@ -299,7 +299,7 @@ describe("public repository paid workflow security", () => { paidExecution, ); expect(everydayOracleStep).toContain( - "if: matrix.suiteId == 'everyday-workflows' && (matrix.caseId == 'build-revise' || matrix.caseId == 'delegate-feedback' || matrix.caseId == 'hire-reuse' || matrix.caseId == 'recover-controller' || matrix.caseId == 'stop-redirect')", + "if: matrix.suiteId == 'everyday-workflows' && (matrix.caseId == 'build-revise' || matrix.caseId == 'delegate-feedback' || matrix.caseId == 'agent-review-handoff' || matrix.caseId == 'hire-reuse' || matrix.caseId == 'recover-controller' || matrix.caseId == 'stop-redirect')", ); expect(everydayOracleStep).toContain( `oracle_image='${everydayOracleImage}'`,