# Les requêtes en attente d’un coffre-fort verrouillé doivent-elles vraiment attendre ?

Une requête d’agent qui atteint un coffre-fort verrouillé doit échouer immédiatement. Elle ne doit pas devenir une commande dormante qui reprend vie lorsqu’une personne déverrouille le coffre-fort une heure plus tard.

Cela peut sembler strict jusqu’à ce qu’on examine ce que prouve réellement un déverrouillage. Il prouve qu’une personne a autorisé l’accès à un magasin de secrets à cet instant. Il ne prouve pas qu’un ancien processus d’agent a toujours la même tâche, qu’un déploiement est toujours souhaitable, qu’une pull request n’a pas changé, ni que l’ancien corps HTTP et l’ancienne commande SSH sont encore pertinents.

C’est le genre de décision que l’on présente comme une simple fonctionnalité de confort. Quelqu’un voit une exécution bloquée et propose une file d’attente : conserver la requête, afficher une notification, puis libérer le travail après Touch ID. La file semble utile parce que la requête est déjà formée. C’est aussi ce qui la rend dangereuse. Une requête formée a franchi la frontière entre un plan et une action qui attend une autorisation.

La règle la plus sûre est simple : tant que le coffre-fort est verrouillé, refusez les actions et supprimez leur forme exécutable. Après le déverrouillage, l’agent peut examiner son état actuel et envoyer une nouvelle requête. Cette seconde requête peut recevoir une autorisation de session ou une approbation par appel, selon le contexte actuel.

## Un coffre-fort verrouillé doit refuser l’action immédiatement

Un verrouillage de coffre-fort est une frontière d’accès, pas une panne réseau temporaire. Le traiter comme une panne encourage les nouvelles tentatives automatiques, exactement ce qu’il faut éviter avec une intention ancienne d’agent.

Lorsqu’un agent demande un appel API ou l’ouverture d’une connexion SSH, la passerelle dispose de suffisamment d’informations pour décider si la requête peut continuer. Si le coffre-fort est verrouillé, elle n’a pas besoin d’examiner un corps, de résoudre un hôte, d’ouvrir une connexion ou d’attendre une personne. Elle doit renvoyer un refus avant tout contact avec le monde extérieur.

Le résultat doit expliquer à l’agent ce qui s’est passé sans l’inciter à rejouer aveuglément la requête. Une structure utile peut ressembler à ceci :

```json
{
  "ok": false,
  "error": {
    "code": "VAULT_LOCKED",
    "message": "The credential vault is locked. This action was not queued or sent.",
    "request_id": "req_7d4c1f",
    "retryable": false
  }
}
```

`retryable: false` peut sembler contre-intuitif. L’agent pourra envoyer une requête plus tard, mais la requête échouée elle-même ne peut pas être réessayée sans risque. Cette distinction empêche les auteurs de clients de créer une boucle générique avec temporisation qui transformerait un déverrouillage en rafale non révisée d’anciennes actions.

Ne remplacez pas ce résultat par `503 Service Unavailable`, un délai d’attente ou une erreur de transport générique. Ces réponses indiquent à un client bien intentionné de répéter exactement la même action. La passerelle a besoin d’un résultat sémantique qui signifie : « Une condition de sécurité contrôlée par une personne a bloqué cet appel, et cet appel précis est terminé. »

Cette règle s’applique aussi bien aux lectures qu’aux écritures. Les équipes réservent souvent leur prudence aux écritures, parce qu’une écriture obsolète peut supprimer ou déployer quelque chose. Une lecture obsolète peut tout de même exposer des informations client, révéler une configuration de production ou pousser un agent à prendre une décision ultérieure à partir de données que l’utilisateur ne souhaitait plus récupérer.

## Le déverrouillage n’approuve pas une intention antérieure

Un événement de déverrouillage et une réponse d’approbation d’action répondent à deux questions différentes. Le déverrouillage demande si le coffre-fort peut utiliser ses secrets. L’approbation d’action demande si ce processus peut effectuer ce type précis d’appel externe dans le cadre de la tâche actuelle.

Mélanger ces décisions crée un problème d’autorisation subtil. Imaginez qu’un agent prépare une commande SSH pour redémarrer un service. Le développeur ferme son ordinateur, le coffre-fort se verrouille et l’agent envoie malgré tout l’appel. Quarante minutes plus tard, le développeur revient, déverrouille son Mac pour examiner un autre problème, et le redémarrage se produit parce que la passerelle a conservé l’ancienne commande. Le développeur n’a pas approuvé un redémarrage à cet instant. Il a autorisé son propre accès au coffre-fort.

