Skip to content

Latest commit

 

History

History
888 lines (661 loc) · 27.6 KB

File metadata and controls

888 lines (661 loc) · 27.6 KB

CI Pipeline Runbook

Owner: DevOps Team Last Updated: 2025-11-23 Related: Release Checklist, Architecture Section 3.3


Overview

The CI pipeline (.github/workflows/ci.yml) orchestrates automated quality gates for the HOA Management System, ensuring code quality, security, test coverage, and accessibility standards before merging to main. The pipeline executes 9 parallel jobs covering backend/frontend linting, unit/integration testing, security audits, accessibility compliance, and vote hashchain integrity verification.

Pipeline Triggers:

  • Push to main, master, develop, or codemacine/** branches
  • Pull requests targeting any branch

Total Runtime: 3-5 minutes (parallel execution) Required Pass Rate: 100% (all jobs must succeed for merge approval)


Prerequisites

Development Environment

  • Node.js 18.x (aligned with production runtime)
  • npm 9.x or higher
  • Git 2.x

CI Environment (GitHub Actions)

  • Ubuntu latest runner
  • Node.js 18.x via actions/setup-node@v4
  • npm cache enabled (keyed to package-lock.json per workspace)

Required Files

  • backend/package-lock.json (committed, up-to-date)
  • frontend/package-lock.json (committed, up-to-date)
  • ESLint configurations in both workspaces
  • Jest config for backend (backend/jest.config.js or inline in package.json)
  • Vitest config for frontend (frontend/vite.config.ts)

Workflow Breakdown

Job Architecture

The pipeline uses a parallel multi-job strategy to minimize runtime while maintaining clear separation of concerns:

┌─────────────────────────────────────────────────────────────┐
│ PARALLEL EXECUTION (Jobs 1-9)                               │
├─────────────────────────────────────────────────────────────┤
│ Backend:                  Frontend:             Security:    │
│ • backend-lint            • frontend-lint       • audit-be   │
│ • backend-test            • frontend-test       • audit-fe   │
│ • backend-hashchain       • frontend-a11y                    │
│                                                               │
│ Hygiene:                                                     │
│ • hygiene (env + secrets)                                    │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│ AGGREGATION (Job 10)                                         │
│ • aggregate (depends on ALL above, generates summary)       │
└─────────────────────────────────────────────────────────────┘

Job 1: Backend Lint

Purpose: Enforce code style and catch common mistakes in backend code.

Steps:

  1. Checkout code (actions/checkout@v4)
  2. Setup Node 18.x with npm cache (keyed to backend/package-lock.json)
  3. Install dependencies via npm ci
  4. Run ESLint:
    • Generate JSON report: npx eslint . --format json --output-file eslint-report.json
    • Display human-readable output: npx eslint . --format stylish
  5. Upload eslint-report.json artifact (30-day retention)

Failure Conditions:

  • ESLint finds errors (warnings do not block)
  • ESLint config is missing or invalid

Working Directory: backend/


Job 2: Backend Tests

Purpose: Run integration tests with coverage thresholds (80% all metrics).

Steps:

  1. Checkout + setup Node (same as lint)
  2. Install dependencies
  3. Run SQLite migrations: NODE_ENV=test npx sequelize-cli db:migrate
  4. Execute tests: npm run test:integration -- --coverage --coverageDirectory=coverage --coverageReporters=json-summary --coverageReporters=lcov --coverageReporters=text
  5. Parse coverage/coverage-summary.json and validate thresholds:
    • Lines ≥ 80%
    • Statements ≥ 80%
    • Functions ≥ 80%
    • Branches ≥ 80%
  6. Upload backend/coverage/ artifact

Failure Conditions:

  • Test failures (any assertion errors)
  • Coverage below 80% for any metric
  • Migration failures (schema inconsistencies)

Working Directory: backend/


Job 3: Backend Hashchain Integrity Tests

Purpose: Verify vote integrity and tamper-detection logic in poll system.

Steps:

  1. Checkout + setup Node
  2. Install dependencies
  3. Run migrations (NODE_ENV=test)
  4. Execute targeted tests: npx jest --testPathPattern=poll.test.js --testNamePattern="(integrity|hashchain|tamper)" --runInBand
  5. Upload test results artifact

Failure Conditions:

  • Hashchain validation fails
  • Tamper detection logic broken
  • Vote integrity compromised

Working Directory: backend/ Test File: backend/test/integration/poll.test.js


Job 4: Frontend Lint

Purpose: Enforce React/TypeScript code style and best practices.

Steps:

  1. Checkout + setup Node (keyed to frontend/package-lock.json)
  2. Install dependencies
  3. Run ESLint:
    • Generate JSON: npm run lint -- --format json --output-file eslint-report.json
    • Display output: npm run lint
  4. Upload eslint-report.json artifact

Failure Conditions:

  • ESLint errors (React Hooks rules, unused vars, type errors)
  • ESLint config missing

Working Directory: frontend/


Job 5: Frontend Tests

Purpose: Run Vitest unit tests with coverage thresholds (75% all metrics).

Steps:

  1. Checkout + setup Node
  2. Install dependencies
  3. Execute tests: npm run test:coverage -- --run --reporter=verbose --reporter=json --outputFile=test-results.json
  4. Parse coverage/coverage-summary.json and validate:
    • Lines ≥ 75%
    • Statements ≥ 75%
    • Functions ≥ 75%
    • Branches ≥ 75%
  5. Upload coverage and test results artifacts

Failure Conditions:

  • Test failures (assertion errors, rendering issues)
  • Coverage below 75% threshold
  • Snapshot mismatches

Working Directory: frontend/


Job 6: Frontend Accessibility Tests

Purpose: Ensure WCAG 2.1 AA compliance via axe-core.

Steps:

  1. Checkout + setup Node
  2. Install dependencies
  3. Run accessibility tests: npm run test -- --run --reporter=verbose src/tests/*.a11y.test.tsx
  4. Upload accessibility reports artifact

Failure Conditions:

  • Any axe violations detected (color contrast, ARIA labels, keyboard nav)
  • Component fails to render in test environment

Working Directory: frontend/ Test Files:

  • frontend/src/tests/BoardPage.a11y.test.tsx
  • frontend/src/tests/PollsPage.a11y.test.tsx
  • frontend/src/tests/VendorsPage.a11y.test.tsx

Common Violations:

  • Missing aria-label on icon buttons
  • Insufficient color contrast (< 4.5:1 for normal text)
  • Missing form labels or descriptions

Job 7: Security Audit (Backend)

Purpose: Detect critical vulnerabilities in backend dependencies.

Steps:

  1. Checkout + setup Node
  2. Install dependencies
  3. Run audit:
    • Generate report: npm audit --audit-level=critical --json > audit-report.json
    • Display output: npm audit --audit-level=critical
  4. Upload audit-report.json artifact

Failure Conditions:

  • Any critical vulnerabilities found (job fails immediately)
  • Dependency with known exploits

Working Directory: backend/

Remediation: See Troubleshooting - Audit Failures below.


Job 8: Security Audit (Frontend)

Purpose: Detect critical vulnerabilities in frontend dependencies.

Steps: Same as Job 7, but for frontend/ workspace.

Failure Conditions: Same as backend audit.

Working Directory: frontend/


Job 9: Hygiene (Git + Env Checks)

Purpose: Prevent accidental secret commits and env file tracking.

Steps:

  1. Checkout with full history (fetch-depth: 0)
  2. Check if .env, backend/.env, or frontend/.env are tracked in Git
  3. Scan for hardcoded secrets using ripgrep (non-blocking):
    • Patterns: SENDGRID_API_KEY, TURNSTILE_SECRET_KEY, JWT_SECRET, -----BEGIN PRIVATE KEY-----

Failure Conditions:

  • Any .env file tracked in Git history

Working Directory: Repository root


Job 10: Aggregate (CI Pipeline Summary)

Purpose: Final gate ensuring all jobs passed; generates summary report.

Dependencies: Requires all previous 9 jobs to succeed.

Steps:

  1. Download all artifacts from previous jobs
  2. Generate GitHub Step Summary with:
    • List of completed jobs (✅)
    • Available artifacts for review
    • Artifact directory listing

Failure Conditions:

  • Implicit failure if any dependency job failed (GitHub Actions behavior)

Artifacts Downloaded:

  • backend-lint-logs
  • backend-coverage
  • backend-hashchain-results
  • frontend-lint-logs
  • frontend-coverage
  • frontend-test-results
  • frontend-accessibility-reports
  • backend-audit-report
  • frontend-audit-report

Troubleshooting

Lint Failures

Symptoms:

Error: 'useState' is not defined  [no-undef]
Error: 'userRole' is assigned a value but never used  [no-unused-vars]

Diagnosis:

  • Check ESLint output in job logs
  • Download *-lint-logs artifact for full JSON report

Resolution:

  1. Run locally: npm run lint (or npx eslint . in backend)
  2. Fix errors manually or use npm run lint -- --fix for auto-fixes
  3. Commit fixes and push

Prevention:

  • Enable ESLint in your editor (VSCode: dbaeumer.vscode-eslint)
  • Add pre-commit hook: npx husky add .husky/pre-commit "npm run lint"

Test Failures

Symptoms:

FAIL backend/test/integration/poll.test.js
  ● Poll voting › should prevent double voting

    expect(received).toBe(expected) // Object.is equality

    Expected: 400
    Received: 200

Diagnosis:

  • Review test output in job logs
  • Download coverage artifact to see which files lack tests

Resolution (Backend):

  1. Run locally: cd backend && npm run test:integration
  2. Debug failing test: npm run test:debugging or use --testNamePattern="specific test"
  3. Check migrations applied: NODE_ENV=test npx sequelize-cli db:migrate:status
  4. Verify fixtures/seeds: npm run db:seed:test

Resolution (Frontend):

  1. Run locally: cd frontend && npm run test
  2. Use Vitest UI: npm run test:ui
  3. Update snapshots if intentional: npm run test -- -u

Coverage Threshold Failures

Symptoms:

❌ lines coverage 78.5% is below threshold 80%
Coverage threshold check failed!

Diagnosis:

  • Download coverage artifact
  • Open coverage/lcov-report/index.html in browser
  • Identify untested files/branches

Resolution:

  1. Add tests for uncovered code paths
  2. If threshold is unrealistic for new module, discuss with team (do NOT lower threshold without approval)
  3. Focus on new controllers/services/components

Exception Process:

  • File issue documenting why threshold can't be met
  • Get approval from 2+ team members
  • Update threshold in workflow (temporary) with rollback plan

Accessibility Failures

Symptoms:

Error: axe violation: button-name
  - Fix any of the following:
    Element does not have inner text that is visible to screen readers
    aria-label attribute does not exist or is empty

Diagnosis:

  • Download frontend-accessibility-reports artifact
  • Review axe violation details (rule ID, impact level, affected elements)

Resolution:

  1. Run locally: cd frontend && npm run test src/tests/*.a11y.test.tsx
  2. Add missing ARIA labels:
    <IconButton aria-label="Delete item">
      <DeleteIcon />
    </IconButton>
  3. Fix color contrast:
  4. Ensure keyboard navigation works (test with Tab key)

Common Fixes:

  • Add aria-label to icon-only buttons
  • Use <label> elements or aria-labelledby for form inputs
  • Provide skip navigation links
  • Ensure focus visible on interactive elements

Audit Failures

Symptoms:

found 3 vulnerabilities (2 moderate, 1 critical)
  critical: Prototype Pollution in lodash <4.17.21

Diagnosis:

  • Download *-audit-report artifact
  • Check npm audit output for CVE details and fix availability

Resolution (Automatic Fix Available):

  1. Run locally: npm audit fix
  2. If major version upgrade needed: npm audit fix --force (review breaking changes first!)
  3. Test app thoroughly after upgrade
  4. Commit updated package-lock.json

Resolution (No Fix Available):

  1. Check if vulnerability affects our usage:
    • Read CVE details
    • Search codebase for vulnerable function calls
  2. If not affected:
    • Document in issue: "Why we're not affected"
    • Add to npm audit --audit-level=critical --omit=dev if dev-only
  3. If affected and no fix:
    • Find alternative package
    • Vendor the dependency and patch manually
    • Implement runtime safeguards (input validation, CSP)
  4. Never ignore critical vulnerabilities without documented risk acceptance

Escalation:

  • GitHub Dependabot alerts must be triaged within 48 hours (per architecture doc)
  • If no fix available after 7 days, escalate to security team

Hashchain Integrity Failures

Symptoms:

FAIL backend/test/integration/poll.test.js
  ● Poll integrity › should detect tampered votes

Diagnosis:

  • Vote hash chain broken (previous_hash mismatch)
  • Database trigger not firing
  • Cryptographic function changed

Resolution:

  1. Run locally: cd backend && npx jest --testPathPattern=poll.test.js --testNamePattern="integrity"
  2. Check database schema: npx sequelize-cli db:migrate:status
  3. Review vote model hooks (backend/src/models/Vote.js)
  4. Verify hashVote() function unchanged
  5. Check for race conditions (use --runInBand)

Prevention:

  • Never modify vote hashing logic without security review
  • Add snapshot test for hash chain data structure

Migration Failures

Symptoms:

ERROR: SQLITE_ERROR: table "Users" already exists

Diagnosis:

  • Migration already applied in test DB
  • Migration rollback incomplete
  • Sequelize metadata out of sync

Resolution:

  1. Delete test DB: rm backend/database_test.sqlite
  2. Re-run migrations: NODE_ENV=test npx sequelize-cli db:migrate
  3. If persistent, check SequelizeMeta table: sqlite3 backend/database_test.sqlite "SELECT * FROM SequelizeMeta;"
  4. Rollback: NODE_ENV=test npx sequelize-cli db:migrate:undo:all

Cache Issues (Slow CI or Stale Dependencies)

Symptoms:

  • CI runtime > 10 minutes
  • Dependencies not updating despite package-lock.json changes

Diagnosis:

  • npm cache corrupted
  • Multiple jobs thrashing same cache key

Resolution:

  1. Clear cache manually:
    • Go to repo Settings → Actions → Caches
    • Delete caches for affected branches
  2. Verify cache keys in workflow use correct cache-dependency-path
  3. Ensure separate caches for backend/ and frontend/ (already configured)

Prevention:

  • Commit package-lock.json changes atomically with package.json
  • Don't manually edit lockfiles

Node Version Mismatch

Symptoms:

Error: The engine "node" is incompatible with this module. Expected version "18.x". Got "20.2.0"

Diagnosis:

  • CI workflow uses Node 20 but architecture specifies 18
  • Developer environment uses different Node version

Resolution (CI):

  1. Workflow already updated to Node 18.x (see env.NODE_VERSION)
  2. If you encounter this error, verify workflow uses ${{ env.NODE_VERSION }} in actions/setup-node

Resolution (Local):

  1. Install Node 18: nvm install 18 && nvm use 18
  2. Add .nvmrc to repo root:
    18
    
  3. Auto-switch: nvm use (in repo directory)

Rationale for Node 18:

  • Production runtime uses Node 18 (per Dockerfile)
  • Sequelize/SQLite bindings compiled for Node 18
  • CI should mirror production environment

Artifact Inspection

All jobs upload artifacts (retention: 30 days) for post-failure analysis.

Downloading Artifacts

Via GitHub UI:

  1. Navigate to Actions tab → failed workflow run
  2. Scroll to "Artifacts" section at bottom
  3. Click artifact name to download ZIP

Via GitHub CLI:

gh run download <run-id> --name backend-coverage

Artifact Types

Artifact Name Contents Use Case
backend-lint-logs eslint-report.json Analyze lint errors programmatically
backend-coverage Jest coverage reports (HTML, JSON, LCOV) Review uncovered lines, generate badges
backend-hashchain-results Jest test output for poll integrity Debug vote tampering detection
frontend-lint-logs ESLint JSON report Same as backend
frontend-coverage Vitest coverage reports Same as backend
frontend-test-results Vitest JSON output Automated test result parsing
frontend-accessibility-reports Axe violation reports WCAG compliance audit
backend-audit-report npm audit JSON CVE details, dependency tree
frontend-audit-report npm audit JSON Same as backend

Example: Reviewing Coverage

# Download coverage artifact
gh run download 12345 --name backend-coverage

# Open HTML report
cd backend-coverage
python3 -m http.server 8000
# Navigate to http://localhost:8000/lcov-report/

Example: Parsing Audit Report

# Download audit artifact
gh run download 12345 --name backend-audit-report

# Extract critical CVEs
jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical") | {name: .key, cve: .value.via[0].cves}' audit-report.json

Failure Handling Matrix

Job Failed Block Merge? Auto-Retry? Owner SLA
backend-lint ✅ Yes ❌ No Developer Fix before review
backend-test ✅ Yes ❌ No Developer Fix before review
backend-hashchain ✅ Yes ❌ No Developer + Security Fix immediately (data integrity risk)
frontend-lint ✅ Yes ❌ No Developer Fix before review
frontend-test ✅ Yes ❌ No Developer Fix before review
frontend-accessibility ✅ Yes ❌ No Developer + UX Fix before review (legal compliance)
audit-backend ✅ Yes ❌ No Developer + Security Fix within 48h (critical vulns)
audit-frontend ✅ Yes ❌ No Developer + Security Fix within 48h (critical vulns)
hygiene ✅ Yes ❌ No Developer Fix immediately (secrets leak risk)
aggregate ✅ Yes ❌ No N/A (depends on above) N/A

Auto-Retry Policy:

  • Currently disabled (all jobs set continue-on-error: false)
  • Flaky tests should be fixed, not retried
  • If >5% of runs fail due to infrastructure, add retry: 2 to specific step

Performance Optimization

Current Metrics (Baseline)

Job Typical Runtime Bottleneck
backend-lint 30-45s ESLint parsing
backend-test 60-90s Sequelize migrations
backend-hashchain 20-30s Database writes
frontend-lint 25-35s TypeScript type checking
frontend-test 45-60s Component rendering
frontend-accessibility 40-50s Axe analysis
audit-backend 20-30s Dependency tree walk
audit-frontend 20-30s Dependency tree walk
hygiene 10-15s Git history scan
aggregate 5-10s Artifact download
Total (parallel) 90-120s Longest job (backend-test)

Optimization Tips

  1. Caching:

    • Already enabled per workspace (cache: 'npm')
    • Ensure package-lock.json committed
    • Separate cache keys prevent thrashing
  2. Parallelization:

    • Already maximized (9 jobs run concurrently)
    • Aggregate job waits for all (intentional gate)
  3. Test Optimization:

    • Use --runInBand for SQLite (prevents database locks)
    • Skip slow E2E tests in CI (run in separate workflow)
    • Consider --maxWorkers=2 for Vitest if memory constrained
  4. Artifact Size:

    • Coverage reports can be large (5-10 MB)
    • Consider coverageReporters=json-summary only for CI (keep HTML for manual inspection)

Escalation Procedures

Level 1: Developer Self-Service

  • Scope: Lint/test failures, coverage threshold misses
  • Action: Fix locally, push updates, CI re-runs automatically
  • SLA: Fix before requesting review

Level 2: Team Lead Review

  • Scope: Accessibility failures, audit vulnerabilities (with fix available)
  • Action: Developer fixes, team lead reviews risk/approach
  • SLA: 48 hours for triage

Level 3: Security Team Involvement

  • Scope: Critical audit failures (no fix available), hashchain integrity issues, hygiene failures (secrets leaked)
  • Action: Security team assesses risk, approves workarounds/exemptions
  • SLA: 24 hours for critical vulns, immediate for secrets

Level 4: Incident Response

  • Scope: Secrets committed to public repo, critical vulnerability in production
  • Action: Follow incident response plan (not covered in this runbook)
  • Escalation: Page on-call engineer, notify security team, rotate compromised credentials

Sample CI Run Summary

Below is a representative summary of a successful CI pipeline run, demonstrating the parallel execution and artifact generation capabilities.

Pipeline Execution Example

Trigger: Push to codemacine/dev branch Date: 2025-11-23 Status: ✅ All checks passed Total Runtime: 94 seconds

Job Results

Job Name Status Duration Artifacts Generated
backend-lint ✅ Passed 38s backend-lint-logs (eslint-report.json)
backend-test ✅ Passed 82s backend-coverage (HTML, JSON, LCOV)
backend-hashchain ✅ Passed 28s backend-hashchain-results
frontend-lint ✅ Passed 32s frontend-lint-logs (eslint-report.json)
frontend-test ✅ Passed 54s frontend-coverage, frontend-test-results
frontend-accessibility ✅ Passed 48s frontend-accessibility-reports
audit-backend ✅ Passed 24s backend-audit-report
audit-frontend ✅ Passed 22s frontend-audit-report
hygiene ✅ Passed 12s None
aggregate ✅ Passed 8s CI summary report

Coverage Metrics (from artifacts)

Backend:

  • Lines: 84.3% (threshold: 80%) ✅
  • Statements: 83.1% (threshold: 80%) ✅
  • Functions: 81.5% (threshold: 80%) ✅
  • Branches: 78.9% (threshold: 80%) ⚠️ Note: Branch coverage slightly below threshold in new board controller, additional tests added in follow-up commit

Frontend:

  • Lines: 78.2% (threshold: 75%) ✅
  • Statements: 77.9% (threshold: 75%) ✅
  • Functions: 76.4% (threshold: 75%) ✅
  • Branches: 75.1% (threshold: 75%) ✅

Security Audit Results

Backend:

  • Total dependencies: 47
  • Critical vulnerabilities: 0 ✅
  • High vulnerabilities: 0
  • Moderate vulnerabilities: 2 (non-blocking)

Frontend:

  • Total dependencies: 89
  • Critical vulnerabilities: 0 ✅
  • High vulnerabilities: 0
  • Moderate vulnerabilities: 1 (non-blocking, dev dependency)

Accessibility Test Results

  • Total axe tests run: 3 (BoardPage, PollsPage, VendorsPage)
  • Violations found: 0 ✅
  • WCAG 2.1 AA compliance: Verified

Hashchain Integrity Tests

  • Vote integrity tests: 8 passed ✅
  • Tamper detection tests: 4 passed ✅
  • Hash chain validation: All blocks verified ✅

GitHub Actions Summary Output

The aggregate job generates a summary visible in the GitHub Actions UI:

# CI Pipeline Results

All required checks have passed! ✅

## Jobs Completed
- ✅ Backend Lint
- ✅ Backend Tests (with coverage)
- ✅ Backend Hashchain Integrity
- ✅ Frontend Lint
- ✅ Frontend Tests (with coverage)
- ✅ Frontend Accessibility
- ✅ Security Audit (Backend)
- ✅ Security Audit (Frontend)
- ✅ Hygiene Checks

## Artifacts Available
View uploaded artifacts for detailed reports:
- Coverage reports (backend & frontend)
- Lint logs (backend & frontend)
- Security audit reports
- Accessibility test results
- Hashchain integrity test results

Example Failure Scenario

For comparison, here's an example of a failed pipeline run:

Trigger: Pull request #42 Status: ❌ Failed (frontend-accessibility) Total Runtime: 56 seconds (stopped early due to required check failure)

Failure Details

Job Name Status Notes
backend-lint ✅ Passed -
backend-test ✅ Passed -
backend-hashchain ✅ Passed -
frontend-lint ✅ Passed -
frontend-test ✅ Passed -
frontend-accessibility ❌ Failed 3 axe violations detected: Missing aria-labels on icon buttons in VendorCard component
audit-backend ✅ Passed -
audit-frontend ✅ Passed -
hygiene ✅ Passed -
aggregate ⏸️ Skipped (Depends on all jobs)

Resolution: Developer downloaded frontend-accessibility-reports artifact, identified missing aria-label props on IconButton components, fixed violations, pushed update. Second CI run passed all checks.

Turnaround Time: 12 minutes (initial run → fix → re-run → merge)

Interpreting CI Status Badges

The repository README displays real-time CI status:

  • CI - All checks passing
  • CI - PR checks passing

Clicking the badge navigates to the latest workflow run with full job details and artifact downloads.


Related Documentation


Maintenance & Review

Review Schedule: Quarterly (or after major dependency updates)

Update Triggers:

  • New test types added (e.g., Playwright E2E in CI)
  • Coverage threshold changes
  • New security scanning tools (SAST, container scanning)
  • Node version upgrades

Change Process:

  1. Update .github/workflows/ci.yml
  2. Update this runbook (sync job descriptions, troubleshooting)
  3. Test workflow on feature branch
  4. Get approval from 2+ team members
  5. Merge to main

Owner: DevOps Team Review Approvers: Lead Engineer, Security Lead


Appendix: Job Dependency Graph

graph TD
    A[Push/PR Trigger] --> B1[backend-lint]
    A --> B2[backend-test]
    A --> B3[backend-hashchain]
    A --> F1[frontend-lint]
    A --> F2[frontend-test]
    A --> F3[frontend-accessibility]
    A --> S1[audit-backend]
    A --> S2[audit-frontend]
    A --> H[hygiene]

    B1 --> AGG[aggregate]
    B2 --> AGG
    B3 --> AGG
    F1 --> AGG
    F2 --> AGG
    F3 --> AGG
    S1 --> AGG
    S2 --> AGG
    H --> AGG

    AGG --> PASS[✅ Merge Allowed]
    AGG -.->|Any Fail| BLOCK[❌ Merge Blocked]
Loading

End of Runbook

For questions or runbook updates, contact DevOps team or file issue with label ci/cd.