tenantpilot/specs/005-backend-arch-pivot/spec.md

237 lines
15 KiB
Markdown

# Feature Specification: Backend Architecture Pivot
**Feature Branch**: `005-backend-arch-pivot`
**Created**: 2025-12-09
**Status**: Draft
**Input**: "Backend Architecture Pivot (n8n Removal & BullMQ Implementation) - Remove n8n legacy code, implement BullMQ job queue with Redis, port sync logic to TypeScript worker"
## Overview
Migration von einer Low-Code-Backend-Architektur (n8n) zu einem Code-First-Backend mit BullMQ Job Queue und TypeScript Worker. Die komplexe Microsoft Graph Synchronisations-Logik wird direkt in TypeScript implementiert, um Wartbarkeit, Testbarkeit und AI-gestütztes Refactoring zu maximieren.
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Manual Policy Sync via Queue (Priority: P1)
Als Intune-Admin möchte ich auf "Sync Now" klicken und erwarten, dass die Synchronisation asynchron in einem Worker-Prozess ausgeführt wird, damit die UI nicht blockiert und ich sofort weiterarbeiten kann.
**Why this priority**: Core-Funktionalität - ohne funktionierenden Sync ist das gesamte Feature unbrauchbar. Queue-basierte Architektur ist Grundlage für spätere Scheduled Syncs.
**Independent Test**: Click "Sync Now", check Redis for job, observe worker logs, verify database updates.
**Acceptance Scenarios**:
1. **Given** der Admin ist auf `/search` eingeloggt, **When** er auf "Sync Now" klickt, **Then** wird ein Job in die Redis Queue eingestellt (keine Wartezeit für den User).
2. **Given** ein Sync-Job wurde erstellt, **When** der Worker-Prozess läuft, **Then** nimmt er den Job aus der Queue und beginnt die Synchronisation.
3. **Given** der Worker führt einen Sync aus, **When** die Synchronisation erfolgreich abgeschlossen ist, **Then** werden alle Policy Settings in der Datenbank aktualisiert (Insert oder Update via `onConflictDoUpdate`).
4. **Given** der Worker synchronisiert Policies, **When** ein Fehler auftritt (z.B. Graph API Timeout), **Then** wird der Job in einen "failed" State versetzt und der Fehler wird geloggt (kein Silent Fail).
5. **Given** der Admin hat mehrere Sync-Jobs gestartet, **When** der Worker mehrere Jobs in der Queue findet, **Then** werden sie sequenziell abgearbeitet (keine parallelen Syncs pro Tenant).
---
### User Story 2 - Microsoft Graph Data Fetching (Priority: P1)
Als System möchte ich alle relevanten Policy-Typen von Microsoft Graph API abrufen können (Device Configurations, Compliance Policies, Configuration Policies, Intents), damit alle Intune-Settings analysierbar sind.
**Why this priority**: Datenbeschaffung ist essentiell - ohne vollständigen Fetch fehlen Policies in der Analyse.
**Independent Test**: Run worker with test tenant, verify all policy types are fetched, check pagination handling.
**Acceptance Scenarios**:
1. **Given** der Worker startet einen Sync, **When** er ein Access Token anfordert, **Then** nutzt er den Azure AD Client Credentials Flow mit `AZURE_AD_CLIENT_ID` und `AZURE_AD_CLIENT_SECRET`.
2. **Given** der Worker hat ein gültiges Token, **When** er Policies abruft, **Then** fetcht er alle relevanten Endpoints:
- `/deviceManagement/deviceConfigurations`
- `/deviceManagement/deviceCompliancePolicies`
- `/deviceManagement/configurationPolicies`
- `/deviceManagement/intents`
3. **Given** eine Graph API Response hat `@odata.nextLink`, **When** der Worker die Response verarbeitet, **Then** folgt er dem Link und lädt alle Seiten bis keine `nextLink` mehr vorhanden ist.
4. **Given** ein Policy Object wird von Graph zurückgegeben, **When** der Worker es parst, **Then** extrahiert er `id`, `displayName`, `@odata.type`, `lastModifiedDateTime` und Policy-spezifische Settings.
5. **Given** der Graph API Call schlägt fehl (401, 429, 500), **When** der Fehler auftritt, **Then** wird ein Retry mit Exponential Backoff durchgeführt (max 3 Versuche).
---
### User Story 3 - Deep Flattening & Data Transformation (Priority: P1)
Als System möchte ich komplexe verschachtelte Policy-Objekte in flache Key-Value-Paare transformieren können, damit sie in der `policy_settings` Tabelle gespeichert und durchsucht werden können.
**Why this priority**: Core Transformation Logic - ohne Flattening können verschachtelte Settings nicht analysiert werden.
**Independent Test**: Run parser with sample Graph responses, verify flattened output matches expected structure.
**Acceptance Scenarios**:
1. **Given** der Worker hat Policy-Daten von Graph erhalten, **When** er ein Settings Catalog Policy verarbeitet (`#microsoft.graph.deviceManagementConfigurationPolicy`), **Then** iteriert er über `settings[]` und extrahiert `settingDefinitionId` und `value`.
2. **Given** ein Policy enthält verschachtelte Objekte (z.B. `value.simple.value` oder `value.children[]`), **When** der Flattening-Algorithmus läuft, **Then** wird jede verschachtelte Ebene mit Dot-Notation als Key dargestellt (z.B. `wifi.ssid.value`).
3. **Given** der Worker verarbeitet ein OMA-URI Policy, **When** er `omaSettings[]` findet, **Then** extrahiert er `omaUri` als Setting Name und `value` als Setting Value.
4. **Given** ein Setting Key enthält technische Bezeichner (z.B. `device_vendor_msft_policy_config_wifi_allowwifihotspotreporting`), **When** der Humanizer läuft, **Then** werden Keys in lesbare Form umgewandelt (z.B. `Allow WiFi Hotspot Reporting`).
5. **Given** ein Policy hat keine Settings (leeres Array), **When** der Worker es verarbeitet, **Then** wird trotzdem ein Eintrag erstellt mit `settingName: "(No settings configured)"` (damit Policy in UI sichtbar ist).
---
### User Story 4 - Legacy Code Removal (Priority: P1)
Als Entwickler möchte ich alle n8n-spezifischen Artefakte entfernen können, damit der Code sauber und wartbar bleibt.
**Why this priority**: Technical Debt Reduction - alte Bridge-APIs verursachen Confusion und Maintenance-Overhead.
**Independent Test**: Search codebase for n8n references, verify all removed, check env validation.
**Acceptance Scenarios**:
1. **Given** der Code wird überprüft, **When** nach `POLICY_API_SECRET` gesucht wird, **Then** existieren keine Referenzen mehr (weder in `.env`, noch in `lib/env.mjs`, noch in Code).
2. **Given** der Code wird überprüft, **When** nach `N8N_SYNC_WEBHOOK_URL` gesucht wird, **Then** existieren keine Referenzen mehr.
3. **Given** das Routing wird analysiert, **When** nach `/api/policy-settings/route.ts` gesucht wird, **Then** existiert die Datei nicht mehr (gelöscht).
4. **Given** das Routing wird analysiert, **When** nach `/api/admin/tenants/route.ts` gesucht wird, **Then** existiert die Datei nicht mehr (gelöscht).
5. **Given** ein Entwickler startet die App, **When** die Umgebungsvariablen validiert werden, **Then** werden `POLICY_API_SECRET` und `N8N_SYNC_WEBHOOK_URL` nicht mehr als erforderlich markiert.
---
### Edge Cases
- Was passiert wenn Redis nicht erreichbar ist beim Job-Erstellen? → Fehler werfen mit User-Feedback "Sync service unavailable".
- Was passiert wenn der Worker abstürzt während eines Jobs? → BullMQ Recovery: Job bleibt in "active" state und wird nach Timeout in "failed" verschoben.
- Wie gehen wir mit Rate Limiting von Microsoft Graph um? → Exponential Backoff + Retry (max 3x), dann Job als "failed" markieren mit Retry-Option.
- Was passiert bei parallelen Sync-Requests für denselben Tenant? → Queue stellt sicher, dass Jobs sequenziell abgearbeitet werden (kein Concurrency Issue).
- Wie werden transiente Netzwerkfehler behandelt? → Retry-Logik mit Backoff, nur permanente Fehler (401, 403) führen zu sofortigem Fail.
- Was passiert mit bestehenden Policy Settings während eines Syncs? → `onConflictDoUpdate` updated bestehende Einträge basierend auf `(tenantId, graphPolicyId, settingName)` Constraint.
## Requirements *(mandatory)*
### Functional Requirements
#### Infrastructure & Queue
- **FR-001**: System MUSS BullMQ als Job Queue Library verwenden mit Redis als Backend.
- **FR-002**: System MUSS eine wiederverwendbare Redis Connection in `lib/queue/redis.ts` bereitstellen.
- **FR-003**: System MUSS einen Worker-Prozess in `worker/index.ts` implementieren, der auf der Queue `intune-sync-queue` lauscht.
- **FR-004**: System MUSS Worker-Prozess als separates npm Script bereitstellen (`worker:start`).
- **FR-005**: System MUSS `REDIS_URL` als Environment Variable validieren.
#### Authentication & Graph API
- **FR-006**: System MUSS Access Tokens via Azure AD Client Credentials Flow holen (`@azure/identity` oder `fetch`).
- **FR-007**: System MUSS folgende Graph API Endpoints fetchen:
- `/deviceManagement/deviceConfigurations`
- `/deviceManagement/deviceCompliancePolicies`
- `/deviceManagement/configurationPolicies`
- `/deviceManagement/intents`
- **FR-008**: System MUSS Pagination via `@odata.nextLink` vollständig abarbeiten (alle Seiten laden).
- **FR-009**: System MUSS Graph API Fehler (401, 429, 500+) mit Exponential Backoff Retry behandeln (max 3 Versuche).
#### Data Processing & Transformation
- **FR-010**: System MUSS Settings Catalog Policies parsen (`settings[]` Array → flache Key-Value Paare).
- **FR-011**: System MUSS OMA-URI Policies parsen (`omaSettings[]` → `omaUri` als Key, `value` als Value).
- **FR-012**: System MUSS Deep Flattening für verschachtelte Objekte implementieren (Dot-Notation für Pfade).
- **FR-013**: System MUSS technische Setting Keys humanisieren (z.B. `device_vendor_msft_policy_config_wifi``WiFi`).
- **FR-014**: System MUSS Policy Typ Detection implementieren (Settings Catalog, OMA-URI, Compliance, etc.).
- **FR-015**: System MUSS leere Policies mit Placeholder-Setting speichern (`settingName: "(No settings configured)"`).
#### Database Persistence
- **FR-016**: System MUSS Drizzle ORM für alle DB-Operationen verwenden.
- **FR-017**: System MUSS `onConflictDoUpdate` für Upsert-Logik nutzen (Constraint: `tenantId + graphPolicyId + settingName`).
- **FR-018**: System MUSS folgende Felder pro Setting speichern:
- `tenantId`, `graphPolicyId`, `policyName`, `policyType`, `settingName`, `settingValue`, `settingValueType`, `lastSyncedAt`
- **FR-019**: System MUSS `lastSyncedAt` Timestamp bei jedem Sync aktualisieren.
#### Frontend Integration
- **FR-020**: System MUSS Server Action `triggerPolicySync` in `lib/actions/policySettings.ts` anpassen (n8n Webhook → BullMQ Job).
- **FR-021**: System MUSS Job-ID an Frontend zurückgeben für späteres Status-Tracking (optional für MVP, siehe FR-022).
- **FR-022**: System KANN (optional) Job-Status-Polling-Endpoint bereitstellen (`/api/sync-status/[jobId]`).
#### Legacy Cleanup
- **FR-023**: System MUSS File `app/api/policy-settings/route.ts` löschen (n8n Ingestion API).
- **FR-024**: System MUSS File `app/api/admin/tenants/route.ts` löschen (n8n Polling API).
- **FR-025**: System MUSS `POLICY_API_SECRET` aus `.env`, `lib/env.mjs` und allen Code-Referenzen entfernen.
- **FR-026**: System MUSS `N8N_SYNC_WEBHOOK_URL` aus `.env`, `lib/env.mjs` und allen Code-Referenzen entfernen.
### Key Entities
Neue Strukturen (keine DB-Schema-Änderungen):
- **SyncJobPayload**: BullMQ Job Data
- `tenantId`: string
- `userId`: string (optional, für Audit)
- `triggeredAt`: Date
- **GraphPolicyResponse**: TypeScript Interface für Graph API Response
- `id`: string
- `displayName`: string
- `@odata.type`: string
- `lastModifiedDateTime`: string
- `settings?`: array (Settings Catalog)
- `omaSettings?`: array (OMA-URI)
- (weitere Policy-spezifische Felder)
- **FlattenedSetting**: Internes Transform-Result
- `settingName`: string
- `settingValue`: string
- `settingValueType`: string
- `path`: string (Dot-Notation Pfad im Original-Objekt)
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: User erhält sofortige Bestätigung nach Click auf "Sync Now" (<200ms Response Zeit, kein Warten auf Sync-Completion).
- **SC-002**: Sync für einen Tenant mit 50 Policies ist innerhalb von 30 Sekunden abgeschlossen.
- **SC-003**: System lädt alle verfügbaren Policies vollständig (auch bei >100 Policies mit mehreren Datenseiten).
- **SC-004**: Mindestens 95% aller Policy Settings werden korrekt extrahiert und gespeichert (validiert mit repräsentativen Sample-Daten).
- **SC-005**: Bei temporären Fehlern (z.B. Service-Überlastung) erfolgt automatische Wiederholung (keine manuellen Eingriffe nötig).
- **SC-006**: Alte Bridge-Komponenten sind vollständig entfernt (keine toten Code-Pfade oder ungenutzten APIs).
- **SC-007**: Sync-Prozess läuft stabil über längere Zeiträume (1+ Stunde mit 10+ Sync-Operationen ohne Abstürze).
- **SC-008**: Re-Sync aktualisiert bestehende Daten korrekt ohne Duplikate oder Datenverluste.
## Assumptions
- System nutzt asynchrone Job-Verarbeitung mit Queue-basierter Architektur für Skalierbarkeit.
- TenantPilot hat bereits Azure AD Multi-Tenant Authentication konfiguriert (Client Credentials verfügbar).
- Die bestehende `policy_settings` Datenbank-Tabelle hat bereits einen UNIQUE Constraint auf `(tenantId, graphPolicyId, settingName)`.
- Die Flattening-Logik aus der bisherigen n8n-Implementation ist dokumentiert oder nachvollziehbar.
- Sync-Prozess wird in Production als persistenter Background-Service betrieben (nicht nur bei Bedarf gestartet).
- Redis oder vergleichbarer In-Memory Store ist verfügbar für Job Queue Management.
## Nicht-Ziele (Out of Scope)
- Kein automatischer Scheduled Sync (zeitbasierte Trigger) in diesem Feature - bleibt manuelle Auslösung.
- Keine Web-UI für Job-Management oder Queue-Monitoring.
- Keine Live-Progress-Updates im Frontend während Sync läuft (kein Echtzeit-Status).
- Keine parallele Verarbeitung mehrerer Tenants gleichzeitig (sequenzielle Abarbeitung).
- Keine erweiterten Retry-Strategien oder Dead Letter Queues in MVP.
- Kein Policy Change Detection oder Diff-Berechnung (nur vollständiger Sync + Update bestehender Daten).
## Technical Notes
**Note**: Detaillierte Implementierungs-Details (Code-Beispiele, API-Calls, Architektur-Patterns) werden in einem separaten Technical Design Document oder im Planning-Phase dokumentiert. Diese Spec fokussiert sich auf das **WAS** und **WARUM**, nicht auf das **WIE**.
### High-Level Architecture Overview
**Queue-Based Sync Architecture**:
- Asynchrone Job-Verarbeitung für nicht-blockierende User Experience
- Worker-Prozess als separater Service für Sync-Operationen
- Persistente Job-Queue für Reliability und Retry-Fähigkeit
**Data Flow**:
1. User triggers sync → Job wird in Queue eingestellt
2. Worker nimmt Job aus Queue → Authentifiziert sich bei Microsoft
3. Worker fetcht Policy-Daten → Transformiert & flacht verschachtelte Strukturen ab
4. Worker speichert Daten → Upsert in Datenbank mit Conflict Resolution
**Migration Strategy**:
- Phase 1: Neue Infrastruktur aufbauen (Queue, Worker)
- Phase 2: Sync-Logik portieren (Auth, Fetch, Transform, Persist)
- Phase 3: Frontend auf neue Architektur umstellen
- Phase 4: Alte n8n-Komponenten entfernen
- Phase 5: End-to-End Validierung mit Production-Daten
## Dependencies
- Job Queue System (z.B. BullMQ, Bee-Queue, oder vergleichbar)
- In-Memory Data Store (z.B. Redis, KeyDB, oder vergleichbar)
- Microsoft Graph API Client Library (z.B. @azure/identity oder vergleichbar)
- TypeScript Runtime für Worker-Prozess (z.B. tsx, ts-node, oder vergleichbar)