Le même problème se manifeste de manière moins visible :

- Un agent a préparé un commentaire de pull request, mais le réviseur a déjà réglé le problème.
- Un agent a préparé un appel API cloud avec un nom de branche qui ne pointe plus vers le même commit.
- Un agent a préparé une recherche dans un système de support, mais la demande client qui la justifiait est terminée.
- Un agent a préparé la publication d’un paquet, mais un test a échoué pendant que l’accès était verrouillé.

L’argument courant est que la requête avait déjà été autorisée avant le verrouillage. C’est parfois vrai, mais cela ne justifie toujours pas un envoi différé. Une autorisation peut rester valable pendant une session, tant que le processus existe. Une intention ne reste pas valable simplement parce qu’une séquence d’octets se trouve dans une file.

Une passerelle doit donc séparer clairement les décisions :

1. Le verrouillage du coffre-fort décide si une action reposant sur un secret peut commencer.
2. L’autorisation de session décide si ce processus d’agent est reconnu pour son exécution actuelle.
3. L’approbation par appel décide si un identifiant marqué pour une confirmation individuelle peut être utilisé maintenant.

Si la première décision est négative, arrêtez-vous. N’évaluez pas les décisions suivantes et ne conservez pas une requête prête à être envoyée lorsque la réponse changera.

## Une notification n’est pas une file de requêtes

Vous pouvez informer une personne qu’un travail est bloqué sans conserver un travail qui pourrait s’exécuter. Ce sont deux conceptions différentes, mais les équipes les confondent parce qu’elles parlent dans les deux cas de « requêtes en attente ».

Une **notification** est un fait qui ne déclenche aucune action. Elle peut indiquer qu’un processus a tenté d’utiliser une référence d’identifiant donnée contre une catégorie de destination, à un moment précis. Elle aide la personne à décider si elle doit déverrouiller le coffre-fort et revenir à la tâche. Elle ne peut pas reconstituer des en-têtes, un corps de requête, une commande SSH ou un jeton d’approbation.

Une **file de requêtes** conserve suffisamment de données pour effectuer un envoi plus tard. Elle peut contenir une méthode HTTP, une URL, un corps, des arguments de commande, la sélection d’un identifiant, une décision d’autorisation ou un jeton de rejeu signé. Dès que vous conservez ces éléments, vous avez créé une exécution différée.

Cette distinction compte dans l’implémentation. Voici un avis acceptable concernant un travail bloqué :

```json
{
  "event": "action_denied",
  "reason": "vault_locked",
  "session_id": "ses_31b8",
  "channel": "ssh",
  "credential_label": "production-deploy",
  "destination": "deploy host",
  "occurred_at": "2026-07-22T21:14:05Z"
}
```

Cela devient inacceptable si le système peut ensuite récupérer la commande complète, l’adresse de destination, le corps privé de la requête ou une autorisation d’utilisation d’identifiant à partir de l’événement. L’enregistrement doit servir à l’enquête, pas au rejeu.

Soyez également prudent avec les condensats. Un condensat est souvent sûr pour établir des corrélations, mais uniquement si la passerelle ne peut pas l’utiliser pour récupérer une charge utile conservée. Un condensat accompagné d’un contenu caché reste une file. Un condensat présent uniquement dans un journal d’audit auquel on ajoute des entrées est différent.

Il existe ici une tentation produit : une liste de requêtes en attente donne à un tableau de bord une apparence de réactivité. Résistez-y, sauf si chaque entrée oblige l’agent à envoyer un nouvel appel après l’action de l’utilisateur. Un écran proposant « tout exécuter après le déverrouillage » a transformé une demande de sécurité en ordonnanceur de tâches différées.

## Donnez à l’agent une machine à états sans ambiguïté

Les agents se comportent mieux lorsque la passerelle expose un modèle d’état réduit et explicite. Des erreurs ambiguës poussent les agents à inventer des plans de récupération, qui peuvent consister à attendre, réessayer ou chercher un autre chemin vers un identifiant.

Utilisez une machine à états dans laquelle une requête reçoit un résultat terminal dès que la porte du coffre-fort la refuse :

```text
received
  |
  +-- vault locked --> denied_locked (terminal)
  |
  +-- vault unlocked --> session check
                           |
                           +-- not approved --> denied_session (terminal)
                           |
                           +-- approved --> per-call check
                                             |
                                             +-- approval declined --> denied_call (terminal)
                                             |
                                             +-- approved --> dispatched --> completed
```

