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

278 lines
25 KiB
Markdown

# 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)
```text
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)
```text
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.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.