GitHub integration
Migration: 0011_webhook_events.sql Verifier: packages/core/src/verification/github.ts (pure, test-injected) Webhook receiver: apps/mcp/src/routes/webhooks-github.ts
Endpoints
| Endpoint | Auth | Purpose |
|---|---|---|
POST /webhooks/github | HMAC SHA-256 (X-Hub-Signature-256) | inbound webhook receiver |
_planned_ /auth/github/callback | OAuth code | post-install identity link (task 35/42) |
The receiver verifies the signature against GITHUB_APP_WEBHOOK_SECRET, validates that X-GitHub-Delivery and X-GitHub-Event are present, then writes the raw payload into webhook_events with a unique index on (provider, delivery_id). Replays return 200 { status: 'replay' }.
Verification contract
The verifier consumes the snapshot stored on each task:
{ "v": 1, "kind": "github_pr", "required_checks": ["ci/build", "ci/test"] }verifyGitHubPr({ owner, repo, prNumber, requiredChecks, expectedFiles? }) returns one of passed | failed | pending:
| PR state | Required checks | Verdict |
|---|---|---|
| open | any | pending |
| closed + not merged | any | failed |
| closed + merged | all green | passed |
| closed + merged | one failure | failed |
| closed + merged | one missing | pending (CI not yet reported) |
| any of the above | expectedFiles glob unmet | failed (if merged) / pending |
The function is pure — it consumes a GitHubClient interface (getPullRequest, getCheckRuns, getChangedFiles) so tests don't touch the network. The runtime adapter (Octokit-backed) lands when the verification engine in task 32 wires it up.
Idempotence
webhook_events.delivery_idisUNIQUE (provider, delivery_id). Replays
short-circuit via ON CONFLICT DO NOTHING.
- A BullMQ
webhooks-inworker is the natural place to drive the
verification pipeline (task 32 owns that handler).
Body-parser bypass
The global express.json() middleware would consume the request stream before our HMAC could read it. apps/mcp/src/index.ts skips the JSON parser when req.path === '/webhooks/github' and the handler reads the raw bytes itself.