# Data Model: Entra Group Directory Cache (Groups v1) ## Entities ### EntraGroup Represents a cached directory group for a single tenant (metadata only). **Fields (conceptual)** - `id` (internal) - `tenant_id` (FK) - `entra_group_id` (GUID/string; external stable identifier) - `display_name` (string) - `group_type` (enum/string, derived; e.g., `Security`, `Microsoft365`, `Unknown`) - `security_enabled` (bool, nullable) - `mail_enabled` (bool, nullable) - `group_types` (array/JSONB, nullable) - `last_seen_at` (timestamp) - `last_seen_run_id` (FK to sync run, optional) - `created_at`, `updated_at` **Constraints & indexes** - Unique: (`tenant_id`, `entra_group_id`) - Index: (`tenant_id`, `display_name`) for search - Index: (`tenant_id`, `last_seen_at`) for stale/retention filtering ### EntraGroupSyncRun Append-only record for one sync attempt. **Fields (conceptual)** - `id` - `tenant_id` (FK) - `initiated_by_user_id` (FK nullable for scheduled) - `selection_key` (string; deterministic for Groups v1 per tenant) - `status` (enum: `pending`, `running`, `succeeded`, `failed`) - `started_at`, `finished_at` - counters: `observed_count`, `upserted_count`, `error_count` - `error_category` (string/enum; e.g., `permission`, `throttling`, `transient`, `unknown`) - `error_summary` (safe string) - `created_at`, `updated_at` **Constraints & indexes** - Index: (`tenant_id`, `created_at`) - Index: (`tenant_id`, `status`) - Optional unique/idempotency: (`tenant_id`, `selection_key`, `status in active`) in code using locks. ## Relationships - `Tenant` 1 → N `EntraGroup` - `Tenant` 1 → N `EntraGroupSyncRun` - `EntraGroupSyncRun` 1 → N `EntraGroup` (via `last_seen_run_id`, optional) ## State transitions - `pending` → `running` → `succeeded` | `failed` ## Retention - Purge rule: delete `EntraGroup` where `last_seen_at` < now - 90 days. - Stale classification: stale if `last_seen_at` < now - N days (default N = 30).