Skip to content

DM-54689: GitHub push webhook + PushEventProcessor #238

@jonathansick

Description

@jonathansick

Metadata

Field Value
Parent PRD #232
Jira Key DM-54689
Jira URL https://rubinobs.atlassian.net/browse/DM-54689
Task Order 6
Type AFK
Blocked by #237, #224
Parallel with None
Branch tickets/DM-54689-github-webhook

What to build

The live-update path: a GitHub webhook endpoint that reacts to push events and enqueues dashboard_sync jobs for every binding whose tracked ref was actually affected by the push.

Scope:

  • Handler. New src/docverse/handlers/webhooks/github.py exposing /docverse/webhooks/github, using safir.github's GitHubWebhookRouter (or the equivalent registration API) for HMAC verification and event dispatch. Do not hand-roll HMAC or payload parsing. Mount the router in src/docverse/main.py.
  • Service. New src/docverse/services/dashboard_templates/push_processor.pyPushEventProcessor.process(event):
    1. Find all bindings matching (github_owner, github_repo, github_ref).
    2. Compute the effective changed-path set: use the push payload's file list when complete; otherwise call GET /repos/{owner}/{repo}/compare/{before}...{after} via the helper from DM-54689: GitHub App client + tree fetcher + respx fake-GitHub fixture #236's changed_paths.py.
    3. Filter bindings by intersection of changed paths with each binding's root_path.
    4. Enqueue one dashboard_sync job per surviving binding via DashboardSyncEnqueuer (from DM-54689: dashboard_sync worker + DASHBOARD_TEMPLATE lock + syncer + fanout + force-sync endpoint #237).
  • Feature-flag behaviour. When the GitHub App secrets are not configured (see DM-54689: GitHub App client + tree fetcher + respx fake-GitHub fixture #236), the webhook endpoint responds 404 — not a 500. Bindings created while the feature was live still exist in the DB but the webhook path is a no-op.

Tests:

  • tests/handlers/webhooks_github_test.py — signed POST with a fabricated push payload lands the expected dashboard_sync jobs in MockArqQueue; unsigned/wrong-signature → 401; unrelated event types → 200 no-op.
  • tests/services/dashboard_templates/push_processor_test.py — (a) binding whose root_path intersects the push is synced; binding whose root_path does not intersect is skipped. (b) Truncated payload triggers compare-API fallback (exercised via the mock_github fixture from DM-54689: GitHub App client + tree fetcher + respx fake-GitHub fixture #236). (c) Push to a ref with no bindings is a no-op.

Out of scope:

Acceptance criteria

  • Webhook endpoint rejects unsigned or mis-signed payloads with 401/403; accepts correctly-signed payloads.
  • PushEventProcessor filters bindings by root_path intersection with the push's changed paths; only surviving bindings get a dashboard_sync job.
  • Truncation branch: simulated truncated payload falls back to the compare API and the processor still correctly identifies affected bindings (test covers both branches).
  • Feature-disabled path (no GitHub App secrets configured) → webhook endpoint 404.
  • uv run --only-group=nox nox -s typing and test pass.

User stories addressed

From PRD #232:

  • User story 8 (push to tracked branch re-renders dependent dashboards within seconds)
  • User story 9 (push touching paths outside root_path is a no-op)
  • User story 10 (Docverse reads from the specified root_path, not repo root)
  • User story 21 (respx-based fake GitHub in the test suite)
  • User story 23 (webhook uses safir.github's webhook router, not hand-rolled HMAC)
  • User story 26 (dashboard reflects latest merged template within a minute)

Metadata

Metadata

Assignees

No one assigned

    Labels

    prd-taskImplementation task from a PRD

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions