Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
self-hosted-runner:
labels:
- ci
- docker
52 changes: 37 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
---
# Pullbox CI Pipeline
# Quality gate — lint, typecheck, test, migration check, accessibility, E2E.
# Docker builds trigger via workflow_run after CI passes on main.
#
# Security: All actions pinned to full SHA. No pull_request_target.
# See: docs/development/INFRASTRUCTURE.md

name: CI

on:
push:
branches: [main]
pull_request:
branches: [main, develop]
merge_group:
types: [checks_requested]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
PYTHON_DEFAULT: "3.14"
Expand All @@ -33,7 +31,7 @@ jobs:
# ──────────────────────────────────────────────
quality-gate:
name: Quality Gate
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
timeout-minutes: 15
permissions:
contents: read
Expand Down Expand Up @@ -79,7 +77,7 @@ jobs:
# ──────────────────────────────────────────────
typecheck:
name: Type Check
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
timeout-minutes: 15
permissions:
contents: read
Expand All @@ -104,7 +102,7 @@ jobs:
# ──────────────────────────────────────────────
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
needs: [quality-gate, typecheck, alembic-check]
timeout-minutes: 30
permissions:
Expand Down Expand Up @@ -142,11 +140,12 @@ jobs:
PYTEST_WORKERS: ${{ env.PYTEST_WORKERS }}

- name: Upload coverage report
if: always() && matrix.python-version == '3.12'
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-report
name: coverage-report-py${{ matrix.python-version }}
path: coverage.xml
if-no-files-found: error
retention-days: 30

- name: Upload test results
Expand All @@ -162,7 +161,7 @@ jobs:
# ──────────────────────────────────────────────
alembic-check:
name: Migration Check
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
timeout-minutes: 15
permissions:
contents: read
Expand Down Expand Up @@ -207,7 +206,7 @@ jobs:
# ──────────────────────────────────────────────
accessibility:
name: Accessibility Checks
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
needs: [quality-gate, typecheck, alembic-check]
timeout-minutes: 30
permissions:
Expand Down Expand Up @@ -262,20 +261,32 @@ jobs:
path: test-results/accessibility-junit.xml
retention-days: 14

- name: Upload accessibility artifacts
if: failure()
- name: Upload accessibility failure artifacts
if: failure() || cancelled()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: accessibility-artifacts
path: test-results/
if-no-files-found: ignore
retention-days: 14

- name: Accessibility job summary
if: always()
run: |
{
echo "## Accessibility Checks"
echo ""
echo "- Contrast audit: semantic color token gate"
echo "- Browser audit: marked accessibility E2E coverage"
echo "- Failure artifacts: uploaded only when the job fails or is canceled"
} >> "$GITHUB_STEP_SUMMARY"

# ──────────────────────────────────────────────
# Job 6: E2E Tests (per-browser isolated pytest sessions, after tests pass)
# ──────────────────────────────────────────────
e2e:
name: E2E Tests (${{ matrix.browser }})
runs-on: ${{ (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) && 'ubuntu-latest' || (vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted') }}
runs-on: ${{ fromJSON(((github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name != github.repository || github.actor == 'dependabot[bot]')) || vars.PULLBOX_CHECKS_RUNNER == 'github-hosted') && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
needs: [test]
timeout-minutes: 30
permissions:
Expand Down Expand Up @@ -331,13 +342,24 @@ jobs:
retention-days: 14

- name: Upload Playwright failure artifacts
if: failure()
if: failure() || cancelled()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-artifacts-${{ matrix.browser }}
path: test-results/
if-no-files-found: ignore
retention-days: 14

- name: E2E job summary
if: always()
run: |
{
echo "## E2E Checks (${{ matrix.browser }})"
echo ""
echo "- Browser: ${{ matrix.browser }}"
echo "- Failure artifacts: uploaded only when the job fails or is canceled"
} >> "$GITHUB_STEP_SUMMARY"

# Stable aggregate required check for branch rulesets.
ci-required:
name: CI Required
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/clean-room.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ concurrency:
jobs:
fresh-install:
name: Fresh Install Verification
runs-on: ${{ vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && 'ubuntu-latest' || 'self-hosted' }}
runs-on: ${{ fromJSON(vars.PULLBOX_CHECKS_RUNNER == 'github-hosted' && '["ubuntu-latest"]' || '["self-hosted","Linux","X64","ci"]') }}
timeout-minutes: 45
permissions:
contents: read
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/codeql-branch-probe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ name: CodeQL Branch Probe
on:
push:
branches:
- main
- develop
- feature/ci-cd-*
- feature/code-scanning-*
- feature/codeql-*
- feature/security-*
Expand Down
106 changes: 0 additions & 106 deletions .github/workflows/docker-pr.yml

This file was deleted.

Loading
Loading