TenantAtlas/specs/173-tenant-dashboard-truth-alignment/research.md
ahmido 3a2a06e8d7 feat: align tenant dashboard truth surfaces (#204)
## Summary
- align tenant dashboard KPI, attention, compare, and operations truth so the page does not read calmer than the tenant's actual state
- preserve tenant-safe drill-through continuity into findings, baseline compare, and canonical operations, including disabled helper states for permission-limited members
- add the Spec 173 artifact set and focused regression coverage for dashboard truth alignment and drill-through behavior

## Validation
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/DashboardKpisWidgetTest.php tests/Feature/Filament/TenantDashboardTruthAlignmentTest.php tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php tests/Feature/Filament/NeedsAttentionWidgetTest.php tests/Feature/Filament/BaselineCompareNowWidgetTest.php tests/Feature/Filament/BaselineCompareSummaryConsistencyTest.php tests/Feature/Findings/FindingsListDefaultsTest.php tests/Feature/Findings/FindingsListFiltersTest.php tests/Feature/Findings/FindingAdminTenantParityTest.php tests/Feature/OpsUx/CanonicalViewRunLinksTest.php tests/Feature/Filament/TenantDashboardTenantScopeTest.php tests/Feature/Filament/TenantDashboardDbOnlyTest.php tests/Feature/Filament/TableStandardsBaselineTest.php tests/Feature/Filament/TableDetailVisibilityTest.php`
- integrated browser smoke on the tenant dashboard, including a permission-limited member scenario

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #204
2026-04-03 20:26:15 +00:00

73 lines
7.7 KiB
Markdown

# Phase 0 Research: Tenant Dashboard KPI & Attention Truth Alignment
## Decision: Reuse the existing `TenantGovernanceAggregate` and compare summary path instead of creating a new dashboard aggregate
**Rationale**: `NeedsAttention` and `BaselineCompareNow` already depend on `TenantGovernanceAggregate`, which itself is derived from `BaselineCompareStats` and `BaselineCompareSummaryAssessor`. The tenant dashboard truth problem is not missing data; it is divergent interpretation and drill-through behavior across existing widgets. Extending the current aggregate-backed guard family is narrower than inventing a new dashboard-specific summary service.
**Alternatives considered**:
- Create a new persisted tenant-dashboard summary record: rejected because the spec explicitly forbids new persistence and the problem is request-time semantics, not a new lifecycle truth.
- Create a second query-backed dashboard aggregate: rejected because it would split ownership away from the existing compare and governance summary path.
## Decision: Treat `Finding::openStatusesForQuery()` and existing findings tabs and quick filters as the canonical active/open universe for dashboard continuity
**Rationale**: The tenant findings destination already defines `Needs action` and `Overdue` tabs using `Finding::openStatusesForQuery()`, and the findings list already exposes `high_severity`, `overdue`, and status-based quick filtering. These are the closest existing source of truth for what the operator should recognize after clicking a dashboard signal. Dashboard metrics should align to these semantics or be visibly renamed when intentionally narrower.
**Alternatives considered**:
- Keep dashboard-local status semantics and accept looser drill-through continuity: rejected because it preserves the trust gap the spec is trying to remove.
- Introduce a new dashboard-only filter vocabulary: rejected because it would add a second surface contract for the same findings universe.
## Decision: Treat high severity at tenant-summary level as `HIGH + CRITICAL` active findings unless a metric is explicitly labeled as narrower
**Rationale**: `BaselineCompareStats` already counts `highSeverityActiveFindingsCount` using open statuses plus `HIGH + CRITICAL`, and the findings list `high_severity` quick filter uses the same severity set. `DashboardKpis` is the current outlier because it only counts `SEVERITY_HIGH` and `STATUS_NEW`. The narrowest fix is to align `high severity` language to the existing broader tenant-summary meaning and reserve narrower wording for explicitly `new` or `drift-only` subsets.
**Alternatives considered**:
- Downgrade every other surface to `SEVERITY_HIGH` only: rejected because it would discard an existing criticality distinction already present in the findings destination and aggregate.
- Let `high severity` continue to mean different things by widget: rejected because the label collision is part of the operator trust problem.
## Decision: Make `NeedsAttention` directly actionable using existing tenant-safe destinations instead of leaving it as summary-only text
**Rationale**: The current widget already computes the right tenant-level problem families but only exposes summary text and, for compare posture, a `nextStep` label without navigation. The spec requires central attention states to become genuine start points. Existing findings, baseline compare, and operations destinations already exist and are the right follow-up surfaces.
**Alternatives considered**:
- Keep `NeedsAttention` non-navigational and rely on adjacent widgets for follow-up: rejected because it leaves the primary attention surface incomplete.
- Introduce a new dedicated attention page: rejected because the spec explicitly avoids new overview architecture.
## Decision: Keep canonical Operations routes and push tenant-prefilter continuity into existing link and filter helpers
**Rationale**: `/admin/operations` and `/admin/operations/{run}` are already the canonical operations destinations. The current dashboard KPI uses a raw `route('admin.operations.index')`, which loses tenant context. `OperationRunLinks` and the Operations page already provide the right seam to carry tenant-aware filter or navigation context without multiplying route families.
**Alternatives considered**:
- Add tenant-specific operations pages under `/admin/t/{tenant}`: rejected because the repo already standardized operations on canonical admin routes.
- Leave dashboard operations drill-through unfiltered: rejected because it breaks the spec's drill-through continuity requirement.
## Decision: Treat attention-worthy operations follow-up as failed, warning, or unusually long-running or stalled tenant runs
**Rationale**: Spec 173 distinguishes governance posture from operations activity. Healthy queued or running operations should remain visible as activity, but they must not be allowed to suppress or replace governance signals. Only runs whose current status or outcome indicates failure, warning, or unusually long-running or stalled execution should escalate into `NeedsAttention` follow-up.
**Alternatives considered**:
- Treat any active operation as attention-worthy: rejected because it would collapse activity and risk into one noisy signal.
- Ignore operations follow-up completely on the dashboard: rejected because failed or stalled tenant runs do require operator follow-up and are explicitly in scope.
## Decision: Permission-limited members may see truth but must not get clickable dead-end drill-throughs
**Rationale**: The constitution allows visible disabled actions for in-scope members who lack capability, while the spec forbids dead-end drill-throughs. The narrowest consistent behavior is to keep dashboard truth visible but render the affordance as disabled or non-clickable with helper text instead of a clickable link that would only fail after navigation.
**Alternatives considered**:
- Hide the state entirely: rejected because it can make the dashboard look calmer than reality for an otherwise entitled tenant member.
- Allow the click and rely on a downstream `403`: rejected because it violates the no-dead-end drill-through requirement and weakens operator trust.
## Decision: Preserve `RecentDriftFindings` and `RecentOperations` as diagnostic recency surfaces rather than queue surfaces
**Rationale**: Both table widgets already use row-click inspection, default sort, and domain-specific empty states, and their queries intentionally include recent history without filtering to only active work. The spec calls out that recent surfaces can remain diagnostic if that role is explicit. Treating them as recency/context surfaces is narrower and avoids conflating history with posture.
**Alternatives considered**:
- Convert recent tables into tightly filtered actionable queues: rejected because it would expand the feature into a dashboard redesign and overlap with existing findings and operations destinations.
- Remove recent surfaces from the dashboard: rejected because the spec is about truth alignment, not surface removal.
## Decision: Protect the feature with cross-widget parity and drill-through tests instead of one-off manual smoke checks only
**Rationale**: The highest-risk regressions are semantic: mismatched count universes, false calm, and tenant context loss on destinations. Focused Pest tests around widgets and route continuity can protect those business truths directly and stay aligned with the repo's existing Livewire-heavy testing style.
**Alternatives considered**:
- Rely on manual dashboard review only: rejected because subtle truth drift will recur.
- Add a broad browser-only suite: rejected because the core logic is already well served by focused Livewire and feature tests, with existing browser coverage reserved for route and smoke scenarios where needed.