Merge pull request #946 from Raven062/fix/W2-B-027-yield-accounting-i… #986
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 | |
| on: | |
| push: | |
| branches: [main, dev, develop] | |
| pull_request: | |
| branches: [main, dev, develop] | |
| jobs: | |
| lint-and-format: | |
| name: Lint, Format & Security Audit | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: "10.33.1" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run security audit | |
| run: pnpm audit --audit-level high | |
| - name: Run non-blocking audit report | |
| if: always() | |
| run: pnpm audit || true | |
| - name: Run ESLint | |
| run: pnpm lint | |
| - name: Check Prettier formatting | |
| run: pnpm format:check | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| # Minimal env vars so Jest can load modules without connecting to real infra. | |
| # All external calls are mocked in tests. | |
| DATABASE_URL: postgresql://test:test@localhost:5432/test | |
| MONGODB_URI: mongodb://localhost:27017/test | |
| RABBITMQ_URL: amqp://localhost:5672 | |
| JWT_SECRET: ci-test-secret-not-used-in-real-traffic | |
| NODE_ENV: test | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: "10.33.1" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Verify bcrypt native build | |
| run: node -e "require('bcrypt')" | |
| - name: Run tests | |
| run: pnpm exec jest --ci --coverage | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: coverage | |
| path: coverage/ | |
| retention-days: 7 | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [lint-and-format, test] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: "10.33.1" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Verify bcrypt native build | |
| run: node -e "require('bcrypt')" | |
| - name: Generate Prisma client | |
| run: pnpm prisma:generate | |
| - name: Build TypeScript | |
| run: pnpm build | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| license-check: | |
| name: License Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: "10.33.1" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "20" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Install license-checker | |
| run: npm install -g license-checker | |
| - name: Check licenses | |
| run: license-checker --production --failOn "GPL;AGPL;LGPL;MPL;CPL;EPL;OSL" --summary | |
| validate-migrations: | |
| name: Validate Migrations | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: build | |
| # Only run on pull requests — push to main/dev triggers deploy, not re-validation | |
| if: github.event_name == 'pull_request' | |
| # Fresh, ephemeral Postgres provisioned for THIS job run only. | |
| # Migrations never touch shared/staging databases — GitHub destroys the | |
| # service container as soon as the job finishes, so every run starts | |
| # from an empty database and full isolation is guaranteed. | |
| env: | |
| DATABASE_URL: postgresql://acbu_ci:acbu_ci_pw@localhost:5432/acbu_ci | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # Fetch full history so the destructive-migration gate can diff | |
| # schema.prisma against the pull request base commit | |
| fetch-depth: 0 | |
| - name: Start local dev infrastructure | |
| env: | |
| POSTGRES_USER: acbu_ci | |
| POSTGRES_PASSWORD: acbu_ci_pw | |
| POSTGRES_DB: acbu_ci | |
| MONGO_USER: acbu_ci | |
| MONGO_PASSWORD: acbu_ci_pw | |
| MONGO_DB: acbu_ci_cache | |
| RABBITMQ_USER: acbu_ci | |
| RABBITMQ_PASSWORD: acbu_ci_pw | |
| run: docker compose up -d --wait | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: "10.33.1" | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Create shadow database for reproducibility check | |
| # `prisma migrate diff --from-migrations` rebuilds the schema purely from the | |
| # migration files into an empty shadow database, then compares it to the deployed | |
| # database. The superuser provisioned by the service container can create it. | |
| run: > | |
| node -e "const{Pool}=require('pg');(async()=>{const p=new Pool({connectionString:process.env.DATABASE_URL});await p.query('CREATE DATABASE acbu_ci_shadow');await p.end();console.log('shadow database created');})().catch(e=>{console.error(e.message);process.exit(1);})" | |
| - name: Block destructive migrations without label | |
| # Shared, unit-tested implementation (see tests/destructiveMigrationGate.test.ts). | |
| # Detects DROP TABLE / DROP COLUMN / TRUNCATE in changed migration files AND | |
| # destructive changes implied by prisma/schema.prisma edits, then requires the | |
| # 'allow-destructive-migration' label to proceed. This is the reversibility gate: | |
| # irreversible schema changes must be acknowledged explicitly. | |
| run: node scripts/ci/check-destructive-migrations.js | |
| - name: Apply all migrations to the isolated database | |
| # Proves every migration applies cleanly from an empty database on a fresh, | |
| # ephemeral instance. A broken migration fails here in isolation instead of | |
| # corrupting shared/staging databases or blocking unrelated PRs. | |
| run: pnpm prisma:migrate:deploy | |
| - name: Verify migration history is reproducible (reversibility check) | |
| # Rebuild the schema from the migration files into the empty shadow database and | |
| # confirm it matches the database produced by `migrate deploy`. This proves the | |
| # migration chain is self-consistent and reproducible — a prerequisite for safe, | |
| # reversible migrations — without depending on unrelated drift between | |
| # prisma/schema.prisma and the live migration history. | |
| run: > | |
| pnpm exec prisma migrate diff | |
| --from-migrations prisma/migrations | |
| --to-url "$DATABASE_URL" | |
| --shadow-database-url "postgresql://acbu_ci:acbu_ci_pw@localhost:5432/acbu_ci_shadow" | |
| --exit-code | |
| docker-build-publish: | |
| name: Build and Publish Docker Image | |
| runs-on: ubuntu-latest | |
| needs: build | |
| # Only publish images on pushes to the main/dev branches, not on PRs | |
| if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/develop') | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata (tags, labels) | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ghcr.io/${{ github.repository }} | |
| tags: | | |
| type=ref,event=branch | |
| type=sha | |
| - name: Build and push Docker image | |
| id: build-and-push | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Output image digest | |
| run: echo "Image pushed with digest ${{ steps.build-and-push.outputs.digest }}" |