--- brief: 1 convoy: owner-cockpit-v02 role: role-implementer title: Forge webhook receiver — GitHub/Forgejo → pr_review→merged status: ready priority: P0 slice_dependencies: [] recommended_model: qwen-coder model_tier: local --- # Brief 1 — `Task-forge-webhook-receiver.md` (Epic 8) ## Goal `POST /api/webhooks/forge` verifies signature via `FORGE_WEBHOOK_SECRET`, finds `echodo-convoy: ` marker in PR body (from `apps/worker/src/git/pr.ts`), transitions `pr_review→merged` via `transitionConvoy` `packages/shared/src/convoy-lifecycle/state-machine.ts:90` as `automation`, writes `convoy_events`. ## Files - `apps/web/app/api/webhooks/forge/route.ts` NEW — parse `X-Hub-Signature-256` (GitHub) or `X-Forgejo-Signature` (Forgejo) HMAC sha256 `FORGE_WEBHOOK_SECRET`, handle `pull_request: closed & merged=true` + `pr.body` marker regex `/echodo-convoy:\s*([0-9a-f-]{36})/`, lookup convoy by `id`, validate `status===pr_review`, call `transitionConvoy(db, {workspaceId, convoyId, toStatus:merged, actor:automation, reason: pr#N merged})`. Return 200 + `ok` even if no convoy (don't leak). Rate-limit per IP. - `apps/web/server/lib/forge/adapters.ts` — reuse `ForgeAdapter` if needed for verify. - `docs/operations/live-e2e-checklist.md` — add webhook step. ## Acceptance - Forgejo/GitHub payload with `body: "… echodo-convoy: …"` + valid HMAC transitions `pr_review→merged`; invalid signature 401; no marker 200 no-op. - Existing `pnpm lint && pnpm type-check` pass. ## Prompt You are `role-implementer` brief 1. Read `packages/shared/src/convoy-lifecycle/state-machine.ts`, `apps/web/server/lib/forge/adapters.ts`, `apps/worker/src/git/pr.ts`, `docs/convoy-lifecycle.md`. Keep verify constant-time, workspace-scoped. ## Depends None.