TenantAtlas/specs/187-portfolio-triage-arrival-context/plan.md
2026-04-09 23:29:20 +02:00

25 KiB

Implementation Plan: Portfolio Triage Arrival Context

Branch: 187-portfolio-triage-arrival-context | Date: 2026-04-09 | Spec: /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/187-portfolio-triage-arrival-context/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/187-portfolio-triage-arrival-context/spec.md

Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.

Summary

Preserve portfolio triage intent when operators move from workspace recovery surfaces or filtered tenant-registry triage into tenant-level destinations by adding one small request-scoped arrival-context contract, propagating it through existing destination URLs, and rendering a compact top-of-page continuity block on the tenant dashboard. The implementation will reuse existing workspace reason_context, existing tenant-registry query-driven triage filters, existing backup/restore list continuity params, and existing capability-aware destination helpers so the feature stays additive, truthful, and free of new persistence or new posture computation.

Technical Context

Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4, Pest v4, existing WorkspaceOverviewBuilder, TenantResource, TenantDashboard, CanonicalAdminTenantFilterState, TenantBackupHealthAssessment, RestoreSafetyResolver, and continuity-aware backup or restore list pages
Storage: PostgreSQL unchanged; no new tables, caches, or durable workflow artifacts
Testing: Pest 4 unit and feature tests, shared feature test concerns where reuse is warranted, and Livewire component coverage, run through Laravel Sail
Target Platform: Laravel monolith web application in Sail locally and containerized Linux deployment in staging and production
Project Type: web application
Performance Goals: No additional external calls at render time; keep the feature DB-only; append arrival context to existing URLs without regressing current drilldown routing; decode and resolve continuity once per request; keep workspace overview and tenant-registry changes query-neutral because they only append bounded URL state; avoid new N+1 queries on tenant-dashboard renders
Constraints: Request-scoped only; no persistence; no new recovery-truth model; generic tenant sessions remain unchanged; next-step guidance must stay RBAC-safe; arrival context must be bookmark-safe and fail closed to the generic tenant experience when invalid
Scale/Scope: One lightweight continuity abstraction spanning three operator surfaces: workspace overview triage, tenant-registry triage, and tenant dashboard arrival, while reusing existing backup and restore destinations for deeper follow-up

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 only carries existing backup-health and recovery-evidence truth into tenant arrival; no new source-of-truth path is introduced.
Read/write separation PASS PASS The slice is read-only. No new write, restore, or operational start surface is added.
Graph contract path N/A N/A No Microsoft Graph calls, provider contracts, or config/graph_contracts.php changes are required.
Deterministic capabilities PASS PASS Existing capability checks remain authoritative for next-step access and destination degradation.
Workspace + tenant isolation PASS PASS Arrival context is scoped to the active workspace and tenant route, and invalid or out-of-scope context degrades to the generic dashboard.
RBAC-UX authorization semantics PASS PASS Non-members remain 404; members missing deeper capabilities receive degraded guidance rather than false CTAs.
Run observability / Ops-UX PASS PASS No new OperationRun, no change to notification surfaces, and no long-running work.
Data minimization PASS PASS Only bounded reason codes, posture states, return filters, and route-safe context travel in the request; nothing new is persisted.
Proportionality / no premature abstraction PASS WITH JUSTIFIED ABSTRACTION PASS WITH JUSTIFIED ABSTRACTION One small arrival-context value object plus token codec and resolver are justified because multiple existing surfaces already share the same triage vocabulary but lose it at navigation boundaries.
Persisted truth / behavioral state PASS PASS No new table, persisted entity, or new posture state family is introduced.
UI semantics / few layers PASS PASS The continuity block reuses existing domain truth and claim boundaries instead of creating a new presentation framework.
Filament v5 / Livewire v4 compliance PASS PASS The design stays inside the existing Filament v5 + Livewire v4 stack. No legacy API is introduced.
Provider registration location PASS PASS No panel or provider registration changes are required. Laravel 11+ provider registration remains in bootstrap/providers.php.
Global search hard rule PASS PASS No global search behavior changes are proposed. Impacted searchable resources already satisfy the rule: TenantResource has View and Edit pages, BackupSetResource has a View page, and RestoreRunResource has a View page.
Destructive action safety PASS PASS No new destructive action is added. All new CTAs are navigation-only.
Asset strategy PASS PASS No new assets are introduced. Existing deploy behavior remains unchanged, including cd apps/platform && php artisan filament:assets when registered assets are in play elsewhere.
Filament-native UI / Action Surface Contract PASS PASS The tenant dashboard change is an additive read-only widget or section. Workspace overview and tenant registry keep their existing action models.
Filament UX-001 PASS PASS No create, edit, or view form layout changes are proposed; only summary and arrival guidance surfaces change.
Testing truth (TEST-TRUTH-001) PASS PASS The plan adds workflow and regression tests that protect operator-visible continuity, truth boundaries, return flow, and RBAC-safe degradation.

