Files
2026-05-27 23:29:53 +01:00

9.9 KiB

Tasks — Webhook receiver

Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.

0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

Audit Note (2026-05-02)

Minimal progress — DEFERRED, do not archive.

  • packages/platform/webhook-client/src/ has only the outbound surface: core/, errors/, types/, utils/. No parser/, verifier/, receiver/, hooks/, or schemas/ directories.
  • grep -rln "WebhookParser\|WebhookVerifier\|WebhookReceiver\|webhookHooks\|ParsedWebhookEvent" packages/platform/webhook-client/src/ returns zero hits.
  • bunx turbo lint typecheck test --filter=@suaveplan/webhook-client is GREEN — but the entire inbound surface is unimplemented.
  • All 64 tasks remain: port WebhookParser (130 L), WebhookVerifier (127 L) with stripe/github/slack/generic schemes, WebhookReceiver (135 L) orchestrator, webhookHooks event bus over @suaveplan/events, six new error codes with metadata.retryHint, WebhookSchema<T> Zod builder, all types (ParsedWebhookEvent<T>, SigningScheme, WebhookHandler<T>, etc.), four subpath exports, OTel histogram + counter, sibling .mds.

1. Spec authoring

  • 1.1 Read ../suaveplan/enterprise/webhook-client.md end-to-end
  • 1.2 Read openspec/specs/webhook/spec.md (canonical capability spec; this change introduces it if absent)
  • 1.3 Read openspec/AGENTS.md Sections 13 and 14
  • 1.4 Draft proposal.md from openspec/templates/proposal.md
  • 1.5 Fill ## Source mapping table with upstream line ranges
  • 1.6 Draft specs/webhook/spec.md from openspec/templates/spec-delta.md
  • 1.7 Every requirement carries a **Verified by:** line
  • 1.8 openspec change validate webhook-receiver exits with no errors

2. Implementation — WebhookParser

  • 2.1 Port WebhookParser from packages/universal/webhook-client/src/parser/parser.ts (L1-L130) into packages/platform/webhook-client/src/parser/parser/parser.ts
  • 2.2 Implement parseJsonBody(request), parseFormUrlencodedBody(request), parseRequest(request) (auto-detects from content-type header)
  • 2.3 Validate parsed payload through caller-supplied Zod schema (or default z.unknown()); reject with WebhookClientError code PARSER_INVALID_BODY / PARSER_UNSUPPORTED_CONTENT_TYPE
  • 2.4 Return ParsedWebhookEvent<T> carrying { id, type, payload, receivedAt, signature?, headers }
  • 2.5 Sibling .md (≥200 words, basic + advanced examples)

3. Implementation — WebhookVerifier

  • 3.1 Port WebhookVerifier from packages/universal/webhook-client/src/verifier/verifier.ts (L1-L127) into packages/platform/webhook-client/src/verifier/verifier/verifier.ts
  • 3.2 Reuse existing generateSignature / verifySignature from packages/platform/webhook-client/src/utils/signature.ts for the generic scheme
  • 3.3 Implement signingScheme: 'stripe' | 'github' | 'slack' | 'generic' — Stripe (HMAC-SHA256 of <timestamp>.<body>), GitHub (HMAC-SHA256 of body, prefix sha256=), Slack (HMAC-SHA256 of v0:<timestamp>:<body>), generic (raw HMAC-SHA256 of body)
  • 3.4 Enforce timestampToleranceMs (default 5 minutes) for schemes that include a timestamp; reject with VERIFIER_TIMESTAMP_OUT_OF_TOLERANCE
  • 3.5 Reject bad signatures with VERIFIER_BAD_SIGNATURE
  • 3.6 Sibling .md

4. Implementation — WebhookReceiver

  • 4.1 Port WebhookReceiver from packages/universal/webhook-client/src/receiver/receiver.ts (L1-L135) into packages/platform/webhook-client/src/receiver/receiver/receiver.ts
  • 4.2 Implement registerHandler<T>(eventType, handler), unregisterHandler(eventType), handle(request) orchestrator
  • 4.3 Pipeline: emit onWebhookReceived → verify (emit onWebhookVerified) → parse → dispatch to handler (emit onWebhookHandled or onWebhookFailed)
  • 4.4 Throw RECEIVER_HANDLER_NOT_FOUND when no handler is registered for the event type
  • 4.5 Throw RECEIVER_HANDLER_FAILED when the handler itself throws; preserve cause
  • 4.6 Return HandleResult carrying { status, parsedEvent, dispatchResult }
  • 4.7 Sibling .md

