Skip to content

Add Railway, CI-failure, and Supabase ingestion webhooks#968

Merged
BigSimmo merged 15 commits into
mainfrom
claude/repository-webhook-recommendations-oviufb
Jul 20, 2026
Merged

Add Railway, CI-failure, and Supabase ingestion webhooks#968
BigSimmo merged 15 commits into
mainfrom
claude/repository-webhook-recommendations-oviufb

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Adds three webhook integrations recommended for this repo (all optional / fail-closed when their secret is unset):

  • Railway deploy → chatPOST /api/webhooks/railway. Receives Railway deploy status webhooks (shared secret via ?token=, since Railway only lets you set a URL; compared constant-time) and forwards notable statuses (SUCCESS/FAILED/CRASHED/REMOVED) for the app + worker services to Slack/Discord. This surfaces the deploy outcome GitHub can't see.
  • CI failure → chat.github/workflows/notify-ci-failure.yml. Fires on workflow_run: completed for the key workflows and pings chat when a run on main/release/* fails. Uses curl only (no external actions, so the pinned-action allowlist is untouched); reads SLACK_WEBHOOK_URL / DISCORD_WEBHOOK_URL repo secrets.
  • Supabase document change → ingestionPOST /api/webhooks/supabase/document-change. A Supabase Database Webhook on public.documents that enqueues one reindex job the worker then claims, turning the polling autopilot into an event-driven path.

Shared building blocks:

  • src/lib/webhooks/secret-auth.ts — constant-time, byte-length-safe secret gate (mirrors deep-probe-auth.ts).
  • src/lib/webhooks/chat-notify.ts — Slack/Discord forwarder that never throws (a failed post still lets the receiver 2xx its provider).
  • src/lib/ingestion-enqueue.ts — reindex enqueue that mirrors the reindex route's rollback-fence + one-open-job-per-document concurrency guards.

Idempotency + loop-safety (Supabase route): INSERT of a not-yet-indexed document enqueues (duplicate app-upload inserts are absorbed by the ingestion_jobs unique index); UPDATE only acts on metadata.reindex_requested === true and then clears that flag, so the worker's own completion writes can't retrigger a loop. Every documents write is owner-scoped.

Also adds env vars, .env.example entries, docs/webhooks.md, and codebase-index + site-map entries.

Verification

  • npm run typecheck — clean
  • npm run lint on the new/changed files — clean
  • Full npm run test unit suite — 2992 passing; new tests/webhooks-*.test.ts (23) included
  • Affected static gates — check:github-actions, check:owner-scope, docs:check-index, check:docs-links, check:ci-scope, check:knip, check:maintainability-budgets, sitemap:check all pass
  • Verification not run: npm run verify:pr-local wrapper and npm run verify:release / live evals — no code path here touches retrieval/ranking/answer generation, and the release/eval gates require live provider (OpenAI/Supabase) keys, which is outside the provider-confirmation boundary. One pre-existing unit failure (tests/pdf-extraction-budget.test.ts) reproduces on clean origin/main in this VM (Python OCR timing) and is unrelated to this change.

Risk and rollout

  • Risk: low; the integrations are inert until their secret env vars are set — each inbound receiver returns 503 when its secret is unset and 401 on a bad secret, and the Supabase enqueue reuses the existing reindex concurrency guards (idempotent + loop-safe).
  • Rollback: revert this commit and delete .github/workflows/notify-ci-failure.yml. No migrations, no schema changes, no new RPCs (the Supabase route calls the existing apply_document_metadata_patch).
  • Provider or production effects: None applied by this PR. Enabling the webhooks is a separate operator action (setting secrets + configuring Railway/Supabase/GitHub), documented in docs/webhooks.md.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Notes: this change is webhook/transport infrastructure only — it does not alter retrieval, ranking, answer generation, source rendering, or any clinical decision-support behavior, so the SaMD classification is unchanged. The receivers use the service-role admin client server-side and scope every documents write by owner_id; the Supabase route no-ops in demo mode.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added inbound webhook endpoints for deployment notifications and Supabase document-change ingestion.
    • Added outbound Slack/Discord notifications for notable deploy events and automated CI failure alerts.
    • Added eligibility-checked, per-document reindex job queuing.
  • Bug Fixes
    • Improved webhook safety/auth handling to reduce duplicate processing and prevent webhook-trigger loops.
  • Documentation
    • Added webhook docs and updated site maps/codebase route listings; expanded .env.example with webhook secrets/destinations (commented by default).
  • Tests
    • Added/extended Vitest coverage for webhook auth, routing outcomes, notifications, and reindex enqueue behavior.

Implements three webhook integrations:

1. POST /api/webhooks/railway — receives Railway deploy status webhooks
   (secret via ?token=, constant-time) and forwards notable statuses to
   Slack/Discord.
2. .github/workflows/notify-ci-failure.yml — pings chat when a workflow_run
   on main/release fails, via curl (no external actions).
3. POST /api/webhooks/supabase/document-change — Supabase Database Webhook
   that enqueues a reindex job on new/flagged documents. Idempotent (relies
   on the one-open-job-per-document unique index) and loop-safe (UPDATE only
   acts on metadata.reindex_requested, then clears the flag).

Shared helpers: src/lib/webhooks/secret-auth.ts (constant-time secret gate),
src/lib/webhooks/chat-notify.ts (Slack/Discord forwarder), and
src/lib/ingestion-enqueue.ts (reindex enqueue mirroring the reindex route's
concurrency guards). Adds env vars, .env.example, docs/webhooks.md, codebase
index + site-map entries, and unit/route tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHMgBiXbH4Q5tUC7WxoyaX
@supabase

supabase Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a865ac70-5289-4c5c-9324-34928eb73530

📥 Commits

Reviewing files that changed from the base of the PR and between 6839ae1 and 2e87c9d.

📒 Files selected for processing (1)
  • tests/ingestion-enqueue.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/ingestion-enqueue.test.ts

📝 Walkthrough

Walkthrough

Adds authenticated Railway and Supabase webhook receivers, shared Slack/Discord delivery, CI failure notifications, ingestion job enqueueing, route tests, environment configuration, and webhook documentation.

Changes

Webhook integrations

Layer / File(s) Summary
Webhook authentication and chat delivery
src/lib/env.ts, src/lib/webhooks/*, tests/webhooks-secret-auth.test.ts, tests/webhooks-chat-notify.test.ts
Adds validated webhook secrets and destinations, timing-safe authentication, and concurrent Slack/Discord notification delivery with tests.
Railway deploy notifications
src/app/api/webhooks/railway/route.ts, tests/webhooks-railway-route.test.ts, docs/webhooks.md
Adds authenticated Railway deploy handling, transient-status filtering, notable-status forwarding, and coverage for authentication and delivery behavior.
Supabase document-change ingestion
src/app/api/webhooks/supabase/document-change/route.ts, src/lib/ingestion-enqueue.ts, tests/webhooks-supabase-document-change-route.test.ts, tests/ingestion-enqueue.test.ts, docs/webhooks.md
Adds validated document-change processing, owner-scoped safety checks, reindex enqueueing, loop prevention, race handling, and route tests.
CI failure chat notifications
.github/workflows/notify-ci-failure.yml, .env.example, docs/webhooks.md
Adds workflow-run failure filtering and conditional Slack/Discord notifications using repository secrets.
Webhook documentation and route indexing
docs/codebase-index.md, docs/site-map.md, scripts/generate-site-map.ts
Adds webhook route descriptions and updates the ingestion/indexing module map.

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

Sequence Diagram(s)

sequenceDiagram
  participant Supabase
  participant DocumentChangeRoute
  participant MutationSafety
  participant SupabaseAdmin
  participant IngestionQueue
  Supabase->>DocumentChangeRoute: POST document change event
  DocumentChangeRoute->>MutationSafety: Check mutation safety
  MutationSafety-->>DocumentChangeRoute: Safety result
  DocumentChangeRoute->>SupabaseAdmin: Reload owner-scoped document
  DocumentChangeRoute->>IngestionQueue: Enqueue reindex job
  IngestionQueue->>SupabaseAdmin: Update document and insert ingestion job
  DocumentChangeRoute->>SupabaseAdmin: Clear reindex_requested
  DocumentChangeRoute-->>Supabase: Return enqueue or skip response
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding Railway, CI-failure, and Supabase ingestion webhooks.
Description check ✅ Passed The description includes all required template sections and gives concrete verification, risk, rollout, and governance details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/repository-webhook-recommendations-oviufb

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

@BigSimmo
BigSimmo marked this pull request as ready for review July 20, 2026 08:28
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 08:41

@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: 3

🧹 Nitpick comments (1)
src/lib/ingestion-enqueue.ts (1)

36-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No direct unit tests for the concurrency-sensitive rollback logic.

enqueueDocumentReindexJob handles two race conditions (FK-deleted document, competing-job rollback via the rollback fence) but is only exercised via the Supabase route test with this function fully mocked out. A dedicated test suite exercising the 23503/23505 branches directly against a mocked Supabase client would catch regressions in this concurrency-critical path.

🤖 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/lib/ingestion-enqueue.ts` around lines 36 - 121, Add dedicated unit tests
for enqueueDocumentReindexJob covering the 23503 document-deleted outcome and
the 23505 competing-job path, including rollback only when no open competing job
remains and respecting the rollback fence. Mock the Supabase client while
verifying queue updates, job insertion, competing-job lookup, and rollback
behavior directly.
🤖 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 @.github/workflows/notify-ci-failure.yml:
- Around line 42-45: Update the workflow_run condition guarding notifications to
also require github.event.workflow_run.event == 'push', while preserving the
existing failure and main/release branch checks. Keep webhook-secret
notifications limited to trusted push runs.

In `@docs/webhooks.md`:
- Around line 7-11: Renumber the integration rows in the webhook documentation
table sequentially from 1 through 3, updating the Supabase document-change row
currently labeled 5 to 3 while leaving the integration details unchanged.

In `@src/app/api/webhooks/supabase/document-change/route.ts`:
- Around line 106-121: Update clearReindexFlagIfRequested to return whether the
requested RPC clear succeeded, and propagate RPC failures instead of only
logging them. In both route call sites around checkIngestionMutationSafety,
inspect that result and return a non-2xx response when reindexRequested is true
but the clear fails, while preserving the existing skip or success responses
after a successful clear or when no clear was requested.

---

Nitpick comments:
In `@src/lib/ingestion-enqueue.ts`:
- Around line 36-121: Add dedicated unit tests for enqueueDocumentReindexJob
covering the 23503 document-deleted outcome and the 23505 competing-job path,
including rollback only when no open competing job remains and respecting the
rollback fence. Mock the Supabase client while verifying queue updates, job
insertion, competing-job lookup, and rollback behavior directly.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

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: f273e36d-ea6c-4d5f-b4f1-3fb29781c79a

📥 Commits

Reviewing files that changed from the base of the PR and between 6d11e53 and d4c9790.

📒 Files selected for processing (16)
  • .env.example
  • .github/workflows/notify-ci-failure.yml
  • docs/codebase-index.md
  • docs/site-map.md
  • docs/webhooks.md
  • scripts/generate-site-map.ts
  • src/app/api/webhooks/railway/route.ts
  • src/app/api/webhooks/supabase/document-change/route.ts
  • src/lib/env.ts
  • src/lib/ingestion-enqueue.ts
  • src/lib/webhooks/chat-notify.ts
  • src/lib/webhooks/secret-auth.ts
  • tests/webhooks-chat-notify.test.ts
  • tests/webhooks-railway-route.test.ts
  • tests/webhooks-secret-auth.test.ts
  • tests/webhooks-supabase-document-change-route.test.ts

Comment thread .github/workflows/notify-ci-failure.yml
Comment thread docs/webhooks.md Outdated
Comment thread src/app/api/webhooks/supabase/document-change/route.ts
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️ Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

claude and others added 8 commits July 20, 2026 09:01
… trigger

- Supabase document-change webhook: clearReindexFlagIfRequested now reports
  success/failure; when a requested clear fails the route responds 500 so
  Supabase retries delivery until the flag is cleared (the enqueue is
  idempotent, so a retry cannot double-queue). Prevents a failed clear from
  silently defeating the documented loop-safety guarantee.
- notify-ci-failure.yml: gate on head_repository.full_name == github.repository
  so only runs originating in this repo can post with repo secrets (blocks fork
  PR forgery/spam). Preferred over an `event == 'push'` filter, which would drop
  the scheduled production monitors (schedule runs, not push).
- docs/webhooks.md: renumber the integration table 1/2/3 and note the
  clear-failure retry behavior.
- Add tests/ingestion-enqueue.test.ts covering the 23503/23505 concurrency
  branches directly, plus a route test for the clear-failure 500 path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHMgBiXbH4Q5tUC7WxoyaX

@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.

🧹 Nitpick comments (3)
tests/ingestion-enqueue.test.ts (3)

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

isCompetingSelect is set but never read.

The state object and its isCompetingSelect flag are dead code — the flag is set in select("id") but never consulted anywhere in the mock. Remove the unused state to reduce confusion.

♻️ Remove dead state
   function builder(table: string) {
-    const state = { isCompetingSelect: false };
     const b: Record<string, unknown> = {};
     b.update = () => {
       if (table === "documents") calls.documentUpdates += 1;
       return b;
     };
     b.insert = () => b;
     b.select = (cols?: string) => {
-      if (cols === "id") state.isCompetingSelect = true;
       return b;
     };

Also applies to: 32-32

🤖 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 `@tests/ingestion-enqueue.test.ts` at line 24, Remove the unused state object
and all assignments to its isCompetingSelect flag in the mock, including the
related select("id") handling. Preserve the mock’s existing behavior while
eliminating this dead state.

59-103: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Missing test for the initial document update error path.

The implementation throws when the initial documents.update fails (if (updateError) throw new Error(updateError.message)), but no test covers this branch. The mock's then always resolves with { error: null }, making the path untestable without modification. Add a configurable update-error path to the mock and a test case asserting the throw.

♻️ Add update-error config and test
 type ClientConfig = {
   jobInsert?: { data: unknown; error: { code?: string; message: string } | null };
   competing?: { data: unknown[]; error: null };
+  updateError?: { message: string };
 };

 // In makeClient, update the then handler:
-  b.then = (resolve: (v: unknown) => void, reject?: (e: unknown) => void) =>
-    Promise.resolve({ error: null }).then(resolve, reject);
+  b.then = (resolve: (v: unknown) => void, reject?: (e: unknown) => void) =>
+    Promise.resolve({ error: cfg.updateError ?? null }).then(resolve, reject);
it("throws when the initial document update fails", async () => {
  const { enqueueDocumentReindexJob, client } = await load({
    updateError: { message: "update failed" },
  });
  await expect(enqueueDocumentReindexJob({ supabase: client as never, document: doc }))
    .rejects.toThrow("update failed");
});
🤖 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 `@tests/ingestion-enqueue.test.ts` around lines 59 - 103, Extend the test
mock’s configurable setup, including load, so the initial documents.update
operation can return an injected updateError instead of always resolving with no
error. Add a test in the enqueueDocumentReindexJob suite that passes updateError
and asserts the call rejects with the configured message, covering the initial
updateError branch.

21-48: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Mock doesn't verify owner_id scoping on document writes.

The PR emphasizes owner-scoped server-side document access, but the mock's .eq() unconditionally returns b without recording arguments. This means the tests cannot confirm that .eq("owner_id", ownerId) is actually called on both the initial queue update and the rollback update. Consider tracking .eq() arguments so tests can assert owner scoping is applied.

♻️ Track eq calls to verify owner scoping
 function makeClient(cfg: ClientConfig) {
-  const calls = { documentUpdates: 0, competingSelects: 0 };
+  const calls = { documentUpdates: 0, competingSelects: 0, eqCalls: [] as string[] };
   function builder(table: string) {
     const b: Record<string, unknown> = {};
     b.update = () => {
       if (table === "documents") calls.documentUpdates += 1;
       return b;
     };
     b.insert = () => b;
     b.select = (cols?: string) => {
       return b;
     };
-    b.eq = () => b;
+    b.eq = (col: string) => { calls.eqCalls.push(col); return b; };
     b.in = () => b;
     // ...
   }
   return { client: { from: (t: string) => builder(t) }, calls };
 }

Then in tests, assert that "owner_id" appears in calls.eqCalls for the initial update and rollback paths.

🤖 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 `@tests/ingestion-enqueue.test.ts` around lines 21 - 48, Update the makeClient
mock’s builder and calls tracking to record .eq() arguments, especially the
column names, instead of returning b without observation. Expose the recorded
eqCalls and add assertions in the initial queue-update and rollback test paths
that owner_id scoping is applied to both document writes.
🤖 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.

Nitpick comments:
In `@tests/ingestion-enqueue.test.ts`:
- Line 24: Remove the unused state object and all assignments to its
isCompetingSelect flag in the mock, including the related select("id") handling.
Preserve the mock’s existing behavior while eliminating this dead state.
- Around line 59-103: Extend the test mock’s configurable setup, including load,
so the initial documents.update operation can return an injected updateError
instead of always resolving with no error. Add a test in the
enqueueDocumentReindexJob suite that passes updateError and asserts the call
rejects with the configured message, covering the initial updateError branch.
- Around line 21-48: Update the makeClient mock’s builder and calls tracking to
record .eq() arguments, especially the column names, instead of returning b
without observation. Expose the recorded eqCalls and add assertions in the
initial queue-update and rollback test paths that owner_id scoping is applied to
both document writes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1fde4e48-916d-42e5-a8c7-8deaa2f50ef2

📥 Commits

Reviewing files that changed from the base of the PR and between d4c9790 and 6839ae1.

📒 Files selected for processing (6)
  • .github/workflows/notify-ci-failure.yml
  • docs/webhooks.md
  • src/app/api/webhooks/supabase/document-change/route.ts
  • src/lib/env.ts
  • tests/ingestion-enqueue.test.ts
  • tests/webhooks-supabase-document-change-route.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/env.ts
  • .github/workflows/notify-ci-failure.yml
  • src/app/api/webhooks/supabase/document-change/route.ts
  • tests/webhooks-supabase-document-change-route.test.ts
  • docs/webhooks.md

- Remove dead isCompetingSelect state from the mock.
- Cover the initial documents.update error branch (throws).
- Record .eq() columns and assert owner_id scoping on both the queue-state
  update and the rollback update.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHMgBiXbH4Q5tUC7WxoyaX
@BigSimmo
BigSimmo merged commit 63b1a91 into main Jul 20, 2026
17 checks passed
@BigSimmo
BigSimmo deleted the claude/repository-webhook-recommendations-oviufb branch July 20, 2026 10:04
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.

2 participants