GitHub Action

Drop a battery into CI. This runs a subset of specimens against your client and fails the build if the grade drops below a threshold — the "your error handling regressed" check nobody has:

# .github/workflows/faultydata.yml
name: faultydata battery
on: [push]
jobs:
  grade:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: mint battery
        run: |
          TOKEN=$(curl -s -X POST https://faultydata.com/battery \
            -d "{\"client\":\"$GITHUB_REPOSITORY@$GITHUB_SHA\"}" \
            | jq -r .token)
          echo "FD_TOKEN=$TOKEN" >> $GITHUB_ENV
      - name: run hang specimen — must time out, not hang forever
        run: |
          timeout 8 curl -s -o /dev/null \
            "https://faultydata.com/run/$FD_TOKEN/FD-2026-017" \
            && echo "FAIL: returned" || true
      - name: report outcome
        run: |
          curl -s -X POST \
            "https://faultydata.com/run/$FD_TOKEN/FD-2026-017/report" \
            -d '{"outcome":"timeout","ms":8000}'
      - name: report card
        run: echo "card: https://faultydata.com/report/$FD_TOKEN"

A proper faultydata/grade@v1 action wrapping the whole battery is on the roadmap — the YAML above works today with nothing but curl.

Any CI, anywhere

The whole integration surface is HTTP — no SDK, no agent, no vendor plugin. If your CI can run curl, it can run a battery:

# Makefile target
grade:
	TOKEN=$$(curl -s -X POST https://faultydata.com/battery \
	  -d '{"client":"$(APP)-ci"}' | jq -r .token) && \
	for id in FD-2026-001 FD-2026-011 FD-2026-017; do \
	  timeout 6 curl -s -o /dev/null https://faultydata.com/run/$$TOKEN/$$id ; \
	done && \
	echo "report: https://faultydata.com/report/$$TOKEN"

With an API key (600 rpm) a full 76-specimen battery fits comfortably in a CI window. Set X-API-Key as a CI secret and forget it exists.

k6

Specimens double as load-test targets that behave like a dependency having a bad day — weighted composites especially:

import http from "k6/http";
import { check } from "k6";

export default function () {
  const r = http.get("https://faultydata.com/r/FD-2026-003");
  check(r, {
    "client handled degraded mode": (x) =>
      x.status === 200 || x.status === 500 || x.status === 503,
  });
}

MCP — for the machines that write the code

faultyd -mcp exposes the registry as MCP tools: list_specimens, get_specimen, replay_instructions, start_battery, get_report. Point Claude Desktop, Cursor, or any MCP client at the binary and your agent can pick specimens, generate test harnesses, and read report cards. Config snippet:

{
  "mcpServers": {
    "faultydata": {
      "command": "/usr/local/bin/faultyd",
      "args": ["-mcp"]
    }
  }
}

Scheduled checks

A cron job that verifies the registry itself is alive — and, more usefully, that your client still handles the classic faults after every dependency bump:

# weekly: does our fetch wrapper still redirect-loop?
0 6 * * 1 timeout 10 curl -s -o /dev/null -w "%{http_code} %{num_redirects}\n" \
  -L https://faultydata.com/r/FD-2026-020

Download the catalog

GET /api/specimens returns the entire registry — every specimen, every signature, every anatomy. Mirror it, embed it in your own test fixtures, run the signatures against a local mock. The Pwned Passwords lesson: giving the corpus away is what makes the registry canonical. The catalog is free; the authority to say "FD-2026-028" and have it mean something is not.

fd runner — design

On the roadmap: a small CLI that runs the full battery honestly — executes each specimen against your HTTP client command, classifies the real outcome itself (no self-reporting), submits results, prints the report URL. If you want to help design it, the specimen schema is public and the report API is documented at /api.