CorrectionMajeur

Correction du resolver de signaux

Correction du resolver de signaux

Catégorie : Correction · Sévérité : Majeure Période impactée : 1er mai 2026 → 4 mai 2026 (environ 3 jours) Projections concernées : 119 signaux MLB et NHL non résolus automatiquement

Ce qui s'est passé

Une migration de sécurité (0006_rls_signals_results.sql) a activé Row Level Security sur la table signals de notre base Supabase, dans le cadre d'un durcissement plus large de la couche d'accès aux données.

Effet de bord non détecté : le composant resolver, qui tourne chaque soir pour aller chercher les résultats officiels MLB et NHL puis marquer les signaux comme résolus, lisait la liste des signaux à résoudre avec la clé d'accès anonyme. Cette clé ne permet plus, depuis la migration, de lire la table signals. Le resolver recevait donc une liste vide à chaque exécution et concluait qu'il n'y avait aucun signal à résoudre.

Le bug s'est manifesté progressivement, en fonction du moment où la migration a été appliquée par rapport aux exécutions du cron :

  • Cron de la nuit du 1er mai : 15 signaux sur 51 résolus avant que le bug ne s'active.
  • Cron de la nuit du 2 mai : 15 signaux sur 47 résolus, même schéma de bascule mid-cron.
  • Cron de la nuit du 3 mai : 0 signal sur 51 résolu — panne totale du resolver.

Conséquence cumulée : 119 signaux MLB et NHL produits entre le 1er et le 3 mai sont restés affichés en attente côté utilisateur, sans résultat W/L apparent dans l'historique public.

Comment l'incident a été identifié

Le matin du 4 mai, en consultant le tableau de bord, l'utilisateur a constaté que les signaux du 3 mai apparaissaient encore avec un statut en attente de résolution, contrairement aux jours précédents. Le diagnostic a confirmé que :

  1. Les matchs concernés étaient bien terminés (statut final côté APIs MLB et NHL)
  2. Aucune entrée n'avait été créée dans la table results pour ces signaux
  3. La requête de lecture du resolver renvoyait zéro ligne, alors qu'une requête équivalente avec la clé service_role en renvoyait des dizaines

L'audit complet via timestamps de la table results a révélé que le bug remontait au 1er mai, pas seulement au 3 mai. La cause racine était la régression silencieuse introduite par la migration RLS.

Ce que nous avons corrigé

  • La fonction _get_unresolved_signals du module data/collectors/results_tracker.py lit désormais avec service_role (qui contourne RLS), de la même manière que le font déjà les écritures du même module
  • Lancement manuel du resolver post-correction : 119 signaux résolus en différé, avec leurs résultats réels
  • Le commentaire en tête de la fonction documente la régression et le pattern à suivre pour les futures lectures côté serveur

Le correctif est en production depuis le 4 mai 2026 à 11h44 UTC. Le cron du soir tourne maintenant correctement et résout les nouveaux signaux dans la fenêtre habituelle.

Ce que nous ne masquons pas

Les 119 signaux affectés ont bien été résolus a posteriori, avec leurs résultats réels (pas de reconstitution, pas d'arrondi). Ils apparaissent désormais dans l'historique avec le résultat correct, daté du jour réel du match.

La fenêtre de panne (3 jours, du 1er au 3 mai) est documentée ici en clair. Aucune projection n'a été supprimée, masquée, ni reformulée a posteriori pour faire mieux paraître la performance.

Ce que nous ajoutons en prévention

  • Test de non-régression sur le pipeline de résolution : vérifie que le resolver lit bien avec une clé qui contourne RLS
  • Surveillance hebdomadaire du nombre de signaux en attente de résolution depuis plus de 24 heures, avec alerte si ce nombre dépasse zéro
  • Audit systématique post-migration de toute nouvelle politique RLS pour vérifier que les composants serveur lisent toujours avec la bonne clé

Comment lire les chiffres maintenant

Le taux de réussite global affiché sur l'accueil est calculé sur l'ensemble des projections résolues, peu importe la date de résolution. Les 119 signaux du 1er au 3 mai (résolus le 4 mai) y contribuent normalement, avec leurs résultats réels.


— L'équipe Diamond Signal