feat: qualify macOS Codex runner defaults and release artifacts

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 07:48:52 -05:00
1 parent 6ca302426b
commit 7b8d29a856
26 files changed
+1185 -111

No files matched your search

+132
View File
@@ -23,6 +23,11 @@ on:
required: false
default: release-smoke
type: string
qualification_source_sha:
description: Optional immutable source SHA for provider-free three-target package qualification
required: false
default: ""
type: string
workflow_call:
inputs:
paperclip_version:
@@ -36,6 +41,13 @@ on:
required: false
default: release-smoke
type: string
qualification_source_sha:
required: false
default: ""
type: string
permissions:
contents: read
jobs:
# The Docker smoke below can never exercise the background-service leg of
@@ -45,6 +57,7 @@ jobs:
# on the runner VM's systemd and require the installed service to end up
# serving.
smoke_service:
if: inputs.qualification_source_sha == ''
runs-on: ubuntu-latest
timeout-minutes: 30
@@ -98,6 +111,7 @@ jobs:
retention-days: 14
smoke:
if: inputs.qualification_source_sha == ''
runs-on: ubuntu-latest
timeout-minutes: 45
@@ -198,3 +212,121 @@ jobs:
- name: Stop Docker smoke container
if: always()
run: docker rm -f "$SMOKE_CONTAINER_NAME" >/dev/null 2>&1 || true
qualification_daemons:
if: inputs.qualification_source_sha != ''
uses: ./.github/workflows/release-verify.yml
with:
ref: ${{ inputs.qualification_source_sha }}
runner_binaries_only: true
qualification_pack:
if: inputs.qualification_source_sha != ''
needs: qualification_daemons
name: Assemble and verify public Linux packages
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.qualification_source_sha }}
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 9.15.4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.qualification_daemons.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind the immutable Linux producer source
env:
SOURCE_SHA: ${{ inputs.qualification_source_sha }}
run: |
set -euo pipefail
[[ "$SOURCE_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$(git rev-parse HEAD)" == "$SOURCE_SHA" ]] || exit 1
- name: Build normal workspace outputs and public CLI
run: |
pnpm install --no-frozen-lockfile
pnpm build
bash scripts/build-npm.sh --skip-checks --skip-typecheck
mv cli/package.dev.json cli/package.json
- name: Assemble exact-source daemons before server packing
env:
SOURCE_SHA: ${{ inputs.qualification_source_sha }}
run: |
node packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs \
assemble-daemons "$RUNNER_TEMP/release-runner-inputs" "$SOURCE_SHA"
cp -R packages/paperclip-runner/dist/bin/. server/dist/vendor/paperclip-runner/bin/
- name: Verify clean public installation and retain the same package bytes
env:
PAPERCLIP_RUNNER_PUBLIC_PACKS_DIR: ${{ runner.temp }}/public-runner-packs
run: node scripts/verify-grok-npm-install.mjs
- name: Locate completed package assembly
id: pack_identity
if: ${{ !cancelled() }}
run: |
if [[ -s "$RUNNER_TEMP/public-runner-packs/manifest.json" ]]; then
echo "available=true" >> "$GITHUB_OUTPUT"
fi
- name: Retain completed package assembly even after a later Linux failure
if: ${{ !cancelled() && steps.pack_identity.outputs.available == 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ inputs.artifact_name }}-public-packages
path: ${{ runner.temp }}/public-runner-packs/
if-no-files-found: error
retention-days: 14
qualification_mac:
if: ${{ !cancelled() && inputs.qualification_source_sha != '' && needs.qualification_daemons.result == 'success' }}
needs: [qualification_daemons, qualification_pack]
name: Clean installed package (${{ matrix.target }})
runs-on: ${{ matrix.host }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- target: darwin-arm64
host: macos-15
- target: darwin-x64
host: macos-15-intel
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.qualification_source_sha }}
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 9.15.4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: ${{ inputs.artifact_name }}-public-packages
path: ${{ runner.temp }}/public-runner-packs
- name: Install only the existing verifier tooling
run: pnpm install --ignore-scripts --no-frozen-lockfile
- name: Verify host identity and actual clean npm consumer
env:
SOURCE_SHA: ${{ inputs.qualification_source_sha }}
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
[[ "$(git rev-parse HEAD)" == "$SOURCE_SHA" ]] || exit 1
[[ "$(node -p 'process.platform + "-" + process.arch')" == "$TARGET" ]] || exit 1
node scripts/verify-grok-npm-install.mjs --mac-consumer \
"$RUNNER_TEMP/public-runner-packs" "$SOURCE_SHA" "$RUNNER_TEMP/mac-public-install.json"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: ${{ inputs.artifact_name }}-${{ matrix.target }}
path: ${{ runner.temp }}/mac-public-install.json
if-no-files-found: warn
retention-days: 14
+77
View File
@@ -7,18 +7,28 @@ on:
description: Commit SHA, branch, or tag to verify
required: true
type: string
runner_binaries_only:
description: Build exact-source release daemons without publishing or provider calls
required: false
default: false
type: boolean
outputs:
runner_artifact_prefix:
value: ${{ jobs.runner_binaries.outputs.artifact_prefix }}
# Caller-provided refs may name unmerged PR code. AWS is eligible only when
# the caller runs on canonical master and verifies that event's exact SHA.
# The organization group also restricts these workflow files to master.
jobs:
runner_chaos_evals:
if: ${{ !inputs.runner_binaries_only }}
name: Pre-release Runner chaos evals
uses: ./.github/workflows/runner-chaos-evals.yml
with:
ref: ${{ inputs.ref }}
typecheck:
if: ${{ !inputs.runner_binaries_only }}
name: Typecheck
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
@@ -97,6 +107,7 @@ jobs:
run: pnpm -r typecheck
general_tests:
if: ${{ !inputs.runner_binaries_only }}
name: General tests (${{ matrix.group_label }})
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
@@ -205,6 +216,7 @@ jobs:
fi
serialized_tests:
if: ${{ !inputs.runner_binaries_only }}
name: Serialized tests (${{ matrix.shard_label }})
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
@@ -254,6 +266,7 @@ jobs:
run: pnpm test:run:serialized -- --shard-index ${{ matrix.shard_index }} --shard-count ${{ matrix.shard_count }}
runner_workflow_evals:
if: ${{ !inputs.runner_binaries_only }}
name: Runner workflow eval scorer contract
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 10
@@ -284,6 +297,7 @@ jobs:
run: pnpm test:runner-workflow-evals
verify_paperclip_runner:
if: ${{ !inputs.runner_binaries_only }}
name: Verify Paperclip Runner (${{ matrix.lane }})
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
@@ -412,6 +426,7 @@ jobs:
run: pnpm --filter @paperclipai/paperclip-runner build:rust
build:
if: ${{ !inputs.runner_binaries_only }}
name: Build
runs-on: ${{ vars.AWS_POST_MERGE_CI_ENABLED == 'true' && github.repository == 'paperclipai/paperclip' && github.repository_id == '1170821064' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.sha != '' && inputs.ref == github.sha && 'runs-on/fleet=paperclip-post-merge-x64/env=public-ci' || 'ubuntu-latest' }}
timeout-minutes: 20
@@ -440,3 +455,65 @@ jobs:
- name: Build
run: pnpm build
runner_binaries:
if: inputs.runner_binaries_only
name: Release daemon (${{ matrix.target }})
runs-on: ${{ matrix.host }}
timeout-minutes: 30
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- target: linux-x64
host: ubuntu-latest
- target: darwin-arm64
host: macos-15
- target: darwin-x64
host: macos-15-intel
outputs:
artifact_prefix: ${{ steps.identity.outputs.artifact_prefix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 9.15.4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24
- name: Bind immutable producer source and target
id: identity
env:
SOURCE_SHA: ${{ inputs.ref }}
TARGET: ${{ matrix.target }}
run: |
set -euo pipefail
[[ "$SOURCE_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$(git rev-parse HEAD)" == "$SOURCE_SHA" ]] || exit 1
[[ "$(node -p 'process.platform + "-" + process.arch')" == "$TARGET" ]] || exit 1
echo "artifact_prefix=runner-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
- name: Install dependencies and build production TypeScript contract
run: |
pnpm install --no-frozen-lockfile
pnpm --filter @paperclipai/paperclip-runner build:typescript
- name: Build the host daemon with the pinned toolchain
working-directory: packages/paperclip-runner
run: pnpm build:binary
- name: Record actual daemon metadata and immutable bytes
env:
SOURCE_SHA: ${{ inputs.ref }}
TARGET: ${{ matrix.target }}
run: |
node packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs record "$SOURCE_SHA" "$TARGET" \
packages/paperclip-runner/dist/bin/paperclip-runnerd "$RUNNER_TEMP/runner-release/$TARGET"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ steps.identity.outputs.artifact_prefix }}-${{ matrix.target }}
path: ${{ runner.temp }}/runner-release/
if-no-files-found: error
retention-days: 14
+112 -8
View File
@@ -336,9 +336,54 @@ jobs:
SOURCE_SHA: ${{ github.sha }}
run: node scripts/cloud-source-verification.mjs "$SOURCE_SHA"
release_runner_source:
# Old promotions retain their already-shipped packaging contract. New
# sources opt into mandatory three-target assembly in their release.sh.
needs: [verify_canary, select_nightly, select_beta, preflight_stable]
if: >-
!cancelled() && (
(github.event_name == 'push' && needs.verify_canary.result == 'success') ||
(needs.select_nightly.result == 'success' && needs.select_nightly.outputs.proceed == 'true') ||
needs.select_beta.result == 'success' || needs.preflight_stable.result == 'success')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
outputs:
sha: ${{ steps.source.outputs.sha }}
required: ${{ steps.source.outputs.required }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ needs.preflight_stable.outputs.sha || needs.select_beta.outputs.sha || needs.select_nightly.outputs.sha || github.sha }}
persist-credentials: false
- name: Read the exact source packaging contract
id: source
env:
SOURCE_SHA: ${{ needs.preflight_stable.outputs.sha || needs.select_beta.outputs.sha || needs.select_nightly.outputs.sha || github.sha }}
run: |
set -euo pipefail
[[ "$SOURCE_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$(git rev-parse HEAD)" == "$SOURCE_SHA" ]] || exit 1
echo "sha=$SOURCE_SHA" >> "$GITHUB_OUTPUT"
source_script="$(git show "$SOURCE_SHA:scripts/release.sh")" || exit 1
if printf '%s\n' "$source_script" | grep -F 'assemble-daemons "$PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR"' > /dev/null; then
echo "required=true" >> "$GITHUB_OUTPUT"
else
echo "required=false" >> "$GITHUB_OUTPUT"
fi
release_runner_binaries:
needs: release_runner_source
if: needs.release_runner_source.outputs.required == 'true'
uses: ./.github/workflows/release-verify.yml
with:
ref: ${{ needs.release_runner_source.outputs.sha }}
runner_binaries_only: true
publish_canary:
if: github.event_name == 'push'
needs: verify_canary
if: ${{ !cancelled() && github.event_name == 'push' && needs.verify_canary.result == 'success' && needs.release_runner_source.result == 'success' && (needs.release_runner_source.outputs.required == 'false' || needs.release_runner_binaries.result == 'success') }}
needs: [verify_canary, release_runner_source, release_runner_binaries]
runs-on: ubuntu-latest
timeout-minutes: 150
environment: npm-canary
@@ -356,6 +401,17 @@ jobs:
with:
fetch-depth: 0
- name: Download exact-source release daemon receipts
if: needs.release_runner_source.outputs.required == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind daemon inputs for release assembly
if: needs.release_runner_source.outputs.required == 'true'
run: echo "PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR=$RUNNER_TEMP/release-runner-inputs" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
@@ -587,12 +643,14 @@ jobs:
artifact_name: nightly-release-smoke
publish_nightly:
needs: [select_nightly, smoke_nightly]
needs: [select_nightly, smoke_nightly, release_runner_source, release_runner_binaries]
# Publish when smoke passed, or when smoke was deliberately skipped by a
# dry-run dispatch (the publish itself is a dry-run in that case).
if: >-
!cancelled() &&
needs.select_nightly.outputs.proceed == 'true' &&
needs.release_runner_source.result == 'success' &&
(needs.release_runner_source.outputs.required == 'false' || needs.release_runner_binaries.result == 'success') &&
(needs.smoke_nightly.result == 'success' ||
(needs.smoke_nightly.result == 'skipped' && github.event_name == 'workflow_dispatch' && inputs.dry_run))
runs-on: ubuntu-latest
@@ -618,6 +676,17 @@ jobs:
ref: ${{ needs.select_nightly.outputs.sha }}
fetch-depth: 0
- name: Download exact-source release daemon receipts
if: needs.release_runner_source.outputs.required == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind daemon inputs for release assembly
if: needs.release_runner_source.outputs.required == 'true'
run: echo "PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR=$RUNNER_TEMP/release-runner-inputs" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
@@ -855,10 +924,12 @@ jobs:
ref: ${{ needs.select_beta.outputs.sha }}
publish_beta:
needs: [select_beta, verify_beta_candidate]
needs: [select_beta, verify_beta_candidate, release_runner_source, release_runner_binaries]
if: >-
!cancelled() &&
needs.select_beta.result == 'success' &&
needs.release_runner_source.result == 'success' &&
(needs.release_runner_source.outputs.required == 'false' || needs.release_runner_binaries.result == 'success') &&
(needs.verify_beta_candidate.result == 'success' ||
(needs.verify_beta_candidate.result == 'skipped' && needs.select_beta.outputs.mode == 'promote'))
runs-on: ubuntu-latest
@@ -884,6 +955,17 @@ jobs:
ref: ${{ needs.select_beta.outputs.sha }}
fetch-depth: 0
- name: Download exact-source release daemon receipts
if: needs.release_runner_source.outputs.required == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind daemon inputs for release assembly
if: needs.release_runner_source.outputs.required == 'true'
run: echo "PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR=$RUNNER_TEMP/release-runner-inputs" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
@@ -1240,8 +1322,8 @@ jobs:
ref: ${{ needs.preflight_stable.outputs.sha }}
preview_stable:
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && inputs.dry_run
needs: [preflight_stable, verify_stable]
if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && inputs.dry_run && needs.preflight_stable.result == 'success' && needs.verify_stable.result == 'success' && needs.release_runner_source.result == 'success' && (needs.release_runner_source.outputs.required == 'false' || needs.release_runner_binaries.result == 'success') }}
needs: [preflight_stable, verify_stable, release_runner_source, release_runner_binaries]
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
@@ -1254,6 +1336,17 @@ jobs:
fetch-depth: 0
ref: ${{ needs.preflight_stable.outputs.sha }}
- name: Download exact-source release daemon receipts
if: needs.release_runner_source.outputs.required == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind daemon inputs for release assembly
if: needs.release_runner_source.outputs.required == 'true'
run: echo "PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR=$RUNNER_TEMP/release-runner-inputs" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
@@ -1282,8 +1375,8 @@ jobs:
./scripts/release.sh "${args[@]}"
publish_stable:
if: github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && !inputs.dry_run
needs: [preflight_stable, verify_stable]
if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' && inputs.channel == 'stable' && !inputs.dry_run && needs.preflight_stable.result == 'success' && needs.verify_stable.result == 'success' && needs.release_runner_source.result == 'success' && (needs.release_runner_source.outputs.required == 'false' || needs.release_runner_binaries.result == 'success') }}
needs: [preflight_stable, verify_stable, release_runner_source, release_runner_binaries]
runs-on: ubuntu-latest
timeout-minutes: 150
environment: npm-stable
@@ -1301,6 +1394,17 @@ jobs:
fetch-depth: 0
ref: ${{ needs.preflight_stable.outputs.sha }}
- name: Download exact-source release daemon receipts
if: needs.release_runner_source.outputs.required == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: ${{ needs.release_runner_binaries.outputs.runner_artifact_prefix }}-*
path: ${{ runner.temp }}/release-runner-inputs
merge-multiple: true
- name: Bind daemon inputs for release assembly
if: needs.release_runner_source.outputs.required == 'true'
run: echo "PAPERCLIP_RELEASE_RUNNER_ARTIFACTS_DIR=$RUNNER_TEMP/release-runner-inputs" >> "$GITHUB_ENV"
- name: Setup pnpm
uses: pnpm/action-setup@v6
with: