Appearance
Scan R1/D2 : déploiement, activation et retour arrière
État documenté le 3 novembre 2026 : le raccordement backend est fusionné sur staging dans la PR #634. Il est désormais activé sur l'API publique Docker, avec SCAN_CARD_ENGINE=r1. L'API Kubernetes staging n'a pas été redéployée par cette opération ; le service de modèles qu'elle utilise est appelé depuis l'API Docker.
Version effectivement déployée
L'image skns-api:r1-b5a37ee correspond au commit de livraison b5a37ee, branche deploy/scan-r1-20261103 : version précédemment en service 25e56308 avec uniquement le commit R1 c45c313 de la PR #634. La suite API a été rejouée sur cette combinaison : 1 648 réussis, 42 ignorés, aucun échec.
Pourquoi ce déploiement isolé ? La PR #632, également présente dans staging, ajoute une dépendance aux colonnes email_hash et email_hint de beta_invitation_codes (migration 0054). Ces colonnes sont absentes de la base contrôlée. Déployer tout staging aurait cassé le parcours d'inscription concerné. Cette opération conserve donc le code d'authentification déjà en service, sans appliquer de migration. La PR #632 reste fusionnée, mais n'est pas incluse dans cette image.
Le checkout historique du serveur reste sur sa version précédente ; l'image API est explicitement épinglée dans l'override Compose serveur. Les sources de livraison sont conservées séparément. Lors du prochain déploiement, ne pas confondre le HEAD de ce checkout, l'image réellement exécutée et staging : préparer la compatibilité de la migration 0054 avant de promouvoir toute la branche.
L'image précédente skns-api:before-r1-20261103 et la configuration avant bascule sont sauvegardées dans le dossier de livraison protégé sur le serveur. Ce dossier contient deployment.json et switch.py : l'action legacy conserve la nouvelle image et désactive R1 ; l'action rollback restaure l'ancienne configuration et l'ancienne image. Les deux recréent uniquement l'API et contrôlent sa santé. Les chemins privés et fichiers contenant des secrets restent sur le serveur.
Est-ce le service de Georgic ?
Oui, lorsque R1 est activé, l'API appelle le service interne utilisé par la page de test du scan dans l'administration staging. Ce service héberge le modèle de reconnaissance r1-2026-11-01 et le détecteur de coins D2. L'API ne passe pas par la page web admin et le téléphone n'appelle pas directement le service de modèles.
text
Application : envoi de la photo
→ API : POST /scan-lab?tcg=pokemon
→ contrôles d'accès, quotas et validation de l'image existants
→ choix du moteur dans la configuration du backend
→ r1 : service interne, POST /scan/test
→ détection D2 (repli Canny possible), reconnaissance R1 et index du bundle
→ API : enrichissement depuis le catalogue et décision de confiance
→ legacy : pipeline historique
→ réponse au format mobile existant
→ Application : affichage du résultatLe backend envoie model=r1-2026-11-01, detector=corners, kind=card, k=10 et include_crop=false. R1 utilise son propre index ; ses vecteurs ne sont pas comparés à l'index historique. Le frontend n'a ni nouveau choix de moteur, ni seuil de confiance, ni logique de repli à implémenter.
Périmètre et configuration
| Réglage | Effet |
|---|---|
SCAN_CARD_ENGINE=legacy ou absent | Ancien moteur ; valeur par défaut |
SCAN_CARD_ENGINE=r1 | R1 pour les demandes explicitement Pokémon |
SCAN_R1_URL | Origine HTTP(S) interne du service, sans identifiants, chemin, paramètres ni fragment |
Exemple uniquement si le service DNS est accessible depuis le réseau de l'API :
dotenv
SCAN_CARD_ENGINE=r1
SCAN_R1_URL=http://embeddings:8000embeddings peut être résolu dans le namespace Kubernetes concerné ; ce nom n'est pas automatiquement disponible depuis l'ancienne stack Docker. Choisir une adresse de service stable, pas l'adresse temporaire d'un pod. Ne pas exposer /scan/test publiquement pour contourner ce problème réseau.
One Piece et les demandes sans sélection explicite du TCG restent sur le pipeline historique. Le bundle R1 contrôlé couvre Pokémon international ; la reconnaissance japonaise n'est pas validée. Conserver le service historique, EMBEDDINGS_URL, son modèle et son index pour ces usages et pour le retour arrière.
Ce qu'il faut déployer ou relancer
- Relever la référence de l'image API actuellement déployée, sa configuration et la procédure de déploiement réellement utilisée, pour pouvoir revenir à cette version.
- Construire l'image API à partir d'un commit
stagingcontenant la PR #634 et l'identifier par une référence immuable. Ne pas réutiliser une ancienne image en supposant qu'un redémarrage charge le code fusionné. - Depuis le réseau de l'API, vérifier que le service répond sur
GET /healthzet queGET /scan/test/modelsannonce le modèle attendu. Vérifier l'accessibilité effective du bundle et de son index, pas seulement la santé du processus. - Configurer les deux variables ci-dessus dans le déploiement API. Si l'API est sous Kubernetes, utiliser le circuit GitOps existant et vérifier que les variables sont effectivement transmises au conteneur ; la modification Compose de la PR ne configure pas Kubernetes.
- Recréer/redéployer uniquement l'API avec la nouvelle image et cet environnement, puis effectuer les vérifications ci-dessous.
Sous Compose, conserver tous les fichiers d'override du déploiement et --env-file .env. Après construction ou récupération de l'image voulue, l'opération ciblée est up -d --no-deps --no-build api, ajoutée à cette commande Compose complète. Sous Kubernetes, mettre à jour la référence d'image et la configuration via GitOps puis contrôler le rollout.
Un simple docker restart ne remplace ni l'image ni l'environnement du conteneur. Il n'y a aucune migration SQL, aucun nouveau build mobile et aucune réindexation historique à effectuer pour ce raccordement. Le service R1/D2 était déjà en fonctionnement lors du contrôle ; son redémarrage n'est pas requis si les contrôles restent bons. PostgreSQL, Redis et les autres workers n'ont pas besoin d'être redémarrés pour cette modification.
Vérifier l'activation
- Vérifier la disponibilité et la préparation de l'API (
/healthz,/readyz) sur ses accès habituels. - Envoyer un scan Pokémon avec une session autorisée à la route mobile habituelle. Vérifier
recognition.engine=r1,recognition.model=r1-2026-11-01et le détecteur effectivement rapporté. Un HTTP 200 seul ne démontre pas l'utilisation de R1 : le moteur historique peut avoir pris le relais. - Vérifier le nom, l'identifiant, l'image et le détail de carte dans l'app ; contrôler les résultats incertains et sans correspondance, pas seulement une carte facile.
- Tester One Piece et le mode automatique : ils doivent conserver le chemin historique.
- Comparer plusieurs vraies photos, leur précision et leur latence. Le contrôle initial sur un visuel catalogue n'est pas une mesure de précision sur téléphone.
- Contrôler les journaux API :
scan.r1_fallbackindique un repli. Ce journal n'inclut ni photo ni corps de réponse du service.
L'API valide l'identité du modèle et de l'index, les scores et la présence de la meilleure carte dans son catalogue. Elle déduplique les rendus et applique des seuils serveur de 0,90 pour une identification et 0,75 pour un résultat incertain, avec la règle existante d'ambiguïté entre impressions. Ces seuils nécessitent une évaluation sur des photos réelles avant une activation générale.
Repli automatique et retour arrière volontaire
Une indisponibilité, un délai de réponse R1 dépassant cinq secondes, une réponse invalide ou une meilleure carte absente du catalogue API déclenchent le pipeline historique dans la même requête. Aucun deuxième appel du téléphone n'est requis. Le résultat de repli porte recognition.engine=legacy et recognition.fallbackReason=r1-unavailable.
Un résultat R1 valide mais incertain ou sans correspondance ne déclenche pas une autre tentative avec l'ancien moteur. Aucun coupe-circuit n'est ajouté : des indisponibilités répétées peuvent ajouter cinq secondes avant le traitement historique.
Pour désactiver R1 :
dotenv
SCAN_CARD_ENGINE=legacyRecréer/redéployer ensuite l'API uniquement pour charger cette valeur. La route mobile reste la même. Vérifier un scan et l'absence d'appel R1 ; la réponse historique normale ne garantit pas la présence d'un champ recognition. Conserver SCAN_R1_URL ne réactive pas R1 tant que le moteur vaut legacy.
Si le défaut concerne le démarrage ou le reste de l'API plutôt que R1, restaurer également la référence de l'image précédemment relevée via le circuit de déploiement habituel.
Preuves et limites connues
Les validations de la PR : 15 tests de routage ciblés, 1 648 tests API réussis (42 ignorés), 569 tests ingestion réussis et vérification TypeScript réussie. La suite E2E complète PostgreSQL/Redis n'a pas pu passer sur le poste, Docker étant absent. Les jobs CI examinés ont également échoué sur l'indisponibilité de Docker ou de rootlesskit sur les runners.
Un appel direct au service staging a reconnu le visuel catalogue bw2-9 avec un cosinus de 0,991335, en environ 989 ms hors chargement initial. Ce contrôle confirme le fonctionnement du service à cet instant, pas le déploiement du nouvel adaptateur ni sa qualité sur photos utilisateur.
Après activation, un contrôle supplémentaire a exécuté le handler de scan dans l'image en service, avec la vraie base en lecture seule, le service R1/D2 et le visuel catalogue bw2-9 :
| Contrôle | Résultat |
|---|---|
| Configuration déployée R1 | HTTP 200, identified, carte bw2-9, détecteur corners, environ 747 ms |
| Moteur historique explicitement choisi dans le test | HTTP 200, carte bw2-9, environ 664 ms |
| Service R1 rendu indisponible uniquement dans le test | HTTP 200, repli legacy, carte bw2-9, environ 676 ms |
| Santé et préparation sur l'URL publique | HTTP 200 pour /api/healthz et /api/readyz |
| Scan public sans authentification | HTTP 401 |
| Redémarrages inattendus et erreurs API pendant le contrôle | Aucun |
Ce test utilise une injection Fastify isolée : il vérifie le handler, PostgreSQL et le service réel, sans consommer le quota d'un utilisateur ni archiver sa photo. Il ne remplace pas un scan authentifié depuis un téléphone et ne mesure pas la précision sur photos réelles. Aucun compteur utilisateur n'a été remis à zéro. Seule l'API Docker a été recréée ; PostgreSQL, Redis, les workers et les services de modèles ont conservé leurs processus.
Références : code fusionné, suivi de la fonctionnalité, documentation backend.