fix(release): repair the release pipeline and sign released artifacts #1646
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: Unit Tests | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| paths: | |
| - 'src/**' | |
| - 'tests/**' | |
| - 'pyproject.toml' | |
| - 'requirements*.txt' | |
| - 'uv.lock' | |
| - 'assets/**' | |
| - 'rxconfig.py' | |
| - '.github/workflows/ci-tests.yml' | |
| pull_request: | |
| branches: [ main, develop ] | |
| paths: | |
| - 'src/**' | |
| - 'tests/**' | |
| - 'pyproject.toml' | |
| - 'requirements*.txt' | |
| - 'uv.lock' | |
| - 'assets/**' | |
| - 'rxconfig.py' | |
| - '.github/workflows/ci-tests.yml' | |
| schedule: | |
| - cron: '0 2 * * *' | |
| # workflow_dispatch: allows re-running tests manually from the Actions UI | |
| workflow_dispatch: | |
| # Cancel in-progress runs when a new push arrives on the same branch/ref. | |
| # Scheduled nightly runs use a stable group key so they never cancel each | |
| # other (there is only ever one running at a time by schedule). | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| config: | |
| name: Configuration | |
| uses: ./.github/workflows/shared-config.yml | |
| setup-cache: | |
| name: Setup Test Cache | |
| needs: config | |
| uses: ./.github/workflows/cache-management.yml | |
| with: | |
| cache-type: dependencies | |
| cache-key-base: deps | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| # Parallel test-group matrix (T2613): splits the non-provider test corpus | |
| # into independent legs that each run on their own runner with xdist within | |
| # the leg. Legs: unit (tests/unit), infrastructure (tests/infrastructure), | |
| # integration (tests/integration). Provider legs are handled by the | |
| # providers-tests matrix below and remain driven by discovery. | |
| # | |
| # The standalone `tests`, `integration-tests`, and `infrastructure-tests` jobs | |
| # that previously ran these same suites have been removed; test-groups is their | |
| # replacement. artifact-prefix uses `ci-tests-group` (not `ci-tests`) so that | |
| # actions/upload-artifact@v4 does not error on duplicate artifact names within | |
| # the same run (each matrix leg's artifact is named `<prefix>-<test-type>-…`). | |
| test-groups: | |
| name: Test Groups (Parallel) | |
| needs: [config, setup-cache] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| test-group: [unit, infrastructure, integration] | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: ${{ matrix.test-group }} | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests-group | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| # e2e-tests run against an in-process TestClient app — no live infrastructure | |
| # required — so they gate like every other leg. (They previously ran with | |
| # continue-on-error: true, which silently masked 23 genuine failures.) | |
| e2e-tests: | |
| name: End-to-End Tests (Default Python) | |
| needs: [config, setup-cache] | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: e2e | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| # Per-provider matrix. One job per discovered provider subtree under | |
| # tests/providers/. Discovery runs in the shared-config job: any directory | |
| # that has a tests/providers/<name>/testconf.mk becomes a matrix entry. | |
| # Adding a new provider directory with testconf.mk requires no workflow edit. | |
| providers-tests: | |
| name: Providers Tests | |
| needs: [config, setup-cache] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Dynamic: fromJSON(needs.config.outputs.providers) is populated by the | |
| # shared-config discover step. The static fallback ['aws'] keeps CI | |
| # valid in forks or contexts where discovery cannot run. | |
| provider: ${{ fromJSON(needs.config.outputs.providers || '["aws"]') }} | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: providers | |
| provider: ${{ matrix.provider }} | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests | |
| # Provider suites are the heaviest legs (aws moto ≈ 270s of CPU-bound | |
| # mocking). Run them on a 16-vCPU runner with one xdist worker per core. | |
| os: ${{ vars.LARGE_RUNNER || 'ubuntu-latest-16-cores' }} | |
| workers: auto | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| # Serial-marked provider tests (currently the live subtree of each provider). | |
| # The make target points pytest directly at ``tests/providers/<provider>/live`` | |
| # because ``norecursedirs`` excludes that path from auto-recursion. Without | |
| # ``--live`` the root conftest stamps every live test with ``skip_live``, so | |
| # the job collects the suite, reports SKIPPED, and exits 0 — that proves the | |
| # collection wiring + skip mechanism still work end-to-end on every PR. | |
| # When CI is wired up with real credentials, set ``PYTEST_LIVE=--live`` | |
| # in the workflow env to flip the suite into actual execution. | |
| providers-tests-serial: | |
| name: Providers Tests (Serial) | |
| needs: [config, setup-cache, providers-tests] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Same dynamic discovery as providers-tests above. | |
| provider: ${{ fromJSON(needs.config.outputs.providers || '["aws"]') }} | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: providers-serial | |
| provider: ${{ matrix.provider }} | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| ui-unit-tests: | |
| name: UI Unit Tests | |
| needs: [config, setup-cache] | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: ui-unit | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| ui-smoke: | |
| name: UI Smoke | |
| needs: [config, setup-cache] | |
| uses: ./.github/workflows/reusable-test.yml | |
| secrets: inherit | |
| with: | |
| test-type: ui-smoke | |
| python-version: ${{ needs.config.outputs.default-python-version }} | |
| default-python-version: ${{ needs.config.outputs.default-python-version }} | |
| continue-on-error: false | |
| # ui-smoke runs run_ui_smoke.sh (no pytest) so no coverage XML is produced. | |
| upload-coverage: false | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| artifact-prefix: ci-tests | |
| aws-region: ${{ needs.config.outputs.aws-region }} | |
| aws-access-key: ${{ needs.config.outputs.aws-access-key }} | |
| aws-secret-key: ${{ needs.config.outputs.aws-secret-key }} | |
| environment: ${{ needs.config.outputs.environment }} | |
| testing-flag: ${{ needs.config.outputs.testing-flag }} | |
| build-and-package: | |
| name: Build & Package | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [config, setup-cache] | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup UV with cache | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| fail-on-cache-miss: false | |
| - name: Build package | |
| # This job builds a wheel artifact for CI validation only and has no | |
| # bun/uv to compile the SPA (it never shipped one). Skip the UI build. | |
| env: | |
| ORB_SKIP_UI_BUILD: "1" | |
| run: make build | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: build-artifacts-${{ needs.config.outputs.package-version }} | |
| retention-days: 60 | |
| path: | | |
| dist/ | |
| build/ | |
| test-report: | |
| name: Generate Test Report | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [config, setup-cache, test-groups, e2e-tests, providers-tests, providers-tests-serial, ui-unit-tests, ui-smoke] | |
| if: always() | |
| permissions: | |
| contents: read | |
| issues: read | |
| checks: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup UV with cache | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| fail-on-cache-miss: false | |
| - name: Download test results | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| path: test-results/ | |
| - name: Publish Test Results | |
| uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2 | |
| if: always() | |
| continue-on-error: true | |
| with: | |
| files: "test-results/**/junit-*.xml" | |
| check_name: "Test Results Summary" | |
| comment_mode: "always" | |
| job_summary: true | |
| action_fail: false | |
| fail_on: "nothing" | |
| # Self-contained combined-coverage gate: merges the per-leg .coverage data | |
| # (uploaded by reusable-test) and enforces the threshold locally, so the hard | |
| # gate does not depend on Codecov's external service. Codecov still receives | |
| # each leg's upload for the PR-comment diff view. | |
| coverage-combine: | |
| name: Coverage Gate (Combined) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [config, setup-cache, test-groups, e2e-tests, providers-tests, ui-unit-tests] | |
| if: always() | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Setup UV with cache | |
| uses: ./.github/actions/setup-uv-cached | |
| with: | |
| cache-key: ${{ needs.setup-cache.outputs.cache-key }} | |
| fail-on-cache-miss: false | |
| - name: Download coverage artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| path: coverage-artifacts/ | |
| - name: Gather per-leg .coverage data files | |
| run: | | |
| # Each leg uploads a .coverage data file inside its test-results-* dir. | |
| # coverage combine expects them in CWD with unique .coverage.<suffix> names. | |
| i=0 | |
| find coverage-artifacts -name '.coverage*' -type f | while read -r f; do | |
| cp "$f" ".coverage.leg${i}" | |
| i=$((i + 1)) | |
| done | |
| ls -la .coverage* 2>/dev/null || echo "no .coverage data files found" | |
| - name: Combine coverage and enforce combined threshold | |
| id: coverage-gate | |
| run: make ci-tests-coverage-check | |
| # Single deterministic Codecov upload: the combined report from ALL legs is | |
| # sent once here, instead of each leg uploading separately. Codecov then | |
| # shows one stable number (no mid-run partial-merge flapping) and this needs | |
| # no hardcoded leg count — it auto-scales to any number of provider legs. | |
| # if: always() so the report uploads even when the threshold gate fails | |
| # above (the job still fails on the gate; Codecov still gets the data). | |
| - name: Upload combined coverage to Codecov | |
| if: always() | |
| continue-on-error: true | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: coverage-combined.xml | |
| name: combined | |
| # Overwrite any prior partial report for this commit with the full merge. | |
| override_commit: ${{ github.event.pull_request.head.sha || github.sha }} | |
| # Single stable required status check for branch protection. Replaces listing | |
| # every individual job name (which drifts silently when jobs are renamed). | |
| # Register "CI Passed" as the sole required check in branch protection. | |
| ci-passed: | |
| name: CI Passed | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| needs: | |
| - config | |
| - setup-cache | |
| - test-groups | |
| - e2e-tests | |
| - providers-tests | |
| - providers-tests-serial | |
| - ui-unit-tests | |
| - ui-smoke | |
| - build-and-package | |
| - coverage-combine | |
| - test-report | |
| steps: | |
| - name: Check all required jobs passed | |
| uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 | |
| with: | |
| jobs: ${{ toJSON(needs) }} | |