TenantAtlas/specs/261-provider-missing-policy-visibility/spec.md
Ahmed Darrazi 91f327a7c2
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m40s
feat(specs/261): add provider missing policy visibility
2026-05-01 22:17:29 +02:00

38 KiB

Feature Specification: Provider-Missing Policy Visibility & Restore Continuity v1

Feature Branch: 261-provider-missing-policy-visibility
Created: 2026-05-01
Status: Ready
Input: User description: "Prepare the next repo-based follow-up under the lifecycle governance candidate: separate provider-missing policy presence from local ignore/delete semantics, keep restore continuity truthful, and avoid SoftDeletes, purge, or broad lifecycle-taxonomy work."

Spec Candidate Check (mandatory - SPEC-GATE-001)

  • Problem: TenantPilot currently collapses at least two different truths into policies.ignored_at: intentional local suppression and sync-driven provider absence or scope loss. That makes policy visibility, backup eligibility, and restore continuity hard to trust.
  • Today's failure: Operators can see a policy as Ignored even when it was not intentionally suppressed, sync can clear ignored_at simply because the provider returns the object again, and backup or restore surfaces cannot distinguish a locally ignored policy from a policy that is only missing from the current supported provider result set.
  • User-visible improvement: Operators can tell whether a policy is intentionally ignored locally or missing from the current supported provider-backed view, and historical backup/restore continuity remains available without masquerading as local deletion.
  • Smallest enterprise-capable version: Add one bounded provider-presence field to policy truth, reserve ignored_at for explicit local suppression only, surface the derived provider-missing state on policy and restore-adjacent surfaces, block current-state backup/export actions for missing policies, and clear provider-missing state when the provider object reappears.
  • Explicit non-goals: No full workspace/tenant lifecycle taxonomy, no SoftDeletes rollout for policies, no purge or retention engine, no new policy hard-delete flow, no multi-object lifecycle framework, no cross-tenant workflow, no customer-facing portal, no billing or workspace-closure work, and no broad managed-object lifecycle engine.
  • Permanent complexity imported: One new tenant-owned provider-presence timestamp on policies, one derived provider-missing state, focused audit semantics for missing/reappeared transitions, and narrow feature/unit coverage across sync, policy, backup, and restore seams.
  • Why now: The active P0-P2 prep queue is already represented by full spec packages through Specs 251-260. The broader Workspace, Tenant & Managed Object Lifecycle Governance v1 candidate is explicitly strategic and not ready, but that same candidate documents Provider-Missing Managed Object Truth v1 and the narrower fallback Provider-Missing Policy Visibility & Restore Continuity v1 if provider-missing policy behavior becomes an immediate product bug. Current repo truth already shows that bug: ignored_at is reused by local policy deletion, sync reclassification, provider result filtering, backup eligibility, and sync revival tests.
  • Why not local: A page-local badge or one-off restore exemption would still leave the canonical truth split across Policy, PolicySyncService, policy actions, backup selection, restore selection, and audit behavior. This slice has to converge those shared seams or the product will keep saying two incompatible things about the same policy.
  • Approval class: Core Enterprise
  • Red flags triggered: New lifecycle-like field, cross-surface semantics, and a smaller follow-up chosen ahead of the full lifecycle taxonomy. Defense: the slice stays strictly policy-only, adds only one provider-presence field instead of a general lifecycle framework, keeps workspace/tenant closure and retention out of scope, and is justified by a current runtime truth bug rather than future-proofing.
  • Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 1 | Gesamt: 10/12
  • Decision: approve

Spec Scope Fields (mandatory)

  • Scope: tenant
  • Primary Routes:
    • /admin/t/{tenant}/policies
    • existing tenant-scoped policy detail surfaces reached from the policies resource
    • existing tenant-scoped backup creation and backup-set policy-picker surfaces that choose current policies for snapshotting
    • existing tenant-scoped restore-run creation surfaces that select historical backup items for restore continuity
  • Data Ownership:
    • Policy remains tenant-owned truth and keeps required workspace_id and tenant_id anchors.
    • This slice adds one tenant-owned provider-observation field, missing_from_provider_at, on policies. It records that the policy is no longer observed in the current supported provider-backed result set for its canonical policy type.
    • ignored_at remains tenant-owned local suppression truth only.
    • PolicyVersion, BackupSet, BackupItem, and existing restore-run truth remain unchanged and continue to own historical snapshot and recovery evidence.
  • RBAC:
    • Existing workspace membership and tenant entitlement remain the first isolation boundary.
    • Existing policy, backup, and restore capabilities remain authoritative; this slice adds no new role or capability family.
    • Non-members and out-of-scope tenant access remain deny-as-not-found.
    • Members missing the existing manage, backup, or restore capability continue to receive forbidden semantics on the affected actions.