Phase 0 Research

Research outcomes are captured in /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/187-portfolio-triage-arrival-context/research.md.

Key decisions:

  • Reuse existing workspace reason_context and existing destination payloads as the authoritative inputs for portfolio-arrival context instead of inventing a second concern model.
  • Carry tenant-dashboard arrival intent in one bounded, versioned base64url token, following the repo's existing token pattern, so the context stays request-scoped, bookmark-safe, and easy to ignore when invalid.
  • Preserve the current backup_health_reason and recovery_posture_reason query-param continuity on backup and restore destinations rather than replacing every downstream surface with a new universal token.
  • Render the continuity treatment as the first non-lazy tenant dashboard widget or section so the operator sees it before scanning Recovery Readiness, KPIs, or Needs Attention.
  • Preserve return-to-portfolio flow by reusing existing tenant-registry filter and sort query params and the canonical workspace overview route instead of creating session-backed triage state.
  • Reuse existing capability-aware destination and disabled-helper patterns from WorkspaceOverviewBuilder so the continuity block does not overpromise inaccessible next steps.

Phase 1 Design

Design artifacts are created under /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/187-portfolio-triage-arrival-context/:

  • data-model.md: request-scoped continuity entities, source inputs, validation rules, and derived relationships
  • contracts/portfolio-triage-arrival-context.logical.openapi.yaml: internal logical contract for source surfaces, arrival token shape, and tenant dashboard continuity rendering
  • quickstart.md: focused implementation and verification workflow

Design decisions:

  • The new continuity layer is one derived runtime contract, not a persisted triage session or new recovery-confidence engine.
  • The arrival token carries only bounded routing and concern metadata. Current tenant truth remains authoritative and is re-derived on arrival.
  • WorkspaceOverviewBuilder remains the source of workspace triage reason and destination selection; ListTenants remains the source of filtered tenant-registry return context.
  • Existing backup-set and restore-run continuity params remain authoritative for deeper follow-up surfaces.
  • The tenant dashboard becomes continuity-aware through an additive top-of-page widget or section, with no redesign of the underlying dashboard architecture.

Project Structure

Documentation (this feature)

specs/187-portfolio-triage-arrival-context/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── portfolio-triage-arrival-context.logical.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md

Source Code (repository root)

apps/platform/
├── app/
│   ├── Filament/
│   │   ├── Pages/
│   │   │   ├── TenantDashboard.php
│   │   │   └── WorkspaceOverview.php
│   │   ├── Resources/
│   │   │   ├── BackupSetResource/
│   │   │   │   └── Pages/ListBackupSets.php
│   │   │   ├── RestoreRunResource/
│   │   │   │   ├── Pages/ListRestoreRuns.php
│   │   │   │   └── Pages/ViewRestoreRun.php
│   │   │   └── TenantResource/
│   │   │       └── Pages/ListTenants.php
│   │   ├── Widgets/
│   │   │   ├── Workspace/
│   │   │   │   ├── WorkspaceNeedsAttention.php
│   │   │   │   └── WorkspaceSummaryStats.php
│   │   │   └── Tenant/
│   │   │       ├── TenantArchivedBanner.php
│   │   │       └── TenantTriageArrivalContinuity.php
│   │   └── Resources/TenantResource.php
│   ├── Support/
│   │   ├── PortfolioTriage/
│   │   │   ├── PortfolioArrivalContext.php
│   │   │   ├── PortfolioArrivalContextToken.php
│   │   │   └── PortfolioArrivalContextResolver.php
│   │   ├── BackupHealth/TenantBackupHealthAssessment.php
│   │   ├── RestoreSafety/RestoreSafetyResolver.php
│   │   └── Workspaces/WorkspaceOverviewBuilder.php
│   └── resources/views/filament/widgets/tenant/
│       └── triage-arrival-continuity.blade.php
└── tests/
    ├── Feature/
    │   ├── Concerns/
    │   │   └── BuildsPortfolioTriageFixtures.php
    │   ├── Filament/
    │   │   ├── WorkspaceOverviewArrivalContextTest.php
    │   │   ├── TenantRegistryArrivalContextTest.php
    │   │   ├── TenantDashboardArrivalContextTest.php
    │   │   ├── TenantDashboardArrivalContextPerformanceTest.php
    │   │   ├── BackupSetListContinuityTest.php
    │   │   ├── RestoreRunListContinuityTest.php
    │   │   ├── WorkspaceOverviewContentTest.php
    │   │   └── TenantRegistryRecoveryTriageTest.php
    │   └── Rbac/
    │       └── TenantDashboardArrivalContextVisibilityTest.php
    └── Unit/
        └── Support/
            └── PortfolioTriage/
                ├── PortfolioArrivalContextTokenTest.php
                └── PortfolioArrivalContextResolverTest.php

