## Summary - implement Spec 224 findings notifications and escalation v1 on top of the existing alerts and Filament database notification infrastructure - add finding assignment, reopen, due soon, and overdue event handling with direct recipient routing, dedupe, and optional external alert fan-out - extend alert rule and alert delivery surfaces plus add the Spec 224 planning bundle and candidate-list promotion cleanup ## Validation - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsNotificationEventTest.php tests/Feature/Findings/FindingsNotificationRoutingTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Alerts/FindingsAlertRuleIntegrationTest.php tests/Feature/Alerts/SlaDueAlertTest.php tests/Feature/Notifications/FindingNotificationLinkTest.php` ## Filament / Platform Notes - Livewire v4.0+ compliance is preserved - provider registration remains unchanged in `apps/platform/bootstrap/providers.php` - no globally searchable resource behavior changed in this feature - no new destructive action was introduced - asset strategy is unchanged and the existing `cd apps/platform && php artisan filament:assets` deploy step remains sufficient ## Manual Smoke Note - integrated-browser smoke testing confirmed the new alert rule event options, notification drawer entries, alert delivery history row, and tenant finding detail route on the active Sail host - local notification deep links currently resolve from `APP_URL`, so a local `localhost` vs `127.0.0.1:8081` host mismatch can break the browser session if the app is opened on a different host/port combination Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #261
349 lines
10 KiB
YAML
349 lines
10 KiB
YAML
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 |