# Feature Specification: Backup/Restore UI Graph-Safety (Phase 1)

**Feature Branch**: `feat/048-backup-restore-ui-graph-safety`  
**Created**: 2026-01-10  
**Status**: Draft  

## Purpose

Ensure Backup/Restore UI follows the same guardrails as Inventory:

- UI renders DB-only (no Microsoft Graph calls during render/mount/options/typeahead)
- UI still remains usable with safe fallbacks for unresolved external identifiers
- Add programmatic guard tests that fail if Graph is touched while rendering

This phase is intentionally minimal-change and high-safety.

## Clarifications

### Session 2026-01-10

- Q: Which pages must the fail-hard `GraphClientInterface` guard tests render? → A: Backup Sets index + Restore wizard.
- Q: How should `<masked-id>` be formatted in fallback labels? → A: `…<last8>`

### Session 2026-01-11

- Q: How should the fail-hard Graph guard tests be structured? → A: Feature tests that `actingAs(...)` then `GET` the Filament pages’ routes.
- Q: For the feature tests, should we assert only HTTP 200, or also a stable UI marker? → A: Assert HTTP 200 + a stable page marker string.

## Users

- Tenant Admin
- MSP Operator (within authorized tenant scope)

## User Stories

### US1 (P1): Backup Sets Index is Graph-safe

As a tenant admin, I can open the Backup Sets index page and it renders DB-only (no Graph calls during request/render/mount/options/typeahead).

**Maps to**: Scenario 1

### US2 (P1): Restore Wizard is Graph-safe incl. Group Mapping UI

As a tenant admin, I can open the Restore wizard page and the group mapping UI remains usable without any Graph calls (including typeahead/search/label resolution).

**Maps to**: Scenario 2 + Scenario 3

## User Scenarios & Testing

### Scenario 1: Open Backup pages without Graph access
- Given a user is authenticated and has access to a tenant
- When they open the Backup Sets index page
- Then the page renders successfully (HTTP 200) without any Graph calls

### Scenario 2: Open Restore Wizard without Graph access
- Given a user is authenticated and has access to a tenant
- When they open the Restore Run wizard page
- Then the wizard renders successfully (HTTP 200) without any Graph calls

### Scenario 3: Group mapping shows safe fallback labels
- Given the UI displays group IDs (e.g., in mapping fields)
- When the UI cannot resolve group names without Graph
- Then it shows safe fallback labels like:
  - `Unresolved (…<last8>)`
  - `Group (external): …<last8>`

## Stable Page Marker Rules (for Guard Tests)

Guard tests MUST assert a stable, static marker string in addition to HTTP 200.

Marker selection rules:

- MUST be static UI text that is not tenant data (avoid names, IDs, timestamps, counts)
- SHOULD be a column label, section heading, or primary action label rendered by Filament
- MUST be present on the initial GET without requiring any Livewire interaction
- MUST NOT depend on Microsoft Graph availability

Markers MUST be recorded in quickstart.md once chosen.

## Functional Requirements

- FR-001: Backup/Restore UI MUST NOT call Microsoft Graph during render/mount/options/typeahead.
- FR-002: The Restore Wizard group mapping controls MUST NOT call Graph for search results or label resolution.

### Group Mapping UX (DB-only)

When group mapping is required, the UI MUST remain Graph-free and MUST provide a DB-only safe input.

- For each unresolved source group, the UI shows a mapping row with:
  - Source label: `<displayName or "Security group"> (…<last8>)`
  - Target input:
    - Allowed values:
      - `SKIP` (skip assignment)
      - A manually entered Entra ID group objectId (GUID/UUID string)
- Validation rules:
  - If not `SKIP`, the value MUST be a syntactically valid UUID
  - No network lookup/verification is performed in Phase 1 (Graph-free). Existence is validated later during preview/restore execution paths.
- Helper text MUST explain:
  - “Paste the target Entra ID group Object ID (GUID). Names are not resolved in this phase.”
  - “Use SKIP to omit the assignment.”

- FR-003: When names cannot be resolved DB-only, the UI MUST show safe fallback labels using masked identifiers.
  - `<masked-id>` format: `…<last8>` (last 8 characters of the external identifier, prefixed with an ellipsis)
- FR-004: Add fail-hard Pest feature tests binding `GraphClientInterface` to throw on any invocation and verify:
  - Backup Sets index renders OK (HTTP 200 + stable page marker)
  - Restore wizard renders OK (HTTP 200 + stable page marker)

## Non-Functional Requirements

- NFR-001: No new Graph calls are introduced in UI code paths.
- NFR-002: No new tables are added in this phase.
- NFR-003: Changes should be low-risk and minimal surface area.

## Out of Scope

- Moving action handlers (snapshot capture, backup create, restore rerun, restore dry-run) to jobs.
- Creating new cache/inventory tables to support group search.

## Success Criteria

- SC-001: Guard tests reliably fail if any Backup/Restore UI render path touches Graph.
- SC-002: Backup and Restore wizard pages render successfully with Graph disabled.
- SC-003: Group mapping UI remains usable with DB-only fallback labels.

## Related Specs

- Follow-up (Phase 2): Backup/Restore job orchestration (TBD)