Structure Decision: Keep the existing Laravel monolith structure under apps/platform. Add one narrow support namespace for the arrival-context contract and resolver, one additive tenant widget or view for rendering, focused Filament feature tests, narrow unit coverage for the token and resolver seam, and one shared feature-test concern instead of creating a broader navigation framework or new base directories.

Implementation Strategy

Phase A — Introduce the Arrival-Context Runtime Contract

Goal: Add one lightweight, request-scoped continuity contract and a bounded token codec.

Step File Change
A.1 apps/platform/app/Support/PortfolioTriage/PortfolioArrivalContext.php Add the runtime value object that holds source surface, concern family and state, reason, claim boundary, next step, and return target for one tenant-opening action
A.2 apps/platform/app/Support/PortfolioTriage/PortfolioArrivalContextToken.php Add a versioned base64url codec following the existing resume-token pattern, with strict decode-to-null behavior for malformed or unsupported state
A.3 apps/platform/app/Support/PortfolioTriage/PortfolioArrivalContextResolver.php Add the resolver that validates decoded payloads, binds them to the current tenant and workspace scope, and derives capability-aware next-step and return targets

Phase B — Emit Arrival Context From Portfolio Sources

Goal: Append the bounded arrival token only where existing portfolio triage already knows why the tenant should open.

Step File Change
B.1 apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php Extend backup-health and recovery-evidence tenant destinations so tenant-dashboard URLs carry the new arrival token while preserving existing destination semantics and disabled-destination fallbacks
B.2 apps/platform/app/Filament/Widgets/Workspace/WorkspaceNeedsAttention.php and apps/platform/app/Filament/Widgets/Workspace/WorkspaceSummaryStats.php Preserve current card and stat CTA behavior while ensuring any tenant-dashboard target emitted from overview data carries arrival context
B.3 apps/platform/app/Filament/Resources/TenantResource.php and apps/platform/app/Filament/Resources/TenantResource/Pages/ListTenants.php When triage filters are active, append arrival context and return-state data to the existing tenant-open affordance without changing generic list behavior

Phase C — Render The Tenant-Dashboard Continuity Block

Goal: Surface arrival reason, next step, and return path at the top of the tenant dashboard with no dashboard redesign.

Step File Change
C.1 apps/platform/app/Filament/Widgets/Tenant/TenantTriageArrivalContinuity.php Add a non-lazy, full-width widget that resolves optional arrival context from the current request and current tenant scope
C.2 apps/platform/resources/views/filament/widgets/tenant/triage-arrival-continuity.blade.php Render a compact continuity block with bounded title, reason, current-truth boundary, next-step CTA, and return link
C.3 apps/platform/app/Filament/Pages/TenantDashboard.php Register the continuity widget first in the dashboard widget stack so it appears before Recovery Readiness and deeper tenant widgets

Goal: Keep deeper backup and restore surfaces authoritative while ensuring next-step navigation stays truthful under capability limits.

