# Data Model: Portfolio Triage Arrival Context ## Overview This feature introduces no new persisted tables or stored entities. The model impact is a small set of request-scoped, derived runtime contracts that connect existing workspace triage truth to tenant-dashboard arrival rendering. ## Existing Source Truths ### Workspace overview attention item **Type**: Existing derived workspace summary payload **Source**: `WorkspaceOverviewBuilder` | Field | Type | Notes | |------|------|-------| | `tenant_route_key` | string | Tenant route identifier already used in workspace attention items | | `family` | string | Existing concern family, such as `backup_health` or `recovery_evidence` | | `title` | string | Operator-facing concern headline | | `body` | string | Operator-facing bounded summary | | `supporting_message` | string or null | Existing claim-boundary or next-step phrasing | | `reason_context.family` | enum | `backup_health` or `recovery_evidence` | | `reason_context.state` | enum | Existing posture state emitted from workspace triage | | `reason_context.reason` | string or null | Existing bounded reason code | | `destination.kind` | string | Existing destination type | | `destination.url` | string or null | Existing destination URL | | `destination.disabled` | bool | Existing capability-aware destination availability | | `destination.helper_text` | string or null | Existing degraded-access explanation | ### Tenant-registry triage state **Type**: Existing query-driven list state **Source**: `ListTenants::applyRequestedTriageIntent()` | Field | Type | Validation | |------|------|------------| | `backup_posture` | array | Sanitized through `TenantResource::sanitizeBackupPostures()` | | `recovery_evidence` | array | Sanitized through `TenantResource::sanitizeRecoveryEvidenceStates()` | | `triage_sort` | string or null | Sanitized through `TenantResource::sanitizeTriageSort()` | ### Existing destination continuity inputs **Type**: Existing route-level continuity params | Surface | Param | Purpose | |--------|-------|---------| | Backup-set list | `backup_health_reason` | Explains why backup detail or backup list was opened | | Restore-run list | `recovery_posture_reason` | Explains why restore history was opened | | Restore-run detail | `recovery_posture_reason` | Explains why a specific restore run was opened | ## New Derived Runtime Contracts ### PortfolioArrivalContext **Type**: Request-scoped value object **Lifecycle**: Decoded from a tenant-dashboard query token, validated against current scope, discarded after the request completes | Field | Type | Validation | |------|------|------------| | `version` | integer | Must match the current token version | | `sourceSurface` | enum | `workspace_overview` or `tenant_registry` | | `tenantRouteKey` | string | Must match the current tenant route or resolve to the same tenant | | `workspaceId` | integer or null | Optional scope binding; when present, must match current workspace context | | `concernFamily` | enum | `backup_health` or `recovery_evidence` | | `concernState` | enum | `absent`, `stale`, `degraded`, `unvalidated`, or `weakened` | | `concernReason` | string or null | Allowlisted per concern family | | `arrivalSummary` | string | Derived bounded explanation of why the operator arrived | | `claimBoundary` | string or null | Derived reminder that arrival reason is not a stronger truth than current evidence supports | | `nextStep` | `NextStepTarget` | Capability-aware navigation target | | `returnTarget` | `ReturnTarget` | Portfolio return link with bounded route and query state | | `currentTruthDelta` | string or null | Optional note when current tenant truth differs from the arrival reason | ### NextStepTarget **Type**: Request-scoped navigation descriptor | Field | Type | Notes | |------|------|-------| | `kind` | enum | `tenant_dashboard`, `backup_sets`, `restore_runs`, `restore_run_detail` | | `label` | string | Operator-facing next-step verb + object label | | `url` | string or null | Null when unavailable under current RBAC | | `disabled` | bool | True when the user can see the arrival block but cannot access the target | | `helperText` | string or null | Truthful reason the next step cannot be opened | | `reasonParam` | array | Existing `backup_health_reason` or `recovery_posture_reason` continuation payload when needed | ### ReturnTarget **Type**: Request-scoped navigation descriptor | Field | Type | Notes | |------|------|-------| | `kind` | enum | `workspace_overview` or `tenant_registry` | | `label` | string | Operator-facing return label | | `url` | string | Canonical return URL | | `filters` | array | Allowlisted triage filters and sort state for registry returns | ## Validation Rules ### Concern family and state compatibility | Concern Family | Allowed States | Allowed Reasons | |---------------|----------------|-----------------| | `backup_health` | `absent`, `stale`, `degraded` | Existing `TenantBackupHealthAssessment` reason constants that justify tenant-opening triage | | `recovery_evidence` | `unvalidated`, `weakened` | Existing restore-safety or recovery-triage reason codes such as `no_history`, `failed`, `partial`, or `completed_with_follow_up` | ### Token decode rules - Empty, malformed, or unsupported-version tokens decode to `null`. - Unknown families, states, reasons, or return kinds decode to `null`. - Tokens bound to a different tenant route or incompatible workspace context decode to `null`. - Successful decode does not replace current tenant truth; it only authorizes rendering of the continuity block. ### Return filter rules - Registry return filters may include only the existing triage keys: `backup_posture`, `recovery_evidence`, and `triage_sort`. - Filter values must pass the same sanitizers used by `ListTenants` on mount. - Unknown or empty filters are dropped from the return target. ## Relationships - One workspace attention item may generate one `PortfolioArrivalContext` when its destination is a tenant-level surface. - One filtered tenant-registry session may generate one `PortfolioArrivalContext` per tenant-open action. - One `PortfolioArrivalContext` owns exactly one `NextStepTarget` and one `ReturnTarget`. - One tenant dashboard request may resolve zero or one valid `PortfolioArrivalContext`. ## Rendering Rules - No valid `PortfolioArrivalContext` means the tenant dashboard renders normally with no continuity block. - A valid `PortfolioArrivalContext` may render even if current tenant truth has changed, but the block must differentiate arrival reason from current truth. - `NextStepTarget.disabled = true` still allows the block to render, but the CTA must be absent or disabled with helper text. - `ReturnTarget` renders only when the origin surface can be reconstituted safely.