Files
Omika/docs/T15-RUNBOOK-DEPLOIEMENT-PRODUCTION.md
T
2026-10-23 18:46:50 +02:00

593 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.