# Feature Specification: TenantPilot v1

> **Status:** Historical  
> **Last reviewed:** 2026-04-30  
> **Use for:** Early product-history context and original v1 framing  
> **Do not use for:** Current implementation truth, current roadmap priority, or current spec structure without repo verification

**Feature Branch**: `tenantpilot-v1`  
**Created**: 2025-12-10  
**Status**: Draft  
**Input**: TenantPilot v1 scope covering policy inventory, backup, version history, and defensive restore for Intune administrators.

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Policy inventory listing (Priority: P1)

Admin can view supported Intune policy types with normalized metadata for selection.

**Why this priority**: Inventory is the entry point for backup/version flows. Without it, no downstream workflows are usable.

**Independent Test**: From Filament, navigate to Policies; verify supported types render with identifiers, platform/type metadata, and tenant scoping.

**Acceptance Scenarios**:

1. **Given** an authenticated admin, **When** they open the Policies list, **Then** they see supported policy types with identifiers, platform, and last-updated metadata.
2. **Given** policy filtering by type, **When** the admin selects a type, **Then** only matching policies appear and the view remains tenant-scoped.

---

### User Story 2 - Backup creation and browsing (Priority: P1)

Admin creates backup sets containing multiple policies with immutable snapshots and can browse backup details in Filament.

**Why this priority**: Backups provide safety and enable restore; immutability and audit are foundational.

**Independent Test**: Initiate a backup set selecting multiple policies; confirm immutable JSONB snapshots persisted, audit log written, and Filament shows backup detail and items.


**Acceptance Scenarios**:

1. **Given** selected policies, **When** the admin creates a backup set, **Then** backup items store immutable payload snapshots with policy identifiers and types.
2. **Given** a completed backup set, **When** the admin opens its detail page, **Then** all items and metadata display along with the audit record of creation.

3. **Given** mehrere Backup-Sets existieren,
   **When** der Admin ein Backup-Set auswählen oder ansehen möchte,
   **Then** sieht er für jedes Set:
   - einen sprechenden Namen (nicht nur Timestamp),
   - das Erstellungsdatum,
   - die Anzahl der enthaltenen Items,
   - und optional eine kurze Beschreibung, damit er das Set sinnvoll unterscheiden kann.

---

### User Story 3 - Version history and diff (Priority: P1)

Admin can capture policy versions, view timelines, and compare any two versions with meaningful diffs.

**Why this priority**: Version visibility and diffs enable rollback readiness and change comprehension.

**Independent Test**: Create multiple versions for a policy; verify timeline ordering, version metadata, and diff output (human summary + JSON diff where feasible) between any two versions.

**Acceptance Scenarios**:

1. **Given** an admin triggers version capture, **When** the version is saved, **Then** an immutable snapshot and metadata (actor, time, type, tenant) are recorded.
2. **Given** two versions of the same policy, **When** the admin requests a comparison, **Then** the UI shows a human-readable summary and structured JSON diff where available.

---

### User Story 4 - Restore with preview and confirmation (Priority: P1)

Admin can run a restore from a backup set with preview/dry-run, selective restore, clear warnings, and required confirmation before execution.

**Why this priority**: Restore is high-risk; safety features are mandatory for production readiness.

**Independent Test**: Start a restore from a backup set in preview; view change summary and warnings; select items; confirm execution; verify audit logs and outcomes recorded (success/failure/partial).

**Acceptance Scenarios**:

1. **Given** a backup set, **When** the admin initiates a restore in preview mode, **Then** the system shows a change summary with selectable items and conflict warnings.
2. **Given** selected items and explicit confirmation, **When** execution proceeds, **Then** applied changes are tenant-scoped and audit logs record start, result, and any failures.

3. **Given** mehrere Backup-Sets existieren,
   **When** der Admin einen Restore Run erstellt,
   **Then** zeigt die Auswahl für das "Backup set" mindestens:
   - den Backup-Namen,
   - das Erstellungsdatum,
   - die Anzahl der Items,
   damit der Admin das richtige Backup-Set sicher auswählen kann.

4. **Given** ein Restore Run wurde erstellt,
   **When** der Admin die Detailseite des Restore Runs öffnet,
   **Then** sieht er, welche Policies/Items in diesem Run enthalten sind
   (z. B. Liste der Policies mit Name/Typ/Plattform).
---

### User Story 5 - Operational readiness and environments (Priority: P2)

Local development uses Sail; deployments target Dokploy staging then production with clear validation steps.

**Why this priority**: Ensures reproducible local setup and safe promotion to production.

**Independent Test**: Run the app locally via Sail; validate migrations on staging before production; confirm required env vars and queues/workers are documented.




