# BFSGuard CI — Barrierefreiheit als Build-Gate

Prüft eine URL gegen WCAG 2.2 AA / BFSG und lässt den Build scheitern, wenn Schwellen
überschritten werden. Befunde erscheinen als SARIF direkt im Pull Request.

Zwei Wege: **GitHub Action** (fertig) oder **CLI** (jede Pipeline: GitLab, Jenkins, Bitbucket, lokal).

---

## GitHub Action

`.github/workflows/accessibility.yml`:

```yaml
name: Barrierefreiheit
on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  security-events: write   # nötig, damit SARIF im PR erscheint

jobs:
  bfsg:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: BFSG-Gate
        uses: bfsguard/accessibility-action@v1
        with:
          url: https://staging.example.de
          api-key: ${{ secrets.BFSGUARD_API_KEY }}
          max-pages: 25
          max-critical: 0
          max-serious: 0
```

**Erst beobachten, dann blockieren.** In einem Bestandsprojekt existieren fast immer Befunde.
Realistischer Einstieg: mit `warn-only: true` starten, den echten Stand ansehen, Schwellen auf
den Ist-Zustand setzen und sie schrittweise senken — so blockiert das Gate nur Verschlechterungen.

```yaml
        with:
          url: https://staging.example.de
          api-key: ${{ secrets.BFSGUARD_API_KEY }}
          warn-only: true          # Phase 1: nur berichten
          # max-serious: 12        # Phase 2: Ist-Zustand einfrieren
          # max-serious: 0         # Phase 3: sauber
```

### Eingaben

| Eingabe | Standard | Bedeutung |
|---|---|---|
| `url` | — | Zu prüfende Adresse (Pflicht) |
| `api-key` | — | API-Schlüssel, **immer** als Secret (Pflicht) |
| `max-pages` | `5` | Seiten je Scan; Plan-Limit gilt (Starter 25, Business 100, Agentur 250) |
| `max-critical` | `0` | Erlaubte kritische Verstöße |
| `max-serious` | `0` | Erlaubte schwere Verstöße |
| `min-score` | `0` | Mindest-Score (0 = aus) |
| `warn-only` | `false` | Befunde melden, Build nicht scheitern lassen |
| `sarif-file` | `bfsguard.sarif` | Leer setzen, um SARIF zu deaktivieren |
| `timeout` | `300` | Maximale Wartezeit in Sekunden |

### Ausgaben

`score`, `critical`, `serious`, `report-url` — z. B. für einen PR-Kommentar:

```yaml
      - name: BFSG-Gate
        id: bfsg
        uses: bfsguard/accessibility-action@v1
        with: { url: 'https://staging.example.de', api-key: '${{ secrets.BFSGUARD_API_KEY }}' }

      - name: Ergebnis kommentieren
        if: always()
        run: echo "Score ${{ steps.bfsg.outputs.score }} — ${{ steps.bfsg.outputs.report-url }}"
```

Zusätzlich schreibt die Action immer eine Zusammenfassung in die Job-Übersicht und lädt
`bfsguard-result.json` als Artefakt hoch — auch wenn das Gate scheitert.

---

## CLI

Eine Datei, keine Abhängigkeiten, Node ab Version 18.

```bash
curl -sO https://bfsguard.de/ci/bfsguard-ci.js

export BFSGUARD_API_KEY=...
node bfsguard-ci.js --url https://staging.example.de --max-pages 25 --max-serious 0
```

### GitLab CI

```yaml
barrierefreiheit:
  image: node:20-alpine
  script:
    - wget -q https://bfsguard.de/ci/bfsguard-ci.js
    - node bfsguard-ci.js --url "$STAGING_URL" --max-pages 25 --json report.json
  artifacts:
    when: always
    paths: [report.json]
```

### Jenkins

```groovy
stage('Barrierefreiheit') {
  steps {
    withCredentials([string(credentialsId: 'bfsguard', variable: 'BFSGUARD_API_KEY')]) {
      sh 'curl -sO https://bfsguard.de/ci/bfsguard-ci.js'
      sh 'node bfsguard-ci.js --url https://staging.example.de --max-serious 0'
    }
  }
}
```

### Exit-Codes

| Code | Bedeutung |
|---|---|
| `0` | Gate bestanden |
| `1` | Schwelle überschritten |
| `2` | Fehler (Schlüssel fehlt/ungültig, Zeitüberschreitung, Netz) |

Ein Fehler (2) ist bewusst **nicht** dasselbe wie ein durchgefallenes Gate (1): eine gestörte
Verbindung darf nicht wie ein Barrierefreiheits-Problem aussehen.

---

## Was geprüft wird — und was nicht

Geprüft wird mit Playwright (Headless Chromium) und axe-core, ergänzt um eigene Header-Checks.
Jeder Befund nennt die **betroffene Seite** und ein **Beispiel-Element**, damit er ohne
Nachforschung behebbar ist.

**Grenze, die ehrlich benannt gehört:** automatisierte Tests decken nur einen Teil der
WCAG-Kriterien ab. Sinnvolle Alternativtexte, Fokusreihenfolge, Verständlichkeit und
Bedienbarkeit mit Hilfsmitteln erfordern eine manuelle Prüfung. Ein grünes Gate bedeutet
„keine automatisch erkennbaren Verstöße" — nicht „barrierefrei" und nicht „BFSG-konform".

Deshalb ist der Score auf 95/100 gedeckelt: die letzten Punkte sind ohne manuelle Prüfung
nicht erreichbar.

---

## Grenzen der Nutzung

- **Rate-Limit:** 60 Scans pro Stunde je API-Schlüssel (nicht je IP — CI-Runner teilen sich IPs).
- **Seitenzahl:** vom Plan begrenzt.
- **Nur erreichbare URLs:** Die Adresse muss vom Internet aus abrufbar sein. Für Preview-
  Deployments mit Basic Auth oder für `localhost` funktioniert der Scan nicht.
- **Der Schlüssel gehört in ein Secret.** Er ist an Ihr Konto und Ihr Kontingent gebunden.
