feat(notifications): add Phase 4 Slice 2 — multi-channel notification system

Full notification infrastructure with in-app, email, Slack, and Teams channels:

Backend:
- NotificationConfig, NotificationLog, Notification models + migration
- Notification service with event routing, channel delivery, retry logic
- 9 API endpoints (config CRUD + in-app notifications)
- APScheduler retry job with exponential backoff (30s, 2m, 10m)
- Wired into escalation, proposal approval, and knowledge flywheel
- Pydantic event key validation, cross-tenant protection on recipients

Frontend:
- TypeScript types + API client for all notification endpoints
- NotificationsPanel: bell icon with unread badge, dropdown, mark-read
- NotificationSettings: channel config, event toggles, test, delete confirm
- Notifications tab on IntegrationsPage
- ARIA attributes, Escape handler, settings link on panel

Review fixes (13 issues resolved):
- notify() no longer commits/rolls back caller's transaction (critical)
- retry_failed_notifications returns count instead of None (critical)
- NotificationSettings moved inside dedicated tab (critical)
- target_user_ids scoped by account_id (security)
- Email loop collects all failures before raising
- Slack webhook validates response body
- events_enabled rejects unknown event keys
- link column widened to String(500)
- Dead code removed from _auto_reinforce
- Delete confirmation, ARIA, Escape key support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 12:37:54 +00:00
parent a8999adef3
commit 0f750e63e0
22 changed files with 3402 additions and 53 deletions

View File

@@ -93,3 +93,4 @@ export type {
export * from './scripts'
export * from './integrations'
export * from './notification'

View File

@@ -0,0 +1,52 @@
export interface NotificationConfig {
id: string
channel: 'email' | 'slack_webhook' | 'teams_webhook'
webhook_url: string | null
email_addresses: string[] | null
is_active: boolean
events_enabled: Record<string, boolean>
created_at: string
updated_at: string
}
export interface NotificationConfigCreate {
channel: 'email' | 'slack_webhook' | 'teams_webhook'
webhook_url?: string
email_addresses?: string[]
events_enabled?: Record<string, boolean>
}
export interface NotificationConfigUpdate {
webhook_url?: string
email_addresses?: string[]
is_active?: boolean
events_enabled?: Record<string, boolean>
}
export interface AppNotification {
id: string
event: string
title: string
body: string | null
link: string | null
is_read: boolean
created_at: string
}
export interface UnreadCount {
count: number
}
export const NOTIFICATION_EVENTS = {
'session.escalated': 'Session Escalated',
'session.high_priority': 'High Priority Session',
'proposal.pending': 'New Flow Proposal',
'proposal.approved': 'Proposal Approved',
'knowledge_gap.detected': 'Knowledge Gap Detected',
} as const
export const CHANNEL_LABELS = {
email: 'Email',
slack_webhook: 'Slack',
teams_webhook: 'Microsoft Teams',
} as const