Skip to content

feat(signals): Signal model + GitHub webhook ingest (event spine)#118

Merged
apps3000 merged 4 commits into
mainfrom
feature/event-spine
Jul 9, 2026
Merged

feat(signals): Signal model + GitHub webhook ingest (event spine)#118
apps3000 merged 4 commits into
mainfrom
feature/event-spine

Conversation

@apps3000

@apps3000 apps3000 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Closes #115

First increment of Phase A (epic #110): the event spine replacing polling-only freshness.

What

  • Signal model (migration signal_event_spine): normalized fleet events with a unique dedupeKey, optional repo link, trimmed JSON payload.
  • Webhook receiver POST /api/ingest/github: verifies X-Hub-Signature-256 (constant-time) against the stored App webhook secret (503 unconfigured / 401 invalid), answers 200 fast and defers to a new ingest:github graphile-worker job (jobKey = delivery GUID → redeliveries coalesce).
  • Idempotency contract: dedupeKey = github:<event>:<delivery-GUID> — GitHub reuses the GUID on redeliveries, so retries upsert onto the same Signal while distinct occurrences stay distinct. (Deliberate refinement of the epic's entity+action sketch: entity-action keys would collapse legitimate repeat occurrences like reopen→close→reopen.)
  • Pure mappers for pull_request, issues, check_suite (failure→error, cancelled→warning), deployment_status (failure/error→error), release, projects_v2_item (old+new field values preserved for phase-C cycle analytics) — fixture-tested.
  • Open-PR cache write-through: pull_request events upsert/drop the cached PullRequest row immediately (closed → delete, matching syncPulls semantics); reviewDecision/checksState/mergeable stay owned by the polling sync.
  • App manifest: hook_attributes.url<APP_URL>/api/ingest/github, default_events extended to the six spine events; manual enablement for pre-existing apps documented in docs/wiki/GitHub-App-Setup.md.

Verification

  • pnpm check / pnpm test (159 tests, incl. new mapper/signature suites) / docstring gate — all green.
  • Migration SQL hand-written (repo convention), folder sorts after repo_provider_keys_multi. Local db:migrate apply pending (local Docker VM down); will be verified before merge feedback ends.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • GitHub webhook events can now be received via a verified webhook endpoint, queued, and processed automatically.
    • GitHub App setup and manifest now enable webhook ingest with an expanded subscribed event list.
  • Bug Fixes
    • Improved deduplication/idempotency for deliveries and safer rejection of invalid or unsupported webhook payloads.
  • Documentation
    • Updated setup guidance for webhook activation, event subscriptions, and webhook secret configuration.
  • Database
    • Added persistent “Signal” storage with deduplication, indexing, and optional linking to repos.

Event spine v1: normalized Signal rows upserted idempotently on a delivery-GUID dedupeKey; HMAC-verified /api/ingest/github route that answers fast and defers to a graphile-worker ingest:github job; pure per-event mappers (pull_request, issues, check_suite, deployment_status, release, projects_v2_item) with fixture tests; open-PR cache write-through so /pulls updates without waiting for the 5-minute cron; App manifest now subscribes the six events and points its webhook at the ingest route (manual enablement for existing apps documented).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 472779fd-40ea-434b-b3fc-f86ee5a78bf8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Prisma Signal model and migration, a GitHub webhook ingest endpoint with HMAC verification, GitHub event mapping and pull-request cache write-through, enqueue and worker wiring, and manifest/documentation updates for webhook subscriptions.

Changes

Signal event spine

Layer / File(s) Summary
Signal schema and migration
prisma/schema.prisma, prisma/migrations/signal_event_spine/migration.sql
Adds Signal with dedupeKey uniqueness, JSON payload, optional repo linkage, and indexes; adds a signals relation on Repo; migration creates the table, constraints, and foreign key.
GitHub signal mapping and processing
src/server/signals/github.ts, src/server/signals/github.test.ts
Implements mapGithubEvent, mapPullRequestCacheUpdate, verifyGithubSignature, processGithubEvent, and applyPullCacheUpdate for normalizing webhook payloads, deduping signals, and syncing the pull request cache; adds corresponding unit tests.
Webhook enqueue and worker flow
src/server/jobs/enqueue.ts, src/server/jobs/worker.ts, src/app/api/ingest/github/route.ts
Adds enqueueIngestGithub, registers the ingest:github worker task, and implements the POST webhook receiver that verifies signatures, validates headers, enqueues the job, and returns an immediate response.
Manifest event subscriptions and setup docs
src/server/github/manifest.ts, src/server/github/manifest.test.ts, docs/wiki/GitHub-App-Setup.md
Adds hook_attributes and expanded default_events in the app manifest with matching tests; updates setup documentation for webhook activation, event subscriptions, and the webhook secret env variable.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a Signal model plus GitHub webhook ingest for the event spine.
Linked Issues check ✅ Passed The PR appears to implement the linked Phase A event spine scope, including the Signal model, ingest route, worker mappers, manifest, and docs.
Out of Scope Changes check ✅ Passed The changes are focused on the event spine work and supporting tests/docs, with no clear unrelated additions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/event-spine

Comment @coderabbitai help to get the list of available commands.

@apps3000

apps3000 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server/github/manifest.ts (1)

26-47: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add deployments: "read" to the manifest
deployment_status requires the Deployments repository permission, so this manifest can’t enable that event without it. Mirror the same permission in the setup docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/github/manifest.ts` around lines 26 - 47, The GitHub manifest in
the manifest builder is missing the Deployments repository permission needed for
the deployment_status event. Update the default_permissions in the GitHub
manifest to include deployments: "read", and mirror the same permission in the
related setup docs so the app can subscribe to deployment_status successfully.
🧹 Nitpick comments (1)
src/server/signals/github.ts (1)

306-308: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant repo lookup within the same job.

processGithubEvent resolves the repo via prisma.repo.findUnique({ where: { nameWithOwner: mapped.repoFullName } }) (Line 307), and applyPullCacheUpdate performs the same lookup again for pull_request events (Line 345). Passing the already-resolved repo through to applyPullCacheUpdate avoids a duplicate DB round trip per webhook delivery.

♻️ Proposed refactor to reuse the resolved repo
-  if (job.event === "pull_request") {
+  if (job.event === "pull_request") {
     try {
-      await applyPullCacheUpdate(mapPullRequestCacheUpdate(payload));
+      await applyPullCacheUpdate(mapPullRequestCacheUpdate(payload), repo);
     } catch (error) {
       // The Signal row is already written — a cache hiccup must not fail (and re-run) the job.
       console.warn("ingest:github pull cache write-through failed", briefError(error));
     }
   }
 }

-async function applyPullCacheUpdate(update: PullCacheUpdate | null): Promise<void> {
+async function applyPullCacheUpdate(
+  update: PullCacheUpdate | null,
+  knownRepo?: { id: string; nameWithOwner: string } | null,
+): Promise<void> {
   if (!update) return;
   if (update.op === "delete") {
     await prisma.pullRequest.deleteMany({ where: { nodeId: update.nodeId } });
     return;
   }
-  const repo = await prisma.repo.findUnique({ where: { nameWithOwner: update.repoFullName } });
+  const repo =
+    knownRepo && knownRepo.nameWithOwner === update.repoFullName
+      ? knownRepo
+      : await prisma.repo.findUnique({ where: { nameWithOwner: update.repoFullName } });
   if (!repo) return; // repo not cached yet — the next syncRepos run picks it up

Also applies to: 339-353

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/signals/github.ts` around lines 306 - 308, `processGithubEvent` is
doing a repo lookup that `applyPullCacheUpdate` repeats for pull_request events,
causing an unnecessary extra DB round trip. Refactor the flow so the repo
resolved in `processGithubEvent` is passed into `applyPullCacheUpdate` (and any
related call sites) instead of re-fetching it there. Update
`applyPullCacheUpdate` to accept and reuse the existing repo object, with
`prisma.repo.findUnique` used only once per webhook delivery.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/ingest/github/route.ts`:
- Around line 37-38: The GitHub ingest route currently ignores the boolean
result from enqueueIngestGithub, so a failed enqueue is still acknowledged with
success. Update the route handler in the GitHub webhook endpoint to check the
return value from enqueueIngestGithub and return a non-200 error response when
it is false, while preserving the current success response only for successful
enqueues. Use the route handler and enqueueIngestGithub as the key symbols to
locate the change.

In `@src/server/signals/github.ts`:
- Around line 297-353: The pull request cache write-through in
processGithubEvent/applyPullCacheUpdate can be overwritten by out-of-order
webhook jobs because updates are applied unconditionally by nodeId. Add a
staleness guard using the existing ghUpdatedAt value in the derived
PullCacheUpdate fields so older events are ignored, or otherwise serialize
updates per nodeId before calling prisma.pullRequest.upsert/deleteMany. Use
applyPullCacheUpdate, PullCacheUpdate, and the prisma.pullRequest write path to
locate the fix.

---

Outside diff comments:
In `@src/server/github/manifest.ts`:
- Around line 26-47: The GitHub manifest in the manifest builder is missing the
Deployments repository permission needed for the deployment_status event. Update
the default_permissions in the GitHub manifest to include deployments: "read",
and mirror the same permission in the related setup docs so the app can
subscribe to deployment_status successfully.

---

Nitpick comments:
In `@src/server/signals/github.ts`:
- Around line 306-308: `processGithubEvent` is doing a repo lookup that
`applyPullCacheUpdate` repeats for pull_request events, causing an unnecessary
extra DB round trip. Refactor the flow so the repo resolved in
`processGithubEvent` is passed into `applyPullCacheUpdate` (and any related call
sites) instead of re-fetching it there. Update `applyPullCacheUpdate` to accept
and reuse the existing repo object, with `prisma.repo.findUnique` used only once
per webhook delivery.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8485f997-8c89-44da-9251-ec24018e142b

📥 Commits

Reviewing files that changed from the base of the PR and between 490ec90 and a22f29b.

📒 Files selected for processing (10)
  • docs/wiki/GitHub-App-Setup.md
  • prisma/migrations/signal_event_spine/migration.sql
  • prisma/schema.prisma
  • src/app/api/ingest/github/route.ts
  • src/server/github/manifest.test.ts
  • src/server/github/manifest.ts
  • src/server/jobs/enqueue.ts
  • src/server/jobs/worker.ts
  • src/server/signals/github.test.ts
  • src/server/signals/github.ts

Comment thread src/app/api/ingest/github/route.ts Outdated
Comment thread src/server/signals/github.ts
apps3000 and others added 3 commits July 8, 2026 21:51
…-order webhooks (#115)

Route returns 503 when the ingest queue is unavailable so GitHub retries instead of silently dropping a verified delivery. Pull-cache write-through now applies conditionally on ghUpdatedAt (WHERE-guarded updateMany/deleteMany, create-race tolerated) so a stale event processed late can neither overwrite nor delete newer cached state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The deployment_status webhook event requires the Deployments repository permission — added deployments:read to the manifest and the setup docs. processGithubEvent now passes its already-resolved repo into applyPullCacheUpdate, avoiding a duplicate DB lookup per pull_request delivery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@apps3000

apps3000 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review's two out-of-diff comments in a129d08:

  1. [Major] Add deployments: read to the manifest — done. deployment_status needs the Deployments repository permission; added deployments: "read" to default_permissions in manifest.ts (+ test) and mirrored it in the Repository-permissions table in docs/wiki/GitHub-App-Setup.md.
  2. [Nitpick] Redundant repo lookup — done. processGithubEvent now passes its already-resolved repo into applyPullCacheUpdate (new optional knownRepo param, reused when nameWithOwner matches), so a pull_request delivery does a single repo lookup.

Also merged origin/main (post-#119) into the branch and resolved the additive schema.prisma conflict (both Signal and DecisionDismissal kept). pnpm check/test (172)/docstrings green; both migrations applied locally.

@coderabbitai please re-review the latest commit.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

@apps3000 Thanks for the detailed update — both fixes sound solid:

  1. Adding deployments: "read" to default_permissions is the correct fix for deployment_status events, and mirroring it in the docs table keeps things consistent.
  2. Passing the already-resolved repo through as knownRepo to applyPullCacheUpdate is a clean way to avoid the redundant lookup on pull_request deliveries.

Good call on also merging origin/main and resolving the additive schema.prisma conflict to keep both Signal and DecisionDismissal. I'll re-review the latest commit now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@apps3000 apps3000 merged commit d1f41bc into main Jul 9, 2026
2 checks passed
@apps3000 apps3000 deleted the feature/event-spine branch July 9, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(signals): Signal model + GitHub webhook ingest (event spine)

1 participant