feat(phase-3): workstream 5 — Webhooks & Event Streaming
- DB migrations 016/017: webhook_subscriptions and webhook_deliveries tables - WebhookService: CRUD for subscriptions, Vault-backed secret storage, delivery history - WebhookDeliveryWorker: Bull queue, HMAC-SHA256 signatures, exponential backoff, SSRF protection (RFC 1918 + loopback + link-local rejection), dead-letter handling - EventPublisher: publishes 10 event types (agent/credential/token lifecycle); optional Kafka adapter activated via KAFKA_BROKERS env var - AgentService, CredentialService, OAuth2Service: wired to EventPublisher - WebhookController + routes: 6 endpoints with webhooks:read / webhooks:write scope guards - KafkaAdapter: optional Kafka producer (kafkajs), no-op when KAFKA_BROKERS unset - OAuthScope extended: webhooks:read, webhooks:write - AuditAction extended: webhook.created, webhook.updated, webhook.deleted - Metrics: agentidp_webhook_dead_letters_total counter added to registry - 523 unit tests passing; TypeScript strict throughout, zero `any` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,7 +28,14 @@ export type DeploymentEnv = 'development' | 'staging' | 'production';
|
||||
export type CredentialStatus = 'active' | 'revoked';
|
||||
|
||||
/** OAuth 2.0 scope values supported by this IdP. */
|
||||
export type OAuthScope = 'agents:read' | 'agents:write' | 'tokens:read' | 'audit:read' | 'admin:orgs';
|
||||
export type OAuthScope =
|
||||
| 'agents:read'
|
||||
| 'agents:write'
|
||||
| 'tokens:read'
|
||||
| 'audit:read'
|
||||
| 'admin:orgs'
|
||||
| 'webhooks:read'
|
||||
| 'webhooks:write';
|
||||
|
||||
/** Audit action identifiers for all significant platform events. */
|
||||
export type AuditAction =
|
||||
@@ -47,7 +54,10 @@ export type AuditAction =
|
||||
| 'org.created'
|
||||
| 'org.updated'
|
||||
| 'org.deleted'
|
||||
| 'org.member_added';
|
||||
| 'org.member_added'
|
||||
| 'webhook.created'
|
||||
| 'webhook.updated'
|
||||
| 'webhook.deleted';
|
||||
|
||||
/** Outcome of an audited action. */
|
||||
export type AuditOutcome = 'success' | 'failure';
|
||||
|
||||
94
src/types/webhook.ts
Normal file
94
src/types/webhook.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Shared TypeScript interfaces and types for the Webhook / Event Streaming subsystem.
|
||||
* Imported by WebhookService, WebhookDeliveryWorker, EventPublisher, and their controllers.
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Event Types
|
||||
// ============================================================================
|
||||
|
||||
/** All event type identifiers that can be published and subscribed to. */
|
||||
export type WebhookEventType =
|
||||
| 'agent.created'
|
||||
| 'agent.updated'
|
||||
| 'agent.suspended'
|
||||
| 'agent.reactivated'
|
||||
| 'agent.decommissioned'
|
||||
| 'credential.generated'
|
||||
| 'credential.rotated'
|
||||
| 'credential.revoked'
|
||||
| 'token.issued'
|
||||
| 'token.revoked';
|
||||
|
||||
// ============================================================================
|
||||
// Webhook Subscription
|
||||
// ============================================================================
|
||||
|
||||
/** A registered webhook subscription (secret fields are never included). */
|
||||
export interface IWebhookSubscription {
|
||||
id: string;
|
||||
organization_id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
events: WebhookEventType[];
|
||||
active: boolean;
|
||||
failure_count: number;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
/** Request body for creating a new webhook subscription. */
|
||||
export interface ICreateWebhookRequest {
|
||||
name: string;
|
||||
url: string;
|
||||
events: WebhookEventType[];
|
||||
}
|
||||
|
||||
/** Request body for partially updating a webhook subscription. */
|
||||
export interface IUpdateWebhookRequest {
|
||||
name?: string;
|
||||
url?: string;
|
||||
events?: WebhookEventType[];
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Webhook Delivery
|
||||
// ============================================================================
|
||||
|
||||
/** A single webhook delivery attempt record. */
|
||||
export interface IWebhookDelivery {
|
||||
id: string;
|
||||
subscription_id: string;
|
||||
event_type: WebhookEventType;
|
||||
payload: Record<string, unknown>;
|
||||
status: 'pending' | 'delivered' | 'failed' | 'dead_letter';
|
||||
http_status_code: number | null;
|
||||
attempt_count: number;
|
||||
next_retry_at: Date | null;
|
||||
delivered_at: Date | null;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
/** The envelope sent in every outbound webhook HTTP request body. */
|
||||
export interface IWebhookPayload {
|
||||
/** UUID uniquely identifying this event occurrence. */
|
||||
id: string;
|
||||
/** The event type identifier. */
|
||||
event: WebhookEventType;
|
||||
/** ISO 8601 timestamp of when the event was published. */
|
||||
timestamp: string;
|
||||
/** The organization that owns the resource that changed. */
|
||||
organization_id: string;
|
||||
/** Event-specific data. Contents vary by event type. */
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Paginated response for listing webhook deliveries. */
|
||||
export interface IPaginatedDeliveriesResponse {
|
||||
deliveries: IWebhookDelivery[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}
|
||||
Reference in New Issue
Block a user