TenantAtlas/specs/184-dashboard-recovery-honesty/plan.md
ahmido f1a73490e4 feat: finalize dashboard recovery honesty (#215)
## Summary
- add a dedicated Recovery Readiness dashboard widget for backup posture and recovery evidence
- group Needs Attention items by domain and elevate the recovery call-to-action
- align restore-run and recovery posture tests with the extracted widget and continuity flows
- include the related spec artifacts for 184-dashboard-recovery-honesty

## Verification
- `cd /Users/ahmeddarrazi/Documents/projects/TenantAtlas/apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd /Users/ahmeddarrazi/Documents/projects/TenantAtlas/apps/platform && ./vendor/bin/sail artisan test --compact --filter="DashboardKpisWidget|DashboardRecoveryPosture|TenantDashboardDbOnly|TenantpilotSeedBackupHealthBrowserFixtureCommand|NeedsAttentionWidget"`
- browser smoke verified on the calm, unvalidated, and weakened dashboard states

## Notes
- Livewire v4.0+ compliant with Filament v5
- no panel provider changes; Laravel 11+ provider registration remains in `bootstrap/providers.php`
- Recovery Readiness stays within the existing tenant dashboard asset strategy; no new Filament asset registration required

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #215
2026-04-08 23:21:36 +00:00

24 KiB
Raw Permalink Blame History

Implementation Plan: Dashboard Recovery Posture Honesty

Branch: 184-dashboard-recovery-honesty | Date: 2026-04-08 | Spec: /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/184-dashboard-recovery-honesty/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/184-dashboard-recovery-honesty/spec.md

Summary

Harden the tenant dashboard so healthy backup inputs never read as validated recovery posture by default. The implementation will keep backup health and restore evidence as separate truths, reuse the existing backup positive-claim boundary and per-run RestoreResultAttention semantics, add an honest recovery-evidence summary to the tenant dashboard KPI and attention surfaces, and route no-history or weak-history signals into canonical restore-run list or detail drilldowns using the repos existing continuity-banner pattern. The slice stays read-only, introduces no new persistence, enum, provider, asset, or recovery-confidence engine, and limits code changes to existing dashboard widgets, existing restore-run list or detail surfaces, and focused Pest coverage.

Technical Context

Language/Version: PHP 8.4, Laravel 12, Blade, Filament v5, Livewire v4
Primary Dependencies: Filament v5 widgets and resources, Livewire v4, Pest v4, existing TenantDashboard, DashboardKpis, NeedsAttention, TenantBackupHealthResolver, TenantBackupHealthAssessment, RestoreRunResource, RestoreSafetyResolver, RestoreResultAttention, OperationRunLinks, and existing RBAC helpers
Storage: PostgreSQL with existing tenant-owned backup_sets, restore_runs, and linked operation_runs; no schema change planned
Testing: Pest feature tests, Livewire widget or page tests, and narrow unit coverage for restore-history derivation, all run through Sail
Target Platform: Laravel web application in Sail locally and containerized Linux deployment in staging and production Project Type: Laravel monolith web application rooted at apps/platform
Performance Goals: Keep tenant dashboard rendering DB-only, avoid external calls at render time, cap recovery-evidence derivation to the most recent 10 tenant-scoped restore-run candidates with only the required relations eager-loaded, and preserve the existing restore-run list scanability without N+1 row lookups
Constraints: No new persisted recovery-confidence field or table, no new recovery-proven signal, no new Graph calls, no new panel or provider registration, no new global-search behavior, no RBAC drift, no summary claim stronger than the evidence visible to the current user, and no new Filament asset registration
Scale/Scope: One tenant dashboard page, two existing dashboard widgets, one restore-run list continuity seam, one restore-run detail reuse seam, one internal dashboard surface contract, and focused unit plus feature regression coverage

Constitution Check

GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.

Principle Status Notes
Inventory-first Pass Existing backup and restore artifacts remain the only evidence sources; no ownership model changes
Read/write separation Pass This slice is read-only summary hardening; no new mutation or remote work is introduced
Graph contract path Pass No Graph calls, contract-registry changes, or provider changes are required
Deterministic capabilities Pass Existing capability registry, policies, and UiEnforcement remain authoritative
RBAC-UX planes and 404 vs 403 Pass Tenant dashboard and restore-run surfaces remain in /admin/t/{tenant}/...; non-members stay 404; in-scope members retain existing capability semantics
Workspace isolation Pass No workspace-scope broadening is planned; workspace overview remains unchanged in this slice
Tenant isolation Pass All evidence and drilldowns stay tenant-owned and tenant-scoped
Destructive confirmation standard Pass No new destructive dashboard actions are added; existing restore-run destructive actions remain confirmed and server-authorized
Global search safety Pass No new globally searchable resource or search behavior is introduced; existing resources retain their current view-page-backed search safety
Run observability / Ops-UX Pass No new OperationRun, background work, notification surface, or lifecycle transition is added
Data minimization Pass The slice reuses existing backup and restore metadata and keeps diagnostics on existing restore surfaces
Proportionality (PROP-001) Pass The design extends existing widgets, list pages, and restore-safety seams instead of adding a new recovery-confidence subsystem
No premature abstraction (ABSTR-001) Pass No new registry, interface, or orchestration layer is planned; at most a narrow extension of existing restore-safety seams or local widget derivation is needed
Persisted truth (PERSIST-001) Pass Recovery posture remains derived at render time; no new stored truth is introduced
Behavioral state (STATE-001) Pass unvalidated, weakened, and no_recent_issues_visible stay derived UI semantics, not new persisted domain state
UI semantics (UI-SEM-001) Pass Recovery honesty is rendered directly on existing widgets and restore surfaces; no new presenter framework is planned
Badge semantics (BADGE-001) Pass Existing badge catalogs remain authoritative; if restore attention appears on the list, it will reuse existing restore status and attention semantics rather than page-local mappings
Filament-native UI (UI-FIL-001) Pass Existing StatsOverviewWidget, dashboard section view, Filament links, badges, and restore-run resource tables remain the primary seams
UI Action Surface Contract Pass No new inspect model or destructive placement is introduced; restore-run list retains clickable-row inspect and grouped mutations
UX-001 / HDR-001 Pass The dashboard keeps its existing widget layout; no new record headers or form layout changes are introduced
Filament v5 / Livewire v4 compliance Pass The plan stays inside current Filament v5 and Livewire v4 surface patterns
Provider registration location Pass No provider change is needed; existing Laravel 11+ registration remains in bootstrap/providers.php
Global-search rule for changed resources Pass No change to searchable resource registration; RestoreRunResource already has detail pages and this slice touches no search-specific behavior
Asset strategy Pass No new assets are planned; deployment continues to include cd apps/platform && php artisan filament:assets unchanged

Phase 0 Research

Research outcomes are captured in /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/184-dashboard-recovery-honesty/research.md.

Key decisions:

  • Keep the implementation scoped to tenant dashboard and restore-history confirmation surfaces; do not expand WorkspaceOverviewBuilder in this slice because the workspace overview does not currently restate backup or recovery posture.
  • Reuse TenantBackupHealthAssessment::positiveClaimBoundary as the canonical backup-health boundary on summary surfaces instead of inventing new copy.
  • Treat only non-dry-run, non-preview, executed restore runs as relevant restore history for overview confidence language.
  • Reuse RestoreSafetyResolver::resultAttentionForRun(...) as the sole authority for failed, partial, completed-with-follow-up, and completed-without-follow-up semantics.
  • Prefer canonical restore-run list drilldowns with a continuity reason banner when no specific run exists or when a direct detail link would be fragile; use restore-run detail only when a recent problematic run exists and is accessible.
  • Keep summary language cautious under RBAC restrictions: if deeper restore evidence cannot be opened, the summary may disable or fall back on the action but must not upgrade the claim.
  • Reuse the existing backup-health list continuity pattern (backup_health_reason) for restore-history continuity rather than adding a new UI shell or route family.
  • Extend existing dashboard, restore-run, and restore-attention tests instead of creating a new browser-level harness.

Phase 1 Design

Design artifacts are created under /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/184-dashboard-recovery-honesty/:

  • research.md: implementation decisions, constraints, and alternatives for recovery-posture honesty
  • data-model.md: existing evidence models and the derived overview recovery-posture projection
  • contracts/dashboard-recovery-posture.openapi.yaml: internal reference contract for tenant dashboard and restore-history drilldown surfaces
  • quickstart.md: focused validation workflow for honest dashboard recovery posture

Design decisions:

  • Keep backup health and recovery evidence separate on the dashboard: backup posture remains the backup-input truth; a dedicated recovery-evidence summary or equivalent dashboard qualifier carries the restore-history truth.
  • Suppress all-clear semantics when relevant restore history is absent by making no-history its own derived overview condition rather than folding it into healthy backup posture.
  • Use existing per-run RestoreResultAttention output to decide whether recent restore history weakens confidence; do not duplicate that logic with raw status checks in widgets.
  • Add restore-history continuity to ListRestoreRuns via a query-string reason and subheading pattern analogous to ListBackupSets and ListBackupSchedules.
  • Make restore-run list rows more self-confirming by surfacing result-attention summary in a default-visible column or equivalent default-visible fact.
  • Reuse existing ViewRestoreRun result-attention presentation for detailed drilldown confirmation instead of building a new recovery detail page.
  • Leave workspace overview unchanged in this spec to avoid introducing a second summary surface with partially duplicated semantics; verify instead that no new contradiction is introduced.

Project Structure

Documentation (this feature)

specs/184-dashboard-recovery-honesty/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── dashboard-recovery-posture.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md

Source Code (repository root)

apps/platform/
├── app/
│   ├── Filament/
│   │   ├── Pages/
│   │   │   └── TenantDashboard.php
│   │   ├── Widgets/
│   │   │   └── Dashboard/
│   │   │       ├── DashboardKpis.php
│   │   │       └── NeedsAttention.php
│   │   └── Resources/
│   │       ├── RestoreRunResource.php
│   │       └── RestoreRunResource/
│   │           └── Pages/
│   │               ├── ListRestoreRuns.php
│   │               └── ViewRestoreRun.php
│   ├── Models/
│   │   ├── BackupSet.php
│   │   ├── RestoreRun.php
│   │   └── OperationRun.php
│   └── Support/
│       ├── BackupHealth/
│       │   ├── TenantBackupHealthAssessment.php
│       │   └── TenantBackupHealthResolver.php
│       ├── RestoreSafety/
│       │   ├── RestoreResultAttention.php
│       │   └── RestoreSafetyResolver.php
│       └── OperationRunLinks.php
├── resources/views/
│   └── filament/widgets/dashboard/
│       └── needs-attention.blade.php
└── tests/
    ├── Feature/
    │   ├── Filament/
    │   │   ├── DashboardKpisWidgetTest.php
    │   │   ├── NeedsAttentionWidgetTest.php
    │   │   ├── RestoreResultAttentionSurfaceTest.php
    │   │   ├── RestoreRunUiEnforcementTest.php
    │   │   ├── DashboardRecoveryPosturePerformanceTest.php
    │   │   └── RestoreRunListContinuityTest.php
    │   └── Rbac/
    │       └── DashboardRecoveryPostureVisibilityTest.php
    └── Unit/
        └── Support/
            └── RestoreSafety/
                └── RestoreResultAttentionTest.php

Structure Decision: Standard Laravel monolith inside apps/platform. The implementation stays inside existing dashboard widgets, existing restore-run resources or pages, existing restore-safety support classes, and existing test directories. No new root folders, no new panel providers, and no new cross-cutting framework are planned.

Implementation Strategy

Phase A — Derive Relevant Restore History Without A New Engine

Goal: Decide whether restore evidence is absent, weakened, or merely non-proving by reusing existing restore-safety truth and a minimal tenant-scoped history query.

Step File Change
A.1 apps/platform/app/Support/RestoreSafety/RestoreSafetyResolver.php or co-located dashboard widget methods Add the minimal tenant-scoped query seam needed to determine relevant restore history, capped to the 10 most recent restore-run candidates and eager-loading only the relations required for the latest relevant visible executed run and its applicable RestoreResultAttention, without introducing a new persistent recovery-confidence model
A.2 Existing dashboard widgets only Keep derived state local and lightweight: no new persisted field, no new enum, and no cross-domain recovery framework; if shared logic is unavoidable, keep it inside the existing restore-safety support namespace or a narrow local helper
A.3 apps/platform/tests/Unit/Support/RestoreSafety/RestoreResultAttentionTest.php and a focused new unit test if needed Cover preview-only exclusion, no-history detection, latest-run precedence, and weak-history precedence over older calmer history

Phase B — Surface Honest Recovery Language On The KPI Strip

Goal: Make the tenant dashboard scan distinguish backup-input health from recovery evidence in the first glance.

Step File Change
B.1 apps/platform/app/Filament/Widgets/Dashboard/DashboardKpis.php Keep the existing Backup posture stat as backup-input truth and append the visible backup claim boundary where the summary could otherwise sound stronger than the evidence
B.2 apps/platform/app/Filament/Widgets/Dashboard/DashboardKpis.php Add a recovery-evidence stat or equivalent adjacent summary signal that expresses unvalidated, weakened, or no_recent_issues_visible without ever claiming recovery proof
B.3 apps/platform/app/Filament/Widgets/Dashboard/DashboardKpis.php Route KPI drilldowns to canonical restore-run list or detail surfaces, with disabled or fallback behavior when a more specific drilldown is unavailable or inappropriate

Phase C — Integrate Recovery Honesty Into Needs Attention And Healthy Checks

Goal: Ensure healthy backup posture does not collapse into a quiet all-clear when restore evidence is missing or concerning.

Step File Change
C.1 apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php Add a recovery-family attention item for no relevant restore history that explains why confidence is unvalidated and points to restore history
C.2 apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php Add a recovery-family attention item for recent failed, partial, or completed_with_follow_up restore evidence using existing RestoreResultAttention semantics
C.3 apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php Add a healthy-check entry for no recent restore issues visible that still preserves the non-proof boundary, and prevent healthy backup messaging from becoming an unqualified all-clear
C.4 apps/platform/resources/views/filament/widgets/dashboard/needs-attention.blade.php only if needed Reuse the existing attention and healthy-check rendering shape; only adjust the lead copy if the current calm sentence would still overclaim once recovery checks are present

Phase D — Restore History Drillthrough Continuity

Goal: Make every summary link land on a restore-history surface that confirms the same truth the dashboard just stated.

Step File Change
D.1 apps/platform/app/Filament/Resources/RestoreRunResource/Pages/ListRestoreRuns.php Add backup_health_reason-style continuity support with a recovery_posture_reason subheading so no history and list-fallback links remain self-explanatory
D.2 apps/platform/app/Filament/Resources/RestoreRunResource.php Add a default-visible result-attention summary column or equivalent row fact derived through RestoreSafetyResolver::resultAttentionForRun(...) so the restore-run list immediately confirms failed, partial, and follow-up states
D.3 apps/platform/app/Filament/Resources/RestoreRunResource/Pages/ViewRestoreRun.php Reuse the existing result-attention section as the canonical detail confirmation surface; add only lightweight context copy if a dashboard-linked reason is not already obvious
D.4 apps/platform/app/Support/OperationRunLinks.php and widget link builders only if needed Keep canonical list and detail routing intact; prefer list fallback when a direct run link is brittle, unavailable, or semantically weaker than the restore-history list context

Phase E — Regression Protection And Focused Verification

Goal: Lock the honesty boundary into automated tests without widening the feature into a larger recovery-confidence program.

Step File Change
E.1 apps/platform/tests/Feature/Filament/DashboardKpisWidgetTest.php Cover healthy backups with no relevant restore history, claim-boundary visibility on the KPI strip, and recovery-evidence summary behavior
E.2 apps/platform/tests/Feature/Filament/NeedsAttentionWidgetTest.php Cover no-history attention, failed or partial or follow-up escalation, healthy-check fallback, and non-overclaim behavior
E.3 apps/platform/tests/Feature/Filament/RestoreRunListContinuityTest.php Add continuity coverage for recovery_posture_reason list drilldowns, especially no_history fallback and weak-history fallback
E.4 apps/platform/tests/Feature/Filament/RestoreResultAttentionSurfaceTest.php Confirm dashboard drilldowns land on restore surfaces that show the same result-attention reason and claim boundary
E.5 apps/platform/tests/Feature/Rbac/DashboardRecoveryPostureVisibilityTest.php and apps/platform/tests/Feature/Filament/RestoreRunUiEnforcementTest.php Prove readonly members still see cautious summary truth, in-scope members lacking restore-history view receive 403 on drillthrough while the dashboard stays truthful, and non-members still receive 404 behavior
E.6 cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent and focused Pest runs Required formatting and targeted verification before implementation is considered complete

Key Design Decisions

D-001 — No Relevant Restore History Is Derived From Executed Runs Only

Preview-only, dry-run, draft, scoped, checked, and previewed restore records already carry a boundary that they do not prove execution. The overview should therefore treat them as insufficient evidence rather than as calming history.

D-002 — Backup Health And Recovery Evidence Stay Orthogonal

TenantBackupHealthAssessment remains the source of backup-input truth. Recovery honesty is a second derived summary that references restore history without changing what backup health means.

D-003 — Existing Claim Boundaries Stay Canonical

The backup positive-claim boundary already exists in TenantBackupHealthAssessment, and per-run restore claim boundaries already exist in RestoreResultAttention. The plan reuses those boundaries instead of inventing a second copy system.

D-004 — Continuity Uses Existing List-Subheading Patterns

The repo already uses query-string continuity copy on list pages (backup_health_reason) to explain why a dashboard drillthrough landed on a list instead of a detail page. Restore history will use the same pattern rather than a new shell, modal, or route family.

D-005 — Workspace Overview Stays Unchanged In This Slice

Current workspace overview logic surfaces governance, compare, findings, alerts, and operations, but not backup or recovery posture. Not changing it is the smallest way to avoid introducing contradictory portfolio-level semantics in this spec.

D-006 — Readonly Users Can Still Validate The Claim Boundary

Existing tests show readonly tenant members can open restore-run history while mutations stay disabled. The plan uses that existing view-rights shape to preserve honesty for lower-capability operators without widening restore permissions.

Risk Assessment

Risk Impact Likelihood Mitigation
Relevant restore history is misclassified because preview-only or dry-run records are counted as real evidence High Medium Add explicit derivation tests for preview-only exclusion and executed-run selection
Dashboard widgets drift semantically because backup health and restore evidence are derived in two different ways High Medium Centralize the minimal history-selection logic in one existing seam or prove the local duplication is identical through tests
Restore-run list drilldowns do not visibly confirm completed_with_follow_up or other weak-history reasons High Medium Add a default-visible result-attention fact on list rows and a continuity subheading on fallback list views
RBAC-restricted users see calmer summary language because a direct restore detail link is unavailable High Low Keep summary claims independent from link availability and add readonly visibility regression coverage
Dashboard render cost grows due to extra restore-history queries Medium Medium Cap candidate selection to the latest 10 restore runs, eager-load only the required relations in that query, and keep workspace overview out of scope in this slice

Test Strategy

  • Extend the existing dashboard widget tests first instead of creating a new browser-driven harness.
  • Add unit coverage for the relevant restore-history selection and weak-history precedence if a shared restore-safety query seam is introduced.
  • Add feature coverage proving that healthy backup posture plus no relevant restore history never yields an all-clear on the dashboard.
  • Add feature coverage proving that failed, partial, and follow-up restore outcomes become overview-visible attention states.
  • Add continuity tests for dashboard-to-restore-history drilldowns, including list fallback and detail confirmation.
  • Add RBAC regression tests proving readonly members still see cautious truth while non-members remain 404 and restore mutations stay disabled.
  • Add query-shape regression coverage proving recovery-evidence derivation stays capped to the latest 10 candidate restore runs and does not introduce N+1 queries on the dashboard or restore-run list surfaces.
  • Keep all tests Livewire v4 compatible and run the smallest affected subset through Sail before asking for a broader suite run.

Complexity Tracking

No constitution violations are currently justified. The plan deliberately avoids new persistence, new enums, new registries, and new page shells. Any shared derivation added during implementation must stay narrower than a new recovery-confidence subsystem and remain inside existing restore-safety or dashboard seams.

Proportionality Review

  • Current operator problem: Operators can currently read healthy backup summaries as stronger recovery assurance than the product can actually prove when restore history is absent or weak.
  • Existing structure is insufficient because: The backup-health boundary and the per-run restore-result truth already exist, but the tenant dashboard does not currently connect them or preserve the same explanation on drilldown.
  • Narrowest correct implementation: Extend existing dashboard widgets, existing restore-run list/detail seams, and existing restore-safety truth rather than adding a new recovery-confidence model or page.
  • Ownership cost created: Additional widget copy, a narrow restore-history derivation path, one list continuity seam, and focused unit plus feature tests.
  • Alternative intentionally rejected: A full recovery-confidence engine, persisted posture state, new enum family, or standalone recovery page were rejected because they add broader truth and maintenance cost than this honesty slice needs.
  • Release truth: Current-release truth hardening.