L’important n’est pas le diagramme. C’est le fait que `denied_locked` ne possède aucune flèche vers `dispatched`. Une requête ultérieure peut commencer à l’état `received`, mais l’ancienne ne peut revenir dans aucun état.

Cette conception facilite aussi le raisonnement sur l’idempotence. Si un appel échoue parce que le coffre-fort était verrouillé, ne réservez pas de jeton d’idempotence comme si le serveur avait accepté l’action. L’agent doit construire une autre requête plus tard, avec un nouvel identifiant de requête. Si l’API externe prend en charge des clés d’idempotence, la requête nouvellement envoyée peut utiliser une clé au niveau de l’application qui reflète l’opération métier souhaitée, mais le refus de la passerelle ne doit pas créer un enregistrement d’action à moitié terminé.

Pour les opérations d’écriture, demandez à l’agent d’inclure sa propre précondition actuelle lorsque la destination en prend une en charge. Il peut s’agir d’un identifiant de révision, d’une version d’entité, d’une tête de branche attendue ou d’un ETag. Lorsque le coffre-fort s’ouvre, l’agent doit relire le contexte actuel et construire un appel qui en tient compte. Une requête obsolète ne peut pas satisfaire une bonne précondition, tandis qu’une requête fraîche prouve que l’agent a vérifié à nouveau.

N’essayez pas d’inférer la fraîcheur à partir du seul temps écoulé. Cinq secondes peuvent être trop longues pour un déploiement qui évolue rapidement, tandis qu’une heure peut être sans conséquence pour une recherche statique. La fraîcheur vient d’une nouvelle lecture de l’état de la tâche et de la reconstruction de l’action, pas d’un minuteur.

## L’approbation doit être liée à l’appel réel

Une nouvelle requête après déverrouillage a toujours besoin d’un modèle d’approbation précis. Sinon, vous aurez supprimé le rejeu différé pour le remplacer par une autorisation vague qui permet à l’agent de changer d’avis après le clic de l’utilisateur.

Une carte d’approbation doit être liée aux éléments qui modifient le sens sécuritaire de l’action. Pour HTTP, cela comprend généralement la méthode, la destination normalisée, la référence d’identifiant et un condensat du corps de la requête. Pour SSH, cela comprend l’identité de l’hôte, le compte, la commande ou son condensat, ainsi que la référence d’identifiant. Elle doit également être liée au processus de l’agent et expirer rapidement.

Évitez une carte qui dit seulement : « Autoriser l’accès de l’agent à la production. » Cette phrase demande à une personne d’approuver une catégorie alors que l’agent contrôle les détails. Une personne peut accepter qu’un processus lise un point de terminaison sans accepter qu’il appelle un point de terminaison administratif sous le même nom d’hôte.

Un enregistrement d’approbation pratique peut ressembler à ceci :

```json
{
  "approval_id": "apr_8c62",
  "session_id": "ses_31b8",
  "process_identity": "signed-authority-and-process-instance",
  "channel": "http",
  "credential_label": "billing-api",
  "method": "POST",
  "destination": "api.example.internal/v1/invoices",
  "payload_digest": "sha256:...",
  "expires_at": "2026-07-22T21:16:00Z",
  "used": false
}
```

La passerelle n’a pas besoin d’afficher chaque octet d’une grande charge utile pour décrire honnêtement ce qu’elle approuve. Elle doit en revanche lier l’approbation aux octets qu’elle enverra. Un résumé humain concis peut être affiché à côté d’un condensat, mais le condensat protège la requête exacte contre toute substitution.

Utilisez des enregistrements d’approbation à usage unique pour les identifiants approuvés par appel. Marquez l’approbation comme utilisée avant le début de l’envoi, et non après le retour d’une réponse. Si la connexion tombe après l’envoi, l’agent devra peut-être examiner la destination pour déterminer si l’action a été effectuée. Réutiliser l’approbation faciliterait les écritures en double.

## Le cas d’échec à tester est le déverrouillage au mauvais moment

Le test le plus révélateur n’est pas « l’appel échoue-t-il lorsque le coffre-fort est verrouillé ? » C’est « que se passe-t-il lorsque le monde change avant le déverrouillage ? »

Mettez en place un service de test sans danger, avec un point de terminaison qui enregistre une cible de déploiement et un autre qui modifie la cible actuellement autorisée. Exécutez ensuite cette séquence :

