# Phase 5 — Diagnostic Center (architecture & décisions) > Outil SUPPORT uniquement (§13). PostgreSQL et OpenTelemetry ne sont **jamais** > critiques pour Omika : `Mobile → DiagnosticOutbox → POST /api/diagnostics → > RemoteDiagnosticIngestionStore (JSONL durable) → ACK mobile` reste inchangé (§4). > L'import PostgreSQL et l'export OTLP sont **asynchrones et optionnels**. ## Architecture retenue ```text APPLICATIONS OMIKA (Mobile / Desktop / API / Worker) │ ▼ IOmikaTelemetry │ ▼ SafeTelemetrySink │ ▼ PriorityTelemetryQueue (P0-P4) │ ┌──────┴─────────┐ ▼ ▼ JSONL / Outbox OpenTelemetry (OTLP HTTP, optional, kill-switch) durable Omika │ │ ▼ │ OpenTelemetry Collector (Windows Service) │ │ ▼ ▼ Diagnostic Import Omika Telemetry Backend ← transport finalisé Phase serveur │ │ └────────┬────────┘ ▼ PostgreSQL (omika_diagnostics) │ ▼ Omika Diagnostic Center (ASP.NET Core Web) ``` ### Projets créés (§12) | Projet | Rôle | |---|---| | `src/Omika.Diagnostics.Core` | Domain / models, abstractions repository, importer, whitelist sécurité | | `src/Omika.Diagnostics.Data` | Persistance PostgreSQL (Npgsql direct), source JSONL, checkpoint | | `src/Omika.Diagnostics.Worker` | Import asynchrone JSONL → PostgreSQL (BackgroundService, backoff) | | `src/Omika.Diagnostics.Web` | Diagnostic Center Web (Razor Pages) + auth support/admin | ## Choix d'accès données (§25) **Npgsql direct (pas d'ORM)**. Justification : - Sous-système simple : 2 tables, peu de patrons de requêtes. - Aucune « magie » (pas de mapping réflexif, pas d'EF Core) — SQL explicite et lisible. - Pooling intégré de Npgsql (§24) : jamais une connexion par événement. - Dapper réduirait le boilerplate mais ajoute une dépendance + réflexion inutile ici. - EF Core est surdimensionné pour 2 tables. Version : **Npgsql 10.0.3** (aligné .NET 10, présent dans le cache local du poste). ## Données interdites (§19) et whitelist (§18) Réutilise `DiagnosticForbiddenData` (Phase 4) : `password`, `token`, `authorization`, `cookie`, `connectionstring`, `requestbody`, `responsebody`, `emailbody`, `smtp`, `imei`, `macaddress`, `advertisingid`, `signature`, `secret`. Les métadonnées `metadata_json` (JSONB) ne sont **pas** un stockage libre : `DiagnosticMetadataPolicy` n'autorise qu'une whitelist de clés low-cardinality (`method`, `routeTemplate`, `statusCode`, `exceptionType`, `retryCount`, …) et rejette toute clé/valeur interdite. Les champs d'identité métier (`DeviceId`, `UserId`, `ClientId`, `CollaborateurId`, `EntityId`) ne sont **pas** persistés dans PostgreSQL (confidentialité, §30/§31). ## Importeur JSONL → PostgreSQL (§20-§23) `DiagnosticImporter.ImportOnceAsync()` : lecture bornée (`BatchSize`, défaut 100) → mapping (whitelist) → `INSERT ... ON CONFLICT (event_id) DO NOTHING` (batch) → avancement du checkpoint **uniquement après insertion réussie**. - **at-least-once** : le checkpoint n'avance qu'après succès → doublons possibles, dédupliqués par `event_id` unique en base (§16). - **fail-open** (§22) : toute erreur PostgreSQL → résultat `Retryable`, JSONL intact, checkpoint non avancé, backoff exponentiel borné (aucune boucle CPU). - **idempotent** : relire un fichier déjà importé est sans effet (ON CONFLICT). ## Checkpoint (§21) `import-checkpoint.json` (curseur `{ fileName, lineIndex }`), distinct du checkpoint du forwarder OpenTelemetry. Reprise après restart / PostgreSQL down / reboot sans réimporter tout l'historique. Les fichiers JSONL bruts ne sont **jamais** supprimés. ## Rétention (§45) `Diagnostics:RetentionDays` (défaut **60 jours**), non hardcodé. La purge (`DELETE ... WHERE timestamp_utc < now() - interval '60 days'`) est **documentée mais non exécutée automatiquement** (purge destructive → maintenance planifiée). ## Métriques (§28-§31) Table `telemetry_metric_hourly` : agrégats horaires (`bucket_utc`, `metric_name`, `component`, `value`, `count`, `min/max`, `tags_json`). **Low-cardinality obligatoire** (`ExternalAttributePolicy`) : jamais `TraceId`/`SessionId`/`UserId`/`DeviceId`/GUID métier en dimension. Utilisateurs actifs/jour = agrégation respectueuse de la confidentialité, jamais de surveillance individuelle (§31). ## Traces (§32/§33/§39/§40) `X-Omika-TraceId` reste l'identifiant applicatif de support (§33). La timeline Mobile → API → Worker se reconstruit par `trace_id` (`ORDER BY timestamp_utc ASC`), et par `operation_id` pour les synchronisations. ## Configuration (générique, jamais de secret §26) ```yaml Observability: External: Enabled: false # kill-switch : aucun OTLP, aucun réseau, aucun crash OtlpEndpoint: "" Remote: Directory: "" # JSONL durable (frontière d'ACK Phase 4) Diagnostics: ConnectionString: "" # configuration / variable d'environnement UNIQUEMENT RetentionDays: 60 Import: BatchSize: 100 CheckpointFileName: "import-checkpoint.json" ``` ## Déploiement (Phase serveur, PAS dans cette phase) - Windows Server 2025 : PostgreSQL + OpenTelemetry Collector (Windows Service) + `Omika.Diagnostics.Worker` + `Omika.Diagnostics.Web`. - Aucune dépendance Linux / Docker / ClickHouse / SigNoz / Kubernetes. - Rien n'est à installer sur le serveur dans cette phase (§ « NE RIEN INSTALLER »).