Spaces:
Running
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; pour l'observabilité, voirobservability.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-02 | Disk full sur le workspace | BLOCKER | §INC-02 |
| INC-03 | Cloud API rate limit / quota dépassé | MAJOR | §INC-03 |
| INC-04 | SQLite database is locked |
MAJOR | §INC-04 |
| INC-05 | Memory leak (RSS qui croît continûment) | MAJOR | §INC-05 |
| INC-06 | Compromission d'une clé API cloud | BLOCKER | §INC-06 |
| INC-07 | Rapport HTML corrompu / non-déterministe | MEDIUM | §INC-07 |
| INC-08 | CI bloquée > 30 min (déjà vu) | MEDIUM | §INC-08 |
| INC-09 | Upgrade qui casse les jobs en cours | MAJOR | §INC-09 |
| INC-10 | Restauration depuis backup | MEDIUM | §INC-10 |
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.
# 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.
# 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.
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.
# 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. 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.
# 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.
# 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.
# 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.
# 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.
# 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._threadsnon nettoyé (FIXÉ en S58).RateLimitMiddleware._bucketsnon borné (FIXÉ en S58 — eviction LRU).- Caches d'artefacts in-memory accumulés (cf. INC-02).
Mitigation.
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).
# 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.
INC-07 — Rapport HTML corrompu
Symptôme. Deux runs identiques produisent des rapports HTML différents byte-for-byte.
Diagnostic.
# 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 :
- Codecov upload hang (déjà vu — 50+ min) → couvert par
timeout-minutes: 5sur l'étape Codecov +fail_ci_if_error: falsedepuis le S59. - Live tests qui s'exécutent au lieu d'être deselected → le
marker
livedoit être dansaddoptsdepyproject.toml(vérifié par les tests dual-lang). - 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.
# 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.
# 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 :
- Documenter l'observation dans un fichier
incidents/<date>.md(snapshot du symptôme + commandes lancées + résultat). - Ouvrir une issue GitHub avec le label
incident. - Pour une vulnérabilité de sécurité, suivre la procédure de
/SECURITY.md(canal privé).
Révisions
| Version | Date | Changements |
|---|---|---|
| 1.0 | 2026-05 | Création initiale (S60), 10 scénarios |