Files
rengine/shared/services/notification_sync.py
T
Yogesh OjhaandClaude Fable 5.1 db030d733c feat(bounty-hub): watch a program
Scope-true continuous discovery for a bounty program, bug bounty mode only
(capability `program_watches`).

Watching a program imports its in-scope targets under the program's
organization, writes out-of-scope entries as exclusions and the rate ceiling
on a `Watch · <program>` scan context, schedules an optional baseline, and
streams certificate transparency for the watched apexes. A new in-scope host
is resolved, written into the covering scan with source `ct_log`, probed by a
focused run with a screenshot, evaluated against the watch's alert query and
alerted once per fingerprint through the chosen channels.

- ct-stream compose service: certspotter 0.24.2 under a supervisor that
  regenerates the watchlist from active watches, spools events through a
  hook script that emits only certspotter fields, and reports over Redis
- worker tasks: certificate, settle, recheck, reconcile; finalize dispatches
  settle for any probe run; reconcile after every program or feed sync
- notifier: named channels bypass per-channel preferences; attachments;
  new `watch` notification type
- API under /bounty-programs/watches with preview, ledger, events, marks
- Bounty Hub: Watching tab, Watch dialog, watch sheet, program badge,
  certificate stream card in Settings
- schedules carry an intensity; scan_schedules.intensity

Two review rounds folded in: compare-and-set probe claim, resolver-down
handling, spool kept on failed dispatch, schedule cap, mute keeps prior
state, ledger-derived totals, event filters, mode lockout of worker and
stream in corporate mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ba2f7gwu2GXiETCKqRztyB
2026-09-12 21:51:18 +05:30

102 lines
3.2 KiB
Python

import logging
import uuid
from datetime import timedelta
from sqlalchemy.orm import Session
from shared.enums.notification import NotificationSeverity, NotificationType
from shared.enums.sse import SSEChannel, SSEEventType
from shared.models.notification import Notification, NotificationMetadata
from shared.services.event_publisher import SyncEventPublisher
from shared.utils.datetime import utc_now
logger = logging.getLogger(__name__)
NOTIFICATION_EXPIRY_DAYS = 7
def single_project(rows) -> uuid.UUID | None:
ids = {r.project_id for r in rows if getattr(r, "project_id", None)}
return next(iter(ids)) if len(ids) == 1 else None
class SyncNotificationPublisher:
def __init__(self, redis_url: str) -> None:
self._event_publisher = SyncEventPublisher(redis_url)
def publish(
self,
session: Session,
type: NotificationType,
severity: NotificationSeverity,
title: str,
message: str,
metadata: NotificationMetadata | dict | None = None,
project_id: uuid.UUID | str | None = None,
channel_ids=None,
attach: str | None = None,
) -> Notification:
if isinstance(metadata, NotificationMetadata):
metadata_dict = metadata.model_dump(exclude_none=True)
elif isinstance(metadata, dict):
validated = NotificationMetadata(**metadata)
metadata_dict = validated.model_dump(exclude_none=True)
else:
metadata_dict = {}
now = utc_now()
notification = Notification(
type=type,
severity=severity,
title=title[:200],
message=message,
notification_metadata=metadata_dict,
project_id=uuid.UUID(str(project_id)) if project_id else None,
created_at=now,
expires_at=now + timedelta(days=NOTIFICATION_EXPIRY_DAYS),
)
session.add(notification)
session.commit()
session.refresh(notification)
self._event_publisher.publish(
channel=SSEChannel.BROADCAST,
event_type=SSEEventType.NOTIFICATION,
data={
"id": notification.id,
"project_id": str(notification.project_id)
if notification.project_id
else None,
"type": notification.type.value,
"severity": notification.severity.value,
"title": notification.title,
"message": notification.message,
"notification_metadata": notification.notification_metadata,
"is_read": notification.is_read,
"created_at": notification.created_at.isoformat(),
},
)
logger.info(
"Published notification: %s/%s - %s", type.value, severity.value, title
)
try:
from shared.services.notifier import dispatch_sync # noqa: PLC0415
dispatch_sync(
session,
type,
severity,
title,
message,
channel_ids=channel_ids,
attach=attach,
)
except Exception as exc:
logger.warning("External notification dispatch failed: %s", exc)
return notification