name: CI on: pull_request: branches: [main] paths-ignore: ['**.md', 'docs/**', 'LICENSE'] workflow_call: # Concurrency convention: see CONTRIBUTING.md → "GitHub Actions — Concurrency Convention". # Hardcoded `CI-` prefix (not `${{ github.workflow }}`) because this workflow is # invoked as a reusable workflow from publish.yml and release-candidate.yml. In # called-workflow context `github.workflow` evaluation is ambiguous across GitHub # Actions versions, and a prefix that could resolve to the caller's name would # share a concurrency group with the caller → deadlock. A literal prefix is # immune. Direct `pull_request` invocations use `CI-`; invocations from a # reusable-workflow caller fall into a per-run-unique group that never serializes # with the caller. `push` to main is handled by release-candidate.yml, which # calls this workflow once before publishing. concurrency: group: ${{ github.event_name == 'pull_request' && format('CI-{0}', github.ref) || format('CI-nested-{0}', github.run_id) }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} # ── Reusable workflow orchestration ───────────────────────────────── # Each concern lives in its own workflow file for maintainability: # ci-quality.yml — typecheck (tsc --noEmit) # ci-tests.yml — unit + integration tests with coverage + cross-platform # ci-e2e.yml — E2E tests (only when gitnexus-web/ changes) # ci-scope-parity.yml — RFC #909 Ring 3 parity gate: legacy DAG + registry-primary # both pass, per migrated language in the JSON registry # # Shared setup is DRY via .github/actions/setup-gitnexus composite action. jobs: quality: uses: ./.github/workflows/ci-quality.yml permissions: contents: read tests: uses: ./.github/workflows/ci-tests.yml permissions: contents: read e2e: uses: ./.github/workflows/ci-e2e.yml permissions: contents: read scope-parity: uses: ./.github/workflows/ci-scope-parity.yml permissions: contents: read # ── Save PR metadata for the reporting workflow ───────────────── # The ci-report.yml workflow (triggered by workflow_run) needs the # PR number and job results to post a comment. We save them as an # artifact because workflow_run context doesn't reliably carry PR # info for fork PRs. save-pr-meta: name: Save PR Metadata if: always() && github.event_name == 'pull_request' needs: [quality, tests, e2e, scope-parity] runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Write metadata shell: bash env: PR_NUMBER: ${{ github.event.number }} QUALITY: ${{ needs.quality.result }} TESTS: ${{ needs.tests.result }} E2E: ${{ needs.e2e.result }} SCOPE_PARITY: ${{ needs.scope-parity.result }} run: | mkdir -p pr-meta echo "$PR_NUMBER" > pr-meta/pr_number echo "$QUALITY" > pr-meta/quality_result echo "$TESTS" > pr-meta/tests_result echo "$E2E" > pr-meta/e2e_result echo "$SCOPE_PARITY" > pr-meta/scope_parity_result # TODO(post-merge): remove backward-compat copies once ci-report.yml # on main reads underscore names. # Backward-compat: ci-report.yml on main still reads hyphenated # names. workflow_run always executes from the default branch, so # the main-branch reader won't find the underscore variants until # this PR is merged. Write both until then. cp pr-meta/pr_number pr-meta/pr-number cp pr-meta/quality_result pr-meta/quality-result cp pr-meta/tests_result pr-meta/tests-result cp pr-meta/e2e_result pr-meta/e2e-result - name: Upload PR metadata uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: pr-meta path: pr-meta/ retention-days: 1 # ── Unified CI gate ────────────────────────────────────────────── # Single required check for branch protection. ci-status: name: CI Gate needs: [quality, tests, e2e, scope-parity] if: always() runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Check all jobs passed shell: bash env: QUALITY: ${{ needs.quality.result }} TESTS: ${{ needs.tests.result }} E2E: ${{ needs.e2e.result }} SCOPE_PARITY: ${{ needs.scope-parity.result }} run: | echo "Quality: $QUALITY" echo "Tests: $TESTS" echo "E2E: $E2E" echo "Scope parity: $SCOPE_PARITY" if [[ "$QUALITY" != "success" ]] || [[ "$TESTS" != "success" ]]; then echo "::error::Quality or test jobs failed" exit 1 fi if [[ "$E2E" != "success" && "$E2E" != "skipped" ]]; then echo "::error::E2E job failed" exit 1 fi # scope-parity is a reusable workflow. With an empty migrated- # languages list, its parity matrix is skipped and the outer # workflow still reports `success`. If any entry's legacy-DAG or # registry-primary run fails, the workflow reports `failure`. # Accept only `success`; `skipped` would mean the entire # discover job was skipped too (upstream failure), which should # still block. if [[ "$SCOPE_PARITY" != "success" ]]; then echo "::error::Scope-resolution parity gate failed (RFC #909 Ring 3)" exit 1 fi