fix(publication): isolate offline release builds #98
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| workflow_call: | |
| inputs: | |
| checkout_ref: | |
| description: Exact candidate commit to verify from a trusted Pylon workflow | |
| required: false | |
| type: string | |
| require_trusted: | |
| description: Fail the aggregate when a trusted caller is not recognized | |
| required: false | |
| default: false | |
| type: boolean | |
| push: | |
| branches: [pylon] | |
| pull_request: | |
| branches: [pylon] | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| concurrency: | |
| group: ci-${{ inputs.checkout_ref || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| trust: | |
| name: Contributor trust | |
| if: github.event_name != 'pull_request' || github.event.pull_request.draft == false | |
| runs-on: ubuntu-latest | |
| outputs: | |
| allowed: ${{ steps.internal.outputs.allowed == 'true' || steps.sync.outputs.allowed == 'true' || steps.vouch.outputs.vouched == 'true' }} | |
| steps: | |
| - name: Recognize a trusted Pylon execution | |
| id: internal | |
| if: github.event_name == 'push' || inputs.checkout_ref != '' | |
| env: | |
| CHECKOUT_REF: ${{ inputs.checkout_ref }} | |
| CALLER_EVENT: ${{ github.event_name }} | |
| CALLER_REF: ${{ github.ref }} | |
| CALLER_REPOSITORY: ${{ github.repository }} | |
| run: | | |
| if [[ "$CALLER_REPOSITORY" != "pylon-code/prime-agent" || "$CALLER_REF" != "refs/heads/pylon" ]]; then | |
| exit 0 | |
| fi | |
| if [[ "$CALLER_EVENT" == "push" && -z "$CHECKOUT_REF" ]]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| elif [[ ("$CALLER_EVENT" == "workflow_dispatch" || "$CALLER_EVENT" == "schedule") && "$CHECKOUT_REF" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Recognize the internal upstream sync pull request | |
| id: sync | |
| if: github.event_name == 'pull_request' | |
| env: | |
| HAS_SYNC_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'pylon-upstream-sync') }} | |
| HEAD_REF: ${{ github.event.pull_request.head.ref }} | |
| HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} | |
| run: | | |
| if [ "$HAS_SYNC_LABEL" = true ] && [ "$HEAD_REPOSITORY" = "pylon-code/prime-agent" ] && [[ "$HEAD_REF" == automation/prime-upstream-* ]]; then | |
| echo "allowed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Check pull request author | |
| id: vouch | |
| if: github.event_name == 'pull_request' && steps.sync.outputs.allowed != 'true' | |
| uses: mitchellh/vouch/action/check-user@d66fa29a64600490892131ad87597c30c91fcac4 # v1 | |
| with: | |
| user: ${{ github.event.pull_request.user.login }} | |
| allow-fail: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| build-check: | |
| name: Build and check | |
| needs: trust | |
| if: needs.trust.outputs.allowed == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ inputs.checkout_ref || github.ref }} | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep | |
| sudo ln -s "$(which fdfind)" /usr/local/bin/fd | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build | |
| run: npm run build | |
| - name: Check | |
| run: npm run check | |
| test: | |
| name: Test (${{ matrix.name }}) | |
| needs: trust | |
| if: needs.trust.outputs.allowed == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: agent-core | |
| package: packages/agent | |
| command: npm test | |
| install_uv: false | |
| - name: ai | |
| package: packages/ai | |
| command: npm test | |
| install_uv: false | |
| - name: tui | |
| package: packages/tui | |
| command: npm test | |
| install_uv: false | |
| - name: coding-agent 1/3 | |
| package: packages/coding-agent | |
| command: npm run test:ci -- --shard=1/3 | |
| install_uv: true | |
| - name: coding-agent 2/3 | |
| package: packages/coding-agent | |
| command: npm run test:ci -- --shard=2/3 | |
| install_uv: true | |
| - name: coding-agent 3/3 | |
| package: packages/coding-agent | |
| command: npm run test:ci -- --shard=3/3 | |
| install_uv: true | |
| - name: coding-agent process smoke | |
| package: packages/coding-agent | |
| command: npm run test:process | |
| install_uv: true | |
| - name: coding-agent kernel | |
| package: packages/coding-agent | |
| command: npm run test:kernel | |
| install_uv: true | |
| - name: runtime python | |
| package: prime-agent-runtime | |
| command: uv run python -m unittest discover -s test | |
| install_uv: true | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ inputs.checkout_ref || github.ref }} | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep | |
| sudo ln -s "$(which fdfind)" /usr/local/bin/fd | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build | |
| run: npm run build | |
| - name: Install uv | |
| if: matrix.install_uv | |
| run: | | |
| python3 -m pip install --user uv | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Test | |
| working-directory: ${{ matrix.package }} | |
| run: ${{ matrix.command }} | |
| owned-session-contract-windows: | |
| name: Caller-owned session contract (Windows named pipe) | |
| needs: trust | |
| if: needs.trust.outputs.allowed == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ inputs.checkout_ref || github.ref }} | |
| persist-credentials: false | |
| - name: Setup Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Test named-pipe owned cleanup | |
| working-directory: packages/coding-agent | |
| run: npx tsx ../../node_modules/vitest/dist/cli.js --run test/daemon-supervisor-process.test.ts -t "proves exact owned cleanup over a Windows named pipe" | |
| pylon-artifact-pack: | |
| name: Pylon artifact pack (${{ matrix.copy }}) | |
| needs: trust | |
| if: needs.trust.outputs.allowed == 'true' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| copy: [a, b] | |
| env: | |
| PYLON_RELEASE_NODE: 22.23.2 | |
| PYLON_RELEASE_MODE: ci | |
| PYLON_RELEASE_NPM: 11.10.1 | |
| steps: | |
| - name: Checkout exact source | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ inputs.checkout_ref || github.ref }} | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Setup pinned Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: ${{ env.PYLON_RELEASE_NODE }} | |
| - name: Run isolated release sandbox | |
| id: sandbox | |
| shell: /usr/bin/bash --noprofile --norc -euo pipefail {0} | |
| run: | | |
| node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')" | |
| /usr/bin/env -i \ | |
| "HOME=/tmp" \ | |
| "PATH=${node_path%/*}:/usr/bin:/bin" \ | |
| "$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \ | |
| --task pack \ | |
| --mode "$PYLON_RELEASE_MODE" \ | |
| --host-os "${{ runner.os }}" \ | |
| --workspace "$GITHUB_WORKSPACE" \ | |
| --github-output "$GITHUB_OUTPUT" \ | |
| --run-id "${{ github.run_id }}" \ | |
| --run-number "${{ github.run_number }}" \ | |
| --publication-policy-revision 3 | |
| - name: Upload isolated subjects | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: pylon-prime-pack-${{ matrix.copy }} | |
| path: ${{ steps.sandbox.outputs.artifact_dir }} | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| retention-days: 3 | |
| pylon-artifact-reproducibility: | |
| name: Pylon artifact reproducibility | |
| needs: pylon-artifact-pack | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Download first isolated pack | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: pylon-prime-pack-a | |
| path: pack-a | |
| - name: Download second isolated pack | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: pylon-prime-pack-b | |
| path: pack-b | |
| - name: Require byte-identical outputs | |
| run: diff --recursive --brief pack-a pack-b | |
| pylon-artifact-install: | |
| name: Pylon artifact install (${{ matrix.os }}) | |
| needs: [trust, pylon-artifact-pack, pylon-artifact-reproducibility] | |
| if: needs.trust.outputs.allowed == 'true' | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| permissions: | |
| actions: read | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-24.04, macos-15] | |
| env: | |
| PYLON_RELEASE_NODE: 22.23.2 | |
| PYLON_RELEASE_MODE: ci | |
| PYLON_RELEASE_NPM: 11.10.1 | |
| steps: | |
| - name: Checkout exact source | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| ref: ${{ inputs.checkout_ref || github.ref }} | |
| persist-credentials: false | |
| fetch-depth: 1 | |
| - name: Setup pinned Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: ${{ env.PYLON_RELEASE_NODE }} | |
| - name: Download reproducible pack | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: pylon-prime-pack-a | |
| path: .npm/pylon-release/artifacts | |
| - name: Run isolated artifact smoke | |
| shell: /usr/bin/bash --noprofile --norc -euo pipefail {0} | |
| run: | | |
| node_path="$(node -e 'process.stdout.write(require("node:fs").realpathSync.native(process.execPath))')" | |
| /usr/bin/env -i \ | |
| "HOME=/tmp" \ | |
| "PATH=${node_path%/*}:/usr/bin:/bin" \ | |
| "$node_path" "$GITHUB_WORKSPACE/scripts/run-pylon-release-sandbox.mjs" \ | |
| --task smoke \ | |
| --mode "$PYLON_RELEASE_MODE" \ | |
| --host-os "${{ runner.os }}" \ | |
| --workspace "$GITHUB_WORKSPACE" \ | |
| --artifact-dir "$GITHUB_WORKSPACE/.npm/pylon-release/artifacts" \ | |
| --run-id "${{ github.run_id }}" \ | |
| --run-number "${{ github.run_number }}" \ | |
| --publication-policy-revision 3 | |
| build-check-test: | |
| name: build-check-test | |
| if: always() && (needs.trust.outputs.allowed == 'true' || inputs.require_trusted) | |
| needs: | |
| - trust | |
| - build-check | |
| - test | |
| - owned-session-contract-windows | |
| - pylon-artifact-pack | |
| - pylon-artifact-reproducibility | |
| - pylon-artifact-install | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify CI results | |
| env: | |
| TRUST_ALLOWED: ${{ needs.trust.outputs.allowed }} | |
| BUILD_CHECK_RESULT: ${{ needs.build-check.result }} | |
| TEST_RESULT: ${{ needs.test.result }} | |
| WINDOWS_OWNED_SESSION_RESULT: ${{ needs.owned-session-contract-windows.result }} | |
| ARTIFACT_PACK_RESULT: ${{ needs.pylon-artifact-pack.result }} | |
| ARTIFACT_REPRODUCIBILITY_RESULT: ${{ needs.pylon-artifact-reproducibility.result }} | |
| ARTIFACT_INSTALL_RESULT: ${{ needs.pylon-artifact-install.result }} | |
| run: | | |
| test "$TRUST_ALLOWED" = true | |
| test "$BUILD_CHECK_RESULT" = success | |
| test "$TEST_RESULT" = success | |
| test "$WINDOWS_OWNED_SESSION_RESULT" = success | |
| test "$ARTIFACT_PACK_RESULT" = success | |
| test "$ARTIFACT_REPRODUCIBILITY_RESULT" = success | |
| test "$ARTIFACT_INSTALL_RESULT" = success |