Files
Omika/docs/phase5-diagnostic-center.md
2026-10-23 18:46:50 +02:00

136 lines
5.7 KiB
Markdown

# 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 »).