TenantAtlas/specs/224-findings-notifications-escalation/plan.md
ahmido e15d80cca5
Some checks failed
Main Confidence / confidence (push) Failing after 48s
Heavy Governance Lane / heavy-governance (push) Has been skipped
Browser Lane / browser (push) Has been skipped
feat: implement findings notifications escalation (#261)
## 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
2026-04-22 00:54:38 +00:00

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: 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)

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: 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

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

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