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

26 KiB
Raw Blame History

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)

$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)

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)

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)

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)

$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) :

$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)

$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

[ ] 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
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

$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) :

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

$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 :

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)

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) :

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) :

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

[ ] 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 :

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

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

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 :

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 :

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) :

[ ] 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)

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)

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.
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 :

& '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)

[ ] 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.