Step File Change
D.1 apps/platform/app/Support/PortfolioTriage/PortfolioArrivalContextResolver.php Map concern families to existing backup-set or restore-run targets and preserve current backup_health_reason and recovery_posture_reason params for downstream continuity
D.2 apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php patterns reused in new resolver or helper Reuse existing disabled-target semantics (disabled, helper_text, null url) so unavailable next-step guidance never looks actionable
D.3 apps/platform/app/Filament/Resources/BackupSetResource/Pages/ListBackupSets.php, apps/platform/app/Filament/Resources/RestoreRunResource/Pages/ListRestoreRuns.php, and apps/platform/app/Filament/Resources/RestoreRunResource/Pages/ViewRestoreRun.php Keep current subheading continuity behavior unchanged and verify the new dashboard continuity flows into these existing destinations without replacing them

Phase E — Preserve Return-To-Portfolio Flow

Goal: Allow operators to resume the originating portfolio triage route without reconstructing filters or context manually.

Step File Change
E.1 apps/platform/app/Support/PortfolioTriage/PortfolioArrivalContextResolver.php Build return targets for workspace overview and tenant-registry triage using bounded route names plus allowlisted filter and sort query params
E.2 apps/platform/app/Filament/Resources/TenantResource/Pages/ListTenants.php Reuse the existing query-driven triage filter contract on re-entry; no new persistence or session-backed return logic
E.3 apps/platform/app/Filament/Widgets/Tenant/TenantTriageArrivalContinuity.php Render the return link only when valid arrival context exists; generic tenant sessions remain calm

Phase F — Regression Protection And Verification

Goal: Prove continuity rendering, route preservation, and RBAC-safe degradation without regressing existing drilldowns.

Step File Change
F.1 apps/platform/tests/Feature/Filament/WorkspaceOverviewArrivalContextTest.php Add focused coverage for workspace overview backup-absent and recovery-evidence drilldowns carrying arrival context into tenant-level destinations
F.2 apps/platform/tests/Feature/Filament/TenantRegistryArrivalContextTest.php Add focused coverage for filtered tenant-registry triage carrying arrival context and preserving return filters and sort
F.3 apps/platform/tests/Feature/Filament/TenantDashboardArrivalContextTest.php Add dashboard-focused coverage for continuity rendering, generic-session suppression, stale-context honesty, and RBAC-safe next-step degradation
F.4 apps/platform/tests/Feature/Filament/WorkspaceOverviewContentTest.php and apps/platform/tests/Feature/Filament/TenantRegistryRecoveryTriageTest.php Extend legacy route-preservation regressions so existing non-dashboard destinations remain authoritative when current routing chooses them
F.5 apps/platform/tests/Feature/Filament/TenantDashboardArrivalContextPerformanceTest.php Add query-shape and request-local resolution coverage that guards against new N+1 behavior and repeated arrival-context resolution on tenant-dashboard renders while overview and registry changes remain URL-only
F.6 apps/platform/tests/Feature/Filament/BackupSetListContinuityTest.php, apps/platform/tests/Feature/Filament/RestoreRunListContinuityTest.php, apps/platform/tests/Feature/Rbac/TenantDashboardArrivalContextVisibilityTest.php, and targeted Sail/Pest/Pint runs Keep current continuity surfaces green and run the complete arrival-context verification pack across new and existing regressions

Key Design Decisions

D-001 — One bounded token beats scattered dashboard query params

The spec explicitly prefers one small arrival-context abstraction. A single versioned base64url token keeps tenant-dashboard arrival state coherent and removable without proliferating ad hoc query params.

D-002 — Existing downstream continuity params remain authoritative

Backup-set and restore-run destinations already explain why the operator landed there via backup_health_reason and recovery_posture_reason. The new feature should feed those surfaces, not replace them.

D-003 — The tenant dashboard should become continuity-aware through an additive widget, not a page rewrite

The dashboard already uses non-lazy tenant banner widgets. Reusing that pattern satisfies the placement requirement while keeping the change narrow and easy to remove.

D-004 — Current tenant truth must remain separate from arrival reason

Arrival context explains why the operator came, but current posture is still recomputed from existing tenant truth. This separation is the main safeguard against stale or misleading continuity copy.

D-005 — Return context should reuse existing tenant-registry query semantics