### User Story 6 - Berechtigungsübersicht & Health-Status (Priority: P1)

Als Admin möchte ich für jeden Tenant sehen, welche Microsoft Graph-Berechtigungen
erforderlich sind, welche bereits erteilt wurden und welche fehlen, damit ich
sicherstellen kann, dass alle Funktionen von TenantPilot sicher und vollständig
arbeiten.

**Why this priority**: Jede neue Funktion kann zusätzliche Berechtigungen benötigen.
Ohne transparente Übersicht und Abgleich besteht das Risiko, dass Features still
kaputt sind oder unsicher laufen.

**Acceptance Scenarios**:

1. **Given** ein Tenant ist in TenantPilot hinterlegt,
   **When** der Admin die Tenant-Detailseite öffnet,
   **Then** sieht er eine Liste aller *erforderlichen* Berechtigungen mit Status
   (z. B. OK, fehlt).

2. **Given** neue Funktionen wurden eingeführt, die zusätzliche Berechtigungen benötigen
   und diese wurden in der zentralen Permissions-Liste hinzugefügt,
   **When** der Admin die Tenant-Detailseite öffnet,
   **Then** erscheinen die neuen Berechtigungen automatisch in der Übersicht und
   fehlende Berechtigungen werden klar als fehlend markiert.

3. **Given** der Admin klickt auf "Verify configuration",
   **When** TenantPilot einen Graph-Twestcall und/oder das Permission-Setup prüft,
   **Then** wird der Status der Berechtigungen aktualisiert (OK/fehlt/Fehler) und
   es wird ein Audit-Eintrag erstellt.

4. **Given** ein Tenant hat fehlende kritische Berechtigungen,
   **When** andere Features (Policy-Sync, Backup, Restore) diesen Tenant verwenden,
   **Then** kann TenantPilot dem Admin entsprechende Warnungen anzeigen oder die
   Funktion mit einem klaren Fehler abbrechen.


**Acceptance Scenarios**:

1. **Given** a fresh checkout, **When** Sail commands run (`./vendor/bin/sail up -d`, `./vendor/bin/sail artisan migrate`), **Then** the app boots with PostgreSQL and Filament admin available.
2. **Given** a pending release, **When** migrations and restore flows are validated on staging, **Then** production deployment proceeds with documented steps and environment parity.

### Edge Cases

- Graph permissions missing or expired, causing policy fetch/restore failures with clear error mapping and audit entries.
- Large policy payloads or many items in a backup set; ensure JSONB storage and pagination handle load without timeouts.
- Restore conflicts when target tenant already has newer versions; preview must surface warnings and allow skip.
- Partial restore failures; audits must capture per-item outcomes and surface retry guidance.
- Diff generation for incompatible or malformed payloads should fail gracefully with admin-facing messaging.
- Retention/size concerns for snapshots; document defaults and guard against unbounded growth.

## Requirements *(mandatory)*

### Functional Requirements

- **FR-001**: System MUST list supported Intune policies with normalized metadata and tenant scoping for selection.
- **FR-002**: System MUST allow admins to create backup sets containing multiple policies with immutable JSONB payload snapshots.
- **FR-003**: Backup creation MUST log audit events including actor, timestamp, tenant, items, and outcome.
- **FR-004**: System MUST capture policy versions on demand and present per-policy timelines.
- **FR-005**: Users MUST be able to diff any two versions with a human-readable summary and structured JSON diff where feasible.
- **FR-006**: Restore MUST support preview/dry-run, selective item restore, and explicit confirmation before applying changes.
- **FR-007**: Restore execution MUST produce audit logs covering success, failure, and partial outcomes.
- **FR-008**: Graph integration MUST route through a dedicated abstraction layer with standardized error mapping, safe retries, and high-level logging without secrets.
- **FR-009**: All policy, version, backup, and restore data MUST be tenant-aware; queries enforce tenant isolation.
- **FR-010**: Application MUST run locally via Laravel Sail with PostgreSQL and provide Filament admin flows.
- **FR-011**: Deployments MUST target Dokploy staging before production with documented migration and worker implications.
- **FR-012**: Tests MUST cover backup composition rules, version immutability, audit events, and Filament backup/restore flows (with Graph boundaries mocked).
- **FR-013**: Raw policy snapshots and backup payloads MUST be stored as JSONB with indexes justified by query needs (e.g., FK and time-based; GIN when filters require).
- **FR-014**: UI MUST provide clear warnings for potential restore conflicts and require confirmation for destructive operations.
- **FR-015**: Admins MUST be able to safely delete (archive) backup sets that are no
  longer needed. Deletion is implemented as soft-delete with audit logging, and
  backup sets referenced by completed restore runs cannot be removed.

