26 KiB
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
- 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).- NE JAMAIS écraser les configurations Production existantes (secrets).
- NE JAMAIS supprimer les JSONL en attente ni remettre le checkpoint à zéro.
- NE JAMAIS faire de
TRUNCATE/DROP TABLE/DELETEd'événements en rollback.- Aucun secret n'apparaît dans ce document (placeholders
<SECRET_*>uniquement).- Toute commande SQL de migration utilise
ON_ERROR_STOP=1et 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,environmentprésentes → 005 déjà appliqué (ne pas réappliquer). - Index
ix_diagnostic_event_client_code_timestampprésent → 005 complet. SELECT+DELETEaccordés àomika_diagnostics_workersurdiagnostic_event→ 004 déjà appliqué.- Roles
omika_diagnostics_owner/worker/webprésents → 003 déjà appliqué. STOP si : la baseomika_diagnosticsest 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.jsonde 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, aucunDROP INDEX, aucunREVOKEautomatique. - Ne jamais
TRUNCATE/DROP TABLE/DELETEles é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.ps1n'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.ps1n'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.