feat: integrate the course-builder line (outline, lessons, quiz unification, inference ladder) #34
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 | |
| # Runs on every pull request targeting master, and on every push to master. | |
| # The `quality-gate` job at the bottom aggregates all others — configure that | |
| # single job as the required status check in branch protection. | |
| on: | |
| push: | |
| branches: [master, mvp-testing] | |
| pull_request: | |
| # mvp-testing is the active development line and receives master's changes | |
| # periodically, so the gate must run there too rather than only on master. | |
| branches: [master, mvp-testing] | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| backend: | |
| name: Backend (Python) | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:15-alpine | |
| env: | |
| POSTGRES_USER: bitcoin_academy | |
| POSTGRES_PASSWORD: bitcoin_academy | |
| POSTGRES_DB: bitcoin_academy | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| defaults: | |
| run: | |
| working-directory: services/ai | |
| env: | |
| DATABASE_URL: postgresql://bitcoin_academy:bitcoin_academy@localhost:5432/bitcoin_academy | |
| SECRET_KEY: CI-AUTH-JWT-KEY-FOR-PIPELINE-ONLY-32! | |
| ENVIRONMENT: development | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| cache-dependency-path: services/ai/pyproject.toml | |
| - name: Install dependencies | |
| run: pip install -e ".[dev]" | |
| - name: Type check (mypy) | |
| run: mypy app | |
| # Coverage thresholds live in services/ai/setup.cfg ([coverage:report] | |
| # fail_under). The run fails if coverage drops below the ratchet. | |
| - name: Run tests with coverage (pytest) | |
| run: | | |
| pytest --cov=app \ | |
| --cov-report=term-missing \ | |
| --cov-report=xml:coverage.xml \ | |
| --cov-report=html:htmlcov | |
| - name: Coverage summary | |
| if: always() | |
| run: | | |
| { | |
| echo "### Backend coverage" | |
| echo "" | |
| echo '```' | |
| coverage report --show-missing | tail -40 | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: backend-coverage | |
| path: | | |
| services/ai/coverage.xml | |
| services/ai/htmlcov | |
| retention-days: 14 | |
| frontend: | |
| name: Frontend (Node.js) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: apps/web | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: apps/web/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Type check (tsc) | |
| run: npm run type-check | |
| - name: Lint | |
| run: npm run lint | |
| env: | |
| NEXT_PUBLIC_API_BASE_URL: http://localhost:8000/api | |
| - name: Run tests with coverage (Jest) | |
| run: npm test -- --ci --coverage --passWithNoTests | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-coverage | |
| path: apps/web/coverage | |
| retention-days: 14 | |
| qvac-service: | |
| name: QVAC Service (Node.js) | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: workers/qvac-service | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: workers/qvac-service/package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run tests | |
| run: npm test | |
| build: | |
| name: Production build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Validates that both compose files parse, merge, and reference only | |
| # declared volumes, services and build targets. Catches the class of | |
| # defect where the stack is broken but nothing builds it in CI. | |
| - name: Validate compose configuration (production) | |
| run: docker compose -f infra/docker-compose.yml config --quiet | |
| - name: Validate compose configuration (development merge) | |
| run: docker compose -f infra/docker-compose.yml -f infra/docker-compose.override.yml config --quiet | |
| - name: Build backend image | |
| run: docker build -t bitcoin-academy-api:ci ./services/ai | |
| - name: Build frontend image (production target) | |
| run: docker build --target runner -t bitcoin-academy-web:ci ./apps/web | |
| - name: Build frontend image (development target) | |
| run: docker build --target dev -t bitcoin-academy-web:ci-dev ./apps/web | |
| - name: Build QVAC service image | |
| run: docker build -t bitcoin-academy-qvac:ci ./workers/qvac-service | |
| # Aggregates every job above into one check. Set THIS as the required status | |
| # check on master: adding a job below does not then require reconfiguring | |
| # branch protection, and a skipped or cancelled job cannot pass silently. | |
| quality-gate: | |
| name: Quality gate | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: [backend, frontend, qvac-service, build] | |
| steps: | |
| - name: Verify every job succeeded | |
| run: | | |
| echo "backend: ${{ needs.backend.result }}" | |
| echo "frontend: ${{ needs.frontend.result }}" | |
| echo "qvac-service: ${{ needs.qvac-service.result }}" | |
| echo "build: ${{ needs.build.result }}" | |
| if [ "${{ needs.backend.result }}" != "success" ] || \ | |
| [ "${{ needs.frontend.result }}" != "success" ] || \ | |
| [ "${{ needs.qvac-service.result }}" != "success" ] || \ | |
| [ "${{ needs.build.result }}" != "success" ]; then | |
| echo "::error::Quality gate failed — one or more jobs did not succeed." | |
| exit 1 | |
| fi | |
| echo "All checks passed." |