Cross-Cutting / Shared Pattern Reuse (mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write N/A - no shared interaction family touched)

  • Cross-cutting feature?: yes
  • Interaction class(es): status messaging, table filters and badges, row/header actions, backup eligibility messaging, restore-item selection descriptions, audit labels
  • Systems touched: Policy, PolicySyncService, policy list/detail actions, current backup creation and backup-set item selection, restore-item selection, and the existing audit logger path
  • Existing pattern(s) to extend: existing policy visibility filter/action family, existing restore-item selection/description builders, existing backup eligibility rules, and existing audit logging
  • Shared contract / presenter / builder / renderer to reuse: PolicyResource, RestoreRunResource restore-item option builders, BackupService, and the current workspace/tenant audit logger path remain the shared seams to extend
  • Why the existing shared path is sufficient or insufficient: the existing policy, backup, and restore helpers already centralize the relevant queries and action affordances. What is missing is one truthful provider-presence distinction inside those shared seams, not a new ghost-policy service or lifecycle framework.
  • Allowed deviation and why: none
  • Consistency impact: Ignored, Provider missing, backup blocked messaging, restore continuity notes, and reappearance wording must mean the same thing across policy list, policy detail, backup selection, restore selection, and sync-driven audit entries.
  • Review focus: reviewers must verify that sync and provider reclassification stop writing ignored_at, that missing policies remain visible and historically restorable, and that no surface keeps using ignored_at as a catch-all proxy for provider absence.
  • Touches OperationRun start/completion/link UX?: yes
  • Shared OperationRun UX contract/layer reused: existing policy Sync and policy export/backup queued-start UX remain authoritative when an action is still allowed
  • Delegated start/completion UX behaviors: when a provider-missing policy still allows an explicit sync retry, the current queued toast and Open operation link behavior remain unchanged. When a missing policy is not eligible for current-state backup/export, the local action is blocked before any run is created.
  • Local surface-owned behavior that remains: policy, backup, and restore surfaces only explain provider-missing continuity and action eligibility. They do not create a new run type or a new recovery workflow.
  • Queued DB-notification policy: unchanged
  • Terminal notification path: unchanged for existing sync and backup runs
  • Exception required?: none

Provider Boundary / Platform Core Check (mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write N/A - no shared provider/platform boundary touched)

  • Shared provider/platform boundary touched?: yes
  • Boundary classification: mixed
  • Seams affected: policy persistence truth, sync result interpretation, provider-presence vocabulary, restore continuity wording, and local suppression semantics
  • Neutral platform terms preserved or introduced: provider missing, local ignore, current provider-backed state, historical restore continuity, supported provider result set
  • Provider-specific semantics retained and why: Microsoft Graph list results and provider-specific subtype filtering remain provider-owned evidence inside the sync layer. They only determine whether the policy is currently observed in the supported provider result set.
  • Why this does not deepen provider coupling accidentally: the persisted field records neutral provider-presence truth on the local policy record. Provider-specific endpoint or OData logic stays inside the existing sync layer and does not leak into operator-facing labels beyond the neutral Provider missing meaning.
  • Follow-up path: the broader lifecycle taxonomy and any future provider-deleted distinction remain separate follow-up specs

UI / Surface Guardrail Impact (mandatory when operator-facing surfaces are changed; otherwise write N/A)

Surface / Change Operator-facing surface change? Native vs Custom Shared-Family Relevance State Layers Touched Exception Needed? Low-Impact / N/A Note
Tenant policies list and detail yes Native Filament + shared policy primitives filters, badges, row actions, detail messaging table, row action, detail, helper copy no Keeps current policy resource as the single current-state policy surface
Tenant backup creation and backup-set policy selection yes Native Filament + shared backup primitives action gating, policy picker descriptions action modal, picker, validation state no Missing policies stop pretending they can produce a fresh provider-backed snapshot
Tenant restore-run item selection yes Native Filament + shared restore primitives restore continuity messaging, item descriptions wizard, grouped options, descriptions no Historical backup items remain visible without being conflated with ignored policies