1. Lancez une tâche d’agent qui prévoit d’envoyer `POST /deploy` avec `{\"revision\":\"a1b2c3\"}`.
2. Verrouillez le coffre-fort avant l’envoi de l’appel par l’agent.
3. Vérifiez que la passerelle renvoie `VAULT_LOCKED` et que le service de test ne reçoit rien.
4. Modifiez la révision autorisée en `d4e5f6` pendant que le coffre-fort reste verrouillé.
5. Déverrouillez le coffre-fort pour une raison sans rapport.
6. Attendez sans toucher à l’agent.

Le résultat correct est banal : le service de test ne reçoit toujours rien. S’il reçoit un déploiement pour `a1b2c3`, votre passerelle possède un chemin d’exécution différée.

Informez ensuite l’agent que l’appel a été refusé, laissez-le relire la révision autorisée et demandez-lui d’envoyer une nouvelle requête. La requête attendue est maintenant `d4e5f6`, et la passerelle peut demander l’autorisation de session ou l’approbation par appel qui s’applique. Vous vérifiez ainsi que le chemin de récupération conserve le contexte actuel au lieu de traiter le temps passé verrouillé comme un bouton pause invisible.

Effectuez le même test pour SSH. Utilisez une commande qui écrit un marqueur sans danger contenant la révision prévue. Ne testez pas uniquement l’établissement de la connexion. L’implémentation dangereuse conserve souvent une commande après avoir sélectionné une clé, puis l’envoie lorsque le coffre-fort redevient disponible. Vous devez démontrer que le texte de la commande lui-même disparaît à la frontière du verrouillage.

## Ne laissez pas les clients dissimuler le refus

Une passerelle peut prendre la bonne décision tout en produisant un mauvais comportement si ses clients réduisent chaque erreur à « réessayez plus tard ». Le protocole doit fournir suffisamment de structure aux frameworks d’agents et aux scripts intermédiaires pour qu’ils traitent délibérément un coffre-fort verrouillé.

Les agents doivent recevoir trois instructions dans le contrat de réponse. Premièrement, l’action n’a pas quitté la passerelle. Deuxièmement, la passerelle a supprimé la requête. Troisièmement, l’agent ne doit pas réessayer automatiquement la même requête.

La boucle de récupération de l’agent devrait plutôt ressembler à ceci :

```text
if result.error.code == "VAULT_LOCKED":
    record_blocked_task()
    ask the user to unlock when appropriate
    stop this action

if user later resumes the task:
    reread relevant state
    decide whether the action is still needed
    create a new request
```

La ligne `decide whether the action is still needed` est importante. Elle ne doit pas être remplacée par `retry request`. Un agent qui a reçu de nouvelles instructions, modifié des fichiers, changé de branche ou appris qu’un test a échoué peut maintenant avoir besoin d’une autre action, ou d’aucune action.

Pour les exécutions sans surveillance, renvoyez le refus à l’orchestrateur et laissez l’exécution se terminer dans un état bloqué. Ne demandez pas à la passerelle d’attendre que quelqu’un déverrouille le coffre-fort. Un processus d’agent en attente conserve de la mémoire et un contexte qui peut devenir sensible, tout en créant une pression pour traiter un déverrouillage ultérieur comme une permission de continuer. Un arrêt propre donne à la personne la possibilité de réviser la tâche avant de la reprendre.

Si votre interface affiche une notification, employez une formulation précise : « Une action d’agent a été refusée parce que le coffre-fort était verrouillé. » Évitez les boutons intitulés « continuer » ou « approuver les requêtes en attente ». Un bouton peut ouvrir le coffre-fort ou afficher les détails de la session, mais il ne doit pas exécuter une ancienne action.

## Les journaux doivent prouver que rien n’a été envoyé

Un refus mérite un enregistrement d’audit, car il répond à une question que les opérateurs finiront par poser : l’agent a-t-il seulement tenté l’action, ou a-t-il réellement contacté le système externe ?

Enregistrez le canal tenté, l’identité de l’exécution de l’agent, la référence d’identifiant, une destination normalisée, le résultat et les horodatages. Indiquez clairement l’état de l’envoi. Les opérateurs doivent pouvoir distinguer `denied_before_dispatch`, `dispatch_started`, `remote_rejected` et `completed` sans devoir interpréter des chaînes d’exception.

