# Data Model: Provider-Missing Policy Visibility & Restore Continuity v1 ## Overview This slice changes one existing persisted entity and introduces only derived projections elsewhere. No new table, registry, or artifact family is planned. ## Entity: Policy (existing, modified) **Table**: `policies` ### Fields | Field | Type | Source | Notes | |---|---|---|---| | `id` | bigint | existing | Primary key | | `workspace_id` | bigint | existing | Required ownership anchor | | `tenant_id` | bigint | existing | Required ownership anchor | | `external_id` | string | existing | Provider-facing stable key | | `policy_type` | string | existing | Canonical local policy type | | `ignored_at` | timestamp nullable | existing | Explicit local suppression only after this slice | | `missing_from_provider_at` | timestamp nullable | new | Provider-missing observation in the current supported provider-backed result set | | `last_synced_at` | timestamp nullable | existing | Last successful provider observation/update on this row | ### Invariants - `workspace_id` and `tenant_id` remain required and non-null. - `ignored_at` may only be set or cleared by explicit local suppression flows. - `missing_from_provider_at` may only be set or cleared by sync/provider-observation logic. - A policy row remains persisted and viewable even when `missing_from_provider_at` is set. ### Derived Visibility States | Derived state | `ignored_at` | `missing_from_provider_at` | Meaning | |---|---|---|---| | `active` | null | null | Current policy is locally visible and currently observed in the provider-backed result set | | `ignored_locally` | set | null | Current policy is intentionally hidden/suppressed locally | | `provider_missing` | null | set | Current policy remains local truth but is not currently observed in the supported provider-backed result set | | `ignored_locally_provider_missing` | set | set | Policy is locally suppressed and also currently missing from the provider-backed result set | ### Filter Membership And Precedence - `active` filter returns only `active` rows. - `ignored` filter returns `ignored_locally` and `ignored_locally_provider_missing` rows. - `provider_missing` filter returns `provider_missing` and `ignored_locally_provider_missing` rows. - `all` returns the complete set. - For current backup/export, `provider_missing` wins as the primary `blocked_reason` when both timestamps are present because fresh provider-backed capture is impossible even if the row is also locally ignored. ### State Transition Rules | Event | From | To | Notes | |---|---|---|---| | Local ignore | `active` or `provider_missing` | `ignored_locally` or `ignored_locally_provider_missing` | Explicit operator intent | | Local restore/unignore | `ignored_locally` or `ignored_locally_provider_missing` | `active` or `provider_missing` | Clears only `ignored_at` | | Sync marks missing | `active` or `ignored_locally` | `provider_missing` or `ignored_locally_provider_missing` | Sets only `missing_from_provider_at` | | Sync reappears | `provider_missing` or `ignored_locally_provider_missing` | `active` or `ignored_locally` | Clears only `missing_from_provider_at` | | Sync reclassifies to supported type | any provider-present state | provider-present state | Updates `policy_type` without using ignore semantics | ## Derived Projection: Current Backup Eligibility **Persistence**: none; computed from `Policy` | Field | Type | Meaning | |---|---|---| | `policy_id` | bigint | Policy being evaluated for current backup/export | | `eligible` | boolean | True only when the policy is neither locally ignored nor provider-missing for current-state capture | | `blocked_reason` | string nullable | `ignored_locally` or `provider_missing` when not eligible; `provider_missing` wins when both timestamps are present | | `historical_continuity_available` | boolean | Indicates whether backup history already exists even if current capture is blocked | ### Rules - Fresh provider-backed capture requires `ignored_at = null` and `missing_from_provider_at = null`. - When both timestamps are set, current backup/export MUST return `blocked_reason = provider_missing` and MUST retain local ignore as secondary UI context. - Historical backup existence does not make a provider-missing policy eligible for fresh capture. ## Derived Projection: Restore Continuity **Persistence**: none; computed from `BackupItem` plus optional linked `Policy` | Field | Type | Meaning | |---|---|---| | `backup_item_id` | bigint | Historical item being offered for restore | | `policy_id` | bigint nullable | Linked current policy row when present | | `selectable` | boolean | Restore eligibility of the historical item | | `provider_missing_notice` | boolean | Shows that the current live policy is missing while the historical item remains valid | | `continuity_message` | string nullable | Calm explanation shown in restore selection | ### Rules - Historical restore selection continues to follow `BackupItem` truth. - Provider-missing status on the live policy is descriptive unless an independent restore rule blocks the historical item. ## Audit Payload (existing infrastructure, new event meanings) **Persistence**: existing `audit_logs` | Field | Type | Meaning | |---|---|---| | `action_id` | string | `policy.provider_missing_detected` or `policy.provider_missing_cleared` or equivalent existing action ids if reused | | `workspace_id` | bigint | Existing scope anchor | | `tenant_id` | bigint | Existing scope anchor | | `subject_type` | string | `policy` | | `subject_id` | bigint | Policy id | | `metadata.external_id` | string | Provider-facing stable key | | `metadata.policy_type` | string | Canonical local policy type | | `metadata.transition_at` | timestamp | When the presence transition was observed | ## Out of Scope Data Shapes - No `provider_deleted_at` - No lifecycle history table - No materialized `policy_state` enum column - No new recovery artifact or backup continuity table