# Data Model: Findings Notifications & Escalation v1 ## Overview This feature introduces no new persisted business entity. Existing finding truth, alert rules, alert deliveries, database notifications, and tenant-membership or capability truth remain canonical. The new work is a bounded derived-event layer over those existing records. ## Existing Persistent Entities ### Finding **Purpose**: Canonical tenant-scoped finding truth for ownership, lifecycle, severity, and due-date evaluation. **Key fields used by this feature**: - `id` - `workspace_id` - `tenant_id` - `severity` - `status` - `due_at` - `sla_days` - `owner_user_id` - `assignee_user_id` - `reopened_at` - `resolved_at` - `closed_at` - `finding_type` - `subject_type` - `subject_external_id` **Relationships**: - belongs to one tenant - belongs to one workspace through tenant ownership - may reference one current owner user - may reference one current assignee user **Rules relevant to notifications**: - Only open findings participate in assignment, due-soon, and overdue notification evaluation. - Terminal findings suppress due-soon and overdue delivery even if they previously entered a reminder window. - The current due cycle is keyed by `due_at`; `reopened_at` remains explanatory lifecycle context and only matters when the existing lifecycle recalculates `due_at`. No extra reminder-state field is added. - Existing aggregate `sla_due` alerts remain separate and are not replaced by finding-level delivery. ### AlertRule **Purpose**: Workspace-scoped configuration for optional external delivery copies. **Key fields used by this feature**: - `workspace_id` - `event_type` - `min_severity` - `destination_ids` - `cooldown_minutes` - `quiet_hours` - `enabled` **Rules relevant to notifications**: - The feature adds four new `event_type` values only. - A direct personal notification does not depend on an alert rule. - External copies still require an enabled matching rule and destination. ### AlertDelivery **Purpose**: Existing persisted artifact for external-copy dispatch outcomes. **Key fields used by this feature**: - `workspace_id` - `tenant_id` - `event_type` - `status` - `destination_snapshot` - `payload` - `fingerprint` - `suppressed_reason` **Rules relevant to notifications**: - Finding-level external copies reuse the same delivery pipeline, cooldown, suppression, and quiet-hours semantics as other alerts. - Delivery-history viewing remains read-only and only gains the new event labels and safe summaries. ### Database Notification (`notifications` table) **Purpose**: Existing persisted artifact for direct in-app notification delivery. **Key fields used by this feature**: - `id` - `type` - `notifiable_type` - `notifiable_id` - `data` - `read_at` - `created_at` **Rules relevant to notifications**: - The feature stores direct-delivery metadata and the deterministic `fingerprint_key` inside `data`; no new table is introduced. - The persisted payload remains Filament-compatible so the existing notification drawer can render it unchanged. ### Tenant Membership and User Entitlement Context **Purpose**: Current authorization truth for whether a resolved direct recipient may still inspect the tenant and finding at send time. **Key inputs used by this feature**: - `tenant_memberships.tenant_id` - `tenant_memberships.user_id` - `User::canAccessTenant($tenant)` - `CapabilityResolver::can($user, $tenant, Capabilities::TENANT_FINDINGS_VIEW)` or the existing findings-view equivalent used by the implementation seam **Rules relevant to notifications**: - Direct delivery is suppressed when the resolved recipient is no longer entitled. - Open-time route authorization remains authoritative even after send-time validation. ## Derived Models ### FindingNotificationEvent **Purpose**: Canonical derived event envelope used by both direct personal delivery and optional external alert copies. **Fields**: - `event_type`: one of `findings.assigned`, `findings.reopened`, `findings.due_soon`, `findings.overdue` - `workspace_id` - `tenant_id` - `finding_id` - `severity` - `title` - `body` - `recipient_reason`: one of `new_assignee`, `current_assignee`, `current_owner` - `resolved_recipient_user_id`: nullable - `fingerprint_key` - `due_cycle_key`: nullable, derived from current `due_at` - `metadata`: object with finding summary, owner and assignee ids, due date, reopen timestamp, and deep-link-safe context **Validation rules**: - Event type must be one of the four new finding events. - `recipient_reason` must match the event-specific precedence rule. - `fingerprint_key` must deterministically distinguish the specific assignment change, reopen occurrence, or due cycle. - `due_cycle_key` is required for `findings.due_soon` and `findings.overdue`, and omitted or null for assignment and reopen. ### RecipientResolutionResult **Purpose**: Bounded contract that picks at most one direct recipient from existing owner and assignee truth without creating a second ownership model. **Fields**: - `user_id`: nullable - `reason`: one of `new_assignee`, `current_assignee`, `current_owner` - `is_entitled`: boolean - `suppression_reason`: nullable string **Rules**: - `findings.assigned` resolves to the new assignee only. - `findings.reopened` resolves to current assignee, else current owner. - `findings.due_soon` resolves to current assignee, else current owner. - `findings.overdue` resolves to current owner, else current assignee. - A recipient who is not currently entitled becomes a suppression result, not a broadened-delivery fallback. ### DirectFindingNotificationMessage **Purpose**: Filament database-notification payload rendered in the existing notification drawer. **Fields**: - `format = filament` - `title` - `body` - `actions[0].label = Open finding` - `actions[0].url = /admin/t/{tenant}/findings/{finding}` - `finding_event.event_type` - `finding_event.recipient_reason` - `finding_event.fingerprint_key` - `finding_event.tenant_name` - `finding_event.severity` **Rules**: - One notification row represents one direct delivery to one entitled user. - The payload must explain why the operator received the notification. - The payload must not include hidden-tenant data beyond what the recipient is entitled to inspect. ## Event Matrix | Event type | Trigger | Recipient precedence | Fingerprint components | Suppression rules | |------------|---------|----------------------|------------------------|-------------------| | `findings.assigned` | An open finding is assigned to a new assignee | new assignee | finding id + target assignee id + assignment change marker | suppress for owner-only changes, assignee clears, no-op saves, terminal findings, or non-entitled recipient | | `findings.reopened` | A terminal finding is reopened by system detection | current assignee, else current owner | finding id + reopened occurrence marker | suppress for manual reopen, missing recipient, or non-entitled recipient | | `findings.due_soon` | An open finding first enters the 24-hour pre-due window for the current due cycle | current assignee, else current owner | finding id + current `due_at` + event type | suppress for terminal findings, missing `due_at`, no entitled recipient, or duplicate within the same due cycle | | `findings.overdue` | An open finding first becomes overdue for the current due cycle | current owner, else current assignee | finding id + current `due_at` + event type | suppress for terminal findings, no entitled recipient, or duplicate within the same due cycle | ## Persistence Boundaries - No new table, enum-backed persistence, or reminder-state model is introduced. - `notifications.data` stores direct-delivery fingerprint metadata only as a delivery artifact. - `alert_deliveries` stores external-copy artifacts only as it already does today. - `Finding` remains the sole business-truth model for ownership, lifecycle, and due-cycle resets.