Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
+592
View File
@@ -0,0 +1,592 @@
# T15 — RUNBOOK DÉPLOIEMENT PRODUCTION MANUEL OMIKA DIAGNOSTICS
> Statut : **PRÉPARATION (aucune action Production effectuée)**.
> Cible : Windows Server 2025 (`SRV-OMIKA`). PowerShell **administrateur**.
> Ce runbook est **manuel** : fenêtre de maintenance contrôlée (arrêt → sauvegarde →
> migration → copie → redémarrage → validation → rollback éventuel) acceptée.
>
> **RÈGLES ABSOLUES**
> 1. NE PAS exécuter le seed local (`seed_diagnostics_local.sql`) ni le clear
> (`clear_diagnostics_local_seed.sql`) en Production. Ils restent dans le dépôt
> source (développement uniquement).
> 2. NE JAMAIS écraser les configurations Production existantes (secrets).
> 3. NE JAMAIS supprimer les JSONL en attente ni remettre le checkpoint à zéro.
> 4. NE JAMAIS faire de `TRUNCATE` / `DROP TABLE` / `DELETE` d'événements en rollback.
> 5. Aucun secret n'apparaît dans ce document (placeholders `<SECRET_*>` uniquement).
> 6. Toute commande SQL de migration utilise `ON_ERROR_STOP=1` et est suivie d'une
> vérification avant de passer à la suivante.
Identifiant de livraison (convention) :
`Omika_Diagnostics_<ApplicationConstants.Version>_<YYYYMMDDHHmm>`
ex. `Omika_Diagnostics_1.0.0.0_20261001_1800`.
(`ApplicationConstants.Version = "1.0.0.0"` — pas de version inventée.)
---
## PHASE 0 — Préconditions
| # | Précondition | Où | Résultat attendu / STOP |
|---|---|---|---|
| 0.1 | Accès PowerShell **administrateur** sur SRV-OMIKA | SRV-OMIKA | `net session` OK ; sinon STOP (droits insuffisants) |
| 0.2 | Runtime .NET 10 présent | SRV-OMIKA | `dotnet --list-runtimes` montre `Microsoft.NETCore.App 10.x` et `Microsoft.AspNetCore.App 10.x`. Absent → STOP (installer le runtime d'hébergement) |
| 0.3 | `psql.exe` et `pg_dump.exe` présents | SRV-OMIKA | `C:\Program Files\PostgreSQL\18\bin\` contient `psql.exe` + `pg_dump.exe`. Absent → STOP |
| 0.4 | PostgreSQL service **Running** | SRV-OMIKA | `Get-Service postgresql*` = Running. Sinon STOP |
| 0.5 | Packages Release générés et **validés** (secret scan OK) | Poste DEV | Dossier de staging complet (4 composants + `Database` + `Deploy` + `SHA256SUMS.txt`). Sinon STOP (re-générer) |
| 0.6 | Backup PostgreSQL et binaires **réussis** (Phase 2) | SRV-OMIKA | Voir Phase 2. Échec → NO-GO |
| 0.7 | Fenêtre de maintenance validée (services arrêtables) | Exploitant | Confirmation humaine. Sinon NO-GO |
**GO uniquement si 0.1–0.7 tous OK.**
---
## PHASE 1 — Audit Production (lecture seule, aucune modification)
### 1.1 État du schéma PostgreSQL (à exécuter sur SRV-OMIKA)
```powershell
$env:PGPASSWORD = '<SECRET_POSTGRES_ADMIN>'
$psql = 'C:\Program Files\PostgreSQL\18\bin\psql.exe'
& $psql -h 127.0.0.1 -p 5432 -U postgres -d omika_diagnostics -c `
"SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name='diagnostic_event' ORDER BY ordinal_position;"
& $psql -h 127.0.0.1 -p 5432 -U postgres -d omika_diagnostics -c `
"SELECT indexname FROM pg_indexes WHERE tablename='diagnostic_event' ORDER BY indexname;"
& $psql -h 127.0.0.1 -p 5432 -U postgres -d omika_diagnostics -c `
"SELECT rolname FROM pg_roles ORDER BY rolname;"
Remove-Item Env:PGPASSWORD
```
**Résultat attendu** : liste des colonnes/indexes/roles réels.
**Décisions** :
- Colonnes `client_code`, `server_instance`, `environment` présentes → **005 déjà appliqué** (ne pas réappliquer).
- Index `ix_diagnostic_event_client_code_timestamp` présent → 005 complet.
- `SELECT` + `DELETE` accordés à `omika_diagnostics_worker` sur `diagnostic_event` → **004 déjà appliqué**.
- Roles `omika_diagnostics_owner/worker/web` présents → **003 déjà appliqué**.
**STOP si** : la base `omika_diagnostics` est absente ou la connexion échoue (ne jamais la créer à l'aveugle — résoudre d'abord).
### 1.2 État des services Windows (lecture seule)
```powershell
Get-Service OmikaApi,OmikaWorker,OmikaDiagnosticsWorker,OmikaDiagnosticsWeb |
Select-Object Name,DisplayName,Status,StartType
sc.exe qc OmikaApi
sc.exe qc OmikaWorker
sc.exe qc OmikaDiagnosticsWorker
sc.exe qc OmikaDiagnosticsWeb
```
**Résultat attendu** : nom réel, chemin binaire, compte, StartupType. Notez tout écart
avec les valeurs attendues (voir section « Services Windows » du rapport).
**STOP si** : un service n'existe pas alors que la phase d'activation n'a pas été
validée (compte de service indéfini) — ne pas créer de service dans ce runbook.
### 1.3 Chemins réels des dossiers (lecture seule)
```powershell
Test-Path 'C:\Omika\Apps\OmikaApi'
Test-Path 'C:\Omika\Apps\OmikaWorker'
Test-Path 'C:\Omika\Apps\DiagnosticsWorker'
Test-Path 'C:\Omika\Apps\DiagnosticsWeb'
Test-Path 'C:\Omika\Data\Api\Diagnostics\Jsonl'
Test-Path 'C:\Omika\Data\DiagnosticsWorker\Checkpoint'
```
**Résultat attendu** : chemins confirmés. Tout chemin absent/différent → **À CONFIRMER**
et ajuster les phases suivantes (ne pas inventer).
### 1.4 JSONL en attente + checkpoint (lecture seule, ne rien modifier)
```powershell
Get-ChildItem 'C:\Omika\Data\Api\Diagnostics\Jsonl\omika-mobile-diagnostic-*.jsonl' |
Select-Object Name,Length,LastWriteTime
Get-Content 'C:\Omika\Data\DiagnosticsWorker\Checkpoint\import-checkpoint.json'
```
**Résultat attendu** : liste des JSONL en attente + contenu du checkpoint
(`{"fileName":...,"lineIndex":...}`). **Notez ces valeurs** (utiles pour la Phase 10).
**STOP si** : vous êtes tenté de supprimer un JSONL ou de réinitialiser le checkpoint —
interdit (§26).
---
## PHASE 2 — Sauvegardes
### 2.1 Sauvegarde PostgreSQL (AVANT toute migration)
```powershell
$env:PGPASSWORD = '<SECRET_POSTGRES_ADMIN>'
$stamp = Get-Date -Format 'yyyyMMdd_HHmm'
$out = "C:\Omika\Backups\omika_diagnostics_${stamp}_before_upgrade.backup"
& 'C:\Program Files\PostgreSQL\18\bin\pg_dump.exe' `
-h 127.0.0.1 -p 5432 -U postgres -d omika_diagnostics -F c -v -f $out
Remove-Item Env:PGPASSWORD
```
**Résultat attendu** : `pg_dump` se termine sans erreur (exit 0), fichier `.backup` créé.
**Vérification obligatoire (§17)** :
```powershell
$f = Get-Item $out
$f.Exists -and $f.Length -gt 0
```
**STOP / NO-GO si** : `pg_dump` échoue, exit ≠ 0, fichier absent ou taille 0.
Aucune migration ne commence tant que ce backup n'est pas vérifié.
### 2.2 Sauvegarde des binaires (AVANT remplacement)
```powershell
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$root = 'C:\Omika\Backups'
Copy-Item 'C:\Omika\Apps\OmikaApi' -Destination "$root\OmikaApi_${stamp}" -Recurse -Force
Copy-Item 'C:\Omika\Apps\OmikaWorker' -Destination "$root\OmikaWorker_${stamp}" -Recurse -Force
Copy-Item 'C:\Omika\Apps\DiagnosticsWorker' -Destination "$root\DiagnosticsWorker_${stamp}" -Recurse -Force
Copy-Item 'C:\Omika\Apps\DiagnosticsWeb' -Destination "$root\DiagnosticsWeb_${stamp}" -Recurse -Force
```
**Note** : exclure les logs très volumineux si présents sous `Apps\*` (copier uniquement
le contenu publié). Le script existant `Install-OmikaServerFiles.ps1` fait déjà cette
sauvegarde horodatée (convention `OmikaApi-yyyyMMdd-HHmmss`).
**STOP / NO-GO si** : copie incomplète (comparer le nombre de fichiers source/cible).
### 2.3 Vérification globale GO/NO-GO partiel
```text
[ ] Backup PostgreSQL présent + taille > 0
[ ] Backup binaires (4 dossiers) présent + complet
[ ] Packages Release validés (Phase 0.5)
[ ] Secrets Development absents des packages
[ ] Migrations nécessaires identifiées (Phase 1.1)
[ ] JSONL/checkpoint inventoriés (Phase 1.4)
[ ] Services arrêtables + fenêtre validée
[ ] Procédure rollback prête (Phase R)
```
Tout prérequis critique faux → **NO-GO, STOP**.
---
## PHASE 3 — Arrêt des services
Ordre retenu (justifié par le flux de données) :
| Ordre | Service | Rôle dans le flux | Pourquoi maintenant |
|---|---|---|---|
| 1 | `OmikaDiagnosticsWeb` | lecture seule (SELECT) | figer l'accès utilisateur/support d'abord |
| 2 | `OmikaApi` | écrit JSONL | figer l'écrivain JSONL #1 |
| 3 | `OmikaWorker` | métier HFSQL + écrit diagnostics | figer l'écrivain #2 |
| 4 | `OmikaDiagnosticsWorker` | import JSONL → PG | arrêté en dernier pour drainer les dernières lignes |
```powershell
Stop-Service OmikaDiagnosticsWeb -Force
Stop-Service OmikaApi -Force
Stop-Service OmikaWorker -Force
Stop-Service OmikaDiagnosticsWorker -Force
# Vérifier :
Get-Service OmikaApi,OmikaWorker,OmikaDiagnosticsWorker,OmikaDiagnosticsWeb | Select Name,Status
```
**Résultat attendu** : les 4 services = `Stopped`.
**STOP si** : un service ne s'arrête pas (résoudre — ne pas forcer le remplacement de
fichiers d'un service encore actif).
---
## PHASE 4 — Migration PostgreSQL (manuelle, auditée)
### 4.0 Principe
Appliquer **uniquement** les migrations manquantes, **dans l'ordre**, avec
`ON_ERROR_STOP=1`, et **vérifier après chaque fichier**. Toutes sont idempotentes
(`CREATE/ADD IF NOT EXISTS`, `GRANT`, `DO $$`). Ne jamais appliquer aveuglément.
Table des migrations (voir rapport section F) :
| Fichier | Objet | Idempotent | Nécessaire cette release |
|---|---|---|---|
| 001 | `diagnostic_event` + `telemetry_metric_hourly` + index | OUI | OUI (si absent) |
| 002 | `actor_key`, `actor_key_version` + index | OUI | OUI (si absent) |
| 003 | rôles least-privilege + ownership + grants | OUI | OUI (si absent) |
| 004 | `GRANT SELECT, DELETE` à `omika_diagnostics_worker` | OUI | OUI (rétention) |
| 005 | `client_code`/`server_instance`/`environment` + index | OUI | **OUI (indispensable)** |
### 4.1 Appliquer 005 (client context) — si absent
```powershell
$env:PGPASSWORD = '<SECRET_POSTGRES_ADMIN>'
& 'C:\Program Files\PostgreSQL\18\bin\psql.exe' -h 127.0.0.1 -p 5432 -U postgres `
-d omika_diagnostics -v ON_ERROR_STOP=1 `
-f '<staging>\Database\005_diagnostics_client_context.sql'
Remove-Item Env:PGPASSWORD
```
**Vérifier ensuite** (doit renvoyer les 3 colonnes + l'index) :
```sql
SELECT column_name FROM information_schema.columns
WHERE table_name='diagnostic_event'
AND column_name IN ('client_code','server_instance','environment');
SELECT indexname FROM pg_indexes
WHERE tablename='diagnostic_event' AND indexname='ix_diagnostic_event_client_code_timestamp';
```
**STOP si** : psql exit ≠ 0, ou la vérification ne renvoie pas les 3 colonnes + l'index.
### 4.2 Appliquer 004 (rétention) — si absent
```powershell
$env:PGPASSWORD = '<SECRET_POSTGRES_ADMIN>'
& 'C:\Program Files\PostgreSQL\18\bin\psql.exe' -h 127.0.0.1 -p 5432 -U postgres `
-d omika_diagnostics -v ON_ERROR_STOP=1 `
-f '<staging>\Database\004_diagnostics_retention.sql'
Remove-Item Env:PGPASSWORD
```
**Vérifier** :
```sql
SELECT has_table_privilege('omika_diagnostics_worker','diagnostic_event','SELECT') AS sel,
has_table_privilege('omika_diagnostics_worker','diagnostic_event','DELETE') AS del;
```
**Résultat attendu** : `sel=t, del=t`. Sinon STOP.
(Rôle de 004 : autoriser le `DiagnosticRetentionWorker` à purger. Sans 004, la purge
échoue mais l'import continue — fail-open ; néanmoins 004 est **requis** pour que la
rétention fonctionne réellement.)
### 4.3 Appliquer 003 (rôles) — si absent (contexte superuser, cf. `Harden-DatabaseRoles.ps1`)
À n'appliquer que si les rôles `omika_diagnostics_owner/worker/web` sont absents
(vérifié Phase 1.1). Sinon **ne pas réappliquer**. Après 003, définir les mots de passe
des rôles LOGIN **manuellement** via `\password` (jamais dans un script/log).
**STOP si** : 003 a déjà été appliqué — ne pas rejouer l'ownership inutilement.
### 4.4 Règle d'ordre Migration ↔ Binaires (§27)
**La migration 005 doit être appliquée AVANT le déploiement des nouveaux binaires.**
Justification : les nouveaux binaires (Web + Worker) lisent `client_code`/`server_instance`/
`environment`. Anciens binaires + nouveau schéma = compatible (colonnes additives
nullable, ignorées) ; **nouveaux binaires + ancien schéma = échec des requêtes**
(colonnes absentes). L'ordre sûr est donc : **migration d'abord, binaires ensuite**.
---
## PHASE 5 — Déploiement des binaires (copie)
### 5.1 Vérifier l'intégrité du package (SHA256)
```powershell
cd '<staging>'
# Install-OmikaServerFiles.ps1 fait cette vérification automatiquement.
Get-Content SHA256SUMS.txt | Select-Object -First 5 # aperçu
```
**Résultat attendu** : `SHA256SUMS.txt` présent et chaque empreinte correspond au fichier.
**STOP si** : un hash diffère ou un fichier manque.
### 5.2 Copie avec préservation de la configuration
Deux options (choisir **une seule**) :
**Option A — script existant (recommandé, idempotent, `-WhatIf` possible) :**
```powershell
cd '<staging>\Deploy'
.\Install-OmikaServerFiles.ps1 -PackageRoot '<staging>'
```
Ce script sauvegarde l'ancienne version, copie, **préserve** `appsettings.json` et
`appsettings.Production.json` du serveur, puis vérifie le nombre de fichiers et les EXE.
**Option B — copie manuelle (si le script ne convient pas) :**
```powershell
Copy-Item '<staging>\OmikaApi\*' 'C:\Omika\Apps\OmikaApi' -Recurse -Force
Copy-Item '<staging>\OmikaWorker\*' 'C:\Omika\Apps\OmikaWorker' -Recurse -Force
Copy-Item '<staging>\DiagnosticsWorker\*' 'C:\Omika\Apps\DiagnosticsWorker' -Recurse -Force
Copy-Item '<staging>\DiagnosticsWeb\*' 'C:\Omika\Apps\DiagnosticsWeb' -Recurse -Force
```
**STOP si** : le nombre de fichiers cible < source (copie incomplète) ou un EXE attendu
est absent (`Omika.Api.exe`, `Omika.Worker.exe`, `Omika.Diagnostics.Worker.exe`,
`Omika.Diagnostics.Web.exe`).
### 5.3 Ce qui ne doit JAMAIS être écrasé (§19)
- `appsettings.json` / `appsettings.Production.json` de chaque composant (secrets serveur).
- Variables d'environnement MACHINE (secrets) : jamais touchées.
- `C:\Omika\Data\Api\Diagnostics\Jsonl\**` (JSONL en attente) : jamais supprimés.
- `C:\Omika\Data\DiagnosticsWorker\Checkpoint\import-checkpoint.json` : jamais réinitialisé.
- `C:\Omika\Config\**` (config serveur) : jamais écrasé.
- Certificats TLS : jamais touchés.
---
## PHASE 6 — Vérification des configurations (avant redémarrage)
### 6.1 Clés de configuration attendues (placeholders — injectées SUR LE SERVEUR)
**Omika.Api** (Kestrel, service `OmikaApi`) :
```
ConnectionStrings__DiagnosticsDatabase = <SECRET> (si l'API lit aussi PG — voir architecture)
DiagnosticsIdentity__HmacKey = <SECRET_SERVEUR> (ActorKey, min 256 bits Base64)
MobileAuth__SigningKey = <SECRET_SERVEUR> (obligatoire, sinon fail-fast)
DiagnosticDeployment__ClientCode = <CODE_CLIENT ou vide → fallback HFSQL CODE_CLIENT>
DiagnosticDeployment__ServerInstanceId = <ex. API-SRV-01 ou vide → MachineName>
HFSQL / LicenseConnection / ServiceConnection / FilePasswords = <SECRETS_EXISTANTS>
Observability__Remote__Directory = <CHEMIN_JSONL> (doit être identique au Worker)
Observability__External__Enabled = false (ne pas activer le forwarder)
ASPNETCORE_ENVIRONMENT = Production
```
**Omika.Worker** (service `OmikaWorker`) :
```
Hfsql / SyncConnection = <SECRETS_EXISTANTS>
DiagnosticDeployment__ClientCode = <idem Api>
DiagnosticDeployment__ServerInstanceId = <ex. WORKER-SRV-01 ou vide → MachineName>
ASPNETCORE_ENVIRONMENT = Production
```
**Omika.Diagnostics.Worker** (service `OmikaDiagnosticsWorker`) :
```
ConnectionStrings__DiagnosticsDatabase = Host=127.0.0.1;Port=5432;Database=omika_diagnostics;Username=omika_diagnostics_worker;Password=<SECRET_LOCAL>
Observability__Remote__Directory = <CHEMIN_JSONL — IDENTIQUE à l'Api>
Diagnostics__Import__CheckpointDirectory = C:\Omika\Data\DiagnosticsWorker\Checkpoint
Diagnostics__Import__CheckpointFileName = import-checkpoint.json
Diagnostics__RetentionDays = 60
Diagnostics__RetentionIntervalHours = 24
Diagnostics__RetentionBatchSize = 1000
```
(Le Worker n'enregistre pas la source HFSQL → `DiagnosticDeployment__ClientCode` est
**config-only** ici ; `ServerInstance` = MachineName si non configuré.)
**Omika.Diagnostics.Web** (service `OmikaDiagnosticsWeb`) :
```
ConnectionStrings__DiagnosticsDatabase = Host=127.0.0.1;Port=5432;Database=omika_diagnostics;Username=omika_diagnostics_web;Password=<SECRET_LOCAL>
DiagnosticsAuth__Operators = <LISTE_OPERATEURS_PRODUCTION>
ASPNETCORE_ENVIRONMENT = Production (default-deny, PAS Development)
```
### 6.2 Contrôles
```text
[ ] Observability__Remote__Directory IDENTIQUE entre OmikaApi et DiagnosticsWorker (chemin absolu)
[ ] ASPNETCORE_ENVIRONMENT = Production pour les 4 services
[ ] ConnectionStrings__DiagnosticsDatabase présent pour DiagnosticsWorker + DiagnosticsWeb
[ ] DiagnosticsIdentity__HmacKey + MobileAuth__SigningKey présents pour Omika.Api
[ ] DiagnosticDeployment__ClientCode/ServerInstanceId cohérents (ou vide → fallback)
[ ] Aucune valeur de test dev (admin-local, smtp.example.com, postgres/postgres) dans les configs serveur
[ ] appsettings.Development.json ABSENT du dossier publié de DiagnosticsWeb (vérifié)
```
**STOP si** : `MobileAuth__SigningKey` absent (l'API refuse de démarrer), ou un secret
Production a été écrasé (restaurer depuis le backup Phase 2.2).
---
## PHASE 7 — Redémarrage (ordre recommandé)
PostgreSQL **déjà Running** (vérifié Phase 0.4). Ordre = démarrer les **consommateurs
avant les producteurs** :
```powershell
Start-Service OmikaDiagnosticsWorker # 1. importeur (consommateur JSONL)
Start-Service OmikaDiagnosticsWeb # 2. UI (lecture seule)
Start-Service OmikaWorker # 3. sync métier (producteur #2)
Start-Service OmikaApi # 4. API (producteur #1, dernier)
Get-Service OmikaApi,OmikaWorker,OmikaDiagnosticsWorker,OmikaDiagnosticsWeb | Select Name,Status
```
**Résultat attendu** : les 4 services = `Running`.
**STOP si** : un service passe en `Stopped`/`Failed` immédiatement → ne pas démarrer les
suivants, passer en diagnostic (Phase 8) puis rollback (Phase R) si non résolu.
---
## PHASE 8 — Smoke tests (non destructifs)
### 8.1 Health checks par service
```text
OmikaDiagnosticsWorker : Running ; log = "Contexte de déploiement Diagnostics initialisé"
puis import/retention sans exception PostgreSQL ;
checkpoint stable ou progressant ; aucun JSONL supprimé.
OmikaDiagnosticsWeb : Running ; HTTP 200 sur la page d'accueil ; login OK ; Dashboard OK.
OmikaWorker : Running ; pas de boucle d'erreur (pas de rafale d'exceptions HFSQL).
OmikaApi : Running ; endpoint de contrôle OK ; pas d'erreur de démarrage
(fail-fast SigningKey absent = NOK).
```
```powershell
# Web (local au serveur) :
Invoke-WebRequest -UseBasicParsing http://127.0.0.1:<PORT_WEB>/ | Select StatusCode
# Logs (Event Viewer ou fichiers) :
Get-EventLog -LogName Application -Newest 50 | Where-Object { $_.Source -match 'Omika' }
```
**Résultat attendu** : aucune exception PostgreSQL, aucun HTTP 500, aucune boucle d'erreur.
### 8.2 Contrôle PostgreSQL non destructif (après redémarrage)
```sql
SELECT count(*) FROM diagnostic_event;
SELECT max(timestamp_utc) AS dernier_evenement,
max(ingested_at_utc) AS dernier_ingere
FROM diagnostic_event;
SELECT client_code, server_instance, environment, count(*)
FROM diagnostic_event
WHERE ingested_at_utc > now() - interval '1 hour'
GROUP BY client_code, server_instance, environment
ORDER BY count(*) DESC;
```
**Résultat attendu** : des événements récents (`ingested_at_utc` proche de `now()`),
avec `client_code`/`server_instance`/`environment` renseignés sur les nouveaux événements
(les anciens restent NULL — normal, pas de backfill).
### 8.3 Test ClientCode Production (§32) — sur des événements réellement générés
Ouvrir Diagnostics.Web → Dashboard → Diagnostics, puis filtrer un client réel :
```text
ClientCode : non NULL (CodeClient de l'instance)
ServerInstance : identifiant d'instance (MachineName ou override)
Environment : Production
Component : Omika.Api / Omika.Worker selon le cas
AppVersion : 1.0.0.0 (ApplicationConstants.Version)
```
**Ne pas** injecter de faux payload de test sur Production.
### 8.4 Test synchronisation (§33) — sur une synchronisation métier existante
Sur `/Synchronisation`, prendre une opération réelle existante et vérifier :
```text
OperationId présent
ClientCode renseigné
Component Omika.Worker (et Omika.App)
ServerInstance instance Worker (jamais l'instance API)
Environment Production
```
**Ne créer aucune donnée métier artificielle risquée.**
---
## PHASE 9 — Validation Diagnostics (équivalent production de la recette T14-B)
| Contrôle | Page / SQL | Attendu |
|---|---|---|
| Dashboard | `/` | les compteurs reflètent les données réelles (24h/7j/30j) |
| Diagnostics | `/Diagnostics` | filtres Client/Serveur/Environnement fonctionnels |
| Pagination | `/Diagnostics?pageNumber=2` | « page 2 / N », filtres conservés dans l'URL |
| Détail | `/Diagnostics/{id}` | contexte client/serveur/env affiché (« Non renseigné » si NULL) |
| Erreurs | `/Erreurs` | groupes par EventCode/exception, multi-clients sans casser le fingerprint |
| Traces | `/Traces` | timeline par `trace_id`, opérations par `operation_id` |
| Synchronisation | `/Synchronisation` | différenciation App/Worker (serveurs distincts) |
| Telemetry | `/Telemetry` | agrégats cohérents avec SQL |
| Versions | `/Versions` | versions réelles distinctes |
**Résultat attendu** : équivalent au verdict T14-B (avec réserves connues : responsive
statique, producteurs réels non testables). Tout écart → STOP + analyse + rollback si bloquant.
---
## PHASE 10 — Surveillance post-déploiement
Période de surveillance manuelle (durée décidée par l'opérateur, ex. 24–48 h) :
```text
[ ] Services toujours Running (Get-Service, aucune transition vers Stopped/Failed)
[ ] Pas d'explosion de fichiers JSONL (nombre/taille stables, MaxFiles non atteint en continu)
[ ] Pas d'erreurs PostgreSQL dans les logs Worker/Web
[ ] Pas de HTTP 500 sur DiagnosticsWeb
[ ] Pas d'erreurs API anormales (pas de rafale 4xx/5xx)
[ ] Pas de boucle Worker (backoff borné, pas de redémarrage en rafale)
[ ] Checkpoint import : stable ou progressant (jamais régressif)
[ ] Dashboard : événements récents présents (pas de trou d'ingestion)
```
Signaux d'alerte (→ Phase R) : service qui ne démarre pas, migration échouée, 500 Web,
API KO, Worker en boucle, import cassé, hausse anormale d'erreurs.
---
## PHASE R — Rollback
### R.1 Conditions de déclenchement (§35)
```text
service ne démarre pas / Failed
migration échoue (ON_ERROR_STOP)
DiagnosticsWeb retourne 500
API ne démarre plus (fail-fast SigningKey, etc.)
Worker entre en boucle d'erreur
import Diagnostics cassé (checkpoint bloqué, erreurs Npgsql)
augmentation anormale d'erreurs
```
Dès qu'une condition est réunie : **STOP** puis **ROLLBACK**.
### R.2 Rollback binaires (§36)
```powershell
Stop-Service OmikaApi,OmikaWorker,OmikaDiagnosticsWorker,OmikaDiagnosticsWeb -Force
$stamp = '<horodatage_du_backup>' # ex. 20261001-180000
# Restaurer les dossiers précédents :
Remove-Item 'C:\Omika\Apps\OmikaApi' -Recurse -Force
Copy-Item "C:\Omika\Backups\OmikaApi_${stamp}" 'C:\Omika\Apps\OmikaApi' -Recurse -Force
Copy-Item "C:\Omika\Backups\OmikaWorker_${stamp}" 'C:\Omika\Apps\OmikaWorker' -Recurse -Force
Copy-Item "C:\Omika\Backups\DiagnosticsWorker_${stamp}" 'C:\Omika\Apps\DiagnosticsWorker' -Recurse -Force
Copy-Item "C:\Omika\Backups\DiagnosticsWeb_${stamp}" 'C:\Omika\Apps\DiagnosticsWeb' -Recurse -Force
# Vérifier la config préservée, puis :
Start-Service OmikaDiagnosticsWorker
Start-Service OmikaDiagnosticsWeb
Start-Service OmikaWorker
Start-Service OmikaApi
Get-Service OmikaApi,OmikaWorker,OmikaDiagnosticsWorker,OmikaDiagnosticsWeb | Select Name,Status
```
**Résultat attendu** : 4 services Running avec la version précédente.
**Préserver** : `appsettings*.json`, JSONL, checkpoint (jamais supprimés par ce rollback).
### R.3 Rollback base de données (§37/§38)
**Décision documentée : PAS de rollback SQL automatique.**
- 004/005 sont **additifs** (colonnes nullable + index + GRANT). Les anciens binaires
restent **compatibles** avec ces colonnes (ils ne les lisent pas).
- Aucun `DROP COLUMN`, aucun `DROP INDEX`, aucun `REVOKE` automatique.
- **Ne jamais** `TRUNCATE` / `DROP TABLE` / `DELETE` les événements créés après migration.
```text
Rollback privilégié : rollback APPLICATIF (binaires) + laisser la migration additive en place.
```
Restaurer la base via `pg_restore` est réservé au cas exceptionnel où une migration
aurait été destructrice (impossible ici) ou mal appliquée — et uniquement sur décision
humaine explicite, en restaurant le backup Phase 2.1 :
```powershell
& 'C:\Program Files\PostgreSQL\18\bin\pg_restore.exe' `
-h 127.0.0.1 -p 5432 -U postgres -d omika_diagnostics -c `
'C:\Omika\Backups\omika_diagnostics_<stamp>_before_upgrade.backup'
```
(Avertissement : `-c` supprime les objets existants — à n'utiliser qu'en dernier recours
et sur décision explicite. Jamais en rollback de routine.)
---
## Checklist GO / NO-GO finale (avant exécution)
```text
[ ] Backup PostgreSQL terminé et vérifié (présent + taille > 0)
[ ] Backup binaires (4 composants) terminé et vérifié
[ ] Packages Release validés (4 composants + Database + Deploy + SHA256SUMS)
[ ] Secrets Development absents des packages (appsettings.Development.json absent)
[ ] Migrations nécessaires identifiées (Phase 1.1 : 003/004/005 manquants ou non)
[ ] JSONL/checkpoint sauvegardés et inventoriés (jamais supprimés/réinitialisés)
[ ] Services arrêtables + fenêtre de maintenance validée
[ ] Ordre d'arrêt / redémarrage compris et validé
[ ] Procédure rollback prête (binaires + décision DB additive)
[ ] Commandes vérifiées (psql/pg_dump/copy paths confirmés)
[ ] Fenêtre de maintenance approuvée par l'exploitant
```
Un prérequis critique faux → **NO-GO**.
---
## Dettes restant hors périmètre (§43)
- `Install-DatabaseSchema.ps1` n'applique que **001 + 002** ; **004 et 005 ne sont
couverts par aucun script** (application manuelle documentée dans ce runbook). Dette
connue — pas corrigée dans T15.
- `Harden-DatabaseRoles.ps1` n'applique que **003**.
- Pas de DeviceInfo, pas de télémétrie d'écrans, pas de nouveaux EventCodes, pas de
refonte UI/dashboard — hors périmètre.