# Implementation Plan: Findings Notifications & Escalation v1 **Branch**: `224-findings-notifications-escalation` | **Date**: 2026-04-22 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/spec.md` **Note**: This plan keeps the work inside the existing findings workflow, workspace alerting, and Filament database-notification primitives. The intended implementation adds four finding event types, one narrow finding-notification service, one database notification class, and focused extensions to the existing alert evaluation and alert-management surfaces. It does not add a new table, a notification center, a preference system, a second findings queue, or a generic workflow engine. ## Summary Extend the existing workspace Alerts event vocabulary with `findings.assigned`, `findings.reopened`, `findings.due_soon`, and `findings.overdue`, then add a narrow `FindingNotificationService` that sends one entitlement-safe direct database notification to the currently responsible operator and forwards the same event into `AlertDispatchService` for optional external copies. Emit assignment and system-reopen events from `FindingWorkflowService` after committed mutations, evaluate due-soon and overdue windows inside the existing `EvaluateAlertsJob` cadence, reuse the existing `notifications` table and Filament database-notification drawer for direct delivery, and keep finding follow-up on the existing tenant finding detail route. ## Technical Context **Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade **Primary Dependencies**: Laravel notifications (`database` channel), Filament database notifications, `Finding`, `FindingWorkflowService`, `FindingSlaPolicy`, `AlertRule`, `AlertDelivery`, `AlertDispatchService`, `EvaluateAlertsJob`, `CapabilityResolver`, `WorkspaceContext`, `TenantMembership`, `FindingResource` **Storage**: PostgreSQL via existing `findings`, `alert_rules`, `alert_deliveries`, `notifications`, `tenant_memberships`, and `audit_logs`; no schema changes planned **Testing**: Pest v4 feature tests with Filament/Livewire assertions and notification-payload checks **Validation Lanes**: fast-feedback, confidence **Target Platform**: Dockerized Laravel web application via Sail locally and Linux containers in deployment **Project Type**: Laravel monolith inside the `wt-plattform` monorepo **Performance Goals**: Keep direct notification dispatch and external alert-copy generation DB-backed and queue-safe, avoid N+1 tenant or recipient lookups, and keep due-window scans bounded to workspace-scoped open findings with due-date indexes already present **Constraints**: No new persisted notification-truth table, no notification-preference center, no new capability family, no hidden-tenant leakage, no manual-reopen notification in v1, no repeated overdue spam within the same due cycle, and no new frontend assets **Scale/Scope**: Four new event types, one narrow finding-notification service, one new database notification class, extensions to two existing alert resources, one extension to the existing alerts evaluation job, and four focused feature suites ## UI / Surface Guardrail Plan - **Guardrail scope**: changed surfaces - **Native vs custom classification summary**: native Filament resources and existing database-notification primitives only - **Shared-family relevance**: workspace alert configuration and delivery-history family, existing admin and tenant database-notification family, existing tenant finding detail family - **State layers in scope**: shell, detail - **Handling modes by drift class or surface**: review-mandatory - **Repository-signal treatment**: review-mandatory - **Special surface test profiles**: standard-native-filament, global-context-shell - **Required tests or manual smoke**: functional-core, state-contract - **Exception path and spread control**: none; the feature extends existing alert resources and notification primitives rather than creating a new page family - **Active feature PR close-out entry**: Guardrail ## Constitution Check *GATE: Passed before Phase 0 research. Re-check after Phase 1 design.* | Principle | Pre-Research | Post-Design | Notes | |-----------|--------------|-------------|-------| | Inventory-first / snapshots-second | PASS | PASS | All event production stays derived from existing `Finding` lifecycle, ownership, severity, and due-date truth; notifications and alert deliveries remain delivery artifacts only | | Read/write separation | PASS | PASS | The feature adds no new operator mutation surface; assignment and reopen writes stay inside existing workflow actions, while due reminders remain scheduled evaluation side effects | | Graph contract path | PASS | PASS | No Microsoft Graph call paths or contract-registry changes are introduced | | Deterministic capabilities / RBAC-UX | PASS | PASS | Workspace-scoped alert configuration remains capability-gated, direct recipients are re-validated against current tenant membership plus findings-view capability at send time, non-members remain `404`, and in-scope capability failures remain `403` | | Workspace / tenant isolation | PASS | PASS | Alert rules and alert deliveries stay workspace-scoped, while direct notifications always deep-link to tenant-scoped finding detail and must not expose hidden tenant data | | Run observability / Ops-UX | PASS | PASS | Scheduled due-event evaluation stays inside the existing `alerts.evaluate` cadence; no `OperationRun` notification semantics are changed and no queued/running DB notifications are introduced | | Proportionality / no premature abstraction | PASS | PASS | The plan allows one narrow `FindingNotificationService` because direct-recipient resolution, dedupe, payload composition, and external alert-copy forwarding would otherwise be duplicated across workflow mutations and scheduled due evaluation | | Persisted truth / few layers | PASS | PASS | No new table or persisted workflow state is added; existing `notifications` JSONB and `alert_deliveries` rows remain the only delivery artifacts | | Behavioral state discipline | PASS | PASS | The four new values are delivery event types, not a new finding lifecycle or responsibility taxonomy | | Filament-native UI (UI-FIL-001) | PASS | PASS | Alert rules and alert deliveries remain native Filament resources; direct notifications use existing Filament database-notification payloads | | Decision-first / action-surface contract | PASS | PASS | Notifications stay as secondary drill-in entry points, alert rules remain config-first, and alert deliveries remain read-only diagnostics | | Test governance (TEST-GOV-001) | PASS | PASS | Proof stays in focused feature suites for event production, routing, alert-rule integration, and deep-link safety, with no browser or heavy-governance expansion | | Filament v5 / Livewire v4 compliance | PASS | PASS | The feature uses existing Filament v5 resources and Livewire v4-compatible database notifications only | | Provider registration / global search / assets | PASS | PASS | Panel providers already live in `apps/platform/bootstrap/providers.php`; no globally searchable resource is added or changed; no new assets are required, so the existing deploy `filament:assets` step remains unchanged | ## Test Governance Check - **Test purpose / classification by changed surface**: `Feature` for finding-event production, direct-recipient routing, alert-rule UI integration, and deep-link authorization behavior - **Affected validation lanes**: `fast-feedback`, `confidence` - **Why this lane mix is the narrowest sufficient proof**: The main risk is integrated workflow behavior: which event fires, who gets it, whether direct delivery leaks scope, whether external copies remain optional, and whether alert-management surfaces expose the new event types coherently. Focused feature tests prove that without adding unit-only abstractions or browser cost. - **Narrowest proving command(s)**: - `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` - **Fixture / helper / factory / seed / context cost risks**: Moderate. Tests need workspace and tenant context, current and removed memberships, owner-versus-assignee combinations, existing alert rules and destinations, notification-table assertions, and time-travel across due windows. - **Expensive defaults or shared helper growth introduced?**: no; any notification-specific helper should stay local to the new tests and reuse existing `createUserWithTenant(...)`, `Finding::factory()`, and alert destination factories - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: `standard-native-filament` for alert resources and `global-context-shell` for database notifications that bridge admin shell context to tenant finding detail - **Closing validation and reviewer handoff**: Reviewers should rely on the exact commands above and verify that owner-only changes do not emit assignment notifications, manual reopen still emits nothing, due-soon and overdue stay one-per-due-cycle, same-user owner/assignee resolution creates one notification, direct delivery suppresses when entitlement is lost, and external alert copies only appear when a matching alert rule exists. - **Budget / baseline / trend follow-up**: none - **Review-stop questions**: Did the implementation introduce new persistence, a preference layer, or a generic workflow-notification engine? Did any path leak hidden tenant information in the notification title, body, or action URL? Did due evaluation widen beyond the existing alert-evaluation cadence without need? Did alert-rule resource changes stay native and read clearly? - **Escalation path**: document-in-feature unless a second delivery abstraction, a preference center, or a new findings-specific notification surface is proposed, in which case split or follow up with a dedicated spec - **Active feature PR close-out entry**: Guardrail - **Why no dedicated follow-up spec is needed**: This feature remains bounded to four concrete event types and one direct-delivery contract built on existing infrastructure ## Project Structure ### Documentation (this feature) ```text specs/224-findings-notifications-escalation/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── findings-notifications-escalation.logical.openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Filament/ │ │ └── Resources/ │ │ ├── AlertDeliveryResource.php │ │ └── AlertRuleResource.php │ ├── Jobs/ │ │ └── Alerts/ │ │ └── EvaluateAlertsJob.php │ ├── Models/ │ │ └── AlertRule.php │ ├── Notifications/ │ │ └── Findings/ │ │ └── FindingEventNotification.php │ └── Services/ │ ├── Alerts/ │ │ └── AlertDispatchService.php │ └── Findings/ │ ├── FindingNotificationService.php │ ├── FindingSlaPolicy.php │ └── FindingWorkflowService.php ├── database/ │ └── factories/ │ └── FindingFactory.php └── tests/ └── Feature/ ├── Alerts/ │ └── FindingsAlertRuleIntegrationTest.php ├── Findings/ │ ├── FindingsNotificationEventTest.php │ └── FindingsNotificationRoutingTest.php └── Notifications/ └── FindingNotificationLinkTest.php ``` **Structure Decision**: Standard Laravel monolith. The feature stays inside existing finding workflow, alerting, and notification seams. No new base directory, panel, or persisted model is required. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | none | — | — | ## Proportionality Review - **Current operator problem**: Finding assignment, automatic reopen, and due-state changes remain silent unless operators keep polling findings pages. - **Existing structure is insufficient because**: `FindingWorkflowService` knows about ownership changes but not delivery, `EvaluateAlertsJob` knows about workspace alert events but not direct responsible-user delivery, and the existing alert-rule UI cannot express these finding-specific workflow events yet. - **Narrowest correct implementation**: Add four event types, extend the existing alert-rule and delivery viewer labels, create one narrow `FindingNotificationService` to unify recipient resolution plus direct and external delivery, and emit events only from existing workflow and alert-evaluation seams. - **Ownership cost created**: One service, one notification class, incremental logic in `FindingWorkflowService` and `EvaluateAlertsJob`, and four focused feature suites. - **Alternative intentionally rejected**: A new `FindingNotificationDelivery` table or generic workflow-notification engine. Both add persistence or framework complexity that current-release truth does not require because existing `notifications` and `alert_deliveries` already capture delivery artifacts. - **Release truth**: Current-release truth. The feature closes an existing workflow loop now rather than preparing a later escalation framework. ## Phase 0 Research Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/research.md`. Key decisions: - Add the four new finding event types to the existing `AlertRule` constant registry and `AlertRuleResource::eventTypeOptions()` instead of creating a second event catalog. - Reuse existing Filament database notifications on the admin panel rather than building a findings-specific notification surface or page. - Emit `findings.assigned` and `findings.reopened` from `FindingWorkflowService` after the write transaction commits so delivery does not race an uncommitted finding state. - Evaluate `findings.due_soon` and `findings.overdue` inside the existing `EvaluateAlertsJob` workspace cadence rather than adding a second scheduler, command, or `OperationRun` family. - Use the existing `notifications` table `data` payload to store a finding-event fingerprint for direct-delivery dedupe; do not add a new persistence model. - Reuse `FindingResource::getUrl(..., panel: 'tenant', tenant: $tenant)` for notification deep links and re-check entitlement before sending any direct notification. ## Phase 1 Design Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/224-findings-notifications-escalation/`: - `research.md`: event-registry, delivery, dedupe, and evaluation-seam decisions - `data-model.md`: existing entities plus the derived finding-event and direct-notification payload models - `contracts/findings-notifications-escalation.logical.openapi.yaml`: internal logical contract for finding-event dispatch, alert-rule event exposure, alert-delivery viewing, and finding deep-link payloads - `quickstart.md`: focused validation workflow for implementation and review Design decisions: - No schema migration is required; direct notifications use the existing `notifications` table and external copies use existing `alert_deliveries` rows. - The canonical new seam is one narrow `FindingNotificationService`, not a reusable workflow-notification framework. - Direct-recipient dedupe uses a fingerprint stored in the existing database notification payload; due-cycle reset keys are derived from the current `due_at` value. - Alert rules remain optional for external copies; direct responsible-user notifications do not depend on any matching alert rule. - Existing tenant finding detail remains the only follow-up surface, and current `404` versus `403` route behavior remains authoritative at open time. ## Phase 1 Agent Context Update Run: - `.specify/scripts/bash/update-agent-context.sh copilot` ## Constitution Check — Post-Design Re-evaluation - PASS — the design remains inside current findings, alerts, and database-notification seams with no new persistence, no Graph work, no new capability family, and no new frontend assets. - PASS — Livewire v4.0+ and Filament v5 constraints remain satisfied, panel provider registration stays in `apps/platform/bootstrap/providers.php`, no globally searchable resource behavior changes, and no new destructive action path is introduced. ## Implementation Strategy ### Phase A — Extend the existing alert-event vocabulary and operator labels **Goal**: Teach existing alert-management surfaces about the four new finding workflow events. | Step | File | Change | |------|------|--------| | A.1 | `apps/platform/app/Models/AlertRule.php` | Add the four new `EVENT_FINDINGS_*` constants alongside the existing alert event types | | A.2 | `apps/platform/app/Filament/Resources/AlertRuleResource.php` | Extend `eventTypeOptions()` and `eventTypeLabel()` with operator-facing labels for assignment, reopened, due soon, and overdue events | | A.3 | `apps/platform/app/Filament/Resources/AlertDeliveryResource.php` | Ensure list, view, and filter surfaces continue to render the new event labels cleanly through the existing event-type label seam without adding a new delivery viewer | ### Phase B — Add one narrow finding-notification delivery seam on existing primitives **Goal**: Send one entitlement-safe direct database notification and one optional external alert copy from the same event envelope. | Step | File | Change | |------|------|--------| | B.1 | `apps/platform/app/Services/Findings/FindingNotificationService.php` | Add a focused service that builds finding-event payloads, resolves the direct recipient by the spec precedence rules, checks current tenant entitlement plus findings-view capability, computes a delivery fingerprint, suppresses duplicates by querying existing database notifications, and forwards the same event array to `AlertDispatchService` for external copies | | B.2 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Add a database notification class that returns Filament notification payloads with tenant-safe title/body copy, recipient-reason copy, one finding-detail action URL, and embedded metadata including the event type and fingerprint | | B.3 | `apps/platform/app/Services/Alerts/AlertDispatchService.php` | Keep the existing workspace alert-copy path intact and only absorb any payload-shape normalization needed for the new finding event titles, body copy, and metadata | ### Phase C — Emit assignment and automatic-reopen notifications from existing finding workflow mutations **Goal**: Turn current write seams into finding-event producers without changing workflow truth. | Step | File | Change | |------|------|--------| | C.1 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | After committed `assign(...)` mutations, compare before and after owner and assignee state, suppress no-op, owner-only, and assignee-clear transitions, and dispatch `findings.assigned` only when a new assignee is set on an open finding | | C.2 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` | After committed `reopenBySystem(...)` mutations, dispatch `findings.reopened` using the refreshed finding state; keep manual `reopen(...)` out of scope for v1 delivery | | C.3 | `apps/platform/app/Services/Findings/FindingWorkflowService.php` and `apps/platform/database/factories/FindingFactory.php` | Preserve existing due-date reset and reopen semantics so notification due-cycle logic stays derived from the recalculated `due_at` value, with reopen metadata remaining explanatory rather than becoming a second cycle key | ### Phase D — Evaluate due-soon and overdue events inside the existing alerts evaluation cadence **Goal**: Keep scheduled due reminders and escalations inside the already-established workspace alert window. | Step | File | Change | |------|------|--------| | D.1 | `apps/platform/app/Jobs/Alerts/EvaluateAlertsJob.php` | Add finding-level due-soon and overdue candidate queries over workspace-scoped open findings with `due_at`, using the existing window semantics and a fixed v1 due-soon horizon of 24 hours | | D.2 | `apps/platform/app/Jobs/Alerts/EvaluateAlertsJob.php` | For each candidate finding event, call `FindingNotificationService` so direct delivery and optional external alert copies stay consistent and no second dispatch pipeline appears | | D.3 | `apps/platform/app/Services/Findings/FindingNotificationService.php` | Define due-cycle fingerprints from the current `due_at` value so due-soon and overdue notifications emit once per cycle and reset only when `due_at` is recalculated by existing lifecycle semantics | ### Phase E — Preserve tenant-safe deep links and existing notification shell behavior **Goal**: Reuse the current notification drawer and finding detail route without widening visibility. | Step | File | Change | |------|------|--------| | E.1 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Build finding action URLs with `FindingResource::getUrl('view', ['record' => $finding], panel: 'tenant', tenant: $tenant)` so links target the tenant panel explicitly | | E.2 | Existing admin panel notification configuration | Rely on the already-configured `->databaseNotifications()` behavior in `AdminPanelProvider`; do not add polling, a new page, or a new notification surface | | E.3 | Existing finding detail route behavior | Keep current route authorization authoritative so an operator who lost access after send time still receives the existing `404` or `403` outcome instead of leaked detail | ### Phase F — Protect event truth, routing, and link safety with focused regression coverage **Goal**: Lock down event production, recipient precedence, alert-rule integration, and tenant-safe finding drilldown. | Step | File | Change | |------|------|--------| | F.1 | `apps/platform/tests/Feature/Findings/FindingsNotificationEventTest.php` | Cover assignment-event production, system-reopen event production, rapid reassignment and repeated automatic-reopen dedupe, due-soon and overdue evaluation windows, terminal-finding suppression, and one-per-due-cycle behavior | | F.2 | `apps/platform/tests/Feature/Findings/FindingsNotificationRoutingTest.php` | Cover recipient precedence, same-user owner and assignee dedupe, owner-only assignment suppression, entitlement-loss suppression, and no direct delivery without a current eligible recipient | | F.3 | `apps/platform/tests/Feature/Alerts/FindingsAlertRuleIntegrationTest.php` and `apps/platform/tests/Feature/Alerts/SlaDueAlertTest.php` | Cover alert-rule event-type option exposure, `ALERTS_VIEW` read versus `ALERTS_MANAGE` mutation boundaries, inherited Alerts v1 external-copy behavior including minimum severity, tenant scoping, cooldown, quiet hours, and dedupe, delivery-history labels and filters, the rule-free case where direct personal delivery still occurs, and non-regression of existing aggregate `sla_due` behavior | | F.4 | `apps/platform/tests/Feature/Notifications/FindingNotificationLinkTest.php` | Cover database notification payload shape, explicit tenant-panel URLs, and finding-detail open behavior with correct tenant-safe `404` and `403` semantics | | F.5 | `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` plus the focused Pest commands above | Run formatting and the narrowest proving suites before closing implementation |