From ac11d245d8e7d5915bf0880bcfe7f75d189ce42c Mon Sep 17 00:00:00 2001 From: emranemran Date: Wed, 4 Nov 2026 16:11:10 -0800 Subject: [PATCH 1/5] ci: prepare fleet checks behind deployment freeze --- .github/FLEET-CI.md | 32 ++++++++++++++ .github/fleet-ci.json | 15 +++++++ .github/workflows/ci.yml | 13 ++++++ .github/workflows/fleet-ci-notify.yml | 61 +++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 .github/FLEET-CI.md create mode 100644 .github/fleet-ci.json create mode 100644 .github/workflows/fleet-ci-notify.yml diff --git a/.github/FLEET-CI.md b/.github/FLEET-CI.md new file mode 100644 index 0000000..34535d1 --- /dev/null +++ b/.github/FLEET-CI.md @@ -0,0 +1,32 @@ +# Fleet CI and deployment freeze + +CI tests source changes. The fleet builder in `livepeer/simple-infra` builds and publishes deployment images. +This PR does not authorize a staging or production deployment. + +## Source contract + +The receiver is `agent-fleet-build.yaml` on `livepeer/simple-infra` main. +Inputs are `source_repository`, `source_sha` (full commit SHA), and `ci_run_id`. +`.github/fleet-ci.json` identifies this repository's trusted source workflow. +The receiver must independently check that the run succeeded for this repository, workflow, SHA, main branch, and push event. +Use the exact SHA from the run. A newer main commit does not invalidate an older successful run. + +## Freeze + +`FLEET_CI_NOTIFY_ENABLED` is false in this repository. +Missing or false disables notification before the GitHub App receives a token. +`FLEET_DEPLOYMENTS_ENABLED` is false in simple-infra. The new CD workflows must enforce it. +The new flag does not disable existing manual workflows. Any Grafana staging work remains separately authorized. +Keep this PR in draft. Do not merge it or enable notifications during the freeze. + +## Activation after the freeze + +1. Merge the reviewed CI PR after its required checks pass. +2. Check that post-merge CI reports success for that exact commit. +3. Confirm Pulumi's receiver validates the source contract and refuses deployment while its gate is false. +4. Configure `FLEET_CI_APP_ID` and `FLEET_CI_APP_PRIVATE_KEY`. The App needs Actions write on simple-infra. +5. Enable source notifications only after Emran authorizes activation. Production remains manual in the CD workflow. + + +For other repositories, retain their existing required checks until the new check is observed on a PR. +Do not enable auto-merge or change release tag rules as part of this freeze preparation. diff --git a/.github/fleet-ci.json b/.github/fleet-ci.json new file mode 100644 index 0000000..181865b --- /dev/null +++ b/.github/fleet-ci.json @@ -0,0 +1,15 @@ +{ + "schema_version": 1, + "repository": "livepeer/node", + "source_workflow": ".github/workflows/build.yml", + "source_workflow_name": "Build", + "source_event": "push", + "source_branch": "main", + "receiver_repository": "livepeer/simple-infra", + "receiver_workflow": "agent-fleet-build.yaml", + "receiver_ref": "main", + "notification_gate": "FLEET_CI_NOTIFY_ENABLED", + "affected_components": [ + "node" + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9995f8..d75fc9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: Go on: pull_request: + merge_group: workflow_call: permissions: @@ -9,6 +10,7 @@ permissions: jobs: test: + timeout-minutes: 30 runs-on: ubuntu-26.04 env: GOTOOLCHAIN: local @@ -72,3 +74,14 @@ jobs: run: | go install golang.org/x/vuln/cmd/govulncheck@v1.8.0 govulncheck ./... + ci-gate: + name: ci-gate + if: always() + needs: [test] + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Require the test job to succeed + env: + TEST_RESULT: ${{ needs.test.result }} + run: test "$TEST_RESULT" = success diff --git a/.github/workflows/fleet-ci-notify.yml b/.github/workflows/fleet-ci-notify.yml new file mode 100644 index 0000000..6a441d7 --- /dev/null +++ b/.github/workflows/fleet-ci-notify.yml @@ -0,0 +1,61 @@ +name: Notify fleet builder + +# Disabled during the deployment freeze. This workflow dispatches builds only. +# It never checks out the source run and never obtains GCP credentials. +on: + workflow_run: + workflows: ["Build"] + types: [completed] + branches: [main] + +permissions: + contents: read + actions: read + +jobs: + notify: + if: >- + vars.FLEET_CI_NOTIFY_ENABLED == 'true' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.head_repository.full_name == github.repository + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check the source run before creating a builder token + env: + GH_TOKEN: ${{ github.token }} + SOURCE_REPOSITORY: ${{ github.repository }} + SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} + CI_RUN_ID: ${{ github.event.workflow_run.id }} + run: | + [[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] + [[ "$CI_RUN_ID" =~ ^[0-9]+$ ]] + gh api "repos/$SOURCE_REPOSITORY/actions/runs/$CI_RUN_ID" > "$RUNNER_TEMP/source-run.json" + jq -e --arg repo "$SOURCE_REPOSITORY" --arg sha "$SOURCE_SHA" ' + .repository.full_name == $repo and .head_repository.full_name == $repo and + .head_sha == $sha and .head_branch == "main" and .event == "push" and + .path == ".github/workflows/build.yml" and + .status == "completed" and .conclusion == "success" + ' "$RUNNER_TEMP/source-run.json" + - name: Create a token for the builder repository only + id: app + uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3 + with: + app-id: ${{ vars.FLEET_CI_APP_ID }} + private-key: ${{ secrets.FLEET_CI_APP_PRIVATE_KEY }} + owner: livepeer + repositories: simple-infra + permission-actions: write + - name: Notify the central builder with the tested source revision + env: + GH_TOKEN: ${{ steps.app.outputs.token }} + SOURCE_REPOSITORY: ${{ github.repository }} + SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} + CI_RUN_ID: ${{ github.event.workflow_run.id }} + run: | + gh workflow run agent-fleet-build.yaml --repo livepeer/simple-infra --ref main \ + --field source_repository="$SOURCE_REPOSITORY" \ + --field source_sha="$SOURCE_SHA" \ + --field ci_run_id="$CI_RUN_ID" From 292d4a6ec6d4acdc2651379f885d9e36b6e98bf4 Mon Sep 17 00:00:00 2001 From: emranemran Date: Wed, 4 Nov 2026 22:20:26 -0800 Subject: [PATCH 2/5] Report a required Node packaging verdict --- .github/workflows/build.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6f6b50..71ff4f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,7 @@ name: Build on: pull_request: + merge_group: push: branches: [main] workflow_dispatch: @@ -19,24 +20,39 @@ jobs: uses: ./.github/workflows/ci.yml package: - if: >- - github.event_name != 'pull_request' || - github.event.pull_request.head.repo.full_name == github.repository uses: ./.github/workflows/package.yml with: - commit: ${{ github.event.pull_request.head.sha || github.sha }} + # Same-repository PR images retain their head SHA. Fork PRs and the merge + # queue validate the candidate merge commit without publishing an image. + commit: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} bundles: ${{ github.event_name != 'push' }} export_images: >- ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref != 'codex/fleet-ci-freeze' && github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]') }} + package-gate: + name: package-gate + if: always() + needs: [package] + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Require both Linux builds and smoke tests to succeed + env: + PACKAGE_RESULT: ${{ needs.package.result }} + run: test "$PACKAGE_RESULT" = success + docker-pr: needs: package + # The fleet CI rollout branch validates images without publishing them. if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref != 'codex/fleet-ci-freeze' && github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]' permissions: From 2032803cf5181ea4b6ef3810a2a39be1bf5700da Mon Sep 17 00:00:00 2001 From: emranemran Date: Thu, 5 Nov 2026 10:30:41 -0800 Subject: [PATCH 3/5] Keep shared repository CI consumer-neutral --- .github/FLEET-CI.md | 32 -------------- .github/fleet-ci.json | 15 ------- .github/workflows/build.yml | 3 -- .github/workflows/fleet-ci-notify.yml | 61 --------------------------- 4 files changed, 111 deletions(-) delete mode 100644 .github/FLEET-CI.md delete mode 100644 .github/fleet-ci.json delete mode 100644 .github/workflows/fleet-ci-notify.yml diff --git a/.github/FLEET-CI.md b/.github/FLEET-CI.md deleted file mode 100644 index 34535d1..0000000 --- a/.github/FLEET-CI.md +++ /dev/null @@ -1,32 +0,0 @@ -# Fleet CI and deployment freeze - -CI tests source changes. The fleet builder in `livepeer/simple-infra` builds and publishes deployment images. -This PR does not authorize a staging or production deployment. - -## Source contract - -The receiver is `agent-fleet-build.yaml` on `livepeer/simple-infra` main. -Inputs are `source_repository`, `source_sha` (full commit SHA), and `ci_run_id`. -`.github/fleet-ci.json` identifies this repository's trusted source workflow. -The receiver must independently check that the run succeeded for this repository, workflow, SHA, main branch, and push event. -Use the exact SHA from the run. A newer main commit does not invalidate an older successful run. - -## Freeze - -`FLEET_CI_NOTIFY_ENABLED` is false in this repository. -Missing or false disables notification before the GitHub App receives a token. -`FLEET_DEPLOYMENTS_ENABLED` is false in simple-infra. The new CD workflows must enforce it. -The new flag does not disable existing manual workflows. Any Grafana staging work remains separately authorized. -Keep this PR in draft. Do not merge it or enable notifications during the freeze. - -## Activation after the freeze - -1. Merge the reviewed CI PR after its required checks pass. -2. Check that post-merge CI reports success for that exact commit. -3. Confirm Pulumi's receiver validates the source contract and refuses deployment while its gate is false. -4. Configure `FLEET_CI_APP_ID` and `FLEET_CI_APP_PRIVATE_KEY`. The App needs Actions write on simple-infra. -5. Enable source notifications only after Emran authorizes activation. Production remains manual in the CD workflow. - - -For other repositories, retain their existing required checks until the new check is observed on a PR. -Do not enable auto-merge or change release tag rules as part of this freeze preparation. diff --git a/.github/fleet-ci.json b/.github/fleet-ci.json deleted file mode 100644 index 181865b..0000000 --- a/.github/fleet-ci.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "schema_version": 1, - "repository": "livepeer/node", - "source_workflow": ".github/workflows/build.yml", - "source_workflow_name": "Build", - "source_event": "push", - "source_branch": "main", - "receiver_repository": "livepeer/simple-infra", - "receiver_workflow": "agent-fleet-build.yaml", - "receiver_ref": "main", - "notification_gate": "FLEET_CI_NOTIFY_ENABLED", - "affected_components": [ - "node" - ] -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71ff4f9..885455d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,6 @@ jobs: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.head.ref != 'codex/fleet-ci-freeze' && github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]') }} @@ -48,11 +47,9 @@ jobs: docker-pr: needs: package - # The fleet CI rollout branch validates images without publishing them. if: >- github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.head.ref != 'codex/fleet-ci-freeze' && github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]' permissions: diff --git a/.github/workflows/fleet-ci-notify.yml b/.github/workflows/fleet-ci-notify.yml deleted file mode 100644 index 6a441d7..0000000 --- a/.github/workflows/fleet-ci-notify.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Notify fleet builder - -# Disabled during the deployment freeze. This workflow dispatches builds only. -# It never checks out the source run and never obtains GCP credentials. -on: - workflow_run: - workflows: ["Build"] - types: [completed] - branches: [main] - -permissions: - contents: read - actions: read - -jobs: - notify: - if: >- - vars.FLEET_CI_NOTIFY_ENABLED == 'true' && - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'push' && - github.event.workflow_run.head_branch == 'main' && - github.event.workflow_run.head_repository.full_name == github.repository - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Check the source run before creating a builder token - env: - GH_TOKEN: ${{ github.token }} - SOURCE_REPOSITORY: ${{ github.repository }} - SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} - CI_RUN_ID: ${{ github.event.workflow_run.id }} - run: | - [[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] - [[ "$CI_RUN_ID" =~ ^[0-9]+$ ]] - gh api "repos/$SOURCE_REPOSITORY/actions/runs/$CI_RUN_ID" > "$RUNNER_TEMP/source-run.json" - jq -e --arg repo "$SOURCE_REPOSITORY" --arg sha "$SOURCE_SHA" ' - .repository.full_name == $repo and .head_repository.full_name == $repo and - .head_sha == $sha and .head_branch == "main" and .event == "push" and - .path == ".github/workflows/build.yml" and - .status == "completed" and .conclusion == "success" - ' "$RUNNER_TEMP/source-run.json" - - name: Create a token for the builder repository only - id: app - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3 - with: - app-id: ${{ vars.FLEET_CI_APP_ID }} - private-key: ${{ secrets.FLEET_CI_APP_PRIVATE_KEY }} - owner: livepeer - repositories: simple-infra - permission-actions: write - - name: Notify the central builder with the tested source revision - env: - GH_TOKEN: ${{ steps.app.outputs.token }} - SOURCE_REPOSITORY: ${{ github.repository }} - SOURCE_SHA: ${{ github.event.workflow_run.head_sha }} - CI_RUN_ID: ${{ github.event.workflow_run.id }} - run: | - gh workflow run agent-fleet-build.yaml --repo livepeer/simple-infra --ref main \ - --field source_repository="$SOURCE_REPOSITORY" \ - --field source_sha="$SOURCE_SHA" \ - --field ci_run_id="$CI_RUN_ID" From b8fcd274b0ac94b6309ad7fe5470e2806981fd89 Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Thu, 5 Nov 2026 12:32:17 -0800 Subject: [PATCH 4/5] ci: skip fork code execution and remove unused gates --- .github/workflows/build.yml | 21 +++----------- .github/workflows/ci.yml | 16 ++--------- .github/workflows/codeql.yml | 43 ++++++++++++++++++++++++++++ .github/workflows/docker-publish.yml | 3 ++ .github/workflows/package.yml | 3 ++ 5 files changed, 56 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 885455d..a6f6b50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,6 @@ name: Build on: pull_request: - merge_group: push: branches: [main] workflow_dispatch: @@ -20,31 +19,19 @@ jobs: uses: ./.github/workflows/ci.yml package: + if: >- + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository uses: ./.github/workflows/package.yml with: - # Same-repository PR images retain their head SHA. Fork PRs and the merge - # queue validate the candidate merge commit without publishing an image. - commit: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.sha || github.sha }} + commit: ${{ github.event.pull_request.head.sha || github.sha }} bundles: ${{ github.event_name != 'push' }} export_images: >- ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && - github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && github.event.pull_request.user.login != 'dependabot[bot]') }} - package-gate: - name: package-gate - if: always() - needs: [package] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Require both Linux builds and smoke tests to succeed - env: - PACKAGE_RESULT: ${{ needs.package.result }} - run: test "$PACKAGE_RESULT" = success - docker-pr: needs: package if: >- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3019aa9..7a36326 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: Go on: pull_request: - merge_group: workflow_call: permissions: @@ -10,7 +9,9 @@ permissions: jobs: test: - timeout-minutes: 30 + if: >- + !github.event.pull_request || + github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-26.04 env: GOTOOLCHAIN: local @@ -74,14 +75,3 @@ jobs: run: | go install golang.org/x/vuln/cmd/govulncheck@v1.8.0 govulncheck ./... - ci-gate: - name: ci-gate - if: always() - needs: [test] - runs-on: ubuntu-24.04 - timeout-minutes: 5 - steps: - - name: Require the test job to succeed - env: - TEST_RESULT: ${{ needs.test.result }} - run: test "$TEST_RESULT" = success diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..2409681 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,43 @@ +name: CodeQL + +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "23 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + if: >- + !github.event.pull_request || + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: go + build-mode: autobuild + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + - uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 33c999d..f459d5e 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -26,6 +26,9 @@ defaults: jobs: publish: + if: >- + !github.event.pull_request || + github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-26.04 environment: docker-publish timeout-minutes: 15 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index bc9f6c3..fa26d2c 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -29,6 +29,9 @@ defaults: jobs: build: + if: >- + !github.event.pull_request || + github.event.pull_request.head.repo.full_name == github.repository name: Linux ${{ matrix.arch }} outputs: version: ${{ steps.metadata.outputs.version }} From 10ea703c638fb2651557bc71c703d4e2499aa23d Mon Sep 17 00:00:00 2001 From: Josh Allmann Date: Thu, 5 Nov 2026 13:24:00 -0800 Subject: [PATCH 5/5] ci: remove custom CodeQL workflow --- .github/workflows/codeql.yml | 43 ------------------------------------ 1 file changed, 43 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 2409681..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: CodeQL - -on: - pull_request: - push: - branches: [main] - schedule: - - cron: "23 6 * * 1" - workflow_dispatch: - -permissions: - contents: read - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - if: >- - !github.event.pull_request || - github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - include: - - language: actions - build-mode: none - - language: go - build-mode: autobuild - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 - with: - category: "/language:${{ matrix.language }}"