Decision-First Surface Role (mandatory when operator-facing surfaces are changed)

Surface Decision Role Human-in-the-loop Moment Immediately Visible for First Decision On-Demand Detail / Evidence Why This Is Primary or Why Not Workflow Alignment Attention-load Reduction
Tenant policies list and detail Primary Decision Surface Decide whether the policy is actively managed, intentionally ignored, or missing from the current provider-backed view current visibility state, display name, type, last sync, and dominant next action historical versions, backup history, and related restore context after opening detail Primary because operators decide what to do with a policy here before entering backup or restore flows Keeps current-state policy truth on the policy surface instead of outsourcing it to restore screens Removes guessing whether Ignored means local suppression or provider absence
Tenant backup creation and backup-set policy selection Secondary Context Surface Decide whether a current policy can still be snapshotted now eligible versus blocked policies and the provider-missing reason when blocked existing backup history and related policy detail Secondary because backup selection follows the current policy decision rather than replacing it Keeps backup selection focused on current provider-backed capture eligibility Prevents avoidable backup failures and manual policy-by-policy checking
Tenant restore-run item selection Secondary Recovery Surface Decide whether historical backup content should be restored even when the current provider object is missing grouped historical items, provider-missing continuity note, and restore mode linked policy detail, backup metadata, and later restore preview Secondary because it is only relevant once the operator is already in a recovery flow Aligns restore continuity with historical backup truth rather than current provider presence Removes the false impression that a missing policy cannot be restored from history

Audience-Aware Disclosure (mandatory when operator-facing surfaces are changed)

Surface Audience Modes In Scope Decision-First Default-Visible Content Operator Diagnostics Support / Raw Evidence One Dominant Next Action Hidden / Gated By Default Duplicate-Truth Prevention
Tenant policies list and detail operator-MSP, support-platform provider-missing versus ignored state, last observed sync timing, and safe next action provider result-set interpretation, subtype mismatch context, and historical versions raw provider payloads and low-level sync traces remain secondary Sync or Restore local visibility depending on state raw provider details and support diagnostics remain hidden/gated Policy surfaces own current-state meaning; restore surfaces only add historical continuity
Tenant backup creation and backup-set policy selection operator-MSP current capture eligibility and the provider-missing reason when blocked last observed timing and existing backup availability raw provider request detail remains hidden Export to Backup when eligible detailed provider diagnostics stay off picker rows Backup surfaces state only capture eligibility; they do not restate full policy lifecycle semantics
Tenant restore-run item selection operator-MSP historical restore eligibility plus provider-missing continuity note backup quality, version metadata, and restore mode raw payloads stay secondary in later restore detail Continue restore low-level provider diagnostics stay hidden Restore surfaces own historical continuity only and do not replace current-state policy truth

UI/UX Surface Classification (mandatory when operator-facing surfaces are changed)

Surface Action Surface Class Surface Type Likely Next Operator Action Primary Inspect/Open Model Row Click Secondary Actions Placement Destructive Actions Placement Canonical Collection Route Canonical Detail Route Scope Signals Canonical Noun Critical Truth Visible by Default Exception Type / Justification
Tenant policies list and detail List / Table / Detail CRUD / List-first Resource Open a policy and decide whether to sync, ignore, or restore local visibility full-row click to policy detail required grouped under More where already used existing confirmation-protected ignore/restore actions only /admin/t/{tenant}/policies existing tenant-scoped policy detail route tenant context, visibility state, policy type, last sync Policies / Policy whether the policy is active, ignored locally, or missing from provider none
Tenant backup creation and backup-set policy selection Contextual action family Action modal / picker Create or extend a backup with eligible current policies explicit action modal and picker forbidden existing helper text and picker descriptions only no new destructive action existing tenant backup set and backup creation routes existing backup-set detail route tenant context, provider-backed eligibility, backup quality Backup selection / Policy snapshot whether a policy can still be snapshotted now none
Tenant restore-run item selection Wizard / Selection Recovery selector Select historical items to restore grouped checkbox or selection list forbidden descriptions and later preview only no destructive action on the selection step existing tenant restore-run collection route existing restore-run creation and preview routes tenant context, backup set, restore mode, historical continuity Restore items / Backup item whether a historical item remains selectable even when current provider presence is missing none

