openapi: 3.1.0 info: title: Findings Notifications & Escalation Surface Contract version: 1.0.0 summary: Logical internal contract for Spec 224 delivery, alert-rule exposure, and finding deep links. description: | This contract documents the structured payloads and UI-facing surfaces that Spec 224 must satisfy. It is intentionally logical rather than public-API only: the feature reuses existing Filament resources, database notifications, and background jobs instead of introducing a new public controller namespace. servers: - url: https://logical.internal description: Non-routable placeholder used to describe internal repository contracts. paths: /internal/findings/notification-events: post: summary: Dispatch one finding event to direct personal delivery and optional external alert copies. description: | Logical internal contract implemented by the bounded finding-notification delivery seam. It normalizes one finding event, resolves at most one entitled direct recipient, writes one Filament-compatible database notification when appropriate, and forwards the same event to the existing workspace alert dispatch pipeline. operationId: dispatchFindingNotificationEvent x-not-public-http: true requestBody: required: true content: application/vnd.tenantpilot.finding-notification-event+json: schema: $ref: '#/components/schemas/FindingNotificationEventDispatch' responses: '202': description: Event accepted and evaluated for direct and optional external delivery. content: application/vnd.tenantpilot.finding-notification-dispatch-result+json: schema: $ref: '#/components/schemas/FindingNotificationDispatchResult' /admin/alert-rules: get: summary: Existing alert-rule surfaces expose the four new finding event types. description: | Existing Filament resource pages continue to render HTML, while this logical media type documents the event-type options that must appear in create, edit, and filter flows. operationId: viewFindingAlertRuleOptions responses: '200': description: Alert-rule surfaces show the finding event options and reuse the existing delivery families. content: text/html: schema: type: string application/vnd.tenantpilot.finding-alert-rule-options+json: schema: $ref: '#/components/schemas/FindingAlertRuleOptionsSurface' '403': description: Workspace operator lacks permission to view alert-rule configuration. '404': description: Workspace operator is not a member of the active workspace or the alert surface is outside their visible scope. /admin/alert-deliveries: get: summary: Existing alert-delivery history can filter and label finding event copies. operationId: viewFindingAlertDeliveries parameters: - name: event_type in: query required: false schema: $ref: '#/components/schemas/FindingEventType' responses: '200': description: Alert-delivery viewer surfaces show finding-event labels and safe summaries. content: text/html: schema: type: string application/vnd.tenantpilot.finding-alert-deliveries+json: schema: $ref: '#/components/schemas/FindingAlertDeliveriesSurface' '403': description: Workspace operator lacks permission to inspect alert deliveries. '404': description: Workspace operator is not a member of the active workspace or the alert-delivery surface is outside their visible scope. /admin/t/{tenant}/findings/{finding}: get: summary: The direct notification action opens the existing tenant finding detail route. operationId: openFindingFromNotification parameters: - name: tenant in: path required: true schema: type: integer - name: finding in: path required: true schema: type: integer responses: '200': description: Existing finding detail renders for an entitled tenant operator. content: text/html: schema: type: string application/vnd.tenantpilot.finding-notification-context+json: schema: $ref: '#/components/schemas/FindingNotificationContext' '403': description: Recipient still has tenant visibility but lacks current capability to inspect the finding. '404': description: Recipient no longer has tenant or record visibility. components: schemas: FindingEventType: type: string enum: - findings.assigned - findings.reopened - findings.due_soon - findings.overdue FindingSeverity: type: string enum: - low - medium - high - critical RecipientReason: type: string enum: - new_assignee - current_assignee - current_owner DirectRecipient: type: object required: - userId - reason properties: userId: type: integer displayName: type: string reason: $ref: '#/components/schemas/RecipientReason' FindingNotificationEventDispatch: type: object required: - eventType - workspaceId - tenantId - findingId - severity - title - body - fingerprintKey - metadata properties: eventType: $ref: '#/components/schemas/FindingEventType' workspaceId: type: integer tenantId: type: integer findingId: type: integer severity: $ref: '#/components/schemas/FindingSeverity' title: type: string body: type: string directRecipient: oneOf: - $ref: '#/components/schemas/DirectRecipient' - type: 'null' fingerprintKey: type: string dueCycleKey: type: - string - 'null' metadata: type: object required: - tenantName - recipientReason properties: tenantName: type: string recipientReason: $ref: '#/components/schemas/RecipientReason' ownerUserId: type: - integer - 'null' assigneeUserId: type: - integer - 'null' dueAt: type: - string - 'null' format: date-time reopenedAt: type: - string - 'null' format: date-time summary: type: string allOf: - if: properties: eventType: enum: - findings.due_soon - findings.overdue then: required: - dueCycleKey properties: dueCycleKey: type: string - if: properties: eventType: enum: - findings.assigned - findings.reopened then: properties: dueCycleKey: type: 'null' FindingNotificationDispatchResult: type: object required: - eventType - fingerprintKey - directDeliveryStatus - externalDeliveryCount properties: eventType: $ref: '#/components/schemas/FindingEventType' fingerprintKey: type: string directDeliveryStatus: type: string enum: - sent - suppressed - deduped - no_recipient externalDeliveryCount: type: integer minimum: 0 externalDeliveryStatuses: type: array items: type: string enum: - queued - deferred - suppressed - none EventTypeOption: type: object required: - value - label properties: value: $ref: '#/components/schemas/FindingEventType' label: type: string FindingAlertRuleOptionsSurface: type: object required: - eventTypes - existingDeliveryFamiliesReused properties: eventTypes: type: array items: $ref: '#/components/schemas/EventTypeOption' existingDeliveryFamiliesReused: type: boolean notes: type: array items: type: string FindingAlertDeliveryRow: type: object required: - deliveryId - eventType - label - status properties: deliveryId: type: integer eventType: $ref: '#/components/schemas/FindingEventType' label: type: string tenantId: type: - integer - 'null' status: type: string destinationName: type: string summary: type: string FindingAlertDeliveriesSurface: type: object required: - filterEventTypes - rows properties: filterEventTypes: type: array items: $ref: '#/components/schemas/EventTypeOption' rows: type: array items: $ref: '#/components/schemas/FindingAlertDeliveryRow' FindingNotificationContext: type: object required: - findingId - tenantId - eventType - recipientReason properties: findingId: type: integer tenantId: type: integer eventType: $ref: '#/components/schemas/FindingEventType' recipientReason: $ref: '#/components/schemas/RecipientReason' title: type: string body: type: string