TenantAtlas/specs/173-tenant-dashboard-truth-alignment/plan.md
2026-04-03 21:15:29 +02:00

279 lines
25 KiB
Markdown

# Implementation Plan: Tenant Dashboard KPI & Attention Truth Alignment
**Branch**: `173-tenant-dashboard-truth-alignment` | **Date**: 2026-04-03 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/173-tenant-dashboard-truth-alignment/spec.md`
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/173-tenant-dashboard-truth-alignment/spec.md`
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
## Summary
Align the tenant dashboard's five existing overview surfaces around one honest tenant truth without adding new persistence, a new dashboard aggregate, or a new posture framework. The first implementation slice will tighten KPI semantics and tenant-safe drill-throughs using the existing findings and operations destination models, make `NeedsAttention` action-capable while preserving its aggregate-backed attention logic, and keep `BaselineCompareNow` on the existing compare and governance guard path so the dashboard cannot look calmer than the tenant's real state. The second slice will protect the distinction between posture, activity, and recency with focused cross-widget regression coverage and tenant-prefilter continuity tests.
## Technical Context
**Language/Version**: PHP 8.4.15
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `TenantDashboard`, `DashboardKpis`, `NeedsAttention`, `BaselineCompareNow`, `RecentDriftFindings`, `RecentOperations`, `TenantGovernanceAggregateResolver`, `BaselineCompareStats`, `BaselineCompareSummaryAssessor`, `FindingResource`, `OperationRunLinks`, and canonical admin Operations page
**Storage**: PostgreSQL unchanged; no new persistence, cache store, or durable dashboard summary artifact
**Testing**: Pest 4 feature and Livewire component tests through Laravel Sail; existing dashboard tenant-scope and DB-only tests remain part of the verification pack
**Target Platform**: Laravel monolith web application in Sail locally and containerized Linux deployment in staging/production
**Project Type**: web application
**Performance Goals**: Keep tenant dashboard rendering DB-only, preserve canonical drill-through routes, and avoid broadening the current dashboard surface family; DB-only rendering remains the explicit verification target for this slice
**Constraints**: No new tables, no new global tenant-posture component, no new dashboard route family, no cross-tenant leakage, no new destructive actions, recent tables must remain diagnostic surfaces, and canonical admin operations routes must preserve tenant context when entered from the tenant dashboard
**Scale/Scope**: One tenant dashboard page, five existing dashboard surfaces, three main destination families (`/admin/t/{tenant}/findings`, `/admin/t/{tenant}/baseline-compare-landing`, `/admin/operations`), and targeted regression coverage for cross-widget consistency and drill-through continuity
## 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 | No new inventory or snapshot truth is introduced. The feature reuses existing findings, compare, and operation records. |
| Read/write separation | PASS | PASS | The slice is read-time dashboard alignment only. No new write path, preview path, or mutation surface is added. |
| Graph contract path | N/A | N/A | No Graph calls or contract-registry changes are required. |
| Deterministic capabilities | PASS | PASS | Existing server-side authorization remains authoritative for dashboard destinations. |
| Workspace + tenant isolation | PASS | PASS | Every dashboard destination remains tenant-scoped or canonical-view with tenant-prefilter continuity and existing entitlement checks. |
| RBAC-UX authorization semantics | PASS | PASS | Non-members remain `404`, in-scope members lacking a destination capability remain `403`, and no raw capability checks are introduced. |
| Run observability / Ops-UX | PASS | PASS | No new `OperationRun` lifecycle or feedback path is added. Existing operations routes remain canonical. |
| Data minimization | PASS | PASS | No new persistence or broader route exposure is introduced; drill-throughs reuse existing destination data only. |
| Proportionality / no premature abstraction | PASS | PASS | The plan explicitly reuses `TenantGovernanceAggregate`, `Finding` status helpers, and existing route helpers instead of adding a dashboard-specific framework. |
| Persisted truth / behavioral state | PASS | PASS | No new tables, status families, or persisted summary artifacts are planned. |
| UI semantics / few layers | PASS | PASS | The feature aligns existing widgets and helper seams rather than creating a new presentation taxonomy. |
| Badge semantics (BADGE-001) | PASS | PASS | Existing badge and tone domains remain authoritative for finding severity, compare posture, and operation status/outcome. |
| Filament-native UI / Action Surface Contract | PASS | PASS | Existing Filament widgets and tables remain in place. No redundant inspect affordances or new destructive actions are introduced. |
| Filament UX-001 | PASS | PASS | No create/edit/view layout changes. The dashboard keeps attention and compare posture above recency surfaces. |
| Filament v5 / Livewire v4 compliance | PASS | PASS | The design remains within the current Filament v5 + Livewire v4 stack. |
| Provider registration location | PASS | PASS | No panel or provider registration change is required; Laravel 11+ registration remains in `bootstrap/providers.php`. |
| Global search hard rule | PASS | PASS | No globally searchable resource behavior changes are part of this slice. |
| Destructive action safety | PASS | PASS | The feature adds no destructive action. |
| Asset strategy | PASS | PASS | No new assets or `filament:assets` deployment changes are needed. |
| Testing truth (TEST-TRUTH-001) | PASS | PASS | The plan adds cross-widget and drill-through tests that verify business truth, not thin UI wiring alone. |
## Phase 0 Research
Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/173-tenant-dashboard-truth-alignment/research.md`.
Key decisions:
- Reuse the existing `TenantGovernanceAggregate` and `BaselineCompareStats` truth path instead of creating a new tenant-dashboard aggregate.
- Treat `Finding::openStatusesForQuery()` and the existing findings list tabs and quick filters as the canonical active/open universe for dashboard drill-through continuity.
- Treat high severity at tenant-summary level as the existing `HIGH + CRITICAL` active-finding universe; any narrower KPI subset must be explicitly labeled as narrower.
- Make `NeedsAttention` directly actionable by routing each item to an existing tenant-safe findings, compare, or operations destination instead of leaving it as summary-only text.
- Treat materially relevant operations follow-up for this slice as tenant-scoped runs in failed, warning, or unusually long-running or stalled states that require operator review; healthy queued or running activity alone remains an activity signal.
- When a tenant member can see a dashboard summary state but lacks the downstream capability for its destination, keep the state visible only as a disabled or non-clickable affordance with helper text instead of a clickable dead-end link.
- Push tenant-prefilter continuity for canonical Operations routes into existing link and filter helpers rather than leaving raw `route('admin.operations.index')` calls in dashboard widgets.
- Keep `RecentDriftFindings` and `RecentOperations` as diagnostic recency surfaces instead of expanding them into the posture layer.
## Phase 1 Design
Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/173-tenant-dashboard-truth-alignment/`:
- `data-model.md`: existing persistent source truth plus the derived dashboard signal and drill-through contracts for this slice
- `contracts/tenant-dashboard-truth-alignment.openapi.yaml`: internal logical contract for tenant dashboard summary semantics and destination continuity
- `quickstart.md`: focused implementation and verification workflow
Design decisions:
- `TenantGovernanceAggregate` and `BaselineCompareSummaryAssessor` remain the governance and compare truth anchors; the implementation will primarily align widgets and existing link or filter helpers around those outputs, with helper changes kept narrow instead of introducing new aggregate logic.
- KPI semantics are aligned by canonically reusing active-status and severity universes where appropriate and by explicitly renaming or filtering any intentionally narrower subset.
- Canonical operations navigation remains `/admin/operations` and `/admin/operations/{run}`; the change is tenant-prefilter continuity, not a new route family.
- `NeedsAttention` becomes action-capable without becoming a mutation surface or a second diagnostics page, and expiring governance plus high-severity active findings remain first-class attention states alongside overdue, lapsed, compare-limited, and defined operations-follow-up states.
- Permission-limited members keep visible dashboard truth, but destination affordances must be disabled or non-clickable with helper text instead of clickable links that only fail after navigation.
- Recent tables keep their existing row-click model and remain clearly subordinate to attention and compare posture.
## Project Structure
### Documentation (this feature)
```text
specs/173-tenant-dashboard-truth-alignment/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── tenant-dashboard-truth-alignment.openapi.yaml
├── checklists/
│ └── requirements.md
└── tasks.md
```
### Source Code (repository root)
```text
app/
├── Filament/
│ ├── Pages/
│ │ ├── TenantDashboard.php
│ │ └── Monitoring/
│ │ └── Operations.php
│ ├── Resources/
│ │ └── FindingResource/
│ │ └── Pages/
│ │ └── ListFindings.php
│ └── Widgets/
│ └── Dashboard/
│ ├── DashboardKpis.php
│ ├── NeedsAttention.php
│ ├── BaselineCompareNow.php
│ ├── RecentDriftFindings.php
│ └── RecentOperations.php
├── Models/
│ ├── Finding.php
│ └── OperationRun.php
└── Support/
├── Baselines/
│ ├── BaselineCompareStats.php
│ ├── BaselineCompareSummaryAssessor.php
│ ├── BaselineCompareSummaryAssessment.php
│ ├── TenantGovernanceAggregate.php
│ └── TenantGovernanceAggregateResolver.php
├── Navigation/
│ └── CanonicalNavigationContext.php
└── OperationRunLinks.php
tests/
├── Feature/
│ ├── Filament/
│ │ ├── NeedsAttentionWidgetTest.php
│ │ ├── BaselineCompareNowWidgetTest.php
│ │ ├── BaselineCompareSummaryConsistencyTest.php
│ │ ├── TenantDashboardDbOnlyTest.php
│ │ ├── TenantDashboardTenantScopeTest.php
│ │ ├── DashboardKpisWidgetTest.php
│ │ └── TenantDashboardTruthAlignmentTest.php
│ ├── Findings/
│ │ ├── FindingsListDefaultsTest.php
│ │ ├── FindingsListFiltersTest.php
│ │ └── FindingAdminTenantParityTest.php
│ ├── Monitoring/
│ │ └── OperationsDashboardDrillthroughTest.php
│ └── OpsUx/
│ └── CanonicalViewRunLinksTest.php
```
**Structure Decision**: Keep the existing Laravel monolith structure. The implementation should extend current dashboard widgets, current findings and operations destinations, and current baseline aggregate helpers instead of introducing new directories or a dashboard-specific domain layer.
## Implementation Strategy
### Phase A — Align Canonical Dashboard Truth Definitions
**Goal**: Make dashboard count meaning and severity meaning reuse existing source truth instead of widget-local interpretations.
| Step | File | Change |
|------|------|--------|
| A.1 | `app/Models/Finding.php` and `app/Filament/Resources/FindingResource/Pages/ListFindings.php` | Treat `openStatusesForQuery()`, existing `Needs action` and `Overdue` tabs, and the existing `high_severity` quick filter as the canonical active/open and high-severity destination semantics for tenant findings drill-throughs. |
| A.2 | `app/Support/Baselines/BaselineCompareStats.php`, `app/Support/Baselines/TenantGovernanceAggregate.php`, and `app/Support/Baselines/TenantGovernanceAggregateResolver.php` | Preserve the current aggregate-backed compare and governance count family as the canonical tenant-level attention guard set. |
| A.3 | `app/Filament/Widgets/Dashboard/DashboardKpis.php` | Replace ambiguous KPI wording and local count universes with metrics that either match the canonical active/severity meaning or are explicitly labeled as narrower subsets, and degrade visible KPI drill-throughs to disabled or non-clickable helper-text affordances when destination capability is missing. |
### Phase B — Make KPI Drill-Throughs Semantically Continuous
**Goal**: Ensure clicking a KPI leads to a target surface where the same problem family is recognizable.
| Step | File | Change |
|------|------|--------|
| B.1 | `app/Filament/Widgets/Dashboard/DashboardKpis.php` and `app/Filament/Resources/FindingResource/Pages/ListFindings.php` | Add or reuse explicit findings tab/filter state so KPI destinations reproduce the named subset instead of opening a broader unqualified findings list. |
| B.2 | `app/Support/OperationRunLinks.php` and `app/Filament/Pages/Monitoring/Operations.php` | Push tenant-prefilter continuity for canonical operations links into the existing operations link helper and destination filter handling instead of relying on raw route calls. |
| B.3 | `tests/Feature/Filament/DashboardKpisWidgetTest.php`, `tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php`, and `tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php` | Prove KPI naming, subset meaning, destination continuity, and permission-limited disabled or non-clickable affordance behavior for findings and operations drill-throughs. |
### Phase C — Make `NeedsAttention` Action-Capable
**Goal**: Turn the existing attention summary into a start surface without changing its role into a mutation or diagnostics surface.
| Step | File | Change |
|------|------|--------|
| C.1 | `app/Filament/Widgets/Dashboard/NeedsAttention.php` | Add direct destination metadata for central attention items while keeping the existing aggregate-backed problem detection, high-severity active-finding coverage, expiring-governance coverage, defined operations-follow-up semantics, and healthy fallback rules. |
| C.2 | `resources/views/filament/widgets/dashboard/needs-attention.blade.php` | Render one primary action or one explicit next-step affordance per attention item while preserving the surface's summary-first hierarchy. |
| C.3 | `tests/Feature/Filament/NeedsAttentionWidgetTest.php` and `tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php` | Prove that overdue findings, high-severity active findings, lapsed or expiring governance, compare posture limitations, relevant operations follow-up, and permission-limited dashboard states now expose the correct tenant-safe destination behavior and suppress false calm. |
### Phase D — Keep Compare Calmness and Dashboard Calmness Aligned
**Goal**: Ensure `BaselineCompareNow` and the dashboard's healthy fallback do not outvote stronger tenant attention conditions.
| Step | File | Change |
|------|------|--------|
| D.1 | `app/Filament/Widgets/Dashboard/BaselineCompareNow.php` | Preserve the existing compare summary guard path and baseline-compare landing continuity by consuming the current aggregate-backed summary outputs consistently, without introducing a second helper-owned compare logic path in this slice. |
| D.2 | `tests/Feature/Filament/BaselineCompareNowWidgetTest.php`, `tests/Feature/Filament/BaselineCompareSummaryConsistencyTest.php`, and `tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php` | Prove that compare summary and dashboard attention continue to agree for stale, unavailable, overdue, expiring-governance, lapsed-governance, and trustworthy scenarios. |
### Phase E — Preserve Posture vs Activity vs Recency Separation
**Goal**: Keep recent tables diagnostic and keep operations activity distinct from governance posture.
| Step | File | Change |
|------|------|--------|
| E.1 | `app/Filament/Widgets/Dashboard/RecentDriftFindings.php` and `app/Filament/Widgets/Dashboard/RecentOperations.php` | Preserve current row-click, empty-state, and recency-only semantics; tighten only copy or surrounding truth signals if needed so these surfaces do not read as the tenant's primary queue. |
| E.2 | `tests/Feature/Filament/TenantDashboardDbOnlyTest.php`, `tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php`, and existing table standards tests | Prove that recency surfaces remain secondary to attention and compare posture, that healthy operations-only scenarios do not produce governance-problem wording, and that failed, warning, or stalled operations follow-up remains distinguishable from simple activity. |
### Phase F — Regression Protection and Verification
**Goal**: Protect the dashboard truth contract against future drift.
| Step | File | Change |
|------|------|--------|
| F.1 | `tests/Feature/Filament/DashboardKpisWidgetTest.php` and `tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php` | Add a focused matrix for active findings, high severity, overdue, lapsed, or expiring governance, compare limitations, healthy operations-only activity, attention-worthy operations follow-up, and calm all-clear scenarios. |
| F.2 | `tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`, `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php`, and `tests/Feature/Filament/TenantDashboardTenantScopeTest.php` | Protect tenant-filter continuity, tenant-safe access, and disabled or non-clickable dashboard affordances for canonical operations routes opened from the dashboard. |
| F.3 | `vendor/bin/sail bin pint --dirty --format agent` plus focused Pest runs | Apply formatting and run the smallest verification pack that covers widgets, dashboard integration, findings filters, and operations drill-through continuity. |
## Key Design Decisions
### D-001 — Reuse the existing tenant governance aggregate instead of creating a dashboard-specific truth layer
The repo already has `TenantGovernanceAggregate`, `BaselineCompareStats`, and `BaselineCompareSummaryAssessor` for governance and compare truth. Spec 173 should extend that existing ownership to dashboard alignment work instead of introducing a second tenant summary abstraction. Unless regression coverage exposes a defect in those helpers themselves, this slice changes widget consumption and drill-through continuity rather than modifying compare helper behavior.
### D-002 — Canonical finding semantics come from existing model helpers and findings list filters
`Finding::openStatusesForQuery()`, the findings list tabs, and the existing `high_severity` quick filter already define the best current meaning of active/open and high-severity work. KPI and attention drill-through continuity should reuse that semantics instead of inventing dashboard-only filters.
### D-003 — Canonical operations drill-through stays canonical but must preserve tenant context
The correct destination remains `/admin/operations` and `/admin/operations/{run}`. The fix is to carry tenant filter state into those existing routes, not to build tenant-specific duplicate operations pages.
### D-004 — `NeedsAttention` becomes actionable but remains a summary surface
The attention widget should tell the operator where to go next, but it should not become a new diagnostics or mutation surface. One action per item is the right level.
### D-005 — Recent tables remain diagnostic recency surfaces
`RecentDriftFindings` and `RecentOperations` should continue to provide recent context only. The plan explicitly avoids turning them into primary queue or posture owners.
### D-006 — Attention-worthy operations follow-up is narrower than generic activity
For this slice, only failed, warning, or unusually long-running or stalled tenant runs count as attention-worthy operations follow-up. Healthy queued or running activity remains visible as activity, not governance risk.
### D-007 — Permission-limited members must not get clickable dead-end drill-throughs
If a tenant member can see dashboard truth but lacks a downstream capability, the dashboard may still expose the state, but the affordance must be disabled or non-clickable with helper text rather than a clickable control that only ends in `403` after navigation.
## Risk Assessment
| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| KPI semantics over-expand and lose the compact value of the strip | Medium | Medium | Keep narrow subsets only when they are explicitly named and filter-matched on the destination. |
| Canonical operations links still drop tenant context | High | Medium | Push tenant-prefilter continuity into `OperationRunLinks` and destination filter handling, backed by regression tests. |
| `NeedsAttention` becomes clickable but semantically drifts from the aggregate-backed truth or exposes dead-end links | High | Medium | Source item semantics from the existing aggregate, define operations follow-up narrowly, and keep one destination per item with disabled or non-clickable fallback for permission-limited members. |
| Compare summary stays calmer than stronger dashboard attention conditions | High | Medium | Add explicit all-clear suppression tests across `NeedsAttention`, `BaselineCompareNow`, and the dashboard bundle. |
| Recent tables begin to read as the primary action queue | Medium | Low | Preserve current headings, empty-state semantics, and row-click-only interaction; verify this in integration coverage. |
## Test Strategy
- Extend or add focused Livewire and feature tests for `DashboardKpis`, `NeedsAttention`, and `BaselineCompareNow` so the dashboard's core summary surfaces agree on active findings, high severity, overdue, lapsed, and expiring governance, compare limitations, healthy operations-only activity, attention-worthy operations follow-up, and calm all-clear states.
- Reuse the current `TenantDashboardDbOnlyTest` and `TenantDashboardTenantScopeTest` to preserve DB-only rendering and tenant isolation.
- Add explicit drill-through coverage so KPI and attention clicks land on findings or operations destinations with recognizable tenant-filtered semantics, and permission-limited members see disabled or non-clickable explanatory states instead of clickable dead ends.
- Reuse current findings list filter coverage to protect the destination-side meaning of `needs_action`, `overdue`, and `high_severity` continuity.
- Preserve existing compare summary consistency tests so compare posture continues to agree across dashboard and landing surfaces.
- Use the quickstart manual smoke check to verify the 10-second operator comprehension outcome on seeded tenants in addition to the automated regression pack.
- Keep all coverage Pest-based and run through Sail with the smallest targeted verification pack.
## Complexity Tracking
No constitution exception or BLOAT-001-triggering addition is planned. The intended implementation reuses existing aggregate, helper, and page/widget structure.
## Proportionality Review
- **Current operator problem**: The tenant dashboard currently lets KPI counts, attention logic, compare calmness, and recent-history surfaces describe different semantic worlds on the same page.
- **Existing structure is insufficient because**: Widget-local queries and raw route links have diverged from existing canonical findings and compare semantics, and canonical operations links do not currently preserve tenant context from the dashboard.
- **Narrowest correct implementation**: Align the existing widgets, existing aggregate-backed compare truth, existing findings filters, and existing operations link helpers instead of adding new persistence, new routes, or a new dashboard domain model.
- **Ownership cost created**: A focused set of widget and drill-through tests plus a small amount of helper and copy maintenance.
- **Alternative intentionally rejected**: A new tenant-dashboard aggregate, a global posture component, or a layout-level dashboard rewrite was rejected because the current-release problem is semantic alignment on existing surfaces.
- **Release truth**: Current-release truth. The affected widgets and routes already exist and are already used by operators.