Operator Surface Contract (mandatory when operator-facing surfaces are changed)

Surface Primary Persona Decision / Operator Action Supported Surface Type Primary Operator Question Default-visible Information Diagnostics-only Information Status Dimensions Used Mutation Scope Primary Actions Dangerous Actions
Tenant policies list and detail Tenant operator Decide what the current policy state means and what action is still safe List/detail Is this policy intentionally ignored, currently missing from provider, or still active? visibility state, type, platform, last sync, and safe next action subtype mismatch context, history, and provider-backed evidence local suppression, provider presence, last observed state TenantPilot only until an allowed sync is started Sync, Ignore, Restore Ignore
Tenant backup creation and backup-set policy selection Tenant operator Decide whether a current policy can be added to a fresh snapshot Action modal/picker Can I still create a current backup of this policy right now? eligible/blocked state, provider-missing reason, and existing backup availability deeper history and provider diagnostics only when needed provider presence, backup quality, restore mode TenantPilot only until current backup export begins Export to Backup, Add to Backup Set none
Tenant restore-run item selection Tenant operator Decide whether historical backup data should be restored despite current provider absence Wizard selector Can I still restore this historical policy item even if the live object is missing now? grouped item labels, backup quality, provider-missing continuity note version metadata and later restore preview historical continuity, restore mode, provider presence TenantPilot only in later restore flow Continue restore none

Proportionality Review (mandatory when structural complexity is introduced)

  • New source of truth?: yes - one new tenant-owned provider-presence timestamp on policies
  • New persisted entity/table/artifact?: no
  • New abstraction?: no
  • New enum/state/reason family?: yes - one derived provider-presence state family built from ignored_at plus missing_from_provider_at
  • New cross-domain UI framework/taxonomy?: no
  • Current operator problem: current policy, backup, and restore workflows cannot truthfully say whether a policy was intentionally suppressed or simply dropped out of the supported provider result set.
  • Existing structure is insufficient because: ignored_at already carries local suppression and sync-driven absence semantics, so any local fix would keep the shared query and action seams contradictory.
  • Narrowest correct implementation: add exactly one provider-presence field on policy truth, reserve ignored_at for user suppression, and update the existing shared sync, policy, backup, and restore seams to consume those two truths consistently.
  • Ownership cost: one new migration, small query/copy adjustments, audit entries for missing/reappeared transitions, and focused tests.
  • Alternative intentionally rejected: the full lifecycle taxonomy was rejected as too broad for this current bug. A local badge-only workaround was rejected because it would not fix sync, backup, restore, and audit semantics together.
  • Release truth: current-release truth driven by a repo-visible runtime ambiguity, not future-release platform preparation.

Compatibility posture

This feature assumes a pre-production environment.

Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.

Canonical replacement is preferred over preservation.

Testing / Lane / Runtime Impact (mandatory for runtime behavior changes)

  • Test purpose / classification: Feature, Unit
  • Validation lane(s): fast-feedback, confidence
  • Why this classification and these lanes are sufficient: the business truth is server-side and sits in policy sync, policy UI, backup eligibility, and restore continuity. Focused feature coverage plus a few narrow unit assertions on derived provider-presence semantics are enough to prove the slice.
  • New or expanded test families: expand the existing policy sync, policy action, backup selection, and restore item selection families; keep one small unit family for provider-presence semantics if the implementation adds a bounded helper
  • Fixture / helper cost impact: low to moderate. Reuse existing tenant, policy, backup set, backup item, and restore-run fixtures. Avoid provider-wide browser harnesses or new heavy support fixtures.
  • Heavy-family visibility / justification: none
  • Special surface test profile: standard-native-filament, shared-detail-family
  • Standard-native relief or required special coverage: standard Filament feature coverage is sufficient for policy, backup, and restore surfaces. No browser proof is required.
  • Reviewer handoff: reviewers must confirm that ignored_at is no longer written/cleared by sync-driven provider absence logic, that missing policies stay historically restorable, that current backup actions do not pretend missing policies can snapshot live state, and that tenant isolation plus 404 versus 403 semantics stay unchanged.
  • Budget / baseline / trend impact: low feature-local increase only
  • Escalation needed: none
  • Active feature PR close-out entry: Guardrail
  • Planned validation commands:
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Jobs/PolicySyncProviderMissingSemanticsTest.php tests/Feature/Jobs/PolicySyncIgnoredRevivalTest.php tests/Feature/Jobs/AppProtectionPolicySyncFilteringTest.php tests/Feature/PolicySyncServiceTest.php tests/Feature/PolicySyncEnrollmentConfigurationTypeCollisionTest.php tests/Feature/PolicySyncStartSurfaceTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/PolicyProviderMissingUiTest.php tests/Feature/PolicyGeneralViewTest.php tests/Feature/BulkDeletePoliciesTest.php tests/Feature/BulkUnignorePoliciesTest.php tests/Unit/Badges/PolicyBadgesTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/BulkExportToBackupTest.php tests/Feature/Filament/BackupCreationTest.php tests/Feature/Filament/BackupSetPolicyPickerTableTest.php tests/Feature/Filament/RestoreItemSelectionTest.php tests/Feature/RestoreUnknownPolicyTypeSafetyTest.php

