# Feature 005: Policy Lifecycle Management ## Overview Implement proper lifecycle management for policies that are deleted in Intune, including soft delete, UI indicators, and orphaned policy handling. ## Problem Statement Currently, when a policy is deleted in Intune: - ❌ Policy remains in TenantAtlas database indefinitely - ❌ No indication that policy no longer exists in Intune - ❌ Backup Items reference "ghost" policies - ❌ Users cannot distinguish between active and deleted policies **Discovered during**: Feature 004 manual testing (user deleted policy in Intune) ## Goals - **Primary**: Implement soft delete for policies removed from Intune - **Secondary**: Show clear UI indicators for deleted policies - **Tertiary**: Maintain referential integrity for Backup Items and Policy Versions ## Scope - **Policy Sync**: Detect missing policies during `SyncPoliciesJob` - **Data Model**: Add `deleted_at`, `deleted_by` columns (Laravel Soft Delete pattern) - **UI**: Badge indicators, filters, restore capability - **Audit**: Log when policies are soft-deleted and restored --- ## User Stories ### User Story 1 - Automatic Soft Delete on Sync **As a system administrator**, I want policies deleted in Intune to be automatically marked as deleted in TenantAtlas, so that the inventory reflects the current Intune state. **Acceptance Criteria:** 1. **Given** a policy exists in TenantAtlas with `external_id` "abc-123", **When** the next policy sync runs and "abc-123" is NOT returned by Graph API, **Then** the policy is soft-deleted (sets `deleted_at = now()`) 2. **Given** a soft-deleted policy, **When** it re-appears in Intune (same `external_id`), **Then** the policy is automatically restored (`deleted_at = null`) 3. **Given** multiple policies are deleted in Intune, **When** sync runs, **Then** all missing policies are soft-deleted in a single transaction --- ### User Story 2 - UI Indicators for Deleted Policies **As an admin**, I want to see clear indicators when viewing deleted policies, so I understand their status. **Acceptance Criteria:** 1. **Given** I view a Backup Item referencing a deleted policy, **When** I see the policy name, **Then** it shows a red "Deleted" badge next to the name 2. **Given** I view the Policies list, **When** I enable the "Show Deleted" filter, **Then** deleted policies appear with: - Red "Deleted" badge - Deleted date in "Last Synced" column - Grayed-out appearance 3. **Given** a policy was deleted, **When** I view the Policy detail page, **Then** I see: - Warning banner: "This policy was deleted from Intune on {date}" - All data remains readable (versions, snapshots, metadata) --- ### User Story 3 - Restore Workflow **As an admin**, I want to restore a deleted policy from backup, so I can recover accidentally deleted configurations. **Acceptance Criteria:** 1. **Given** I view a deleted policy's detail page, **When** I click the "Restore to Intune" action, **Then** the restore wizard opens pre-filled with the latest policy snapshot 2. **Given** a policy is successfully restored to Intune, **When** the next sync runs, **Then** the policy is automatically undeleted in TenantAtlas (`deleted_at = null`) --- ## Functional Requirements ### Data Model **FR-005.1**: Policies table MUST use Laravel Soft Delete pattern: ```php Schema::table('policies', function (Blueprint $table) { $table->softDeletes(); // deleted_at $table->string('deleted_by')->nullable(); // admin email who triggered deletion }); ``` **FR-005.2**: Policy model MUST use `SoftDeletes` trait: ```php use Illuminate\Database\Eloquent\SoftDeletes; class Policy extends Model { use SoftDeletes; } ``` ### Policy Sync Behavior **FR-005.3**: `PolicySyncService::syncPolicies()` MUST detect missing policies: - Collect all `external_id` values returned by Graph API - Query existing policies for this tenant: `whereNotIn('external_id', $currentExternalIds)` - Soft delete missing policies: `each(fn($p) => $p->delete())` **FR-005.4**: System MUST restore policies that re-appear: - Check if policy exists with `Policy::withTrashed()->where('external_id', $id)->first()` - If soft-deleted: call `$policy->restore()` - Update `last_synced_at` timestamp **FR-005.5**: System MUST log audit entries: - `policy.deleted` (when soft-deleted during sync) - `policy.restored` (when re-appears in Intune) ### UI Display **FR-005.6**: PolicyResource table MUST: - Default query: exclude soft-deleted policies - Add filter "Show Deleted" (includes `withTrashed()` in query) - Show "Deleted" badge for soft-deleted policies **FR-005.7**: BackupItemsRelationManager MUST: - Show "Deleted" badge when `policy->trashed()` returns true - Allow viewing deleted policy details (read-only) **FR-005.8**: Policy detail view MUST: - Show warning banner when policy is soft-deleted - Display deletion date and reason (if available) - Disable edit actions (policy no longer exists in Intune) --- ## Non-Functional Requirements **NFR-005.1**: Soft delete MUST NOT break existing features: - Backup Items keep valid foreign keys - Policy Versions remain accessible - Restore functionality works for deleted policies **NFR-005.2**: Performance: Sync detection MUST NOT cause N+1 queries: - Use single `whereNotIn()` query to find missing policies - Batch soft-delete operation **NFR-005.3**: Data retention: Soft-deleted policies MUST be retained for audit purposes (no automatic purging) --- ## Implementation Plan ### Phase 1: Data Model (30 min) 1. Create migration for `policies` soft delete columns 2. Add `SoftDeletes` trait to Policy model 3. Run migration on dev environment ### Phase 2: Sync Logic (1 hour) 1. Update `PolicySyncService::syncPolicies()` - Track current external IDs from Graph - Soft delete missing policies - Restore re-appeared policies 2. Add audit logging 3. Test with manual deletion in Intune ### Phase 3: UI Indicators (1.5 hours) 1. Update `PolicyResource`: - Add "Show Deleted" filter - Add "Deleted" badge column - Modify query to exclude deleted by default 2. Update `BackupItemsRelationManager`: - Show "Deleted" badge for `policy->trashed()` 3. Update Policy detail view: - Warning banner for deleted policies - Disable edit actions ### Phase 4: Testing (1 hour) 1. Unit tests: - Test soft delete on sync - Test restore on re-appearance 2. Feature tests: - E2E sync with deleted policies - UI filter behavior 3. Manual QA: - Delete policy in Intune → sync → verify soft delete - Re-create policy → sync → verify restore **Total Estimated Duration**: ~4-5 hours --- ## Risks & Mitigations | Risk | Mitigation | |------|------------| | Foreign key constraints block soft delete | Laravel soft delete only sets timestamp, constraints remain valid | | Bulk delete impacts performance | Use chunked queries if tenant has 1000+ policies | | Deleted policies clutter UI | Default filter hides them, "Show Deleted" is opt-in | --- ## Success Criteria 1. ✅ Policies deleted in Intune are soft-deleted in TenantAtlas within 1 sync cycle 2. ✅ Re-appearing policies are automatically restored 3. ✅ UI clearly indicates deleted status 4. ✅ Backup Items and Versions remain accessible for deleted policies 5. ✅ No breaking changes to existing features --- ## Related Features - Feature 004: Assignments & Scope Tags (discovered this issue during testing) - Feature 001: Backup/Restore (must work with deleted policies) --- **Status**: Planned (Post-Feature 004) **Priority**: P2 (Quality of Life improvement) **Created**: 2025-12-22 **Author**: AI + Ahmed **Next Steps**: Implement after Feature 004 Phase 3 testing complete