TenantAtlas/specs/261-provider-missing-policy-visibility/data-model.md
ahmido feeaadd5ad feat: add provider-missing policy visibility and restore continuity (#316)
## Summary
- separate provider-missing policy presence from local ignore semantics by introducing `missing_from_provider_at`
- update policy, backup, and restore surfaces so current-state capture stays honest while historical restore continuity remains available
- add focused sync, Filament, backup, restore, localization, and badge coverage for the new provider-missing behavior

## Scope
- policy sync and model truth
- policy resource visibility, badges, labels, and action gating
- backup/export eligibility and restore continuity messaging
- spec 261 artifacts and focused tests

## Validation
- feature-specific Pest coverage is included in the branch
- validation was not re-run as part of this commit/push/PR handoff

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #316
2026-05-01 20:18:27 +00:00

113 lines
5.9 KiB
Markdown

# 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