ci(hermes): provision pinned runtime for selected paid campaigns

Select candidate assets consistently for immutable image identity and remote packs; provision local Linux assets before the protected paid step exposes credentials. Preserve default-branch dispatch and qualification gates.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-08 12:28:11 -05:00
1 parent 4a0b327727
commit 5c2a78fa45
4 files changed
+70 -7

No files matched your search

+28 -7
View File
@@ -223,6 +223,7 @@ jobs:
needs_runner_typescript: ${{ steps.catalog.outputs.needs_runner_typescript }}
needs_native_binaries: ${{ steps.catalog.outputs.needs_native_binaries }}
needs_remote_provider_pack: ${{ steps.catalog.outputs.needs_remote_provider_pack }}
candidate_providers: ${{ steps.catalog.outputs.candidate_providers }}
execution_ids: ${{ steps.catalog.outputs.execution_ids }}
max_parallel: ${{ steps.catalog.outputs.max_parallel }}
daytona_image_content_id: ${{ steps.daytona_image_content.outputs.content_id }}
@@ -271,12 +272,6 @@ jobs:
- run: pnpm install --frozen-lockfile
# The v2 contract fails closed unless every Docker FROM is digest-pinned,
# and hashes those exact base references into the immutable image tag.
- name: Compute Daytona image content ID with pinned bases
id: daytona_image_content
run: echo "content_id=$(pnpm --silent test:e2e:runner:image-id)" >> "$GITHUB_OUTPUT"
- name: Validate selectors and emit matrix
id: catalog
env:
@@ -341,6 +336,7 @@ jobs:
echo "needs_runner_typescript=$(jq -r '[.include[] | select((.profileId == "runner-opencode") or (.profileId | startswith("runner-acpx-")) or (.suiteId == "openrouter-model-breadth"))] | length > 0' <<< "$catalog_json")"
echo "needs_native_binaries=$(jq -r '[.include[] | select((.profileId | startswith("runner-")) or (.suiteId == "openrouter-model-breadth"))] | length > 0' <<< "$catalog_json")"
echo "needs_remote_provider_pack=$(jq -r '[.include[] | select((.environmentId == "daytona") and ((.profileId == "runner-opencode") or (.profileId | startswith("runner-acpx-"))))] | length > 0' <<< "$catalog_json")"
echo "candidate_providers=$(jq -r 'if any(.include[]; .profileId == "runner-acpx-hermes") then "hermes" else "" end' <<< "$catalog_json")"
echo "execution_ids=$(jq -c '.executionIds' <<< "$catalog_json")"
} >> "$GITHUB_OUTPUT"
if ! [[ "$MAX_PARALLEL_LIMIT" =~ ^[1-9][0-9]*$ ]] || [ "$MAX_PARALLEL_LIMIT" -gt 100 ]; then
@@ -360,6 +356,14 @@ jobs:
fi
echo "max_parallel=$MAX_PARALLEL" >> "$GITHUB_OUTPUT"
# Exact optional provider assets are part of the immutable image identity.
# Selecting assets never promotes a pending profile to qualified.
- name: Compute Daytona image content ID with pinned bases
id: daytona_image_content
env:
CANDIDATE_PROVIDERS: ${{ steps.catalog.outputs.candidate_providers }}
run: echo "content_id=$(pnpm --silent test:e2e:runner:image-id "--candidate-providers=$CANDIDATE_PROVIDERS")" >> "$GITHUB_OUTPUT"
daytona_image:
name: Publish verified Daytona image
needs: [authorize, target_lock, catalog]
@@ -433,6 +437,7 @@ jobs:
IMAGE_CACHE: ghcr.io/paperclipai/paperclip-daytona-runner:e2e-buildcache-amd64
TARGET_SHA: ${{ needs.authorize.outputs.target_sha }}
TARGET_LOCK_SHA256: ${{ needs.target_lock.outputs.lock_sha256 }}
CANDIDATE_PROVIDERS: ${{ needs.catalog.outputs.candidate_providers }}
TARGET_REF: ${{ needs.authorize.outputs.target_ref }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
@@ -467,6 +472,7 @@ jobs:
--build-arg "PAPERCLIP_RUNNER_CONTENT_ID=${IMAGE_CONTENT_ID}" \
--build-arg "PAPERCLIP_RUNNER_SOURCE_REVISION=${TARGET_SHA}" \
--build-arg "PAPERCLIP_RUNNER_LOCK_SHA256=${TARGET_LOCK_SHA256}" \
--build-arg "PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS=${CANDIDATE_PROVIDERS}" \
--file docker/daytona-runner/Dockerfile \
--tag "$IMAGE_TAG" \
"${cache_args[@]}" \
@@ -721,6 +727,13 @@ jobs:
test -d packages/paperclip-eval-kernel/dist
test -d packages/paperclip-runner/dist
- name: Provision pinned Hermes assets before assembling the remote pack
if: needs.catalog.outputs.needs_remote_provider_pack == 'true' && needs.catalog.outputs.candidate_providers == 'hermes'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends python3-venv
bash tests/runner-e2e/provision-hermes-linux.sh
- name: Assemble native remote provider pack
if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
env:
@@ -728,7 +741,8 @@ jobs:
# content ID. Matching that revision lets remote execution reuse the
# verified pack already installed in the immutable image.
PAPERCLIP_RUNNER_SOURCE_REVISION: ${{ needs.daytona_image.outputs.source_revision }}
run: node packages/paperclip-runner/scripts/build-provider-pack.mjs packages/paperclip-runner/provider-pack
CANDIDATE_PROVIDERS: ${{ needs.catalog.outputs.candidate_providers }}
run: node packages/paperclip-runner/scripts/build-provider-pack.mjs packages/paperclip-runner/provider-pack "--candidate-providers=$CANDIDATE_PROVIDERS"
- name: Package verified remote provider pack
if: needs.catalog.outputs.needs_remote_provider_pack == 'true'
@@ -890,6 +904,13 @@ jobs:
if: matrix.environmentId == 'local' && (matrix.profileId == 'legacy-opencode' || matrix.profileId == 'runner-opencode' || matrix.suiteId == 'openrouter-model-breadth')
run: node packages/paperclip-runner/scripts/materialize-opencode-binary.mjs
- name: Provision pinned Hermes before the paid local test
if: matrix.environmentId == 'local' && matrix.profileId == 'runner-acpx-hermes'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends python3-venv bubblewrap
bash tests/runner-e2e/provision-hermes-linux.sh
- name: Install checksum-verified Grok executable
if: matrix.environmentId == 'local' && (matrix.profileId == 'runner-acpx-grok' || matrix.profileId == 'runner-acpx-grok-subscription')
run: sudo node packages/paperclip-runner/scripts/provision-grok.mjs /opt/paperclip/providers/grok/1.0.13/grok
+7
View File
@@ -1477,6 +1477,13 @@ Hermes uses an existing managed OpenRouter connection through the ordinary
connection fixture. It remains pending qualification; the native transport
fixture does not substitute for these browser and remote workflows.
When a Hermes cell is selected, the trusted workflow provisions its pinned
Python closure on Linux before exposing credentials, and selects the same
candidate assets for the image content identity, Docker build and remote
provider pack. The workflow must be available on the default branch before
dispatching a branch campaign; dispatching from a development branch remains
forbidden. The asset selection leaves Hermes pending qualification.
```sh
pnpm test:e2e:runner -- --list --suite extended-harnesses
pnpm test:e2e:runner -- --id extended-harnesses.runner-acpx-pi.local.hello-complete
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Credential-free setup for an explicitly selected native Hermes campaign.
set -euo pipefail
test "$(uname -s)" = Linux
test "$(uname -m)" = x86_64
repository_root="$(cd "$(dirname "$0")/../.." && pwd)"
hermes_build_root="$(mktemp -d "${RUNNER_TEMP:-${TMPDIR:-/tmp}}/hermes-build.XXXXXX")"
trap 'rm -rf "$hermes_build_root"' EXIT
python3 -m venv "$hermes_build_root"
"$hermes_build_root/bin/pip" install --disable-pip-version-check --no-input uv==0.12.17
PATH="$hermes_build_root/bin:$PATH" node "$repository_root/packages/paperclip-runner/scripts/provision-hermes.mjs" \
"$repository_root/packages/paperclip-runner/provider-assets/hermes/linux-x64"
@@ -15,6 +15,29 @@ const everydayOracleImage =
"python@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285";
describe("public repository paid workflow security", () => {
it("provisions selected Hermes assets before credentials and uses the same selection for image identity and packs", async () => {
const workflow = await readFile(path.join(repositoryRoot, ".github/workflows/runner-full-stack-e2e.yml"), "utf8");
const catalog = workflow.slice(workflow.indexOf(" catalog:"), workflow.indexOf(" daytona_image:"));
expect(catalog).toContain('if any(.include[]; .profileId == "runner-acpx-hermes") then "hermes" else "" end');
expect(catalog.indexOf("Validate selectors and emit matrix")).toBeLessThan(catalog.indexOf("Compute Daytona image content ID"));
expect(catalog).toContain('"--candidate-providers=$CANDIDATE_PROVIDERS"');
const image = workflow.slice(workflow.indexOf(" daytona_image:"), workflow.indexOf(" build_runner_artifacts:"));
expect(image).toContain('--build-arg "PAPERCLIP_RUNNER_CANDIDATE_PROVIDERS=${CANDIDATE_PROVIDERS}"');
const pack = workflow.slice(workflow.indexOf(" build_remote_provider_pack:"), workflow.indexOf(" test:"));
expect(pack).toContain("needs.catalog.outputs.candidate_providers == 'hermes'");
expect(pack.indexOf("Provision pinned Hermes assets")).toBeLessThan(pack.indexOf("Assemble native remote provider pack"));
expect(pack).toContain('"--candidate-providers=$CANDIDATE_PROVIDERS"');
expect(pack).not.toContain("secrets.");
const paid = workflow.slice(workflow.indexOf(" test:"), workflow.indexOf(" aggregate:"));
expect(paid).toContain("matrix.environmentId == 'local' && matrix.profileId == 'runner-acpx-hermes'");
const setup = paid.indexOf("Provision pinned Hermes before the paid local test");
expect(setup).toBeGreaterThan(0);
expect(setup).toBeLessThan(paid.indexOf("secrets.OPENROUTER_API_KEY"));
expect(paid.slice(setup, paid.indexOf("Install checksum-verified Grok"))).not.toContain("secrets.");
const provision = await readFile(path.join(repositoryRoot, "tests/runner-e2e/provision-hermes-linux.sh"), "utf8");
expect(provision).toContain("uv==0.12.17");
expect(provision).toContain("scripts/provision-hermes.mjs");
});
it("keeps the manual EC2 image build credential-free and pins the authorized target", async () => {
const workflow = await readFile(path.join(repositoryRoot, ".github/workflows/docker-runner-check.yml"), "utf8");
const manual = workflow.slice(workflow.indexOf(" authorize_manual:"));