# Feature Specification: TenantPilot v1 **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.