TenantAtlas/specs/230-findings-notification-convergence/plan.md
ahmido 742d65f0d9
Some checks failed
Main Confidence / confidence (push) Failing after 51s
feat: converge findings notification presentation (#265)
## 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
2026-04-22 20:26:18 +00:00

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

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