# 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 `` 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__` 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 = '' $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 = '' $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 = '' & '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 '\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 = '' & '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 '\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 '' # 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 '\Deploy' .\Install-OmikaServerFiles.ps1 -PackageRoot '' ``` 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 '\OmikaApi\*' 'C:\Omika\Apps\OmikaApi' -Recurse -Force Copy-Item '\OmikaWorker\*' 'C:\Omika\Apps\OmikaWorker' -Recurse -Force Copy-Item '\DiagnosticsWorker\*' 'C:\Omika\Apps\DiagnosticsWorker' -Recurse -Force Copy-Item '\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 = (si l'API lit aussi PG — voir architecture) DiagnosticsIdentity__HmacKey = (ActorKey, min 256 bits Base64) MobileAuth__SigningKey = (obligatoire, sinon fail-fast) DiagnosticDeployment__ClientCode = DiagnosticDeployment__ServerInstanceId = HFSQL / LicenseConnection / ServiceConnection / FilePasswords = Observability__Remote__Directory = (doit être identique au Worker) Observability__External__Enabled = false (ne pas activer le forwarder) ASPNETCORE_ENVIRONMENT = Production ``` **Omika.Worker** (service `OmikaWorker`) : ``` Hfsql / SyncConnection = DiagnosticDeployment__ClientCode = DiagnosticDeployment__ServerInstanceId = 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= Observability__Remote__Directory = 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= DiagnosticsAuth__Operators = 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:/ | 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 = '' # 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__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.