Files
PaperClipAI/.github/workflows/runner-hermes-native.yml
T
DottaandPaperclip 3561e1edca ci(hermes): qualify the fixture Node interpreter
Remove group/world write bits from the dedicated CI job's Node interpreter,
matching the protected paid workflow setup. Preserve the Rust launch verifier
and record the first Linux fixture result and interrupted PR checks.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-08 12:28:11 -05:00

144 lines
5.9 KiB
YAML

name: Hermes Native Transport
on:
pull_request:
paths:
- .github/workflows/runner-hermes-native.yml
- packages/paperclip-runner/src/providers/hermes/**
- packages/paperclip-runner/src/drivers/acpx/**
- packages/paperclip-runner/src/live/runnerd-*.ts
- packages/paperclip-runner/runner/crates/runner-core/src/acpx_*.rs
- packages/paperclip-runner/scripts/*hermes*
- packages/paperclip-runner/acpx-profiles.json
- tests/runner-e2e/provision-hermes-linux.sh
workflow_dispatch:
permissions:
contents: read
concurrency:
group: hermes-native-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
native_transport:
name: Hermes native fixture (Linux amd64, no credentials)
runs-on: ubuntu-22.04
timeout-minutes: 30
steps:
- name: Checkout source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
package-manager-cache: false
- name: Setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
env:
NPM_CONFIG_AUDIT: "false"
NPM_CONFIG_FUND: "false"
NPM_CONFIG_UPDATE_NOTIFIER: "false"
with:
version: 9.15.4
- name: Qualify the job's provider Node interpreter
run: |
node <<'NODE'
const fs = require('node:fs');
const mode = fs.statSync(process.execPath).mode & 0o777;
fs.chmodSync(process.execPath, mode & ~0o022);
if ((fs.statSync(process.execPath).mode & 0o022) !== 0) {
throw new Error('provider Node interpreter remains group- or world-writable');
}
NODE
- name: Install workspace dependencies
run: |
set -euo pipefail
if ! pnpm install --frozen-lockfile; then
pnpm install --resolution-only --ignore-scripts --no-frozen-lockfile
pnpm install --frozen-lockfile
fi
- name: Restore Rust dependencies (read only)
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: packages/paperclip-runner/runner -> target
cache-workspace-crates: false
cache-bin: false
save-if: false
- name: Build verified runner and provider entrypoints
run: |
set -euo pipefail
pnpm --filter @paperclipai/paperclip-runner build:typescript
pnpm --filter @paperclipai/paperclip-runner build:binary
- name: Provision the pinned Python closure without credentials
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends python3-venv bubblewrap
bash tests/runner-e2e/provision-hermes-linux.sh
- name: Run native fixtures against a deterministic loopback model
id: fixtures
working-directory: packages/paperclip-runner
run: |
set -euo pipefail
mkdir -m 700 "$RUNNER_TEMP/hermes-fixture-home"
# No paid environment, secrets, or credentials reach either fixture.
# The execution host must pass the real bubblewrap namespace probe.
env -i PATH="$PATH" HOME="$RUNNER_TEMP/hermes-fixture-home" \
PAPERCLIP_HERMES_QUALIFY=1 \
node --test --test-concurrency=1 scripts/qualify-hermes-runtime.test.mjs \
scripts/qualify-hermes-runnerd.test.mjs \
2>&1 | tee "$RUNNER_TEMP/hermes-native-fixture.log"
- name: Record source and runtime provenance
if: always()
env:
HERMES_FIXTURE_OUTCOME: ${{ steps.fixtures.outcome }}
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/hermes-native-evidence"
if [ -f "$RUNNER_TEMP/hermes-native-fixture.log" ]; then
cp "$RUNNER_TEMP/hermes-native-fixture.log" "$RUNNER_TEMP/hermes-native-evidence/fixture.log"
fi
node --input-type=module <<'NODE'
import { readFile, writeFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { join } from 'node:path';
const root = 'packages/paperclip-runner';
const version = JSON.parse(await readFile(join(root, 'src/providers/hermes/version.json'), 'utf8'));
const manifest = JSON.parse(await readFile(join(root, 'provider-assets/hermes/linux-x64/manifest.json'), 'utf8').catch(() => 'null'));
const evidence = {
qualification: 'native transport fixture only; no paid model, browser or Daytona proof',
sourceSha: execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(),
platform: `${process.platform}-${process.arch}`, node: process.version,
fixtureOutcome: process.env.HERMES_FIXTURE_OUTCOME,
kernel: execFileSync('uname', ['-r'], { encoding: 'utf8' }).trim(),
osRelease: await readFile('/etc/os-release', 'utf8'),
runtime: version,
closureSha256: manifest ? createHash('sha256').update(JSON.stringify(manifest.entries)).digest('hex') : null,
files: manifest?.entries?.length ?? null,
model: 'deterministic loopback fixture', credentials: 'none',
};
await writeFile(join(process.env.RUNNER_TEMP, 'hermes-native-evidence/provenance.json'), JSON.stringify(evidence, null, 2) + '\n');
NODE
- name: Upload fixture evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: hermes-native-linux-amd64-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/hermes-native-evidence/
if-no-files-found: warn
retention-days: 14