## Summary - preserve portfolio triage arrival context from workspace overview and tenant registry drill-throughs - add a tenant dashboard continuity widget plus bounded arrival token and resolver support - add focused Pest coverage for arrival routing, return flow, RBAC degradation, and request-local performance - include the Spec 187 spec, plan, research, data model, quickstart, contract, and tasks artifacts ## Validation - integrated browser smoke: workspace overview -> tenant dashboard arrival -> backup sets CTA - integrated browser smoke: tenant registry triage -> tenant dashboard arrival -> return to tenant triage - branch includes focused automated test coverage for the new arrival-context surfaces Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #218
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_contextand 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_reasonandrecovery_posture_reasonquery-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
WorkspaceOverviewBuilderso 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 relationshipscontracts/portfolio-triage-arrival-context.logical.openapi.yaml: internal logical contract for source surfaces, arrival token shape, and tenant dashboard continuity renderingquickstart.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.
WorkspaceOverviewBuilderremains the source of workspace triage reason and destination selection;ListTenantsremains 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 |
Phase D — Reuse Existing Deep-Link Continuity And RBAC Degradation
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.phpandtests/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.phpso 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, andRestoreRunListContinuityTestgreen 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, andViewRestoreRun. - Run the minimum focused Sail pack before completion:
cd apps/platform && ./vendor/bin/sail artisan test --compactwith 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 withcd 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.