Ne journalisez pas par défaut les secrets, les en-têtes d’autorisation bruts, le contenu des clés privées ou les corps complets. Pour les corps sensibles, enregistrez un condensat et un petit résumé approuvé si le système peut le produire sans révéler de contenu. L’objectif est d’établir ce qui s’est passé, pas de créer une seconde copie des données que le coffre-fort était censé protéger.

Une requête d’audit doit pouvoir répondre à un rapport d’incident de ce type :

```text
21:14:05  session ses_31b8 attempted SSH action using production-deploy
21:14:05  vault gate denied action before dispatch
21:15:41  vault unlocked by local user action
21:16:09  no action dispatched from ses_31b8
```

La dernière ligne peut être déduite de l’absence d’enregistrements d’envoi, mais des états terminaux explicites accélèrent les enquêtes et réduisent l’ambiguïté. Si vous utilisez un journal chaîné par hachage, vérifiez également la chaîne pendant l’examen d’un incident, et pas seulement lors des contrôles courants. La preuve d’altération a peu de valeur si personne ne l’utilise lorsque l’enregistrement devient important.

La séparation de Sallyport entre un journal Sessions pour les exécutions et un journal Activity pour les appels individuels convient bien à ce problème, car un refus dû au verrouillage du coffre-fort appartient à la fois à l’historique de l’exécution et à la trace de l’action. Sa vérification hors ligne `sp audit verify` permet également à une équipe de tester l’intégrité de cet historique sans ouvrir le coffre-fort.

## Les files de confort créent un second système d’autorisation

Dès qu’une passerelle conserve des requêtes pour les libérer plus tard, elle commence à accumuler des règles : durée de vie des requêtes, personnes autorisées à les libérer, obligation ou non de conserver le processus d’origine, possibilité de modifier le contenu, comportement après un redémarrage, et portée d’un déverrouillage, une requête ou toutes les requêtes.

Ces règles forment en réalité un moteur de politiques. Elles sont difficiles à expliquer aux utilisateurs, car chaque exception modifie le sens du déverrouillage. Une courte durée d’attente ne résout pas le problème de signification. Exiger que le processus d’origine reste actif ne le résout pas non plus, car le processus peut être compromis ou simplement fonctionner avec un contexte obsolète.

Gardez une conception plus réduite. La porte du coffre-fort refuse toutes les actions lorsqu’il est verrouillé. Une nouvelle session de processus peut nécessiter une approbation. Un identifiant marqué pour une approbation par appel exige une confirmation explicite à chaque utilisation. Toutes les autres fonctions de confort doivent rester du côté de l’agent, sous forme de récupération de tâche, où l’agent doit reconstruire son plan et où la personne peut voir ce qui a changé.

Cela donne aussi aux utilisateurs une habitude fiable : le déverrouillage rétablit la possibilité d’examiner de nouvelles actions. Il ne libère jamais des actions qu’ils avaient oublié d’attendre. Les personnes peuvent prendre de bonnes décisions avec ce modèle mental. Elles ont plus de mal lorsque l’écran de verrouillage devient aussi une file de travail cachée.

## Rendez le chemin sûr moins pénible que le chemin dangereux

Les équipes créent des files parce qu’un échec complet peut sembler perturbant pendant le développement courant. Réduisez cette friction sans conserver de requêtes exécutables.

Gardez l’autorisation de session limitée à la durée de vie du processus d’agent afin qu’un développeur n’ait pas à approuver chaque appel ordinaire. Réservez la confirmation par appel aux identifiants qui méritent une friction volontaire, comme l’administration de la production ou la publication externe. Renvoyez un refus clair qui permet à l’agent de signaler le travail bloqué en termes simples. Donnez à la personne un moyen de déverrouiller le coffre-fort, d’inspecter la session de l’agent et de reprendre consciemment la tâche.

Rendez ensuite les instructions destinées aux agents explicites. Indiquez-leur que les identifiants restent hors de leur contexte, qu’un résultat de coffre-fort verrouillé met fin à l’action tentée et qu’une action ultérieure doit être reconstruite après vérification de l’état actuel. Une consigne d’agent ne peut pas imposer cette règle, mais elle réduit les tentatives inutiles et facilite l’utilisation correcte du protocole.

Le test de cette conception est simple. Si une personne déverrouille le coffre-fort alors qu’elle est distraite, fatiguée ou occupée par une tâche sans rapport, aucune ancienne action d’agent ne doit se produire. Si ce n’est pas vrai, supprimez la file avant qu’elle ne devienne la cause d’un rapport d’incident.