ListTenants already knows how to sanitize and reapply backup_posture, recovery_evidence, and triage_sort. Reusing that contract avoids session-backed state or a broader navigation system.

Risk Assessment

Risk Impact Likelihood Mitigation
Arrival token becomes stale or hand-edited and shows misleading copy High Medium Strict allowlist decode, bind the token to current tenant and workspace scope, and degrade to the generic dashboard on invalid or unsupported input
Existing workspace or registry drilldown routes regress High Medium Only append continuity context to existing URLs and preserve existing destination kinds, labels, helper text, and regression tests
Return links drop triage filters and collapse into generic browsing Medium Medium Reuse ListTenants triage query contract exactly and add explicit filtered-registry return tests
Next-step CTA overpromises access High Medium Reuse existing capability-aware disabled-destination semantics and add RBAC-limited dashboard arrival tests
Continuity block adds noise to generic sessions Medium Low Render only when arrival context resolves successfully; no token means no block, no return affordance

Test Strategy

  • Add focused unit tests for token encoding, malformed-token handling, allowlisted decode behavior, and resolver scope validation in tests/Unit/Support/PortfolioTriage/PortfolioArrivalContextTokenTest.php and tests/Unit/Support/PortfolioTriage/PortfolioArrivalContextResolverTest.php.
  • Add focused Filament feature tests that cover workspace overview backup-absent and recovery-unvalidated drilldowns into the tenant dashboard with the continuity block visible.
  • Add focused tenant-registry triage tests that prove triage filters and sort survive a tenant-open and a return-to-registry action.
  • Add tenant-dashboard arrival tests that cover backup-absent, backup-stale, backup-degraded, recovery-unvalidated, recovery-weakened, stale-context honesty, multiple-concern wording, generic-session suppression, and RBAC-safe CTA degradation.
  • Add query-shape regression coverage in tests/Feature/Filament/TenantDashboardArrivalContextPerformanceTest.php so arrival-context resolution stays request-local and does not introduce new N+1 behavior on tenant-dashboard renders; workspace overview and tenant-registry changes stay URL-only and are protected through their existing functional regressions rather than a separate performance suite.
  • Keep existing WorkspaceOverviewContentTest, TenantRegistryRecoveryTriageTest, BackupSetListContinuityTest, and RestoreRunListContinuityTest green so source-route preservation and deeper continuity surfaces remain authoritative.
  • No relation managers or destructive actions are added in this slice. Covered Filament surfaces are WorkspaceOverview, ListTenants, TenantDashboard, ListBackupSets, ListRestoreRuns, and ViewRestoreRun.
  • Run the minimum focused Sail pack before completion: cd apps/platform && ./vendor/bin/sail artisan test --compact with the unit tests, the new arrival-context feature tests, the existing workspace overview and tenant-registry regressions, the backup or restore continuity regressions, the RBAC arrival-visibility test, the performance regression, then run Pint with cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent.

Complexity Tracking

Violation Why Needed Simpler Alternative Rejected Because
New request-scoped arrival-context contract and token codec Workspace overview and tenant-registry triage already know why a tenant should open, but the dashboard currently loses that intent at the navigation boundary Referrer-only inference is unreliable, scattered query params would sprawl across surfaces, and persisted triage sessions would add unnecessary storage and lifecycle cost

Proportionality Review

  • Current operator problem: Operators arrive on the correct tenant but lose the portfolio triage reason, next step, and return path as soon as the tenant dashboard loads.
  • Existing structure is insufficient because: Existing workspace attention items, summary metrics, and tenant-registry filters already compute concern family and destination choice, but none of them preserve that intent into the tenant dashboard render.
  • Narrowest correct implementation: Add one small request-scoped arrival-context contract plus a bounded token codec and resolver, then render one additive dashboard continuity block using existing backup or restore destinations for deeper follow-up.
  • Ownership cost created: One small support namespace, one additive dashboard widget or view, small URL propagation changes, and focused regression tests across overview, registry, and dashboard surfaces.
  • Alternative intentionally rejected: Persisted triage sessions, a new recovery-confidence model, and a broad breadcrumb or navigation framework were rejected because they create new truth or new workflow machinery for a narrow continuity problem.
  • Release truth: Current-release truth. The feature hardens an existing operator workflow without adding new posture semantics or persistence.