# Implementation Plan: Findings Notification Presentation Convergence **Branch**: `230-findings-notification-convergence` | **Date**: 2026-04-22 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/230-findings-notification-convergence/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/230-findings-notification-convergence/spec.md` **Note**: This plan keeps the work inside the existing Filament database-notification drawer, the current `notifications` table payload shape, the existing operation-link helpers, and the current `OperationUxPresenter` seam. The intended implementation converges the three in-scope operator-facing database notification consumers on one bounded shared presentation contract. It does not add a table, a notification center, a new panel, a preference system, a new asset family, or new notification-routing semantics. ## Summary Extend the existing `OperationUxPresenter::terminalDatabaseNotification()` seam into one bounded operator-facing database-notification presentation contract, then align `FindingEventNotification`, `OperationRunQueued`, and `OperationRunCompleted` to that shared structure while preserving their existing deep-link sources, delivery semantics, and domain-specific metadata. Keep the existing Filament database-notification surface, keep the current admin-plane and system-plane run destinations, and add focused regression coverage that proves one primary structure, one status emphasis with the existing Filament icon treatment, one primary action, unchanged authorization behavior, and the preserved FR-015 out-of-scope boundary across the in-scope consumers. ## Technical Context **Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade **Primary Dependencies**: Laravel database notifications, Filament notifications and actions, `App\Support\OpsUx\OperationUxPresenter`, `App\Notifications\Findings\FindingEventNotification`, `App\Notifications\OperationRunQueued`, `App\Notifications\OperationRunCompleted`, `FindingResource`, `OperationRunLinks`, `SystemOperationRunLinks`, `ReasonPresenter` **Storage**: PostgreSQL via the existing `notifications` table and existing `findings` plus `operation_runs` truth; no schema changes planned **Testing**: Pest v4 feature tests with notification-payload assertions and route-authorization coverage **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 notification payload composition request-local, avoid extra N+1 lookups for titles or links, and preserve current drawer rendering without new polling or asset work **Constraints**: No schema migration, no new notification center, no new preference model, no new `OperationRun` type or emit point, no change to current queued or terminal run-notification semantics, no global-search changes, and no destructive action paths **Scale/Scope**: Three existing notification consumers, one existing shared presenter seam, three existing deep-link helpers, and six focused feature or guard suites ## UI / Surface Guardrail Plan - **Guardrail scope**: changed surfaces - **Native vs custom classification summary**: native Filament database-notification drawer and existing detail destinations only - **Shared-family relevance**: operator-facing database notifications, action links - **State layers in scope**: shell, detail - **Handling modes by drift class or surface**: review-mandatory - **Repository-signal treatment**: review-mandatory - **Special surface test profiles**: global-context-shell, standard-native-filament - **Required tests or manual smoke**: functional-core, state-contract - **Exception path and spread control**: one named exception only; platform-user operation notifications keep their existing system-panel destination while sharing the same card structure and primary action grammar - **Active feature PR close-out entry**: Guardrail ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes - **Systems touched**: `FindingEventNotification`, `OperationRunQueued`, `OperationRunCompleted`, `OperationUxPresenter`, `FindingResource`, `OperationRunLinks`, `SystemOperationRunLinks`, the existing Filament database-notification surface - **Shared abstractions reused**: `OperationUxPresenter::terminalDatabaseNotification()`, `FindingResource::getUrl(...)`, `OperationRunLinks`, `SystemOperationRunLinks` - **New abstraction introduced? why?**: one bounded shared operator-facing database-notification presentation contract, needed because three real consumers already exist and two still bypass the only shared presenter anchor - **Why the existing abstraction was sufficient or insufficient**: the current `OperationUxPresenter` seam is sufficient as the starting anchor because it already owns the terminal run-notification grammar. It is insufficient as-is because findings and queued run notifications still build local Filament payloads, which leaves the shared interaction family without one explicit contract. - **Bounded deviation / spread control**: domain-specific metadata stays namespaced and secondary, and platform-user operation links keep their existing system-panel route. No other divergence from the shared primary structure is allowed in the in-scope consumers. ## Constitution Check *GATE: Passed before Phase 0 research. Re-check after Phase 1 design.* | Principle | Pre-Research | Post-Design | Notes | |-----------|--------------|-------------|-------| | Shared pattern first (XCUT-001) | PASS | PASS | The plan extends the existing `OperationUxPresenter` seam rather than creating a second local notification grammar or a framework | | Proportionality / no premature abstraction | PASS | PASS | One bounded shared contract is justified by three real consumers; no registry, factory, or universal notification platform is introduced | | RBAC-UX / tenant isolation | PASS | PASS | Finding links remain tenant-scoped and operation links keep current admin-plane or system-plane resolution; `404` versus `403` behavior remains unchanged | | Ops-UX scope discipline | PASS | PASS | The feature does not add new `OperationRun` notifications or emit points; it only converges payload composition for the already-existing queued and terminal notification consumers | | Filament-native UI / action-surface contract | PASS | PASS | Existing Filament database notifications remain the only shell, each card keeps exactly one primary action, and no destructive action is introduced | | Livewire v4.0+ / Filament v5 compliance | PASS | PASS | The feature stays within the current Filament v5 and Livewire v4 notification primitives | | Provider registration / global search / assets | PASS | PASS | Provider registration remains in `apps/platform/bootstrap/providers.php`; no globally searchable resource changes; no new assets, and the existing deploy step `cd apps/platform && php artisan filament:assets` remains sufficient | | Test governance (TEST-GOV-001) | PASS | PASS | Focused feature and guard coverage prove payload convergence and route safety without browser or heavy-governance expansion | ## Test Governance Check - **Test purpose / classification by changed surface**: `Feature` for notification presentation convergence, deep-link safety, and guardrail enforcement across the in-scope consumers - **Affected validation lanes**: `fast-feedback`, `confidence` - **Why this lane mix is the narrowest sufficient proof**: The risk is shared operator-facing payload drift and route-safety regression, not browser rendering or background orchestration. Focused feature suites plus one guard test prove the contract with minimal cost. - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Notifications/SharedDatabaseNotificationContractTest.php tests/Feature/Notifications/OperationRunNotificationTest.php tests/Feature/Notifications/FindingNotificationLinkTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsNotificationEventTest.php tests/Feature/Findings/FindingsNotificationRoutingTest.php tests/Feature/OpsUx/Constitution/LegacyNotificationGuardTest.php` - **Fixture / helper / factory / seed / context cost risks**: Moderate. Tests need a tenant user, a platform user, a tenant, findings with existing event payloads, operation runs in queued and terminal states, and existing authorization helpers for tenant and system planes. - **Expensive defaults or shared helper growth introduced?**: no; any shared notification assertion helper should stay local to notification-contract tests and reuse existing factories and route helpers - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: `global-context-shell` for drawer payloads that open tenant and system detail pages, with `standard-native-filament` relief because the shell itself remains native Filament - **Closing validation and reviewer handoff**: Reviewers should rely on the commands above and verify that the in-scope consumers now share the same primary title, body, status with the corresponding existing Filament icon treatment, and action structure; that `reason_translation` and `finding_event` metadata stay secondary; that the platform-user run destination remains `/system/ops/runs/{run}`; that alert delivery, escalation, and `My Work` admission behavior remain untouched; and that no in-scope class still builds its primary payload through a fully local `FilamentNotification::make()->getDatabaseMessage()` path. - **Budget / baseline / trend follow-up**: none - **Review-stop questions**: Did the implementation introduce a framework beyond one bounded shared contract? Did any in-scope consumer change its delivery semantics or action target instead of only its presentation path? Did the work widen payload disclosure or flatten plane-specific route rules? Did alert delivery, escalation, or `My Work` admission behavior change? Did a new asset, polling, or custom notification shell appear? - **Escalation path**: document-in-feature unless convergence pressure expands beyond the current three consumers or requires new persistence, 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 closes one current-release drift seam across three real consumers without introducing broader notification-platform scope. ## Project Structure ### Documentation (this feature) ```text specs/230-findings-notification-convergence/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── findings-notification-convergence.logical.openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Filament/ │ │ ├── Resources/ │ │ │ └── FindingResource.php │ │ └── System/ │ │ └── Pages/ │ │ └── Ops/ │ │ └── ViewRun.php │ ├── Notifications/ │ │ ├── Findings/ │ │ │ └── FindingEventNotification.php │ │ ├── OperationRunCompleted.php │ │ └── OperationRunQueued.php │ └── Support/ │ ├── OperationRunLinks.php │ ├── OpsUx/ │ │ └── OperationUxPresenter.php │ └── System/ │ └── SystemOperationRunLinks.php └── tests/ └── Feature/ ├── Findings/ │ ├── FindingsNotificationEventTest.php │ └── FindingsNotificationRoutingTest.php ├── Notifications/ │ ├── FindingNotificationLinkTest.php │ ├── OperationRunNotificationTest.php │ └── SharedDatabaseNotificationContractTest.php └── OpsUx/ └── Constitution/ └── LegacyNotificationGuardTest.php ``` **Structure Decision**: Standard Laravel monolith. The feature stays inside existing notification classes, presenter helpers, and Pest feature suites. No new base directory, no new panel, and no new persisted model are required. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | One bounded shared database-notification presentation contract | Three real consumers already exist and two still bypass the only shared presenter anchor | Separate local edits would preserve parallel primary grammars and keep the interaction family drifting | ## Proportionality Review - **Current operator problem**: Operators see finding and operation notifications through different primary structures even though both are the same secondary context surface. - **Existing structure is insufficient because**: `OperationUxPresenter` currently covers only terminal operation notifications, while findings and queued operation notifications still compose their payloads locally. - **Narrowest correct implementation**: Extend the existing presenter seam into one bounded database-notification contract, align the current three real consumers, and stop there. - **Ownership cost created**: One shared presentation seam, small contract-level assertions, and maintenance of the domain-specific secondary metadata boundaries. - **Alternative intentionally rejected**: A universal notification platform, a new notification page, or consumer-by-consumer local tweaks. These either add premature infrastructure or fail to solve the shared-interaction drift. - **Release truth**: Current-release truth. This is a convergence change for already-existing operator-facing notifications. ## Phase 0 Research Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/230-findings-notification-convergence/research.md`. Key decisions: - Reuse `OperationUxPresenter` as the shared anchor and extend it rather than creating a new notification framework. - Standardize the primary title, body, status, and action structure only; keep domain-specific metadata such as `finding_event` and `reason_translation` secondary and namespaced. - Preserve deep-link truth through `FindingResource::getUrl(...)`, `OperationRunLinks`, and `SystemOperationRunLinks` rather than rebuilding routes in notification classes. - Keep the existing Filament database-notification drawer, existing `notifications` table payload storage, and current deploy asset strategy unchanged. - Prove convergence through focused feature and guard tests instead of browser coverage, including an explicit guard on the FR-015 out-of-scope boundary. ## Phase 1 Design Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/230-findings-notification-convergence/`: - `research.md`: design decisions and rejected alternatives for the shared presentation seam - `data-model.md`: existing notification truth plus the derived shared presentation contract and consumer input shapes - `contracts/findings-notification-convergence.logical.openapi.yaml`: internal logical contract for shared database-notification presentation and the preserved destination routes - `quickstart.md`: focused implementation and review workflow Design decisions: - No schema migration is required; the feature only changes derived payload composition in the existing `notifications` table. - The canonical shared seam is an extension of the existing `OperationUxPresenter` path, not a new registry or interface family. - In-scope consumers retain one primary action and keep their current deep-link authority: tenant finding view, admin operation view, or system operation view. - Domain-specific metadata remains secondary and opt-in: findings keep `finding_event`, completed runs keep `reason_translation` and diagnostic fields, and queued runs keep only the minimal supporting context they already need. - Existing delivery semantics from Spec 224 and current operation notification behavior remain unchanged. ## Phase 1 Agent Context Update Run: - `.specify/scripts/bash/update-agent-context.sh copilot` ## Constitution Check — Post-Design Re-evaluation - PASS - the design stays inside current notification and presenter 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, provider registration stays in `apps/platform/bootstrap/providers.php`, no globally searchable resource behavior changes, no destructive action is introduced, and the existing deploy step `cd apps/platform && php artisan filament:assets` remains unchanged. ## Implementation Strategy ### Phase A - Define the shared database-notification presentation seam on the existing presenter anchor **Goal**: Establish one bounded shared primary payload structure without creating a new framework. | Step | File | Change | |------|------|--------| | A.1 | `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` | Add one shared operator-facing database-notification builder path that standardizes title, body, status with the existing Filament icon treatment, one primary action, and optional supporting lines for the in-scope consumers | | A.2 | `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` | Preserve the current terminal run-specific presentation logic by feeding it into the shared builder rather than bypassing it | | A.3 | `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` | Keep any extraction bounded to the existing namespace and avoid registries, factories, or a universal notification taxonomy | ### Phase B - Align findings notifications to the shared contract **Goal**: Remove the local primary payload grammar from `FindingEventNotification` while preserving Spec 224 delivery and metadata semantics. | Step | File | Change | |------|------|--------| | B.1 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Replace the local `FilamentNotification::make()` primary payload build with the shared presentation seam | | B.2 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Preserve `FindingResource::getUrl('view', ...)` as the action target and keep `finding_event` metadata keys unchanged | | B.3 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Keep recipient-reason language secondary and subordinate to the shared primary body structure | ### Phase C - Align queued and terminal operation notifications to the same contract **Goal**: Keep current operation notification semantics while eliminating the second local primary payload grammar. | Step | File | Change | |------|------|--------| | C.1 | `apps/platform/app/Notifications/OperationRunQueued.php` | Replace local payload composition with the shared contract while preserving current queued copy and current link-resolution rules | | C.2 | `apps/platform/app/Notifications/OperationRunCompleted.php` | Keep terminal presentation, summary lines, and reason-translation metadata, but route the primary card structure through the same shared contract | | C.3 | `apps/platform/app/Support/OperationRunLinks.php` and `apps/platform/app/Support/System/SystemOperationRunLinks.php` | Verify canonical action labels and plane-specific route generation remain authoritative, and apply only minimal normalization if the shared contract needs a common label accessor | ### Phase D - Preserve route and scope truth across tenant and system destinations **Goal**: Ensure shared presentation does not flatten or widen current access rules. | Step | File | Change | |------|------|--------| | D.1 | `apps/platform/app/Notifications/Findings/FindingEventNotification.php` | Preserve tenant-panel finding detail routing and current `404` versus `403` behavior | | D.2 | `apps/platform/app/Notifications/OperationRunQueued.php` and `apps/platform/app/Notifications/OperationRunCompleted.php` | Preserve current admin-plane, tenantless, and platform-user system-plane route selection | | D.3 | Existing Filament notification shell | Keep the existing database-notification drawer as the only collection surface; do not add polling, a new page, or a second notification center | ### Phase E - Lock the shared contract with focused regression coverage **Goal**: Make future local bypasses of the shared primary structure visible in CI. | Step | File | Change | |------|------|--------| | E.1 | `apps/platform/tests/Feature/Notifications/SharedDatabaseNotificationContractTest.php` | Add direct contract-level assertions for the shared primary structure, including shared status-to-icon treatment, across findings, queued runs, and completed runs | | E.2 | `apps/platform/tests/Feature/Notifications/OperationRunNotificationTest.php` | Update queued and terminal operation notification tests to assert shared structure plus preserved route, status, and metadata behavior | | E.3 | `apps/platform/tests/Feature/Notifications/FindingNotificationLinkTest.php` | Update finding notification tests to assert shared structure, shared status semantics, and unchanged tenant-safe action behavior | | E.4 | `apps/platform/tests/Feature/Findings/FindingsNotificationEventTest.php` and `apps/platform/tests/Feature/Findings/FindingsNotificationRoutingTest.php` | Keep Spec 224 event and recipient semantics protected while the presentation path changes underneath them | | E.5 | `apps/platform/tests/Feature/OpsUx/Constitution/LegacyNotificationGuardTest.php` | Extend the guard so future in-scope notification consumers cannot silently bypass the shared primary presentation seam and so alert delivery, escalation, and `My Work` admission behavior stay outside this spec | ### Phase F - Validate formatting and the narrow proving set **Goal**: Close the feature with the smallest executable proof set. | Step | File | Change | |------|------|--------| | F.1 | `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` | Normalize style for touched PHP files | | F.2 | `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Notifications/SharedDatabaseNotificationContractTest.php tests/Feature/Notifications/OperationRunNotificationTest.php tests/Feature/Notifications/FindingNotificationLinkTest.php` | Prove the shared contract and preserved destination semantics | | F.3 | `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsNotificationEventTest.php tests/Feature/Findings/FindingsNotificationRoutingTest.php tests/Feature/OpsUx/Constitution/LegacyNotificationGuardTest.php` | Prove no regression in Spec 224 behavior, no reintroduction of local notification bypasses, and no accidental expansion into alert or `My Work` behavior |