# Implementation Plan: Monitoring Page-State Contract **Branch**: `198-monitoring-page-state` | **Date**: 2026-04-15 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/198-monitoring-page-state/spec.md` **Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/198-monitoring-page-state/spec.md` **Note**: This plan keeps the work inside the existing Filament v5 / Livewire v4 page layer, current query and session helpers, and the current monitoring pages. It explicitly avoids a new global page-state framework, new persistence, or a shell-context refactor. ## Summary Codify one bounded monitoring page-state contract for Operations, Audit Log, Finding Exceptions Queue, Evidence Overview, Baseline Compare Landing, and Baseline Compare Matrix, with Baseline Compare Landing remaining the compare launch-context support surface. Reuse existing Livewire page properties, `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, existing session-backed table persistence, and the current compare draft/apply pattern to make contextual prefilter state, active state, inspect state, draft state, and shareable or restorable state explicit and consistent without adding a new runtime state engine. ## Technical Context **Language/Version**: PHP 8.4.15 **Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, `OperateHubShell`, Filament `InteractsWithTable`, and page-local Livewire state on the affected Filament pages **Storage**: PostgreSQL plus existing Laravel session-backed table filter, search, and sort persistence; no schema change planned **Testing**: Pest feature tests, Filament or Livewire page tests, existing table-state persistence tests, and focused Pest browser smoke tests run through Laravel Sail **Target Platform**: Laravel monolith web application under `apps/platform`, with canonical monitoring routes under `/admin` and tenant-bound compare routes under `/admin/t/{tenant}` or active tenant context **Project Type**: web application **Performance Goals**: Keep monitoring pages DB-only at render time, preserve current session-backed table persistence, avoid new outbound HTTP or queued work during state hydration, avoid N+1 query regressions, and keep first-mount state hydration deterministic and cheap **Constraints**: No new global page-state framework, no new persistence, no panel or route-family changes, no shell or context refactor that belongs in Spec 199, no authorization-plane changes, no new Graph calls, no new badge taxonomy, and no forced flattening of Baseline Compare Matrix into a generic table contract **Scale/Scope**: 5 primary in-scope page-state surfaces, 1 compare launch-context support surface, 6 page classes, 7 existing focused test files, likely 1 new cross-surface contract test, and 1 focused browser smoke suite or extension of existing smoke coverage ## Constitution Check *GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.* | Principle | Pre-Research | Post-Design | Notes | |-----------|--------------|-------------|-------| | Inventory-first / snapshots-second | PASS | PASS | The feature does not alter inventory, snapshot, or backup truth. It standardizes page-state semantics only. | | Read/write separation | PASS | PASS | Existing writes such as exception approval or rejection keep their current confirmation, audit, and authorization behavior. No new write workflow is introduced. | | Graph contract path | N/A | N/A | No Microsoft Graph calls or contract-registry changes are planned. | | Deterministic capabilities | PASS | PASS | Capability checks stay in the existing registries and page actions. The work does not add new raw capability strings or new auth planes. | | Workspace + tenant isolation | PASS | PASS | Requested tenant filters, selected-record deeplinks, and compare focus state remain subject to existing workspace and tenant entitlement checks. | | RBAC-UX authorization semantics | PASS | PASS | Non-members remain `404`, members without capability remain `403`, and server-side authorization remains authoritative for mutations and restricted drilldowns. | | Run observability / Ops-UX | PASS | PASS | No new `OperationRun` is introduced. Monitoring pages remain DB-only at render, and no start surface behavior is changed. | | Data minimization | PASS | PASS | No new persistence, caches, or derived artifacts are introduced. The contract remains derived from existing page and query state. | | Proportionality / anti-bloat | PASS WITH JUSTIFIED TAXONOMY | PASS WITH JUSTIFIED TAXONOMY | The feature introduces a bounded page-state taxonomy because five primary monitoring surfaces plus the compare launch surface already need the same explicit contract. It avoids a runtime framework or registry unless implementation proves one tiny helper is truly unavoidable. | | UI semantics / few layers | PASS | PASS | The design uses direct page-state declarations and existing helpers, not a presenter or explanation framework. | | Filament-native UI | PASS | PASS | Existing Filament pages, tables, actions, and Blade views remain the implementation path. No custom status system or new UI framework is needed. | | Shell boundary / Spec 199 separation | PASS | PASS | Global workspace or tenant shell concerns remain out of scope. Any discovered shell drift is documented for Spec 199 instead of solved here. | | Filament v5 / Livewire v4 compliance | PASS | PASS | All touched surfaces remain on Filament v5 and Livewire v4 page patterns. | | Provider registration location | PASS | PASS | No panel or provider change is required; Laravel 11+ provider registration remains in `bootstrap/providers.php`. | | Global search hard rule | PASS | PASS | No globally searchable resource is added or modified. Existing search behavior remains unchanged. | | Destructive action safety | PASS | PASS | Existing destructive-like governance actions in Finding Exceptions Queue keep `->requiresConfirmation()` and current authorization and audit semantics. | | Asset strategy | PASS | PASS | No new global or lazy-loaded assets are planned. Existing deployment handling of `cd apps/platform && php artisan filament:assets` remains sufficient. | ## Filament-Specific Compliance Notes - **Livewire v4.0+ compliance**: The implementation stays on Filament v5 + Livewire v4 page, table, and action APIs. No legacy Livewire or Filament APIs are introduced. - **Provider registration location**: No provider or panel registration changes are planned. Laravel 11+ provider registration remains in `bootstrap/providers.php`. - **Global search**: No in-scope page is a globally searchable resource surface, and this plan does not change global search visibility for any existing resource. - **Destructive actions**: `Approve exception` and `Reject exception` remain the only destructive-like actions directly affected by state-contract cleanup. They continue to execute via `Action::make(...)->action(...)` with `->requiresConfirmation()`, existing server-side authorization, and existing audit behavior. Operations, Audit Log, Evidence Overview, and Baseline Compare Matrix do not gain new destructive actions. - **Asset strategy**: No new assets or build steps are planned. Existing deployment handling of `cd apps/platform && php artisan filament:assets` remains unchanged. - **Testing plan**: Extend the current Operations, Audit Log, Finding Exceptions Queue, Evidence Overview, Baseline Compare Landing, Baseline Compare Matrix, `ActionSurfaceRbacSemanticsTest`, `BaselineCompareMatrixAuthorizationTest`, and table-persistence suites, and add one narrow cross-surface contract test plus focused browser smoke coverage for refresh, back, deeplink, share behavior, and unauthorized requested-state fallback. ## Phase 0 Research Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/198-monitoring-page-state/research.md`. Key decisions: - Reuse `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, existing session-backed table persistence, and page-local Livewire state instead of introducing a global page-state framework. - Make state precedence explicit: supported query or deeplink input hydrates first on mount, session provides baseline only for explicitly persisted filter/search/sort state, and active local state becomes authoritative after hydration. - Unify selected-record inspect on Audit Log and Finding Exceptions Queue so action-based inspect and deeplinked selected IDs express the same inspect state. - Keep Baseline Compare Matrix as the only explicit draft/apply special case, with draft state remaining local-only and unapplied draft state never being shareable or restorable. - Treat Baseline Compare Landing as a launch-context broker for matrix state, not as a competing owner of compare state. - Extend existing Pest and Livewire test seams rather than leaning on browser-only validation or adding a new guard framework. ## Phase 1 Design Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/198-monitoring-page-state/`: - `research.md`: page-state decisions, rationale, and rejected alternatives - `data-model.md`: derived page-state contract model, state-field descriptors, hydration rules, and inspect-state descriptors - `contracts/monitoring-page-state.logical.openapi.yaml`: internal logical contract for the bounded page-state model and per-surface expectations - `quickstart.md`: implementation and verification workflow for Spec 198 Design highlights: - Keep the contract derived and page-local. No new persisted truth or generalized runtime registry is planned. - Reuse existing helpers for tenant-sensitive session state and navigation context rather than adding a second helper layer. - Make the role of each state field explicit per surface: contextual prefilter, active, draft, inspect, and shareable/restorable. - Keep Operations and Evidence Overview in the simple query or session plus active-state pattern, keep Audit Log and Finding Exceptions Queue in the unified selected-record inspect pattern, and keep Baseline Compare Matrix in the explicit draft/applied/focus special-case pattern. - Add cross-surface regression protection through focused feature tests instead of a new runtime state validator. ## Phase 1 — Agent Context Update Planned command: - `.specify/scripts/bash/update-agent-context.sh copilot` This feature does not introduce a new language or framework, but the required agent-context refresh still runs after the design artifacts are complete. ## Project Structure ### Documentation (this feature) ```text specs/198-monitoring-page-state/ ├── spec.md ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── monitoring-page-state.logical.openapi.yaml ├── checklists/ │ └── requirements.md └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Filament/ │ │ └── Pages/ │ │ ├── Monitoring/ │ │ │ ├── Operations.php # MODIFY │ │ │ ├── AuditLog.php # MODIFY │ │ │ ├── FindingExceptionsQueue.php # MODIFY │ │ │ └── EvidenceOverview.php # MODIFY │ │ ├── BaselineCompareLanding.php # MODIFY │ │ └── BaselineCompareMatrix.php # MODIFY │ └── Support/ │ ├── Filament/ │ │ └── CanonicalAdminTenantFilterState.php # REUSE / possible small extend │ ├── Navigation/ │ │ └── CanonicalNavigationContext.php # REUSE / possible small extend │ └── OperateHub/ │ └── OperateHubShell.php # REUSE ├── resources/ │ └── views/ │ └── filament/ │ └── pages/ │ ├── monitoring/ │ │ ├── operations.blade.php # MODIFY │ │ ├── audit-log.blade.php # MODIFY │ │ ├── partials/ │ │ │ └── audit-log-inspect-event.blade.php # MODIFY │ │ ├── finding-exceptions-queue.blade.php # MODIFY │ │ └── evidence-overview.blade.php # MODIFY │ ├── baseline-compare-landing.blade.php # MODIFY │ └── baseline-compare-matrix.blade.php # MODIFY └── tests/ ├── Feature/ │ ├── Monitoring/ │ │ ├── OperationsDashboardDrillthroughTest.php # MODIFY │ │ ├── AuditLogInspectFlowTest.php # MODIFY │ │ ├── FindingExceptionsQueueHierarchyTest.php # MODIFY │ │ └── MonitoringPageStateContractTest.php # NEW │ ├── Evidence/ │ │ └── EvidenceOverviewPageTest.php # MODIFY │ ├── Filament/ │ │ ├── BaselineCompareMatrixPageTest.php # MODIFY │ │ ├── BaselineCompareLandingStartSurfaceTest.php # MODIFY │ │ └── TableStatePersistenceTest.php # MODIFY │ └── Rbac/ │ ├── BaselineCompareMatrixAuthorizationTest.php # REUSE / possible extend │ └── ActionSurfaceRbacSemanticsTest.php # REUSE / possible extend └── Browser/ ├── Spec190BaselineCompareMatrixSmokeTest.php # REUSE / possible extend ├── Spec194GovernanceFrictionSmokeTest.php # REUSE / possible extend └── Spec198MonitoringPageStateSmokeTest.php # NEW ``` **Structure Decision**: Keep the work entirely inside the existing Laravel/Filament monolith under `apps/platform`. Modify the affected page classes, their existing Blade views, and focused test suites. Reuse current support helpers and extract a new support helper only if repeated implementation proves that at least three pages share the same normalization code in a way that cannot stay page-local. ## Complexity Tracking | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | Cross-surface monitoring page-state taxonomy and per-surface contract documentation (BLOAT-001 trigger) | Five primary monitoring surfaces plus the compare launch surface already have overlapping but divergent page-state behavior, and the product needs explicit hydration, inspect, and restore rules now. | Pure page-by-page local cleanup would reduce individual bugs but would not produce a shared operator contract or durable regression coverage across the monitoring family. | ## Proportionality Review - **Current operator problem**: Similar monitoring pages currently give different answers about what a deeplink sets, what refresh restores, when a selected record is authoritative, and whether draft state exists. - **Existing structure is insufficient because**: The current page-local implementations contain the behavior, but not a consistent or documented contract. Without an explicit shared model, the same drift will continue page by page. - **Narrowest correct implementation**: Reuse current helpers and page-local Livewire state, add explicit per-surface state declarations and targeted tests, and keep Compare Matrix as one documented special case. Do not add persistence or a global runtime framework. - **Ownership cost created**: Reviewers must maintain one bounded page-state taxonomy, a small set of per-surface declarations, and focused regression coverage for deeplink, inspect, and restoration behavior. - **Alternative intentionally rejected**: A global page-state engine or shell-level state resolver was rejected because the affected surfaces can be standardized with existing helpers and local state. - **Release truth**: current-release operator predictability and monitoring-family consistency ## Implementation Strategy ### Phase A — Declare the bounded page-state contract per surface **Goal**: Make the state classes, query roles, and shareable or restorable subset explicit without adding a global framework. | Step | File | Change | |------|------|--------| | A.1 | `apps/platform/app/Filament/Pages/Monitoring/Operations.php`, `AuditLog.php`, `FindingExceptionsQueue.php`, `EvidenceOverview.php`, `BaselineCompareMatrix.php`, `BaselineCompareLanding.php` | Add or align explicit page-local contract declarations for contextual prefilter, active, draft, inspect, and shareable or restorable state, plus invalid-state fallback behavior | | A.2 | Existing page-local mount and hydrate methods | Make first-mount precedence explicit between query input, session state, and page-local defaults | | A.3 | `apps/platform/tests/Feature/Monitoring/MonitoringPageStateContractTest.php` | Add one cross-surface contract test that asserts every in-scope page exposes the expected state classes, query roles, shareable or restorable semantics, and Audit Log/Finding Exceptions inspect-vocabulary compatibility | ### Phase B — Standardize the simple monitoring pattern on Operations and Evidence Overview **Goal**: Align the surfaces that should behave like query or session plus active-state pages, not special-case workbenches. | Step | File | Change | |------|------|--------| | B.1 | `apps/platform/app/Filament/Pages/Monitoring/Operations.php` | Make requested dashboard prefilter, requested tenant scope, active tab, and persisted table state follow one deterministic precedence rule | | B.2 | `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php` | Align query hydration, session persistence, clear-filter behavior, and shareable filter semantics with the shared simple monitoring contract | | B.3 | `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`, `apps/platform/tests/Feature/Filament/TableStatePersistenceTest.php`, and `apps/platform/tests/Feature/Rbac/ActionSurfaceRbacSemanticsTest.php` | Cover query-first hydration, tenant-sensitive reset behavior, active tab restoration, persistence boundaries, and unauthorized requested-tenant fallback | | B.4 | `apps/platform/tests/Feature/Evidence/EvidenceOverviewPageTest.php` | Extend multi-filter hydration, clear-filter, and refresh or reopen scenarios | ### Phase C — Unify inspect-state semantics on Audit Log and Finding Exceptions Queue **Goal**: Ensure action-based inspect and deeplinked selected-record entry use one primary inspect model on each surface. | Step | File | Change | |------|------|--------| | C.1 | `apps/platform/app/Filament/Pages/Monitoring/AuditLog.php` and `apps/platform/resources/views/filament/pages/monitoring/audit-log.blade.php` | Make `selectedAuditLogId` the only inspect contract, with consistent open, close, refresh, and invalid-selection fallback behavior | | C.2 | `apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php` and `apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php` | Make `selectedFindingExceptionId` the only inspect and decision state, with summary and action lanes deriving from that single selection | | C.3 | `apps/platform/resources/views/filament/pages/monitoring/partials/audit-log-inspect-event.blade.php` | Align inline inspect rendering with the single selected-event contract | | C.4 | `apps/platform/tests/Feature/Monitoring/AuditLogInspectFlowTest.php`, `apps/platform/tests/Feature/Monitoring/FindingExceptionsQueueHierarchyTest.php`, and `apps/platform/tests/Feature/Rbac/ActionSurfaceRbacSemanticsTest.php` | Add positive and negative cases for query-selected inspect, invalid or unauthorized IDs, close-detail behavior, refresh behavior, selection clearing when filters no longer match, and compatible selected-record vocabulary across both surfaces | ### Phase D — Preserve the Compare Matrix special case while making it explicit **Goal**: Keep Baseline Compare Matrix powerful while making draft, applied, focus, and launch-context semantics predictable. | Step | File | Change | |------|------|--------| | D.1 | `apps/platform/app/Filament/Pages/BaselineCompareLanding.php` and `apps/platform/resources/views/filament/pages/baseline-compare-landing.blade.php` | Treat landing state as launch context only, and make matrix launch parameters explicit and non-competing | | D.2 | `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` and `apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php` | Make applied filter state, draft filter state, presentation mode, focus state, and shareable slices explicit; keep draft state local-only until apply | | D.3 | `apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php` | Extend draft-versus-applied, focus restoration, refresh behavior, and non-shareable draft discard coverage | | D.4 | `apps/platform/tests/Feature/Filament/BaselineCompareLandingStartSurfaceTest.php` and `apps/platform/tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php` | Cover launch-context hydration, focus handoff, and invalid or unauthorized requested compare context | ### Phase E — Browser verification and closure documentation **Goal**: Prove the contract through UI-level flows and leave a clear handoff boundary for shell-context concerns. | Step | File | Change | |------|------|--------| | E.1 | `apps/platform/tests/Browser/Spec198MonitoringPageStateSmokeTest.php` | Add a focused browser smoke suite for deeplink, refresh, back, close-detail, and share behavior across the in-scope surfaces | | E.2 | Existing browser suites | Reuse or extend `Spec190BaselineCompareMatrixSmokeTest.php` and `Spec194GovernanceFrictionSmokeTest.php` where they already cover relevant state flows | | E.3 | `specs/198-monitoring-page-state/quickstart.md` and final implementation notes | Record the final state-class mapping, shareable/restorable subset, and any shell or context handoff that remains for Spec 199 | ## Key Design Decisions ### D-001 — Reuse current helpers and keep the contract page-local `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, and current page-local Livewire properties already cover most of the needed mechanics. The narrowest implementation is to make the contract explicit on the pages that already own the state. ### D-002 — Query or deeplink input wins on first hydration, session only persists the state the page already owns The consistent precedence rule is: supported query or deeplink input hydrates first, session supplies only explicitly persisted table filter, search, or sort state, and page-local active state becomes authoritative after mount. ### D-003 — Selected-record inspect is the authoritative inspect model on Audit Log and Finding Exceptions Queue The inspect action, the selected summary, and the deeplinked selected ID must all express the same state. There must be no second inspect world beside the selected-record contract. ### D-004 — Baseline Compare Matrix keeps explicit draft, applied, and focus slices Compare Matrix already has a genuine draft/apply interaction model. The right move is to document and tighten it, not to flatten it into a direct-active table pattern. ### D-005 — Regression protection belongs in focused tests, not a new runtime state engine The contract is enforceable through page-local declarations and focused Pest coverage. That keeps the implementation surface small and avoids importing a new state registry or framework. ## Risk Assessment | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| | Query and session precedence regressions change what first mount restores | High | Medium | Make precedence explicit per page, extend query-param and table-persistence tests, and cover tenant-sensitive filter resets | | Invalid deeplinks leave phantom selected state or stale action lanes | High | Medium | Add invalid-ID fallback rules and test them on Audit Log, Finding Exceptions Queue, and Baseline Compare launch context | | Compare Matrix accidentally persists or shares draft state | High | Medium | Keep draft and applied state separate in code and tests, and assert that shared links restore only applied and focused state | | Shell-context concerns leak into Spec 198 and widen scope | Medium | Medium | Keep workspace or tenant shell concerns documented only as handoff items for Spec 199 | | Implementation drifts into a generic page-state framework | Medium | Medium | Keep helpers page-local by default and extract only a tiny shared helper if duplication across multiple pages proves it necessary during implementation | ## Test Strategy - Extend Operations, Audit Log, Finding Exceptions Queue, Evidence Overview, Baseline Compare Landing, Baseline Compare Matrix, and `TableStatePersistenceTest` with deterministic query, session, refresh, back, and restore coverage. - Add one focused cross-surface contract test for the explicit page-state declarations on the in-scope pages. - Reuse current RBAC suites where requested state can become unauthorized or cross-tenant. - Add one browser smoke suite for the top user journeys: Operations deeplink and refresh, Audit Log selected-event open and close, Finding Exceptions selected-exception review state, Evidence Overview filter clear behavior, and Baseline Compare draft/apply plus focus restoration. - Run focused verification through Sail and format only touched files with Pint.