mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-11 14:10:50 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Paperclip uses a paid full-stack campaign to verify runner behavior across providers and environments. > - The campaign already creates an interactive report, workflow logs, and retained evidence artifacts. > - The merge job summary shows result totals but does not link to those resources. > - Reviewers must search several workflow jobs and artifacts to find the executed cells. > - This pull request adds direct and safe links to the exact campaign, each cell, the workflow logs, and the artifacts. > - The benefit is that a reviewer can inspect a result from the Actions summary with one click. ## Linked Issues or Issue Description **What existing behavior does this improve?** This improves the `Merge and enforce campaign result` summary in the `Runner Full-Stack E2E` workflow. **Subsystem affected** The runner E2E report generator and its GitHub Actions workflow are affected. **Current behavior** The summary lists each selected cell and its result. It does not link to the published campaign report, the workflow logs, or the evidence artifacts. **Proposed behavior** The summary includes a `View results` section. It links to the exact immutable campaign report, the workflow logs, and the artifacts. Each cell name links to its stable section in the campaign report. **Reason and benefit** The current summary does not show reviewers where to inspect the run. Direct links make the result evidence discoverable without manual URL construction or artifact searches. **Breaking changes** None. This change only adds links and stable HTML anchors to existing report output. **Additional context** Related: #12904. The cited successful campaign is [run 34026735033](https://github.com/paperclipai/paperclip/actions/runs/34026735033). ## What Changed - Add a safe URL builder for public campaign, workflow, and artifact links. - Add a `View results` section to the GitHub Actions campaign summary. - Link each summary table cell to its exact section in the immutable campaign report. - Add stable execution anchors to the generated dashboard. - Reject non-HTTPS, credential-bearing, malformed, and ambiguous link destinations. - Document the new links and their retention or publication timing. ## Verification - `pnpm test:e2e:runner:unit` — 116 tests passed. - `pnpm test:e2e:runner:typecheck` — passed. - `pnpm typecheck` — passed, including migration safety. - `pnpm build` — passed. - `pnpm exec prettier --check ...` for all changed files — passed. - `git diff --check origin/master...HEAD` — passed. - The full local server suite also ran. One unrelated macOS workspace-runtime file passed 157 tests and failed 4 existing path and port assumptions. Two failures compare `/var` with `/private/var`. Two failures cannot reserve a port outside a hard-coded range. This PR does not change that file or its dependencies. ## Risks - The immutable campaign link becomes available after the history publisher completes. The workflow and artifact links remain available while publication runs. - The artifact link requires GitHub access and follows the existing 30-day retention period. - Invalid configured URLs are omitted instead of being rendered into the summary. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex desktop agent with GPT-5. The runtime does not expose the context-window size. The agent used repository inspection, agentic reasoning, code execution, and GitHub CLI tools. ## 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/no-internal-issue-references`, `fix/sandbox-secret-resolution`, `feat/adapter-retry-backoff`) 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
429 lines
22 KiB
Markdown
429 lines
22 KiB
Markdown
# Paid runner full-stack E2E
|
||
|
||
This is the billable browser acceptance campaign system for Paperclip runner
|
||
profiles. It is deliberately separate from `tests/e2e`: every independently
|
||
scheduled execution gets
|
||
a fresh Paperclip home, embedded Postgres database, instance configuration,
|
||
port, workspace, company, encrypted secrets, environment, and agent.
|
||
|
||
The vocabulary is: a **campaign** is one workflow invocation against one SHA; a
|
||
**suite** is a durable testing purpose; a **matrix** is that suite's profiles ×
|
||
environments × cases; an **execution/cell** is one parallel job; and an
|
||
**attempt** is one isolated harness run, including an infrastructure retry.
|
||
|
||
The browser creates and assigns the task. The harness does not call a private
|
||
runner hook or write fixtures directly to the database.
|
||
|
||
## Credentials
|
||
|
||
Copy `.env.runner-e2e.example` to `.env.runner-e2e.local` and fill only the
|
||
credentials needed by the selected cells:
|
||
|
||
```bash
|
||
cp .env.runner-e2e.example .env.runner-e2e.local
|
||
chmod 600 .env.runner-e2e.local
|
||
```
|
||
|
||
Shell variables take precedence over the local file. The recognized names are:
|
||
|
||
- `OPENAI_API_KEY`
|
||
- `ANTHROPIC_API_KEY`
|
||
- `OPENROUTER_API_KEY`
|
||
- `DAYTONA_API_KEY`
|
||
- `PAPERCLIP_E2E_DAYTONA_IMAGE` (Daytona only)
|
||
|
||
The image must be an immutable `image@sha256:...` reference. The launcher
|
||
reports missing variable names but never prints values. It passes raw provider
|
||
keys only to Playwright, which posts each value once to the company-secrets API.
|
||
Paperclip receives secret references in agent/environment payloads. Provider
|
||
keys, Daytona keys, `DATABASE_URL`, and `DATABASE_MIGRATION_URL` are removed
|
||
from the Paperclip child process.
|
||
|
||
Never put credentials in `catalog.ts`, screenshots, fixture metadata, workflow
|
||
inputs, or a tracked env file.
|
||
|
||
## Local commands
|
||
|
||
Install dependencies and Chromium once. Native local cells also need the local
|
||
runner binaries:
|
||
|
||
```bash
|
||
pnpm install
|
||
pnpm exec playwright install chromium
|
||
pnpm --filter @paperclipai/paperclip-runner build:runner-binaries
|
||
```
|
||
|
||
List cells without loading credentials or starting Paperclip:
|
||
|
||
```bash
|
||
pnpm test:e2e:runner -- --list
|
||
```
|
||
|
||
Examples of explicit billable runs:
|
||
|
||
```bash
|
||
pnpm test:e2e:runner -- --id core-compatibility.legacy-codex.local.message-marker --headed
|
||
pnpm test:e2e:runner -- --suite openrouter-model-breadth --case hello-complete
|
||
pnpm test:e2e:runner -- --group native --environment local
|
||
pnpm test:e2e:runner -- --profile runner-codex --case message-marker
|
||
pnpm test:e2e:runner -- --case plan-revise-accept --group local
|
||
pnpm test:e2e:runner -- --case ask-question --group native
|
||
pnpm test:e2e:runner -- --suite daytona-warm-continuity
|
||
pnpm test:e2e:runner -- --all
|
||
```
|
||
|
||
The catalog contains four suites. `core-compatibility` (**Core Runner
|
||
Compatibility**) is seven major runner profiles × local/Daytona × three
|
||
workflows: 42 cells. Its cases are:
|
||
|
||
- `message-marker`: one basic visible response and Done transition;
|
||
- `plan-revise-accept`: an initial Plan, a browser-requested revision on the
|
||
same Plan, browser acceptance of the new revision, and verified execution;
|
||
- `ask-question`: a direct answer from a task created in Ask mode.
|
||
|
||
`openrouter-model-breadth` (**OpenRouter Model Breadth**) is four qualified
|
||
models from the tracked weekly tool-capable ranking snapshot × native OpenCode
|
||
× local, with 10 supported model/workflow cells. Xiaomi MiMo V2.5 remains
|
||
recorded in the immutable ranking snapshot but is excluded from paid
|
||
qualification because its latency repeatedly exhausts the cell deadline.
|
||
DeepSeek V4 Flash remains qualified for hello and question/resume, but its Plan
|
||
cell is excluded after three successful semantic completions consistently
|
||
ignored the required exact final response. Tencent HY3 likewise remains
|
||
qualified for hello and question/resume, but its Plan cell is excluded after
|
||
two fresh attempts completed every durable Plan and finalization operation yet
|
||
consistently replaced the required exact visible terminal marker with prose.
|
||
Its cases are:
|
||
|
||
- `hello-complete`: a basic nonce response and explicit Done transition;
|
||
- `question-resume-complete`: one structured question, browser selection of
|
||
“Cobalt,” then a resumed completion on the same task; and
|
||
- `plan-approve-complete`: one exact two-step Plan, browser approval of that
|
||
revision, then a resumed completion on the same task.
|
||
|
||
`local-session-integrity` (**Local Session Integrity**) is the seven supported
|
||
local native and direct-adapter profiles × two two-run structured-question
|
||
workflows: 14 cells. Both prove that a required structured interaction is
|
||
rendered, answered in the browser, and resumed once on the same task without
|
||
duplicating the final response. The second workflow restarts the isolated
|
||
Paperclip server while the interaction is waiting, reloads that state, and
|
||
then resumes it. The suite has no Daytona cells.
|
||
|
||
`daytona-warm-continuity` (**Daytona Warm Continuity**) is exactly two paid
|
||
cells: legacy Codex and Runner Codex against one reusable warm Daytona
|
||
configuration. Each cell creates a real project with a primary local-path
|
||
workspace through the API, selects it in the browser task dialog, and performs
|
||
three browser-driven turns on one issue. Every turn reads and extends the same
|
||
nonce file, verifies host copy-back, records scheduler/run/end-to-end timing,
|
||
and asserts `created`, `resumed`, `resumed` lease acquisition on one sandbox.
|
||
Runner Codex additionally proves stable native session, provider session,
|
||
runner instance, PID, and process-start identity. Each turn is bounded to ten
|
||
minutes, the cell to thirty minutes, and cleanup explicitly deletes the
|
||
sandbox rather than waiting for Daytona's idle timeout.
|
||
|
||
The complete catalog is 68 cells (45 local and 23 Daytona) and 120 expected
|
||
paid agent turns. Follow-up steps remain ordered within their cell; all other
|
||
cells are independent. Narrow selectors are strongly recommended while
|
||
developing fixtures.
|
||
|
||
`--suite`, `--group`, `--profile`, `--environment`, and `--case` are repeatable. Repeated
|
||
values in one dimension use OR semantics; dimensions and repeated groups use
|
||
AND semantics. `--id` is exclusive with dimension selectors and `--all`.
|
||
`--headed`, `--ui`, and `--debug` are forwarded to Playwright. An unknown
|
||
selector, an empty selection, or a run with no explicit selector exits before
|
||
Paperclip starts. `--max-parallel <n>` controls the number of isolated
|
||
profile/environment/case harnesses that can overlap (default 1, also configurable
|
||
with `PAPERCLIP_E2E_MAX_PARALLEL`). Headed/UI/debug runs are forced to one worker.
|
||
The Plan case is still sequential internally because its turns share one task;
|
||
it runs in parallel with unrelated scenarios.
|
||
|
||
Use a single `--id` smoke test for routine local verification. Full-matrix
|
||
parallelism is intended for GitHub Actions; raising local parallelism starts
|
||
multiple Paperclip/Postgres/Chromium stacks and can consume substantial CPU and
|
||
memory.
|
||
|
||
Credential-free checks are:
|
||
|
||
```bash
|
||
pnpm test:e2e:runner:unit
|
||
pnpm test:e2e:runner:typecheck
|
||
```
|
||
|
||
The OpenRouter ranking snapshot is tracked in `openrouter-models.json`; nightly
|
||
runs never mutate it. Refresh it deliberately, review the source/capture/hash
|
||
diff, and rerun credential-free checks:
|
||
|
||
```bash
|
||
pnpm test:e2e:runner:models:update
|
||
```
|
||
|
||
## Daytona image
|
||
|
||
Use the immutable digest printed by the `Publish verified Daytona image` job,
|
||
or publish the current source locally:
|
||
|
||
```bash
|
||
content_id="$(pnpm --silent test:e2e:runner:image-id)"
|
||
source_revision="$(git rev-parse HEAD)"
|
||
image="ghcr.io/paperclipai/paperclip-daytona-runner:e2e-content-${content_id}"
|
||
if ! docker buildx imagetools inspect "$image" >/dev/null 2>&1; then
|
||
docker buildx build \
|
||
--platform linux/amd64 \
|
||
--build-arg "PAPERCLIP_RUNNER_CONTENT_ID=${content_id}" \
|
||
--build-arg "PAPERCLIP_RUNNER_SOURCE_REVISION=${source_revision}" \
|
||
--file docker/daytona-runner/Dockerfile \
|
||
--tag "$image" \
|
||
--push \
|
||
.
|
||
fi
|
||
docker buildx imagetools inspect "$image"
|
||
```
|
||
|
||
The content ID hashes the audited image inputs, including the Dockerfile,
|
||
platform, root package/lock/build configuration, dependency patches,
|
||
`paperclip-eval-kernel`, and `paperclip-runner`. Changes elsewhere in the
|
||
repository keep the same tag and reuse the already signed image. The Git SHA is
|
||
stored separately as image provenance. CI reads that provenance back from a
|
||
reused image when it builds the controller-side provider pack, preserving the
|
||
exact manifest match required to avoid restaging the pack into Daytona.
|
||
|
||
Resolve the manifest digest and set `PAPERCLIP_E2E_DAYTONA_IMAGE` to
|
||
`ghcr.io/paperclipai/paperclip-daytona-runner@sha256:...`. The repository
|
||
workflow signs that digest with Cosign/OIDC and verifies that it is publicly
|
||
pullable, includes the provider pack, and advertises `dial_ws_loopback`,
|
||
`dial_wss`, and `listen_ws`. The GHCR package must be configured as public;
|
||
the image job deliberately fails its anonymous-pull check otherwise. Existing
|
||
content tags are never rebuilt or overwritten by the workflow.
|
||
|
||
## Evidence and cleanup
|
||
|
||
Packaged, access-controlled evidence is written beneath
|
||
`tests/runner-e2e/results/<campaign>/...`. Passing attempts include
|
||
`final-state.png`, Plan draft/revision screenshots when applicable, matcher
|
||
outcomes, sanitized fixture/API metadata, a result record, JUnit, HTML, and a
|
||
blob report. Failures additionally retain the Playwright trace/video, browser
|
||
diagnostics, failure screenshot, and sanitized Paperclip/run logs when
|
||
produced. WebM files remain limited to the local results directory and
|
||
access-controlled GitHub Actions artifact. Declared PNG screenshots are also
|
||
published with permanent campaign dashboards; fixture authors must therefore
|
||
keep credentials and other private data out of every captured UI state. SVG is
|
||
active content and is rejected from the packaged evidence entirely.
|
||
|
||
Every completed local campaign also writes
|
||
`tests/runner-e2e/results/<campaign>/dashboard.html`. The self-contained page
|
||
shows the complete profile/environment grid with screenshot thumbnails.
|
||
Expanding a case shows its matchers, pass/fail details, provider/model/runtime,
|
||
timings, token and cost accounting, and evidence links. The campaign header
|
||
aggregates input, output, and cached tokens, provider-reported LLM spend,
|
||
Daytona list-price runtime estimates, and pricing coverage. Missing provider
|
||
usage is labeled `unavailable` or `unpriced`; it is never presented as zero
|
||
cost. The CI report job stages the same portable site at
|
||
`normalized/index.html` inside the access-controlled merged report artifact.
|
||
|
||
Permanent publication uses two explicit bundles. Both retain only normalized
|
||
result PNG files with the explicit `public-runner-fixture` publication marker,
|
||
including marked `failure.png` captures, so every campaign dashboard has its
|
||
screenshot thumbnails and gallery. The capture helper adds this marker only
|
||
for the reviewed runner fixture and blocks public capture outside the exact
|
||
issue route for the fixture that the harness created. A blocked failure capture
|
||
remains private. The CloudFront-backed S3
|
||
history also contains one publisher-generated
|
||
`public-images/campaign-summary.png`.
|
||
Trusted publisher code renders it offline from fixed catalog labels and
|
||
sanitized status/count/duration fields; provider output, error text, comments,
|
||
and target-produced pixels are never inputs. The PNG must pass a 12 MiB bound
|
||
and signature validation before entering the immutable manifest. S3 also
|
||
retains allowlisted inert per-attempt evidence (`.json`, `.log`, `.md`, and
|
||
`.txt`); `.log` copies have already passed exact-value/key-shape scanning and
|
||
redaction. The GitHub Pages bundle is regenerated separately with the same
|
||
declared-screenshot boundary.
|
||
|
||
Both public bundles exclude video, archives, raw/unallowlisted logs, SVG or
|
||
other active content, generated Playwright/blob/HTML report trees, and
|
||
undeclared PNG files, and per-attempt XML. The root `junit.xml` remains public
|
||
because the report aggregator builds it from fixed markup and XML-escaped
|
||
fields. Full evidence remains available only in the access-controlled workflow
|
||
artifact.
|
||
|
||
### Billing interpretation
|
||
|
||
Each result contains raw sanitized `usage`, normalized `billing`, and
|
||
`runtimeUsage`:
|
||
|
||
- LLM token and dollar values come from the persisted heartbeat-run usage. A
|
||
multi-turn case aggregates every selected run and records how many runs
|
||
supplied tokens and provider-reported cost.
|
||
- Local execution records agent run time but is `not_metered` because there is
|
||
no external environment provider charge to attribute.
|
||
- Daytona records every public-API lease window and its pinned 4 vCPU, 4 GiB
|
||
RAM, and 10 GiB disk allocation. Its runtime dollar value is an estimate at
|
||
the versioned public list rates in `billing.ts`, not an invoice amount.
|
||
Credits, discounts, the storage allowance, and delayed billing adjustments
|
||
can make the eventual Daytona charge lower.
|
||
|
||
`normalized-results.json` uses the v2 campaign schema and includes per-test,
|
||
per-suite, and overall billing. The compact `history.json` index retains the
|
||
same metrics per campaign/suite/execution, source SHA/ref, definition
|
||
fingerprints, completeness, retries, and cleanup. Trend charts compare only
|
||
complete campaigns by default; partial/manual selections remain browsable.
|
||
`summary.md` carries the current totals into the GitHub Actions job summary.
|
||
In CI, its **View results** section links to the exact immutable public campaign
|
||
report, the workflow and per-cell logs, and the access-controlled report
|
||
artifacts. Each cell name links to its exact section in the campaign report.
|
||
The public campaign links become available after the history publisher
|
||
finishes. The artifact links remain available for 30 days.
|
||
|
||
### Iterate on a published dashboard without rerunning paid tests
|
||
|
||
Download and extract the `github-pages` artifact from an existing workflow run,
|
||
then regenerate only its HTML from the retained `normalized-results.json` and
|
||
public structured evidence files. The Pages artifact has already had private
|
||
visual and generated report evidence removed:
|
||
|
||
```bash
|
||
gh run download <run-id> --repo paperclipai/paperclip --name github-pages --dir /tmp/runner-e2e-pages
|
||
mkdir /tmp/runner-e2e-site
|
||
tar -xf /tmp/runner-e2e-pages/artifact.tar -C /tmp/runner-e2e-site
|
||
pnpm test:e2e:runner:dashboard -- /tmp/runner-e2e-site
|
||
# Optionally use a downloaded history index:
|
||
pnpm test:e2e:runner:dashboard -- /tmp/runner-e2e-site --history /tmp/history.json
|
||
```
|
||
|
||
Serve that directory with any static file server. This path does not start
|
||
Paperclip, invoke an agent, create a Daytona lease, or consume provider tokens.
|
||
|
||
Before an access-controlled evidence artifact is uploaded, the launcher:
|
||
|
||
1. copies only allowlisted file types;
|
||
2. scans raw API snapshots before sanitizing them;
|
||
3. scans the closed Paperclip home/database and workspace as streams;
|
||
4. redacts loaded exact values and known provider-key shapes from text;
|
||
5. expands ZIP reports for secret scanning;
|
||
6. rejects SVG and other unsafe files and fails the cell if a leak is detected;
|
||
and
|
||
7. verifies that a passing attempt has its final-state screenshot.
|
||
|
||
The temporary Paperclip home, embedded database, raw workspace, master key,
|
||
and unredacted logs are removed after each attempt. Daytona teardown destroys
|
||
the environment and any reusable leases through the public API; provider-side
|
||
auto-stop/archive/delete values remain as cancellation backstops.
|
||
|
||
## GitHub Actions
|
||
|
||
`Runner Full-Stack E2E` has only `schedule` and `workflow_dispatch` triggers; it
|
||
never runs for a pull request or ordinary push. Start the trusted workflow from
|
||
the default branch. A CODEOWNER can set the optional `target_branch` input to
|
||
any branch in `paperclipai/paperclip`. The authorization job resolves that
|
||
branch to one immutable commit before any checkout. A separate credential-free
|
||
job checks out the resolved commit and regenerates `pnpm-lock.yaml` once with
|
||
`--ignore-scripts --no-frozen-lockfile --lockfile-only`. It uploads that exact
|
||
lockfile under a run-attempt-scoped artifact ID and records its SHA-256.
|
||
Catalog, image, shared-build, provider-pack, and paid test jobs download the
|
||
artifact by ID, verify its digest, and restore it before setup or a frozen
|
||
install. The shared-build, provider-pack, and paid test jobs all disable
|
||
dependency lifecycle scripts, and provider secrets are introduced only in the
|
||
final test step. This permits an authorized target branch to exercise an
|
||
intentionally uncommitted workspace patch while keeping every target job on one
|
||
identical dependency resolution. The shared-build job compiles the selected
|
||
campaign's TypeScript outputs and native binaries once, then each paid cell
|
||
verifies and extracts the immutable bundle. Remote native cells similarly reuse
|
||
one verified provider pack. Report
|
||
sanitization and AWS history publication do not consume the target lockfile;
|
||
they explicitly check out and install from the trusted workflow commit. The
|
||
workflow definition, runner-group permission, and protected-environment
|
||
deployment still come from the default branch. Do not select the target branch
|
||
in GitHub's **Use workflow from** control.
|
||
|
||
Because this repository is public, manual campaigns fail before checkout unless
|
||
the trusted workflow runs from the default branch and both the original actor
|
||
and rerun actor have numeric GitHub user IDs in the non-empty JSON-array
|
||
repository variable `RUNNER_E2E_ALLOWED_ACTOR_IDS`. Keep this stable-ID list in
|
||
sync with the owners of `.github/**` in `.github/CODEOWNERS`. Usernames are
|
||
intentionally not trusted. The first scheduled attempt is trusted automation;
|
||
any human rerun of a scheduled campaign must pass the triggering-actor
|
||
allowlist.
|
||
|
||
For example, this command runs one branch cell through the trusted default-branch
|
||
workflow:
|
||
|
||
```bash
|
||
gh workflow run runner-full-stack-e2e.yml \
|
||
--ref master \
|
||
-f target_branch=fix/example \
|
||
-f all=false \
|
||
-f id=core-compatibility.runner-codex.local.message-marker
|
||
```
|
||
|
||
Create a protected `runner-e2e-paid` GitHub environment, restrict it to the
|
||
default branch, limit environment administration to trusted maintainers, and
|
||
store the four provider secrets there. This is a second authorization boundary:
|
||
the pre-check prevents unauthorized scheduling, while the environment prevents
|
||
secret release if the workflow gate is accidentally weakened. Also restrict
|
||
Actions to approved actions and require review of `.github/workflows/**` and
|
||
`tests/runner-e2e/**` through CODEOWNERS and branch protection. Manual inputs
|
||
accept comma-separated values for repeatable dimensions.
|
||
|
||
The nightly cron is `08:47 UTC`, but scheduled execution is intentionally gated
|
||
by the repository variable `RUNNER_FULL_STACK_E2E_NIGHTLY_ENABLED=true`. Set it
|
||
only after the live acceptance ladder in the architecture plan is green.
|
||
Set `RUNNER_E2E_AWS_ENABLED=true` to route paid cells to the repository-scoped
|
||
ephemeral AWS RunsOn fleet selected by
|
||
`runs-on/fleet=paperclip-public-pr-x64/env=public-ci`. Any other value uses the
|
||
proven GitHub-hosted `ubuntu-latest` target. Set `RUNNER_E2E_MAX_PARALLEL` to an
|
||
integer from 1–100 on AWS (default 100); use at least 68 to run the current
|
||
complete catalog in one wave. The fallback runner retains its 1–57 limit and
|
||
default of 32. Multi-turn steps are sequential inside their cell while
|
||
independent cells overlap. Artifacts and merged HTML/JUnit/normalized reports
|
||
are retained for 30 days.
|
||
|
||
Restrict the RunsOn fleet to this repository and independently trusted
|
||
workflows. Do not let untrusted pull-request or fork-triggered workflows target
|
||
it, and require a fresh ephemeral instance for each job so one paid cell cannot
|
||
leave state for the next. Provider secrets remain protected by the stable-ID
|
||
authorization checks and the default-branch-only `runner-e2e-paid` environment;
|
||
the fleet itself is not an authorization boundary. These external fleet controls
|
||
are as important as the workflow checks in a public repository. A CODEOWNER
|
||
dispatch is an explicit authorization to execute the selected repository branch
|
||
with the cell's scoped provider credential.
|
||
|
||
Development branch campaigns share a concurrency key per target branch and
|
||
cancel an older run when a replacement is dispatched. Default-branch target
|
||
campaigns are retained and are never auto-cancelled, preserving their audit
|
||
trail.
|
||
|
||
GitHub Actions artifacts are access-controlled 30-day operational copies, not
|
||
the permanent public history. They retain packaged PNG/WebM and generated
|
||
reports for debugging. Create a second protected `runner-e2e-history`
|
||
environment, restricted to the default branch and trusted environment
|
||
administrators, then configure these repository variables:
|
||
|
||
- `RUNNER_E2E_HISTORY_AWS_ROLE_ARN`
|
||
- `RUNNER_E2E_HISTORY_AWS_REGION`
|
||
- `RUNNER_E2E_HISTORY_S3_BUCKET`
|
||
- `RUNNER_E2E_HISTORY_PUBLIC_BASE_URL`
|
||
- optional `RUNNER_E2E_HISTORY_PREFIX` (default `runner-e2e`)
|
||
|
||
The job exchanges GitHub OIDC for short-lived AWS credentials; never add AWS
|
||
access-key secrets. Its IAM role must trust only
|
||
`repo:paperclipai/paperclip:environment:runner-e2e-history`, and permit only
|
||
Get/List/Put under the configured prefix—never Delete. Enable S3 versioning and
|
||
Block Public Access. CloudFront reads the private bucket through Origin Access
|
||
Control. Immutable campaign bundles live under `campaigns/<run-id>-<attempt>/`;
|
||
mutable `history.json`, `latest.json`, and `latest-green.json` are updated by a
|
||
globally serialized publisher. An existing campaign key with a different
|
||
bundle digest fails closed.
|
||
|
||
GitHub Pages remains the stable latest dashboard. Enable Pages with GitHub
|
||
Actions as its source and set `RUNNER_FULL_STACK_E2E_PUBLISH_PAGES=true`.
|
||
The publisher creates an S3 stage with the trusted synthetic summary PNG and a
|
||
separate Pages stage. Both surfaces publish only per-result PNG screenshots
|
||
with the explicit `public-runner-fixture` marker alongside sanitized structured
|
||
evidence. The runner capture helper refuses to mark a screenshot outside the
|
||
exact live fixture issue route. Neither surface publishes video, archives,
|
||
SVG/active content, databases, Paperclip homes, workspaces, raw/unallowlisted
|
||
logs, or credentials.
|
||
|
||
See [FIXTURES.md](./FIXTURES.md) before adding or changing a profile,
|
||
environment, task, matcher, or future Paperclip object fixture.
|
||
See [SECURITY.md](./SECURITY.md) before enabling paid dispatch, the runner
|
||
group, or permanent public history in this public repository.
|