Scope Boundaries

In Scope

  • one new missing_from_provider_at field on policies
  • reserving ignored_at for explicit local suppression only
  • sync semantics for policies that disappear from the supported provider-backed result set for their canonical policy type
  • policy list/detail visibility that distinguishes local ignore from provider-missing state
  • current backup eligibility changes so provider-missing policies are not treated as live snapshot candidates
  • restore continuity messaging and selection behavior for historical backup items tied to provider-missing policies
  • explicit auditability for provider-missing and provider-reappeared transitions

Non-Goals

  • full managed-object lifecycle taxonomy
  • workspace or tenant suspension, closure, deletion, retention, or purge
  • SoftDeletes or local deletion semantics for policies
  • a new PolicyLifecycleService, GhostPolicyRegistry, or other broad framework
  • multi-object rollout of provider-presence semantics beyond policies
  • explicit provider_deleted_at semantics when the provider cannot yet prove hard deletion distinctly from absence in the supported result set
  • customer-facing read-only surfaces, cross-tenant portfolio workflows, or billing-state overlays

Dependencies

  • existing policy sync and canonical policy-type reclassification logic in PolicySyncService
  • existing policy list/detail action surfaces in PolicyResource
  • existing backup selection and backup-set creation flows in BackupService and related Filament surfaces
  • existing restore-item option builders in RestoreRunResource
  • existing audit logging infrastructure
  • explicit sequencing awareness with Spec 251, which owns workspace commercial lifecycle and must remain separate from this policy-level provider-presence slice

Assumptions

  • the current provider integration can truthfully detect when a previously seen policy is no longer present in the supported provider-backed result set for its canonical type, even if it cannot always distinguish hard deletion from provider-side scope change
  • last_synced_at continues to mean last successful provider observation for the current local policy row; it does not become a generic heartbeat field for missing policies
  • local policy suppression must remain explicit and reversible by operator action rather than being cleared automatically by a later sync result
  • historical PolicyVersion and BackupItem records already provide enough restore continuity evidence that no new recovery persistence family is needed
  • Filament v5 on Livewire v4 remains the UI substrate, and no panel/provider registration change is required

Risks

  • repo code and tests currently assume sync can revive ignored policies, so the change may reveal hidden dependencies on the old conflated meaning
  • some provider subtype or canonical-type transitions may still need careful wording so Provider missing does not overstate certainty about hard deletion
  • backup and restore surfaces could drift again if one seam continues to query only ignored_at after the new provider-presence field lands
  • policy actions could become misleading if current-state backup is blocked without also surfacing the historical restore path clearly

