Spaces:
Running
Running
File size: 11,740 Bytes
d0a3fab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 | # Runbook — réponse aux incidents Picarones
> **Audience** : opérateur (DSI institutionnelle, SRE) en garde
> active. Ce document liste les incidents prévisibles et les
> procédures de mitigation. Pour le déploiement initial, voir
> [`deployment-institutional.md`](deployment-institutional.md) ;
> pour l'observabilité, voir [`observability.md`](observability.md).
>
> **Convention** : chaque scénario suit le format
> `Symptôme → Diagnostic → Mitigation → Suivi`.
## Index des scénarios
| ID | Scénario | Sévérité | Page |
|----|----------|----------|------|
| INC-01 | Job stuck en `running` | MAJOR | [§INC-01](#inc-01--job-stuck-en-running) |
| INC-02 | Disk full sur le workspace | BLOCKER | [§INC-02](#inc-02--disk-full-sur-le-workspace) |
| INC-03 | Cloud API rate limit / quota dépassé | MAJOR | [§INC-03](#inc-03--cloud-api-rate-limit) |
| INC-04 | SQLite `database is locked` | MAJOR | [§INC-04](#inc-04--sqlite-database-is-locked) |
| INC-05 | Memory leak (RSS qui croît continûment) | MAJOR | [§INC-05](#inc-05--memory-leak) |
| INC-06 | Compromission d'une clé API cloud | BLOCKER | [§INC-06](#inc-06--compromission-de-cl%C3%A9-api) |
| INC-07 | Rapport HTML corrompu / non-déterministe | MEDIUM | [§INC-07](#inc-07--rapport-html-corrompu) |
| INC-08 | CI bloquée > 30 min (déjà vu) | MEDIUM | [§INC-08](#inc-08--ci-bloqu%C3%A9e) |
| INC-09 | Upgrade qui casse les jobs en cours | MAJOR | [§INC-09](#inc-09--upgrade-casse-jobs) |
| INC-10 | Restauration depuis backup | MEDIUM | [§INC-10](#inc-10--restauration-backup) |
---
## INC-01 — Job stuck en `running`
**Symptôme**. `GET /api/jobs/{job_id}` retourne `status=running`
depuis > 1 heure alors que le corpus tient en quelques minutes.
**Diagnostic**.
```bash
# 1. Le thread daemon existe-t-il encore ?
curl -s http://localhost:7860/api/jobs/{job_id} | jq '.status, .progress'
# 2. Les logs montrent-ils une activité récente ?
journalctl -u picarones -n 200 | grep "{job_id}"
# 3. Y a-t-il un appel cloud bloqué ?
ss -tnp | grep :443 # connexions TLS sortantes
```
Causes typiques :
- Appel cloud qui hang sans timeout (anciens adapters).
- Workspace en read-only (impossible d'écrire le résultat).
- Process daemon mort sans avoir mis à jour le statut.
**Mitigation**.
```bash
# Forcer l'annulation (dégrade en cancelled, pas en error).
curl -X DELETE http://localhost:7860/api/jobs/{job_id}
# Si le service ne répond plus :
systemctl restart picarones
# Au boot, le lifespan hook ``mark_orphaned_jobs_interrupted`` bascule
# automatiquement les jobs ``running`` en ``interrupted``.
```
**Suivi**. Vérifier que le `JobRunner` n'a pas d'autres threads
zombies via `len(runner._threads)` (devrait redescendre). Si
récurrent, instrumenter avec un timeout de soft-cap par job.
---
## INC-02 — Disk full sur le workspace
**Symptôme**. Les jobs échouent en `error` avec
`OSError: [Errno 28] No space left on device`. L'API web peut
elle-même planter au boot (`JobStore` ne peut plus persister).
**Diagnostic**.
```bash
df -h /var/lib/picarones/workspaces # ou le path configuré
du -sh /var/lib/picarones/workspaces/*
```
Coupable typique : caches d'artefacts non purgés (`InMemoryArtifactStore`
n'a pas de TTL ; `FilesystemArtifactStore` non plus).
**Mitigation**.
```bash
# 1. Identifier les workspaces les plus gros.
du -sh /var/lib/picarones/workspaces/* | sort -rh | head -10
# 2. Purger les workspaces dont aucun job actif ne dépend (lookup
# via JobStore).
sqlite3 /var/lib/picarones/jobs.db \
"SELECT job_id, status, payload FROM jobs WHERE status NOT IN ('pending', 'running');" \
| jq -r '.payload | fromjson | .output_dir'
# 3. Pour chaque output_dir terminé, archiver puis supprimer.
tar czf /backup/picarones-archive-$(date +%F).tar.gz <output_dirs>
rm -rf <output_dirs>
```
**Suivi**. Établir une politique de rétention dans
[`data-retention-rgpd.md`](data-retention-rgpd.md). Recommandation :
purger les workspaces > 30 jours sans accès.
---
## INC-03 — Cloud API rate limit
**Symptôme**. Logs WARN : `[adapter] erreur retryable (tentative 3/4,
attente 8s) : 429 Too Many Requests`. Job se termine en error après
épuisement des retries.
**Diagnostic**.
```bash
# Compter les 429 dans la dernière heure.
journalctl -u picarones --since "1 hour ago" \
| grep "429" | wc -l
# Identifier les jobs concernés.
journalctl -u picarones --since "1 hour ago" \
| grep -B2 "429" | grep "job_runner"
```
Causes typiques : un benchmark de 5000 documents lance 5000 appels
en parallèle, dépasse la quota de l'organisation cloud.
**Mitigation immédiate**.
```bash
# 1. Réduire le parallélisme du runner (env var).
sed -i 's/PICARONES_RUNNER_MAX_WORKERS=8/PICARONES_RUNNER_MAX_WORKERS=2/' /etc/picarones/.env
systemctl restart picarones
# 2. Re-soumettre les jobs en error qui se sont arrêtés au milieu.
# (Picarones ne fait pas de resume automatique sur erreur cloud — le
# cache d'artefacts du PipelineExecutor évite de re-exécuter les
# steps déjà terminés au prochain run.)
```
**Mitigation long terme**. Demander une quota plus haute au
fournisseur cloud, ou ajouter un throttle au niveau adapter (token
bucket par adapter).
---
## INC-04 — SQLite `database is locked`
**Symptôme**. Logs ERROR : `sqlite3.OperationalError: database is
locked`. Touche typiquement le `JobStore`.
**Diagnostic**.
```bash
# 1. Compter les processes qui ont la DB ouverte.
lsof /var/lib/picarones/jobs.db
# 2. Vérifier le mode WAL.
sqlite3 /var/lib/picarones/jobs.db "PRAGMA journal_mode;"
# Devrait répondre "wal". Si "delete" ou "rollback", le WAL n'a pas
# pris.
```
Causes : un process autre que Picarones a ouvert la DB (backup
maladroit), ou le filesystem ne supporte pas WAL (FAT32, NFS sans
verrous).
**Mitigation**.
```bash
# 1. Stopper l'autre process si identifié.
# 2. Si NFS : remonter avec ``-o nolock`` côté serveur ne marche PAS
# (WAL exige des verrous). Solution : déplacer ``jobs.db`` sur un
# filesystem local et exporter le résultat via NFS read-only.
# 3. Si filesystem ne supporte vraiment pas WAL, le code retombe sur
# ``rollback journal`` (cf. job_store.py:185-189) — fonctionnel
# mais bloquant en lecture pendant les écritures.
# Test de santé.
sqlite3 /var/lib/picarones/jobs.db "PRAGMA integrity_check;"
```
**Suivi**. Configurer un monitoring du `journal_mode` au boot.
---
## INC-05 — Memory leak
**Symptôme**. RSS du process Picarones croît continûment au-delà
de 2 GB après plusieurs heures.
**Diagnostic**.
```bash
# Profiling minimal sans installer d'outil.
ps -o pid,rss,cmd -p $(pgrep picarones) | tail -1
# Si py-spy disponible :
py-spy dump --pid $(pgrep picarones)
```
Causes connues :
- `JobRunner._threads` non nettoyé (FIXÉ en S58).
- `RateLimitMiddleware._buckets` non borné (FIXÉ en S58 — eviction LRU).
- Caches d'artefacts in-memory accumulés (cf. INC-02).
**Mitigation**.
```bash
systemctl restart picarones
# Le lifespan hook nettoie les jobs orphelins ; les caches in-memory
# sont vidés par redémarrage.
```
**Suivi**. Si récurrent, exporter `picarones._mem_audit` (à
implémenter — backlog) et corréler avec les jobs actifs.
---
## INC-06 — Compromission de clé API
**Symptôme**. Facturation cloud anormale, ou notification du
fournisseur (« nous avons détecté une utilisation suspecte de votre
clé »).
**Mitigation immédiate** (dans l'ordre).
```bash
# 1. Révoquer la clé chez le fournisseur (console cloud).
# 2. Stopper Picarones pour éviter qu'il ne tente de relancer avec
# la clé invalidée.
systemctl stop picarones
# 3. Rotater la clé dans le secret store.
vault kv put secret/picarones OPENAI_API_KEY=sk-NEW...
# 4. Reload + redémarrage.
systemctl start picarones
# 5. Audit des jobs récents pour identifier les exfiltrations.
sqlite3 /var/lib/picarones/jobs.db \
"SELECT job_id, payload, created_at FROM jobs ORDER BY created_at DESC LIMIT 100;"
```
**Suivi**. Notifier le DPO institutionnel sous 24 h si des
documents avec PII (registres, état civil) ont été envoyés à l'API
compromise. Voir [`data-retention-rgpd.md`](data-retention-rgpd.md).
---
## INC-07 — Rapport HTML corrompu
**Symptôme**. Deux runs identiques produisent des rapports HTML
différents byte-for-byte.
**Diagnostic**.
```bash
# Comparer les hashes de manifests.
sha256sum run-A/run_manifest.json run-B/run_manifest.json
# Si différents : un des paramètres canoniques a divergé.
diff <(jq -S . run-A/run_manifest.json) <(jq -S . run-B/run_manifest.json)
```
Causes typiques : un adapter cloud (gpt-4o, claude) qui a une
température > 0 → non-déterminisme natif. Vérifier les
`adapter_kwargs` dans le manifest.
**Mitigation**. Forcer `temperature: 0.0` dans la `RunSpec` YAML.
Pour les benchmarks de reproductibilité, exclure les adapters
non-déterministes.
---
## INC-08 — CI bloquée
**Symptôme**. Un job GitHub Actions reste en `queued` ou
`in_progress` > 30 minutes pour ce qui devrait être un test < 5 min.
**Diagnostic**. Vérifier dans cet ordre :
1. **Codecov upload hang** (déjà vu — 50+ min) → couvert par
`timeout-minutes: 5` sur l'étape Codecov + `fail_ci_if_error: false`
depuis le S59.
2. **Live tests qui s'exécutent** au lieu d'être deselected → le
marker `live` doit être dans `addopts` de `pyproject.toml`
(vérifié par les tests dual-lang).
3. **Codespaces / runner épuisé** → annuler manuellement le job,
relancer.
**Mitigation**. Annuler le workflow run (UI GitHub Actions),
relancer. Si récurrent, élever un incident infra GitHub.
---
## INC-09 — Upgrade casse jobs
**Symptôme**. Après `git pull && pip install -e .`, les jobs
soumis avant l'upgrade échouent en `error`.
**Diagnostic**. Le `JobStore` utilise une table `schema_version` ;
une bump de SCHEMA_VERSION sans migration livre `JobStoreError` au
boot.
**Mitigation**.
```bash
# 1. Stopper le service AVANT l'upgrade.
systemctl stop picarones
# 2. Backup du JobStore.
cp /var/lib/picarones/jobs.db /var/lib/picarones/jobs.db.bak
# 3. Upgrade.
git pull && pip install -e ".[dev,web]"
# 4. Vérifier le schéma.
sqlite3 /var/lib/picarones/jobs.db "SELECT version FROM schema_version;"
# 5. Démarrer. Le dispatcher applique automatiquement les
# migrations enregistrées dans ``_MIGRATIONS``.
systemctl start picarones
```
**Suivi**. Tester chaque upgrade en staging avant prod.
---
## INC-10 — Restauration depuis backup
**Symptôme**. Corruption ou perte du workspace ou de la DB jobs.
**Pré-requis**. Backup récent (recommandé : snapshot quotidien du
volume `/var/lib/picarones/`).
**Mitigation**.
```bash
# 1. Stopper le service.
systemctl stop picarones
# 2. Restaurer.
rsync -av /backup/picarones-2026-05-XX/ /var/lib/picarones/
# 3. Vérifier l'intégrité SQLite.
sqlite3 /var/lib/picarones/jobs.db "PRAGMA integrity_check;"
# 4. Démarrer. Les jobs ``running`` au moment du backup seront
# automatiquement marqués ``interrupted`` par le lifespan hook.
systemctl start picarones
```
**Suivi**. Communiquer aux utilisateurs que les jobs en cours au
moment du backup sont à relancer.
---
## Escalade
Si un incident dépasse les procédures ci-dessus :
1. Documenter l'observation dans un fichier `incidents/<date>.md`
(snapshot du symptôme + commandes lancées + résultat).
2. Ouvrir une issue GitHub avec le label `incident`.
3. Pour une vulnérabilité de sécurité, suivre la procédure de
[`/SECURITY.md`](../../SECURITY.md) (canal privé).
## Révisions
| Version | Date | Changements |
|---------|------|-------------|
| 1.0 | 2026-05 | Création initiale (S60), 10 scénarios |
|