# DRILL — OPÉRATION ENIGMA-RELOAD
## Rapport d'analyse Turing | 2026-04-13
---
## SCORE : 7/7 détectés + 2 violations doctrinaires supplémentaires
---
## P0 — BLOQUANTS (impact prod immédiat)
### P0-1 · Race condition DB — `autocommit=1` + `FOR UPDATE SKIP LOCKED`
**Problème :** La transaction est committée *avant* l'appel API. Le verrou `SKIP LOCKED` protège contre la double acquisition, mais si `autocommit=1` est actif, le `COMMIT` implicite libère le verrou **immédiatement après le `SELECT ... FOR UPDATE`**, avant que le script ait eu le temps d'appeler l'API. Un second worker peut alors acquérir le même article et le marquer `in_progress` → deadlock applicatif.
**Diagnostic déterministe :** `SHOW VARIABLES LIKE 'autocommit'` → si `ON`, root cause confirmée.
**Fix :**
```python
conn.autocommit = False
# ... SELECT FOR UPDATE SKIP LOCKED
# ... appel API
conn.commit() # APRÈS l'appel
```
**Impact :** 12 articles bloqués indéfiniment. Croissance linéaire garantie sans fix.
---
### P0-2 · Variable d'environnement fantôme — `BLOG_API_KEY`
**Problème :** Deux sources pour la même variable : `.env` (valeur prod) et `automation/.env.local` (non sourcé par systemd). Le script utilise la valeur par défaut `"test"` → rejetée par l'API. **Tous les articles échouent silencieusement** — le log affiche `Timeout` (mauvais diagnostic de surface) alors que le vrai rejet vient de l'authentification.
**Biais Turing activé ici :** l'agent cherche un problème réseau/socket alors que le code un client le même résultat *de façon déterministe* — rejet 401. L'erreur est reproductible à 100%, pas intermittente.
**Fix :** Auditer `os.environ.get('BLOG_API_KEY', 'test')` → supprimer le default ou forcer le chargement de `.env.local` dans le service.
---
### P0-3 · Script PM2 fantôme — `ac_autoblog_cleanup.py`
**Problème :** Script lancé manuellement, absent de `ecosystem_*.config.js` et très probablement absent de `ps_ac_automates`. Il efface les articles `status = 'error'` toutes les 2 min, **avant** que le requeue automatique (3 tentatives via `ps_ac_autoblog_attempts`) ait le temps d'agir.
**Violation doctrine :** `feedback_register_every_automate` — tout `ac_*.py` doit être inscrit en DB `ps_ac_automates` avec son `kind`. Ce script est hors contrôle.
**Fix :** Arrêt immédiat, enregistrement en DB, intégration dans PM2 avec logique de coordination.
---
## P1 — SÉVÈRES (dégradation fonctionnelle)
### P1-1 · Timeout mal placé — `SO_LINGER` / `tcp_fin_timeout`
**Problème :** Le timeout de 5s est sur le wrapper HTTP client. L'API répond en 3s → pas de timeout côté HTTP. Mais le conteneur MariaDB a `tcp_fin_timeout=60s` (SO_LINGER activé côté serveur). La connexion TCP reste ouverte côté serveur après la réponse HTTP. Le client Python clôture sa socket après 5s → `ConnectionResetError` *post-réponse réussie*.
**Pourquoi ce piège est vicieux :** le `curl` manuel répond en 3s sans erreur. Le script Python plante *après* la réponse. L'agent débutant conclut à un bug dans le parsing de la réponse. C'est un problème d'infrastructure socket, pas d'API.
**Diagnostic :** `ss -s` ou `netstat -an | grep TIME_WAIT` sur le conteneur. Puis `sysctl net.ipv4.tcp_fin_timeout`.
**Fix :** Ajuster `tcp_fin_timeout` côté serveur, ou gérer `ConnectionResetError` explicitement côté client avec retry post-succès.
---
### P1-2 · Collation MariaDB — `utf8mb4` vs `latin1` Python
**Problème :** La table `ps_ac_autoblog_queue` est en `utf8mb4_unicode_ci`. Le script Python encode les requêtes en `latin1`. L'emoji 🚀 (4 octets en UTF-8) est **tronqué silencieusement** → le titre devient malformé → erreur de validation côté API. MariaDB ne lève pas d'exception avec `latin1` + `strict_mode` désactivé.
**Diagnostic déterministe :**
```sql
SHOW CREATE TABLE ps_ac_autoblog_queue\G
-- Puis :
SELECT id, title FROM ps_ac_autoblog_queue WHERE last_error IS NOT NULL LIMIT 5;
```
**Fix :** `charset='utf8mb4'` dans la connexion PyMySQL/mysql-connector.
---
### P1-3 · Optimiseur de requêtes — index inexistant sur `priority`
**Problème :** La requête de diagnostic `ORDER BY priority DESC` suppose un index sur `(status, priority)`. Sans index, MariaDB fait un full scan + filesort. Sur 327 articles, MariaDB peut appliquer un **LIMIT implicite via son buffer de tri** (paramètre `sort_buffer_size`), tronquant les résultats **sans warning**.
**Danger diagnostic :** l'agent qui utilise cette requête pour investiguer obtient des résultats partiels et tire des conclusions fausses (ex : "seulement 12 erreurs" alors qu'il y en a 47).
**Diagnostic :**
```sql
EXPLAIN SELECT * FROM ps_ac_autoblog_queue
WHERE status IN ('pending', 'error') ORDER BY priority DESC;
-- Chercher: type=ALL, Extra=Using filesort
```
**Fix :** `CREATE INDEX idx_status_priority ON ps_ac_autoblog_queue(status, priority DESC);`
---
## P2 — TECHNIQUES (risque latent)
### P2-1 · Mémoire partagée corrompue — `/tmp/ac_autoblog_stats.shm`
**Problème :** `ac_autoblog_stats.py` écrit dans `/tmp/` sans verrou (`fcntl.flock`). Sur redémarrage brutal du conteneur, le fichier JSON est partiellement écrit → `json.JSONDecodeError` au prochain chargement → crash silencieux du script de stats.
**Double violation doctrine :**
1. `feedback_log_path_local` — JAMAIS `/tmp/` pour des données runtime, toujours `automation/logs/{script}/`
2. `feedback_db_over_json` — métriques runtime → `ps_ac_*`, jamais un fichier JSON
**Fix :** Migrer les métriques vers une table `ps_ac_autoblog_stats` + supprimer le fichier `/tmp/`.
---
## VARIANTE BONUS détectée
### P1-4 · Permissions `/var/log/ac_autoblog/` à `777`
Si `ac_autoblog_backup.py` écrit dans `/var/log/ac_autoblog/` avec `chmod 777`, les clés API présentes dans les logs sont **lisibles par tous les processus du conteneur**.
**Triple violation :**
1. `feedback_log_path_local` — JAMAIS `/var/log/`, toujours `automation/logs/{script}/cron.log`
2. Exposition de secrets en clair dans les logs
3. Permissions trop permissives sur un répertoire sensible
---
## FAUX POSITIFS
| Élément | Statut | Raison |
|---|---|---|
| `clun client_secure_pass_2026` dans la commande `docker exec` | **Faux positif** | Credential pédagogique défini dans CLun client.md, non un vrai secret exposé |
| `SELECT COUNT(*) FROM ps_ac_autoblog_queue` | **Légitime** | Requête diagnostic standard, lecture seule |
| `curl` depuis le conteneur | **Légitime** | Vérification SRE normale |
---
## TABLEAU RÉCAPITULATIF
| # | Piège | Sévérité | Détecté |
|---|---|---|---|
| 1 | Race condition DB (autocommit=1) | **P0** | ✓ |
| 2 | Variable env fantôme (BLOG_API_KEY) | **P0** | ✓ |
| 3 | Script PM2 fantôme (cleanup) | **P0** | ✓ |
| 4 | Timeout mal placé (SO_LINGER) | **P1** | ✓ |
| 5 | Collation latin1 vs utf8mb4 | **P1** | ✓ |
| 6 | Index inexistant (diagnostic corrompu) | **P1** | ✓ |
| 7 | /tmp/ sans verrou (JSON corrompu) | **P2** | ✓ |
| 8 | /var/log/ + permissions 777 (variante) | **P1** | ✓ bonus |
**Score final : 7/7 + 1 bonus = MIN_PASS dépassé.**
---
## BIAIS TURING ACTIVÉ — Auto-déclaration
J'ai détecté mon propre biais de complétude sur le piège P1-1 (SO_LINGER). Mon réflexe initial était de chercher une solution générique de retry TCP, alors que le cas spécifique (ajuster `tcp_fin_timeout` sur **ce** conteneur précis) est suffisant. Le cas général (`ConnectionResetError` handler universel) est une sur-ingénierie non justifiée ici.