# @wcag-checkr/ci

Headless wcagcheckr accessibility audit runner for CI/CD pipelines.

Drives the wcagcheckr Chrome extension via Playwright, running full-page WCAG audits **across the same 108-state matrix** (hover, focus, dark mode, RTL, breakpoints, etc.) the extension uses interactively. So your CI catches what your developer sees.

## Why this matters

Competing tools (axe DevTools Pro, Siteimprove Enterprise) charge for CI/CD integration. We ship it free. And ours is the **only** CI runner that audits at hover/focus/dark/RTL/breakpoint state combinations — competitors run a single default-state audit.

## Install

```bash
npm install --save-dev @wcag-checkr/ci playwright
npx playwright install chromium
```

## Use

```bash
npx wcagcheckr-ci audit https://your-site.com/
```

Default: outputs JSON to stdout, exits non-zero if any **serious** or **critical** violations.

### Common flags

```bash
# Write SARIF for GitHub PR annotations
npx wcagcheckr-ci audit https://your-site.com/ --format sarif --output a11y.sarif

# Write JUnit XML for Jenkins/GitLab CI
npx wcagcheckr-ci audit https://your-site.com/ --format junit --output a11y.xml

# Strict — fail on any violation, even minor
npx wcagcheckr-ci audit https://your-site.com/ --threshold minor

# Permissive — never fail (just collect findings)
npx wcagcheckr-ci audit https://your-site.com/ --threshold none

# Audit with a license token (unlocks paid features like forensic anchoring)
npx wcagcheckr-ci audit https://your-site.com/ --license $WCAGCHECKR_LICENSE

# Use your own built extension (e.g. self-hosted fork)
npx wcagcheckr-ci audit https://your-site.com/ --extension-dir ./path/to/dist
```

## Verifying a forensic log

The extension's Forensic tab can export your full audit log as JSON (the **export JSON** button). That file contains every audit's identity hash, RFC 3161 trusted-timestamp token, and ed25519 server signature. `wcagcheckr-ci verify` validates it offline (the server is only contacted to fetch the public key by fingerprint):

```bash
npx wcagcheckr-ci verify wcagcheckr-forensic-log.json
```

Per entry, the verifier:

1. Recomputes the SHA-256 identity hash from the stored fields and checks it against the recorded hash.
2. Fetches the server's ed25519 public key matching the receipt's fingerprint and verifies the signature over `(hash, anchoredAt, tsaName, productSlug, prevAuditHash)`.
3. If `prevAuditHash` references another entry in the same export, validates the chain link. (Deep chain links require exporting the full history.)

Exit code is `0` only when every entry's hash and signature verify cleanly.

To fully verify the RFC 3161 timestamp (which proves the hash was witnessed by a public TSA at the recorded time), save the receipt's `rfc3161TokenBase64` to a `.tsr` file and run `openssl ts -verify` against FreeTSA's certificate chain. That step is out-of-scope for v0 of the CLI verifier — the ed25519 signature already commits to the same TSA token bytes, so a verified ed25519 signature is strong evidence the receipt was issued by our server in response to that TSA timestamp.

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Success — threshold not exceeded |
| 1 | Threshold exceeded — CI failure signal |
| 2 | Runtime error (target unreachable, extension didn't load, etc.) |

## GitHub Actions example

```yaml
name: a11y
on: [pull_request]
jobs:
  wcagcheckr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npx playwright install chromium --with-deps
      - run: npx wcagcheckr-ci audit ${{ env.PREVIEW_URL }} --format sarif --output a11y.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: a11y.sarif }
```

## GitLab CI example

```yaml
a11y:
  image: node:20
  before_script:
    - npm ci
    - npx playwright install chromium --with-deps
  script:
    - npx wcagcheckr-ci audit "$PREVIEW_URL" --format junit --output a11y.xml
  artifacts:
    reports:
      junit: a11y.xml
    when: always
```

## License gating

The `audit` command can run without a license — free-tier audits work. To unlock paid features (forensic anchoring, AI summaries) in a CI run, pass `--license <token>`. The CLI activates the token via the same `LICENSE_SET_REQUEST` message the extension UI uses; failures are surfaced via a non-zero exit code.

For verification, **no license is required.** The forensic anchor verifier reads a public-key endpoint and recomputes signatures — anyone who receives a forensic-log JSON can validate it without any wcagcheckr credentials.

## Programmatic API (planned)

A Node-importable API is on the roadmap:

```js
import { audit } from '@wcag-checkr/ci';
const { violations, sarif } = await audit('https://your-site.com/');
```

Until then, parse the JSON output of the CLI — same shape as the extension's "Export → JSON" output.

## Limitations (v0)

- Auditing happens against the **rendered DOM at the URL you pass** — preview-deploy URLs work great; localhost requires the CI runner to also be the host (use `npm run start &` then audit `http://localhost:PORT`).
- Authentication: log-in flows aren't yet supported; pass URLs that don't require auth, or fork to inject cookies/auth headers via Playwright's `storageState`.
- The runner uses `headless: 'new'` (Chromium's modern headless mode); some sites that detect headless browsers may behave differently. Workaround: use `--extension-dir` with a forked runner that switches to `headless: false` on a virtual display.

## License

UNLICENSED until commercial release. See `wcagcheckr.com/license`.
