TenantAtlas/specs/180-tenant-backup-health/data-model.md
2026-04-07 23:33:38 +02:00

203 lines
10 KiB
Markdown

# 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<string> | 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.