24 KiB
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:
Featurefor 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.phpcd 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-filamentfor alert resources andglobal-context-shellfor 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)
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)
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:
FindingWorkflowServiceknows about ownership changes but not delivery,EvaluateAlertsJobknows 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
FindingNotificationServiceto 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
FindingWorkflowServiceandEvaluateAlertsJob, and four focused feature suites. - Alternative intentionally rejected: A new
FindingNotificationDeliverytable or generic workflow-notification engine. Both add persistence or framework complexity that current-release truth does not require because existingnotificationsandalert_deliveriesalready 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
AlertRuleconstant registry andAlertRuleResource::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.assignedandfindings.reopenedfromFindingWorkflowServiceafter the write transaction commits so delivery does not race an uncommitted finding state. - Evaluate
findings.due_soonandfindings.overdueinside the existingEvaluateAlertsJobworkspace cadence rather than adding a second scheduler, command, orOperationRunfamily. - Use the existing
notificationstabledatapayload 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 decisionsdata-model.md: existing entities plus the derived finding-event and direct-notification payload modelscontracts/findings-notifications-escalation.logical.openapi.yaml: internal logical contract for finding-event dispatch, alert-rule event exposure, alert-delivery viewing, and finding deep-link payloadsquickstart.md: focused validation workflow for implementation and review
Design decisions:
- No schema migration is required; direct notifications use the existing
notificationstable and external copies use existingalert_deliveriesrows. - 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_atvalue. - 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
404versus403route 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 |