TenantAtlas/specs/261-provider-missing-policy-visibility/research.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

5.1 KiB

Research: Provider-Missing Policy Visibility & Restore Continuity v1

Decision 1: Persist provider-missing observation on policies via missing_from_provider_at

  • Decision: Add one nullable timestamp, missing_from_provider_at, to the existing policies table.
  • Rationale: The current ambiguity is persisted on the policy row itself. The narrowest truthful fix is therefore another field on that same row, not a second table or lifecycle engine.
  • Why this is enough: The slice only needs to know whether a previously observed policy is currently absent from the supported provider-backed result set. A timestamp captures that fact while preserving historical local truth.
  • Alternatives considered:
    • Reuse ignored_at: rejected because it keeps local suppression and provider absence conflated.
    • Add SoftDeletes: rejected because provider absence is not local deletion.
    • Add a lifecycle history table: rejected as premature for this policy-only follow-up.

Decision 2: Keep ignored_at user-owned and stop sync from reviving ignored policies

  • Decision: Treat ignored_at as explicit local suppression only. Sync may clear missing_from_provider_at on reappearance, but it must not clear ignored_at automatically.
  • Rationale: Existing bulk delete and restore flows already use ignore() and unignore() as user intent. A later provider sync should not silently reverse that operator choice.
  • Repo anchor: ../../apps/platform/app/Jobs/Operations/PolicyBulkDeleteWorkerJob.php currently treats ignore as local deletion, so sync revival is the inconsistent behavior, not the bulk action.
  • Alternatives considered:
    • Preserve PolicySyncIgnoredRevivalTest semantics: rejected because it keeps local suppression non-deterministic.
    • Auto-unignore when a provider object reappears: rejected because it makes provider observation override explicit local intent.

Decision 3: Current backup/export requires provider-present policy truth, but historical restore continuity remains available

  • Decision: Current backup/export flows keep provider-missing policies visible but blocked, while restore creation continues to offer historical BackupItem continuity when otherwise eligible.
  • Rationale: A fresh snapshot depends on current provider-backed state. Historical restore depends on already captured backup truth. Those are different claims and should not share one eligibility gate.
  • Repo anchor: ../../apps/platform/app/Services/Intune/BackupService.php currently only checks ignored_at; ../../apps/platform/app/Filament/Resources/RestoreRunResource.php already has a historical-item grouping seam that can carry provider-missing continuity messaging.
  • Alternatives considered:
    • Keep current backup/export behavior for missing policies: rejected because it pretends a fresh provider-backed snapshot is still possible.
    • Hide missing policies from restore flows too: rejected because it throws away existing backup value and breaks recovery continuity.

Decision 4: Audit provider-missing and reappeared transitions through the existing audit path

  • Decision: Reuse the current audit infrastructure for two explicit transition events: provider-missing detected and provider-missing cleared.
  • Rationale: Reviewers need to understand why a policy is missing now and when it returned later. The current audit path is already the bounded place for that truth.
  • Why no new OperationRun: The transition is a side effect of existing sync work, not a new long-running workflow or operator-owned run family.
  • Alternatives considered:
    • No audit changes: rejected because the state would be hard to explain later.
    • New lifecycle event subsystem: rejected as broader than the slice needs.

Decision 5: Keep the existing policy resource as the primary current-state surface

  • Decision: Extend the existing PolicyResource list/view surfaces instead of adding a ghost-policy page or lifecycle registry.
  • Rationale: Operators already decide what a policy means from the current policy surface. Adding another page would duplicate vocabulary and spread the bug fix across more UI.
  • Repo anchor: ../../apps/platform/app/Filament/Resources/PolicyResource.php already owns list/view routes, action availability, and visibility filters.
  • Alternatives considered:
    • New diagnostics page for provider-missing policies: rejected as unnecessary surface expansion.
    • Backup or restore pages owning current-state explanation: rejected because those are secondary flows.

Open Follow-Up Questions Deferred Intentionally

  • Distinguishing provider_deleted_at from generic provider absence remains a later follow-up.
  • Rolling provider-presence semantics out to other managed objects remains a later follow-up.
  • Full lifecycle taxonomy and retention or purge policy remain blocked on the broader lifecycle spec.