# Data Model: Tenant Backup Health Signals ## Overview This feature does not add or change a top-level persisted domain entity. It introduces a narrow derived tenant backup-health model over existing tenant-owned backup and schedule records and integrates that derived truth into dashboard and drillthrough surfaces. The central design task is to make the tenant dashboard answer four operator-visible states without changing: - `BackupSet`, `BackupItem`, or `BackupSchedule` ownership - existing backup-quality source-of-truth rules - existing backup or schedule route identity - existing mutation, audit, or `OperationRun` responsibilities - the no-new-table and no-recovery-score boundary of the feature ## Existing Persistent Entities ### 1. BackupSet - Purpose: Tenant-owned backup collection that records capture lifecycle state and groups captured backup items. - Existing persistent fields used by this feature: - `id` - `tenant_id` - `workspace_id` - `name` - `status` - `item_count` - `metadata` - `completed_at` - `created_at` - Existing relationships used by this feature: - `tenant` - `items` - `restoreRuns` #### Notes - `completed_at` is the primary timestamp used to decide whether a completed backup basis exists and whether it is fresh enough. - No new `backup_health` or `freshness_state` column is introduced on `backup_sets`. ### 2. BackupItem - Purpose: Tenant-owned captured recovery input for one backed-up policy or foundation record. - Existing persistent fields used by this feature: - `id` - `tenant_id` - `backup_set_id` - `payload` - `assignments` - `metadata` - `captured_at` - Existing relationships used by this feature: - `tenant` - `backupSet` - `policyVersion` #### Existing metadata signals used by this feature | Key | Type | Meaning | |---|---|---| | `source` | string or null | Direct source marker; may indicate metadata-only capture | | `snapshot_source` | string or null | Copied source marker from a linked policy version | | `warnings` | array | Warning messages that may imply metadata-only fallback or other quality concerns | | `assignments_fetch_failed` | boolean | Assignment capture failed for the item | | `assignment_capture_reason` | string or null | Explanatory capture reason; not all values are degradations | | `has_orphaned_assignments` | boolean | One or more captured assignment targets were orphaned | | `integrity_warning` | string or null | Existing integrity or redaction warning carried into the backup item | ### 3. BackupSchedule - Purpose: Tenant-owned schedule configuration for automated backup execution. - Existing persistent fields used by this feature: - `id` - `tenant_id` - `workspace_id` - `name` - `is_enabled` - `frequency` - `time_of_day` - `days_of_week` - `policy_types` - `last_run_status` - `last_run_at` - `next_run_at` - `timezone` - `created_at` - Existing relationships used by this feature: - `tenant` - `operationRuns` #### Notes - `last_run_at`, `last_run_status`, and `next_run_at` are sufficient to derive `schedule_follow_up`. - No new `schedule_health_state` or `backup_health_state` column is introduced. ## Derived Inputs ### 1. BackupHealthConfig This is configuration, not persistence. | Key | Type | Purpose | |---|---|---| | `tenantpilot.backup_health.freshness_hours` | integer | Defines the canonical age window for the latest relevant completed backup basis | | `tenantpilot.backup_health.schedule_overdue_grace_minutes` | integer | Defines how far past `next_run_at` an enabled schedule may drift before the feature raises `schedule_follow_up` | ## Derived Models All derived models in this section are lightweight concrete value objects in `app/Support/BackupHealth/`. The concrete file set is `TenantBackupHealthAssessment.php`, `BackupFreshnessEvaluation.php`, `BackupScheduleFollowUpEvaluation.php`, `BackupHealthActionTarget.php`, and `BackupHealthDashboardSignal.php`, with `TenantBackupHealthResolver.php` orchestrating them. ### 1. TenantBackupHealthAssessment Tenant-level backup-health truth used by dashboard and drillthrough logic. | Field | Type | Source | Notes | |---|---|---|---| | `tenantId` | integer | tenant context | Identity | | `posture` | string | derived | One of `absent`, `stale`, `degraded`, `healthy` | | `primaryReason` | string or null | derived | One of `no_backup_basis`, `latest_backup_stale`, `latest_backup_degraded`, or `schedule_follow_up`; `null` only when posture is healthy and no remaining follow-up reason exists | | `headline` | string | derived | Primary operator-facing summary for dashboard surfaces | | `supportingMessage` | string or null | derived | Secondary operator-facing explanation | | `latestRelevantBackupSetId` | integer or null | derived | The backup set that currently governs posture | | `latestRelevantCompletedAt` | datetime or null | derived from `BackupSet.completed_at` | Null when no usable completed basis exists | | `qualitySummary` | `BackupQualitySummary` or null | reused `BackupQualityResolver` output | Reused quality truth for the latest relevant backup basis | | `freshnessEvaluation` | `BackupFreshnessEvaluation` | derived | Separates recency truth from degradation truth | | `scheduleFollowUp` | `BackupScheduleFollowUpEvaluation` | derived | Secondary caution about automation timing | | `healthyClaimAllowed` | boolean | derived | True only when the evidence supports a positive healthy statement and no unresolved `schedule_follow_up` remains | | `primaryActionTarget` | `BackupHealthActionTarget` | derived | Reason-driven drillthrough destination | | `positiveClaimBoundary` | string | derived | Explains that backup health does not imply recoverability or restore success | ### 2. BackupFreshnessEvaluation Derived recency truth for the latest relevant completed backup basis. | Field | Type | Source | Notes | |---|---|---|---| | `latestCompletedAt` | datetime or null | `BackupSet.completed_at` | Null when no usable completed basis exists | | `cutoffAt` | datetime | derived from config and `now()` | The point after which a backup basis becomes stale | | `isFresh` | boolean | derived | False when no basis exists or when `latestCompletedAt` is older than `cutoffAt` | | `policySource` | string | derived | Initially `configured_window` to make the canonical freshness rule explicit and testable | #### Rules - If there is no relevant completed backup basis, the feature does not produce `fresh`; it produces `absent`. - If a latest relevant completed backup basis exists but predates `cutoffAt`, the primary posture becomes `stale`. - `stale` outranks `degraded` as a primary posture when both conditions are true. ### 3. BackupScheduleFollowUpEvaluation Derived automation follow-up truth for enabled schedules. | Field | Type | Source | Notes | |---|---|---|---| | `hasEnabledSchedules` | boolean | derived from `BackupSchedule.is_enabled` | False when no enabled schedules exist | | `enabledScheduleCount` | integer | derived | Informational count | | `overdueScheduleCount` | integer | derived from `next_run_at` and grace window | Counts enabled schedules that appear overdue | | `failedRecentRunCount` | integer | derived from `last_run_status` | Counts enabled schedules whose recent status indicates failure or warning | | `neverSuccessfulCount` | integer | derived from `last_run_at` and schedule timing | Counts enabled schedules with no success evidence even though they should already have produced it | | `needsFollowUp` | boolean | derived | True when schedule timing or status indicates operator review | | `primaryScheduleId` | integer or null | derived | Optional single record for direct continuity if the result is unambiguous | | `summaryMessage` | string or null | derived | Operator-facing explanation of the schedule caution | #### Rules - Schedule follow-up is secondary. It adds attention but does not upgrade a tenant into `healthy`. - Enabled schedules with `next_run_at` past the grace window or with missing success evidence after the schedule should already have produced its first successful run count toward `schedule_follow_up`. ### 4. BackupHealthActionTarget Reason-driven drillthrough target chosen by the resolver. | Field | Type | Notes | |---|---|---| | `surface` | string | `backup_sets_index`, `backup_set_view`, or `backup_schedules_index` | | `recordId` | integer or null | Used only when the target is a specific backup set | | `label` | string | Operator-facing action label such as `Open backup sets` or `Open latest backup` | | `reason` | string | The problem class the destination is expected to confirm | ### 5. BackupHealthDashboardSignal Shared dashboard-facing model for KPI, attention, and healthy-check rendering. | Field | Type | Source | |---|---|---| | `title` | string | derived | | `body` | string | derived | | `tone` | string | derived | | `badge` | string or null | derived via shared badge primitives when the signal is rendered as an attention item | | `badgeColor` | string or null | derived via shared badge primitives when the signal is rendered as an attention item | | `actionTarget` | `BackupHealthActionTarget` or null | derived | | `actionDisabled` | boolean | derived from authorization and target availability | | `helperText` | string or null | derived | ## Validation Rules - `absent` applies when no usable completed backup basis exists for the tenant. - `stale` applies when the latest relevant completed backup basis exists but fails the freshness rule, regardless of whether it is also degraded. - `degraded` applies when the latest relevant completed backup basis is fresh enough but existing `BackupQualitySummary` shows material degradation. - `healthy` applies when a latest relevant completed backup basis exists, passes freshness, and shows no material degradation. - `schedule_follow_up` is additive and must never be used as proof of health. - If `schedule_follow_up` is the only remaining caution, posture may remain `healthy`, but `primaryReason` must be `schedule_follow_up` and `healthyClaimAllowed` must be `false` until the follow-up resolves. - Existing `BackupQualitySummary` produced by `BackupQualityResolver` remains the authority for degradation families and degraded item counts. - Dashboard summary truth must remain visible to entitled tenant-dashboard viewers even when a downstream action target is suppressed or disabled.