TenantAtlas/specs/005-policy-lifecycle/spec.md
Ahmed Darrazi 0e42164937 docs(004): Add Graph API permissions documentation
- Created docs/PERMISSIONS.md with complete permission requirements
- Added logging for 403 errors in ScopeTagResolver
- Updated README with link to permissions documentation

Issue: Scope tags show 'Unknown (ID: 0)' due to missing permission
Required: DeviceManagementRBAC.Read.All with admin consent

User must:
1. Go to Azure Portal → App Registration
2. Add DeviceManagementRBAC.Read.All permission
3. Grant admin consent
4. Wait 5-10 min for propagation
5. Clear cache: php artisan cache:clear
2025-12-22 16:03:21 +01:00

7.6 KiB

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:

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:

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

  • 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