Les invités du palier gratuit ne reçoivent plus le défi du navigateur pour les connecteurs : le guest token à portée limitée


Vous avez laissé une tâche planifiée sur un serveur qui lit votre agenda, consulte quelques listes et appelle quelques outils managés sur le palier gratuit. Elle a encore échoué la nuit dernière. Aucune trace de pile dans les logs — juste un lien imprimé, puis la tâche se termine sans bruit. Vous avez cliqué une fois sur le lien et tout est rentré dans l’ordre, mais la semaine prochaine elle pourra de nouveau échouer à 3 heures du matin.

Ce lien, c’est le défi du navigateur du palier gratuit. Nous avons expliqué pourquoi il apparaît et comment Hermes en a fait un flux non bloquant « imprimer le lien, sonder l’état ». Cela n’a résolu que la moitié du problème : un invité qui n’utilise rien d’autre que des connecteurs n’aurait jamais dû avoir à prouver qu’il est humain.

Un changement fusionné dans la fenêtre v0.21.7 boucle cette moitié. Les connecteurs et les outils managés utilisent désormais un token dédié qui n’entre jamais dans le flux de défi.

Un invité détient en réalité deux types de permission

Commençons par la raison d’être des deux tokens. Sur le palier gratuit, un invité peut faire deux choses très différentes :

  • Appeler l’inférence — faire générer quelque chose par un modèle gratuit. Cela consomme du calcul.
  • Appeler des connecteurs et des outils managés — lire le compte, atteindre le gateway d’outils, passer par les connecteurs. Surtout du transfert, et cela ne consomme pas de quota d’inférence.

Le défi, barrière de proof-of-work comprise, existe pour protéger le calcul contre les abus ; il n’a donc sa place que du côté de l’inférence. Dans l’ancienne implémentation, un invité qui touchait un connecteur était tout de même entraîné dans un échange de token d’inférence — et donc dans un défi dont personne n’avait besoin.

Le serveur comprend désormais la différence : l’échange de token anonyme prend un paramètre purpose, et purpose: "connectors" émet un JWT dont la seule portée est connectors:invoke. Le gateway lève les barrières de défi et de PoW pour ce token, et refuse de l’utiliser pour l’inférence.

La claim de portée choisit le slot, pas le mint qui l’a produit

Voici le détail de conception à retenir : le slot qu’un token remplit vient de sa propre claim JWT scope, et non du mint qui l’a produit.

  • Le token de connecteurs vit dans providers.nous.connectors_token (access_token + expires_at), juste à côté du slot d’inférence existant.
  • Le mint d’inférence est inchangé : toujours paresseux, toujours au premier appel réel à un modèle gratuit, toujours enveloppé dans le flux de défi.
  • Les appelants de connecteurs réutilisent désormais le token déjà actif — un token d’inférence couvre aussi les connecteurs, donc rien de supplémentaire n’est émis.

Lire la portée plutôt que l’origine garde la compatibilité propre. Un portail plus ancien ignore purpose et renvoie un token d’inférence, qui atterrit donc dans le slot d’inférence. Et si ce serveur plus ancien répond à une requête de connecteurs par un 428, l’appel de connecteurs retombe sur la gestion de défi actuelle au lieu d’échouer.

Trois chemins d’arrière-plan qui émettaient de l’inférence par accident

C’est la partie qui rend le débogage déroutant — aucun de ces chemins n’est déclenché par l’utilisateur :

  • Keepalive d’authentification. Il rafraîchissait autrefois le token d’inférence à chaque tick pour n’importe quel invité, et le gateway de messagerie comme le backend desktop le démarrent sans condition. Sous mise en application, cela signifiait un 428 par tick pour un invité limité aux connecteurs, plus un défi caché sur le desktop. Il ignore désormais complètement un invité, en accord avec le garde-fou que la fonction de démarrage avait déjà : les tokens d’un invité sont émis à la demande.
  • Le catalogue et la tarification des modèles Nous. _nous_catalog (atteint via provider_model_ids("nous")) et _resolve_nous_pricing_credentials s’exécutaient au premier plan, si bien que lister les modèles Nous en tant qu’invité depuis le CLI pouvait ouvrir un défi de navigateur. Les deux tournent désormais comme appelants d’arrière-plan et retombent sur la liste curatée et la tarification publique, exactement comme ils le faisaient déjà en cas d’échec d’identification.
  • Les lectures de compte. Elles se situent du côté des connecteurs, elles prennent donc le token de connecteurs.

L’effet combiné : un défi n’apparaît désormais qu’au moment où vous appelez réellement un modèle gratuit.

Ce qui n’a pas changé

  • Les comptes connectés, le chemin d’inférence et la forme persistée de l’invité sont intacts.
  • Un 403 (signin_required / access_denied) sur un mint de connecteurs se présente avec le même code et le même libellé qu’avant — aucun nouveau texte destiné à l’utilisateur.
  • Rien n’est partagé entre les deux mints, donc aucun verrou par usage ni mémo indexé n’a été ajouté : le mémo d’échec de mint couvre la création d’identité (qui n’a pas d’usage), le mémo de token de 5 secondes n’existe que sur le résolveur de connecteurs, et l’état de défi (verrou de travail, temps de renoncement) n’est atteint que par un mint qui reçoit un 428 — ce qui, sur un serveur actuel, est le mint d’inférence.

Reconnaître le chemin emprunté par un appel

Ce qui vous intéresse pendant le débogage, c’est à qui l’appel est réellement allé. Quelques endroits à consulter :

# échange de token anonyme et état de défi (identité d'invité)
hermes logs --follow | grep -i -E "anon|challenge|connectors"

# statut côté compte
hermes account status

# où atterrissent les slots d'authentification (sous le home de l'hôte)
ls -l "$HERMES_HOME/auth.json"

Si vous voyez un lien de défi alors que votre charge de travail ne touche que des connecteurs ou des outils managés, vous êtes sur un build plus ancien : depuis v0.21.7, ce chemin ne passe plus par un défi. Si vous appelez réellement un modèle gratuit, un défi est attendu — cet article précédent traite de la manière d’éviter qu’il bloque des exécutions sans surveillance.

Réserves

  • Le token dédié n’est pas utilisable pour l’inférence — le gateway le refuse. Copier la valeur de connectors_token dans une config d’inférence ne vous vaudra qu’un échec d’authentification.
  • Le défaut d’image du pool gratuit n’a pas suivi le changement « les abonnés passent par défaut à Krea » ; les pools de ressources sont toujours décidés indépendamment.
  • Sur un déploiement auto-hébergé ou privé plus ancien, le comportement dépend de ce serveur : un vieux serveur ignore purpose et tout reste comme avant, et un 428 retombe sur l’ancienne gestion plutôt que de planter.

À retenir

La valeur ici n’est pas une nouvelle API — c’est une permission plus fine. Le côté qui exige que vous prouviez être humain (l’inférence) garde le défi ; le côté purement transférant (connecteurs, outils managés) passe avec un token à portée limitée. Pour les tâches planifiées sans surveillance, cela supprime un échec auparavant intermittent et difficile à reproduire — non pas en le contournant, mais en supprimant la raison de son existence.