- **FR-016**: Admins MUST be able to delete individual policy versions for housekeeping.
  Deletion is implemented as soft-delete with audit logging.

- **FR-017**: Admins MUST be able to deactivate (soft-delete) a tenant. 
Deactivated
  tenants:
  - do not appear in default lists,
  - cannot be used for new sync/backup/restore operations,
  - keep their historical data and audit logs for traceability.
- **FR-018**: Admins MAY soft-delete restore runs to keep the UI clean; underlying
  backup and policy data remains untouched.

### Key Entities *(include if feature involves data)*

- **tenants**: Represents the deployment tenant context; referenced by all scoped data.
- **policies**: Normalized metadata for supported Intune policies.
- **policy_versions**: Immutable snapshots with metadata (actor, timestamp, tenant, policy type).
- **backup_sets**: Group of backup items with creator, timestamp, and tenant context.
- **backup_items**: Individual policy snapshots within a backup set (immutable JSONB payload + identifiers).
- **restore_runs**: Execution records for restores, including preview/actual flags and outcomes.
- **audit_logs**: Audit trail entries for backups, restores, version captures, and significant Graph actions.

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Admin can create a backup set selecting multiple policies and view immutable backup items with audit logs in Filament.
- **SC-002**: Policy version history timeline is available per policy and supports comparing any two versions with summary and JSON diff outputs.
- **SC-003**: Restore preview shows change summaries and conflict warnings; execution requires explicit confirmation and produces audit logs for all outcomes.
- **SC-004**: Core flows run locally via Sail; staging validation of migrations and restore paths completes before production deployments.
- **SC-005**: Automated tests covering backup composition, version immutability, audit logging, and Filament backup/restore flows pass via `./vendor/bin/sail artisan test`.


### Technical Story – Enforce Single Current Tenant ("Highlander Principle")

**Context**

Aktuell können mehrere Tenants `status = active` sein. Graph-Operationen (Policy Sync,
Backup, Restore) wählen den Kontext über Heuristiken (`findOrCreateDefault`,
`local-tenant`), was zu falschen Tenants und Fehlern führt.

**Goal**

Es soll **immer genau einen klar definierten "current" Tenant** geben, über den
alle Graph-Operationen laufen. Die Auswahl dieses Tenants ist explizit und
transparent (UI + Env), nicht implizit.

**Requirements**

- Es gibt ein Flag `is_current` in `tenants`, das den aktuell verwendeten Kontext
  markiert.
- Die Datenbank erzwingt per partiellem Unique Index, dass höchstens ein
  nicht-gelöschter Tenant `is_current = true` haben kann.
- `Tenant::current()` liefert:
  - falls `INTUNE_TENANT_ID` gesetzt ist, **genau diesen** Tenant (Fehler, wenn
    er nicht existiert oder deaktiviert ist),
  - sonst den Tenant mit `is_current = true` und `status = active`.
  - falls keiner gefunden wird, eine klare Exception (“No current tenant selected”);
    es werden keine Dummy-Tenants erzeugt.
- In der Tenant-Verwaltung gibt es eine Action "Make current", die:
  - in einer Transaktion alle anderen Tenants auf `is_current = false` setzt
    und den gewählten Tenant auf `is_current = true`,
  - nur für aktive Tenants verfügbar ist.
- Der frühere Placeholder `local-tenant` darf nicht mehr als Graph-Kontext genutzt
  werden; sobald ein echter Tenant existiert, wird er archiviert und ist nie
  `is_current`.
- Alle Graph-basierten Funktionen (Policy Sync, Backup, Restore) verwenden
  konsistent `Tenant::current()` oder einen explizit übergebenen Tenant.

  Tenant-level actions such as "Admin consent" and "Verify configuration"
MUST be exposed on the tenant detail view (and/or row actions), not as a
global button without explicit tenant context.


### UX Guideline – Table Actions / Dropdowns

- Tabellen in Filament mit mehr als zwei Zeilen-Aktionen (z.B. View, Edit,
  Admin consent, Verify, Deactivate, Force delete) MÜSSEN ihre Aktionen in
  einem kompakten Dropdown / ActionGroup bündeln, statt alle Buttons nebeneinander
  anzuzeigen.
- Ausnahmen: besonders häufige, nicht-destruktive Aktionen (z.B. "View")
  dürfen weiterhin als einzelner Button sichtbar bleiben; alle weiteren
  Aktionen (z.B. Admin-Aktionen, Housekeeping) sollen im Dropdown liegen.
- Ziel: die Tabellen bleiben übersichtlich, Spaltenbreite wird begrenzt,
  und Admins bekommen eine konsistente "⋯"-Interaktion für erweiterte Aktionen.


