## Summary - converge finding, queued, and completed database notifications on one shared `OperationUxPresenter` presentation contract - preserve existing finding and operation deep-link authorities while standardizing title, body, status/icon treatment, and single primary action - add focused notification, findings, and guard coverage plus the full feature 230 spec artifacts ## Validation - `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` ## Filament / Platform Notes - Livewire v4.0+ compliance preserved on Filament v5 primitives - provider registration remains unchanged in `apps/platform/bootstrap/providers.php` - no globally searchable resource behavior changed in this feature - no destructive actions were introduced - asset strategy is unchanged; the existing `cd apps/platform && php artisan filament:assets` deploy step remains sufficient Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #265
23 KiB
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
OperationUxPresenterseam 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:
Featurefor 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 agentcd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Notifications/SharedDatabaseNotificationContractTest.php tests/Feature/Notifications/OperationRunNotificationTest.php tests/Feature/Notifications/FindingNotificationLinkTest.phpcd 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-shellfor drawer payloads that open tenant and system detail pages, withstandard-native-filamentrelief 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_translationandfinding_eventmetadata stay secondary; that the platform-user run destination remains/system/ops/runs/{run}; that alert delivery, escalation, andMy Workadmission behavior remain untouched; and that no in-scope class still builds its primary payload through a fully localFilamentNotification::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 Workadmission 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)
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)
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:
OperationUxPresentercurrently 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
OperationUxPresenteras 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_eventandreason_translationsecondary and namespaced. - Preserve deep-link truth through
FindingResource::getUrl(...),OperationRunLinks, andSystemOperationRunLinksrather than rebuilding routes in notification classes. - Keep the existing Filament database-notification drawer, existing
notificationstable 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 seamdata-model.md: existing notification truth plus the derived shared presentation contract and consumer input shapescontracts/findings-notification-convergence.logical.openapi.yaml: internal logical contract for shared database-notification presentation and the preserved destination routesquickstart.md: focused implementation and review workflow
Design decisions:
- No schema migration is required; the feature only changes derived payload composition in the existing
notificationstable. - The canonical shared seam is an extension of the existing
OperationUxPresenterpath, 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 keepreason_translationand 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 stepcd apps/platform && php artisan filament:assetsremains 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 |