Candidate Selection Rationale

  • Selected candidate: Provider-Missing Policy Visibility & Restore Continuity v1
  • Source locations:
    • docs/product/spec-candidates.md under Workspace, Tenant & Managed Object Lifecycle Governance v1, especially the documented follow-up Provider-Missing Managed Object Truth v1 and the explicit narrower fallback Provider-Missing Policy Visibility & Restore Continuity v1
    • docs/product/roadmap.md lifecycle-governance boundary that rejects a broad ghost-policy patch before the full taxonomy is agreed
    • current repo truth in apps/platform/app/Models/Policy.php, apps/platform/app/Services/Intune/PolicySyncService.php, apps/platform/app/Services/Intune/BackupService.php, apps/platform/app/Filament/Resources/PolicyResource.php, apps/platform/app/Filament/Resources/RestoreRunResource.php, and the related policy sync/restore tests
  • Why selected: the active prep queue from P0 through P2 is already represented by Specs 251-260. The broader lifecycle-governance candidate remains too strategic for immediate prep, but the repo already documents a narrower provider-missing follow-up and current code clearly shows a live ignored_at conflation bug that affects policy, backup, and restore truth.
  • Why this is the smallest viable implementation slice: it touches only policy provider-presence truth and the directly dependent backup/restore seams, introduces one bounded field, and keeps workspace lifecycle, purge, retention, and multi-object rollout deferred.
  • Intentional narrowing from source candidate: this slice does not define a general lifecycle taxonomy. It only separates provider-missing policy presence from local suppression and preserves restore continuity where historical backups already exist.
  • Why close alternatives are deferred:
    • the full Workspace, Tenant & Managed Object Lifecycle Governance v1 candidate remains too broad and explicitly strategic
    • Workspace & Tenant Closure Lifecycle v1 is a later follow-up once the broader lifecycle taxonomy is ready
    • Cross-Tenant Compare and Promotion v1 already has refreshed spec work in Spec 043 and is not the next open prep target here
    • Spec 251 already owns workspace commercial state and would be a duplicate vehicle for broader lifecycle semantics

Follow-up Candidates

  • the full Workspace, Tenant & Managed Object Lifecycle Governance v1 taxonomy
  • explicit provider_deleted_at versus missing_from_provider_at distinction if the provider surface later proves hard deletion truth cleanly
  • multi-object rollout of provider-presence semantics beyond policies
  • export-before-delete and retention governance after the shared lifecycle taxonomy is agreed

User Scenarios & Testing (mandatory)

User Story 1 - Tell provider missing apart from local ignore (Priority: P1)

As a tenant operator, I want policy surfaces to show whether a policy is intentionally ignored locally or simply missing from the current supported provider-backed result set so I can choose the right next action.

Why this priority: This is the core trust fix. If operators still have to guess what Ignored means, the slice has failed.

Independent Test: Can be fully tested by syncing policies into missing and locally ignored states, then opening the existing policy resource and verifying the two states render differently and keep their own actions.

Acceptance Scenarios:

  1. Given a previously observed policy is no longer returned in the supported provider-backed result set, When an entitled operator opens the policy list/detail, Then the policy renders as provider-missing rather than locally ignored.
  2. Given a policy was intentionally ignored by the operator, When a later sync still sees the same provider object, Then the sync does not clear the local ignore state automatically.

User Story 2 - Keep restore continuity honest (Priority: P1)

As a tenant operator, I want missing policies to remain historically restorable from existing backups without being treated as active live policies for new snapshots.

Why this priority: The product already has backup and restore truth. The slice must preserve that value while stopping misleading current-state actions.

Independent Test: Can be fully tested by marking a policy provider-missing, then verifying that current backup selection blocks it while historical backup items for that policy remain selectable in restore creation.

Acceptance Scenarios:

  1. Given a policy is provider-missing but has existing backup history, When the operator enters the restore-item selection flow, Then the historical backup items remain selectable with explicit provider-missing continuity messaging.
  2. Given a policy is provider-missing, When the operator attempts a current-state backup/export flow, Then the product keeps that policy visible but blocked with an explicit provider-missing reason instead of pretending it is a normal active policy.

User Story 3 - Record and clear provider-missing transitions predictably (Priority: P2)

As an operator or support reviewer, I want provider-missing and reappeared transitions to be auditable so later policy history makes sense without new lifecycle frameworks.

Why this priority: The state is only trustworthy if later reviewers can see why it changed and when it returned.

Independent Test: Can be fully tested by running sync once with the policy missing and once with the policy reappearing, then checking the local policy state and audit evidence.

Acceptance Scenarios:

  1. Given a previously observed policy disappears from the supported provider result set, When sync completes, Then the policy remains local, receives missing_from_provider_at, and records an audit entry for the provider-missing transition.
  2. Given a provider-missing policy appears again in a later sync, When sync completes, Then missing_from_provider_at clears, local ignore state remains untouched if it exists, and an audit entry records the reappearance.