5. Implementation — webhookHooks event bus

  • 5.1 Port webhookHooks from packages/universal/webhook-client/src/hooks.ts (L1-L58) into packages/platform/webhook-client/src/hooks/hooks/hooks.ts
  • 5.2 Back the bus with @suaveplan/events (not upstream's bespoke Hooks class) — webhookHooks is an EventBus<WebhookHookMap>
  • 5.3 Export onWebhookDelivering, onWebhookReceived, onWebhookVerified, onWebhookHandled, onWebhookFailed typed-handler registration helpers
  • 5.4 Sibling .md

6. Error codes

  • 6.1 Extend WEBHOOK_CLIENT_CODES in packages/platform/webhook-client/src/errors/webhook-client-error.ts with: PARSER_INVALID_BODY, PARSER_UNSUPPORTED_CONTENT_TYPE, VERIFIER_BAD_SIGNATURE, VERIFIER_TIMESTAMP_OUT_OF_TOLERANCE, RECEIVER_HANDLER_NOT_FOUND, RECEIVER_HANDLER_FAILED
  • 6.2 Each new code carries metadata.retryHint: 'transient' | 'permanent' — transient for handler-failure, permanent for bad-signature / unsupported-content-type / parser-invalid-body / handler-not-found / timestamp-out-of-tolerance
  • 6.3 Update error-codes.md doc to document each code's retryHint

7. Types and schemas

  • 7.1 Add ParsedWebhookEvent<T>, SigningScheme, WebhookHandler<T>, WebhookHandlerRegistry, WebhookHookMap, HandleResult, WebhookReceiverOptions, WebhookVerifierOptions, WebhookParserOptions to packages/platform/webhook-client/src/types/webhook-client.types.ts
  • 7.2 Add WebhookSchema<T>(payloadSchema) Zod-builder helper to packages/platform/webhook-client/src/schemas/webhook.schema.ts
  • 7.3 Sibling .md for the schema module

8. Wiring

  • 8.1 Update packages/platform/webhook-client/src/index.ts to re-export the parser, verifier, receiver, hooks, schema-builder, and new types
  • 8.2 Add subpath exports ./parser/parser/parser, ./verifier/verifier/verifier, ./receiver/receiver/receiver, ./hooks/hooks/hooks, ./schemas/webhook.schema to packages/platform/webhook-client/package.json
  • 8.3 Update root README.md ## Modules section to link the new co-located .mds

9. Tests

  • 9.1 packages/platform/webhook-client/src/parser/parser/parser.test.ts — JSON round-trip, form-urlencoded round-trip, unsupported content-type rejection, malformed-body rejection
  • 9.2 packages/platform/webhook-client/src/verifier/verifier/verifier.test.ts — each scheme (stripe, github, slack, generic) round-trip; tampered signature rejected; out-of-tolerance timestamp rejected
  • 9.3 packages/platform/webhook-client/src/receiver/receiver/receiver.test.ts — full pipeline with handler dispatch; handler-not-found path; handler-throws path
  • 9.4 packages/platform/webhook-client/src/receiver/receiver/receiver.spec-conformance.test.ts — webhookHooks events fire in order (onWebhookReceived before onWebhookVerified before onWebhookHandled); OTel webhook.receiver.handle.duration_ms histogram; error codes carry metadata.retryHint
  • 9.5 packages/platform/webhook-client/src/hooks/hooks/hooks.test.ts — webhookHooks.on/off/emit round-trip
  • 9.6 packages/platform/webhook-client/src/errors/error-codes.spec-conformance.test.ts — every new code is registered; every new code carries metadata.retryHint
  • 9.7 Every it(...) carries { timeout: 5000 }

10. Documentation

  • 10.1 Sibling .md for every new implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 10.2 README links the new .mds under ## Modules
  • 10.3 Update error-codes.md to enumerate each new code with its retryHint and a one-line description

11. Pre-archive verification (the 5-gate triple)

  • 11.1 bun run check — Biome 0/0/0 across the monorepo
  • 11.2 bunx turbo lint typecheck test --filter=@suaveplan/webhook-client — green
  • 11.3 bunx turbo test:coverage --filter=@suaveplan/webhook-client — 100/100/100/100
  • 11.4 bun scripts/openspec-conformance.ts @suaveplan/webhook-client — exit 0, no error findings
  • 11.5 bunx turbo build --filter=@suaveplan/webhook-client — green

12. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 12.1 Generics threaded; expectTypeOf assertions on ParsedWebhookEvent<T> and WebhookHandler<T>
  • 12.2 Zod schemas wired at every public boundary (WebhookSchema, WebhookReceiverOptionsSchema, WebhookVerifierOptionsSchema)
  • 12.3 Every new error code thrown from at least one production path
  • 12.4 Strict-mode tested: schemes with vs without timestamp tolerance
  • 12.5 OTel histogram webhook.receiver.handle.duration_ms and counter webhook.receiver.handle_total (tagged by status) emitted from production paths and asserted with a real in-memory MeterProvider
  • 12.6 Lifecycle dedup: handler registry rejects duplicate registrations for the same event type unless replace: true is set
  • 12.7 No retroactive tick — every box reflects work done in this change

13. Archive

  • 13.1 Fold spec delta into openspec/specs/webhook/spec.md (creating it if absent)
  • 13.2 Move openspec/changes/webhook-receiver/ → openspec/archive/<yyyy-mm-dd>-webhook-receiver/
  • 13.3 Refresh MANIFEST.md