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 |