La première moitié du travail de rétro-ingénierie — découpage mécanique, identification de la famille algorithmique, vérification différentielle — permet d’affirmer : « je sais ce que ce code calcule ». Mais cette conclusion ne se déploie pas telle quelle. La logique reste prisonnière de son runtime d’origine, et il faut choisir sa forme finale : une fonction pure intégrée à la CI, ou un processus externe qu’il faudra surveiller. Un mauvais choix annule rapidement le temps gagné en amont, avec les intérêts côté exploitation.
La vue d’ensemble en quatre étapes résume ce point en une ligne : « Étape 4, dégrader par couche de transport. » Cet article détaille ce principe. L’idée essentielle tient en une phrase : à chaque niveau inférieur, la surface de dépendances, les modes de défaillance et le coût de déploiement augmentent d’un ordre de grandeur ; par défaut, il faut donc se battre pour remonter.
Trois niveaux, dans un ordre non négociable
Il n’existe que trois façons d’intégrer cette logique, à classer du meilleur au moins bon. Cet ordre doit figurer dans vos conventions ; il ne doit pas être décidé au cas par cas selon « ce qui tourne le plus vite » :
Réécriture native. Réécrivez dans le langage cible, sans dépendre du runtime d’origine ni d’autre chose que de la bibliothèque standard. À condition d’avoir correctement identifié la famille algorithmique — une fois l’étape de fingerprinting validée, 90 % proviennent de l’implémentation publique, les quelques écarts restants sont traités séparément, puis le tout devient une fonction pure.
Moteur JS local exécutant un fragment minimal. Certaines logiques coûteraient trop cher à purifier à court terme. On conserve alors une petite portion du JavaScript d’origine et on n’exécute que les quelques dizaines de lignes nécessaires dans un Node/V8 local — jamais la page entière.
Pont navigateur passif. Une catégorie d’état n’existe que dans le runtime d’une page réelle, ouverte et connectée : une signature fournie au runtime, un identifiant dynamique lié à la session. La reconstruction statique ne peut pas la reproduire ; pour l’instant, elle ne peut être lue qu’à l’intérieur du navigateur. Cette forme est temporaire, doit être signalée dans la documentation d’interface et ne doit jamais devenir le comportement par défaut.
Pourquoi chaque niveau coûte bien plus cher
Si cet ordre est fixe, c’est que les coûts ne progressent pas linéairement : chaque niveau représente un ordre de grandeur de plus que le précédent.
Niveau | Surface de dépendances | Mode de défaillance | Compatible CI ? |
|---|---|---|---|
Réécriture native | Bibliothèque standard, aucun processus externe | Sortie différente de l’attendu, localisée par un | Oui — c’est une fonction pure |
Moteur JS local | Un runtime Node supplémentaire ; le contexte V8 n’est pas thread-safe, la concurrence demande un verrou | Version du moteur, globale dont dépend le fragment absente | À peine — il faut installer le moteur |
Pont navigateur passif | Un vrai Chrome + une extension + une session entretenue par un humain + un canal local loopback | Page non ouverte, session expirée, structure modifiée, onglet fermé | Non — une personne active est nécessaire |
Au premier niveau, un test unitaire détecte les pannes ; au troisième, la panne devient « l’utilisateur a fermé cet onglet aujourd’hui ». Transformer en composant de troisième niveau ce qui pourrait être une fonction pure revient à rattacher un humain à chaque appel. Point de repère : après identification de la famille algorithmique, un SDK de signature obfusqué peut devenir une implémentation autonome de moins de 600 lignes, reposant uniquement sur le crypto intégré et fonctionnant au premier niveau. Ce que vous pensiez ne pouvoir faire passer que par un pont navigateur est souvent simplement un algorithme pas encore complètement identifié.
Pont passif : un instantané, jamais une action
Un pont passif ne dérape que d’une seule manière : lorsqu’il commence à « aider » — rafraîchissement automatique, connexion automatique, attente automatique du chargement. Chaque automatisme le fait glisser de simple relais vers crawler. Sa frontière doit donc être extrêmement étroite. Ces contraintes ont été apprises à la dure :
Un unique relevé instantané, sans jamais modifier la page. Interrogez une fois, et une seule, les cookies, l’état de session et le runtime d’une page déjà ouverte. Ne créez pas d’onglet, ne rafraîchissez pas, ne naviguez pas, ne mettez pas la page au premier plan, n’attendez pas en polling. Une page absente est absente : ne l’ouvrez pas à la place de l’utilisateur.
Renvoyer une erreur explicite dès qu’il manque quelque chose. En l’absence d’onglet correspondant, renvoyez tab_unavailable ; si la page est présente mais non connectée, not_logged_in ; si la connexion est établie mais que le runtime n’est pas prêt, runtime_unavailable. Ces trois codes décrivent chacun un état réel et une action suivante — attendre la page, se connecter ou changer de cible — au lieu d’un vague « échec » que l’appelant devrait interpréter lui-même.
L’état sensible ne sort jamais du navigateur. L’extension ne demande aucune permission cookies / webRequest. Elle interroge uniquement un onglet correspondant déjà ouvert, termine la requête dans le contexte de cette page et nettoie les champs avant le retour. Les cookies et l’état de signature de la page ne quittent donc jamais Chrome, pas même une étape ; le canal se connecte par défaut à une adresse loopback locale. Le pont transporte des résultats, pas des identifiants.
Pourquoi 15 scopes ne couvrent que quelques plateformes
Le pont passif ne relaie que des requêtes placées sur liste blanche : chemin, paramètres et referer sont tous contraints par un adaptateur. Sa granularité est contre-intuitive : on compte 15 scopes pour seulement quelques plateformes, car les scopes sont découpés par « contexte de page », et non par « plateforme ». TikTok comprend par exemple Creative Center, Top Ads, Creator platform, la bibliothèque d’influenceurs et Ads Manager : cinq scopes distincts, cinq états de session indépendants, cinq runtimes de page indépendants. Être connecté au back-office publicitaire ne donne pas accès au runtime de Creator platform. Découpez un seul scope par plateforme, et le premier cas « connecté au sous-site A, mais impossible de servir le sous-site B » vous ramènera à la case départ. De la même façon, le site principal de Xiaohongshu, ses chemins équivalents à l’application et sa marketplace de créateurs forment trois scopes.
La granularité de l’ordonnancement en découle : le verrou ne s’applique qu’au niveau de la famille de plateformes. Les requêtes de la même famille, par exemple la famille Douyin, s’exécutent en série : elles réutilisent le même onglet réel et des appels concurrents dans un seul contexte de page se marcheraient dessus. En revanche, les familles différentes, telles que Douyin et Xiaohongshu, tournent en parallèle puisqu’elles utilisent deux onglets sans rapport. Ajoutez aussi un intervalle minimal entre les requêtes d’une même famille. Trop large, le verrou sérialise un travail parallélisable ; trop fin, les requêtes qui partagent un onglet entrent en collision. La famille de plateformes est précisément la frontière naturelle de ce qui « partage le même runtime de page ».
Relais de connexion explicite : la seule intervention humaine admise
Le pont passif n’opère pas la page, mais les sessions expirent. La solution consiste à réduire l’intervention humaine à une action explicite et ponctuelle : une commande interactive lance un relais, le programme ouvre via le système d’exploitation la page métier concernée, attend que vous vous connectiez manuellement et que la page soit prête, puis rejoue la requête initiale. Pendant tout ce temps, l’extension ne clique sur aucun bouton, ne remplit aucun formulaire et n’exporte aucun cookie. La connexion se fait dans un vrai navigateur, par vous ; le programme ne reprend la requête qu’une fois l’opération terminée.
La contrainte essentielle est la suivante : ce relais ne doit jamais être déclenché implicitement. Une commande non interactive — CI ou tâche planifiée — ne doit jamais ouvrir un navigateur. Elle renvoie simplement une erreur de session propre, puis laisse la couche supérieure décider. Le relais doit également éviter les faux positifs : après une connexion réussie, le runtime ne dispose que d’un court délai supplémentaire, 20 secondes dans l’implémentation, avant qu’un verdict soit rendu. Si la page métier a déjà redirigé vers une page de compte qui n’offre pas le contexte d’interface attendu, terminez immédiatement l’étape en cours. Ne confondez pas un « impossible d’y accéder » déterministe avec un « chargement en cours » et n’attendez pas inutilement. Un flux autorisant la dégradation enregistre cette étape comme unavailable et continue, plutôt que de faire échouer tout le processus.
Des statuts d’étape explicites : terminé, ignoré ou session requise
Le socle commun de tout ce qui précède : une étape ne doit jamais renvoyer uniquement « succès » ou « échec ». Dans un pipeline d’orchestration, le résultat de chaque étape peut prendre six formes : completed (terminée), empty (exécutée, sans données), ready (préparée, en attente de soumission), skipped (ignorée volontairement selon une règle), unavailable (indisponible pour le moment, ce qui signifie généralement qu’une session est requise), blocked (une précondition n’est pas satisfaite).
« Ignorée volontairement », « session requise » et « réellement vide » sont trois signaux totalement différents. Avec un résultat opaque unique, impossible de savoir si ce vide est normal ou si la session est morte sans que personne ne s’en aperçoive ; le pipeline devient inexploitable. Modélisez le statut de l’étape comme une enum finie : la couche d’orchestration — script ou modèle — pourra alors décider de dégrader, de réauthentifier ou d’abandonner. C’est le même principe que les trois codes d’erreur du pont passif, remonté au niveau du flux.
Utiliser le modèle pour choisir le bon niveau
Le modèle n’intervient qu’ici, dans un rôle volontairement limité : il ne purifie pas la logique à votre place ; il aide à déterminer si elle doit l’être, et jusqu’où. Il s’agit d’un jugement d’architecture, pas du cassage d’une signature. La question centrale est simple : l’état dont dépend la capacité peut-il être reconstruit statiquement, ou n’est-il disponible qu’au runtime ? À partir de là, mettez en balance l’effort de purification et la fréquence des changements. Plusieurs étapes sollicitent le modèle de façons différentes :
Étape | Capacité requise | Choix | model id |
|---|---|---|---|
Lire le module entier pour cartographier la surface de dépendances | Contexte long, lecture du graphe d’appels en une passe | Kimi K3 |
|
Argumenter les deux options et contester le réflexe « faisons-le simplement marcher » | Raisonnement solide ; capable de défendre une journée supplémentaire de purification | Claude Opus 5 |
|
Tri initial en masse de dizaines à centaines de capacités | Économique, forte concurrence | Claude Sonnet 5 |
|
Attribution après une dégradation | Raisonnement intermédiaire ; explication à partir d’un journal d’échec | GPT-5.6 Sol |
|
Le deuxième cas est le plus important. L’erreur la plus facile au moment de choisir un niveau est d’adopter le ton du « faisons-le simplement marcher » : le modèle répond alors que « le pont navigateur est le plus simple ». Il vous imite au lieu d’évaluer le coût à long terme. Un modèle de raisonnement solide rétorquera : « ce segment est un hash standard avec une perturbation constante ; il vaut une journée de travail pour devenir une fonction pure et ne devrait pas passer par le pont ». Ne me croyez pas sur parole : prenez 3 morceaux de logique, dont au moins 1 dont vous connaissez déjà le bon placement pour servir de contrôle. Envoyez le même prompt — « donne un niveau, justifie-le et conteste un passage prématuré par le pont navigateur » — à claude-opus-5 et gpt-5.6-sol, puis observez une seule chose : le modèle se bat-il pour vous faire remonter d’un niveau, ou choisit-il paresseusement le troisième par défaut ?
Le vrai frein, c’est le coût du changement
Quatre niveaux chez trois fournisseurs : trois SDK, trois schémas d’authentification, trois formats d’erreur. Réécrire votre client pour changer de modèle à chaque étape n’en vaut pas la peine. C’est pourquoi la plupart des équipes finissent par utiliser un seul modèle pour tout, puis emploient un modèle qui se contente d’acquiescer précisément là où le jugement de placement exige le plus de raisonnement.
AIReiter aplatit cette couche : une clé, une interface compatible OpenAI, les quatre niveaux derrière, et le changement se résume à modifier le champ model du corps de requête.
# Argumentation de placement : niveau de raisonnement
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<prompt de placement + fragment de logique rétro-ingéniérée + liste des dépendances>"}]
}'
# Tri initial en masse : modifier un seul champ
# "model": "claude-sonnet-5"
# Attribution de dégradation :
# "model": "gpt-5.6-sol"
Vous utilisez déjà le SDK OpenAI ? Faites pointer base_url vers https://aireiter.com/api/v1. Avec le SDK Anthropic, appelez POST /api/v1/messages avec la même clé. Côté tarifs, les modèles Claude sont proposés avec 30 % de réduction sur le prix catalogue, et les modèles GPT à moitié prix. Le coût de ce workflow se concentre à deux endroits : le tri initial en masse de dizaines à centaines de capacités — Sonnet, donc un fort volume d’appels — et la lecture d’un module complet pour cartographier sa surface de dépendances — Kimi, donc beaucoup de tokens par appel. Le tri en masse repose sur un modèle Claude : la réduction s’applique donc à l’étape la plus dense. L’argumentation de placement au niveau raisonnement utilise également un modèle Claude, avec 30 % de réduction. Le niveau long contexte de Kimi K3 est disponible avec la même clé.
Essayer sans créer de compte — commencez par soumettre manuellement quelques fragments de logique et observez si les deux modèles vous suivent ou contestent la question « cela doit-il passer par le pont navigateur ? ».
Pour conclure
Intégrer une logique rétro-ingéniérée n’est pas un problème purement technique : c’est un problème de coût. La hiérarchie à trois niveaux — réécriture native > moteur JS local > pont navigateur passif — ne peut pas être inversée, car chaque niveau inférieur remplace une fonction pure par un processus dépendant d’éléments externes, d’une intervention humaine et d’un onglet actif. Le pont navigateur passif n’est pas interdit ; c’est un composant temporaire aux frontières strictes : un instantané unique, jamais d’action, une erreur explicite dès qu’une page manque, aucun état sensible hors du navigateur, une connexion uniquement via relais explicite et des statuts d’étape toujours lisibles. Respectez ces règles, et il reste une solution transitoire fiable ; abandonnez-en une seule, et vous obtenez une boîte noire que personne n’osera maintenir. Le modèle vous aide à décider du niveau d’une capacité et à résister à l’inertie du « faisons-le simplement marcher » ; pour déterminer si chaque réécriture est correcte, le juge reste le test différentiel, pas le modèle.