Edge Cases

  • A policy may be locally ignored and later also become provider-missing. Local suppression remains the primary local control, while provider-missing stays secondary continuity context.
  • A combined-state policy appears in both the ignored and provider_missing filter views so operators can reach it from either workflow.
  • A policy may move between supported canonical policy types. Where the canonical type can still be resolved, the record should be reclassified instead of marked provider-missing.
  • A provider subtype can fall out of the current supported result set without proving hard deletion. The slice must not overstate that as local deletion.
  • A backup item may reference a policy row that no longer exists locally. Historical restore continuity must keep following existing BackupItem truth rather than depending on a current policy row.
  • Wrong-tenant access or missing capability must keep the current 404 versus 403 semantics even when provider-missing copy is added.

Requirements (mandatory)

Constitution alignment (required): This feature touches existing sync and backup behavior but introduces no new provider call family and no new OperationRun type. It must keep tenant isolation, current sync observability, and auditability intact while avoiding any new purge or deletion behavior.

Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001): The slice introduces one new persisted provider-presence field and one derived state family because the current product truth is already wrong without it. A narrower UI-only workaround is insufficient because shared sync, backup, restore, and audit seams would stay contradictory. No new registry, resolver, strategy system, or lifecycle engine is justified.

Constitution alignment (XCUT-001): Policy visibility, backup eligibility, restore continuity, and audit copy are cross-cutting interaction classes already served by shared paths. This slice must land in those shared seams rather than through page-local conditionals.

Constitution alignment (RBAC-UX): Membership and tenant entitlement remain the isolation boundary. Non-members stay 404, members lacking surviving manage/restore capability stay 403, and provider-missing business-state messaging must not be used as an authorization substitute.

Constitution alignment (PROV-001): Provider-specific detection stays inside the provider-backed sync layer. The persisted and operator-facing truth remains neutral provider-presence language rather than Microsoft endpoint semantics.

Constitution alignment (TEST-GOV-001): Proof stays in focused feature and unit lanes. No browser or heavy-governance family is needed for this slice.

Functional Requirements

  • FR-261-001: The system MUST reserve policies.ignored_at for explicit local suppression only and MUST NOT set or clear it as a side effect of provider absence or provider result-set loss.
  • FR-261-002: The system MUST persist provider-missing observation on the policy record through missing_from_provider_at when a previously observed policy is no longer present in the current supported provider-backed result set for its canonical policy type.
  • FR-261-003: A provider-missing policy MUST remain visible in tenant policy surfaces as a local historical record and MUST NOT be treated as hard-deleted or soft-deleted.
  • FR-261-004: Policy list/detail surfaces MUST present provider-missing state distinctly from local ignore state, with truthful action guidance for each state.
  • FR-261-005: Current backup/export flows that depend on live provider-backed policy state MUST keep provider-missing policies visible but blocked with explicit reason text instead of treating them as normal active policies.
  • FR-261-006: Existing restore flows backed by BackupItem history MUST continue to offer historical restore continuity for provider-missing policies when the historical item is otherwise eligible.
  • FR-261-007: When sync later observes a provider-missing policy again, the system MUST clear missing_from_provider_at and keep any local ignore state unchanged.
  • FR-261-008: Where sync can reclassify the same external object into another supported canonical policy type, it MUST reclassify the local row rather than marking the policy ignored.
  • FR-261-009: The system MUST emit explicit audit evidence when a policy first becomes provider-missing and when it later reappears.
  • FR-261-010: The slice MUST NOT introduce SoftDeletes, a new deletion action, a purge path, or a general managed-object lifecycle framework.
  • FR-261-011: When both ignored_at and missing_from_provider_at are set, the policy MUST remain discoverable from both ignored and provider-missing filter views, and current backup/export surfaces MUST use provider_missing as the primary blocked reason while retaining local ignore as secondary context.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: Zero sync paths continue to write or clear ignored_at for provider-missing semantics after the slice lands.
  • SC-002: Policy surfaces and visibility filters show distinct provider-missing versus locally ignored states, including the combined-state rule, for 100% of covered validation scenarios.
  • SC-003: Provider-missing policies are excluded from current backup eligibility while historical restore continuity remains available in 100% of covered restore-selection scenarios.
  • SC-004: Reappearance of a provider-missing policy clears the provider-missing flag without automatically restoring local ignore state in 100% of covered sync scenarios.