8 min de lecture

L'ordre des appels dans une file d'approbation résiste-t-il à une course ?

L'ordre des appels dans une file d'approbation détermine si une approbation humaine reste pertinente lorsque deux écritures d'agent arrivent ensemble. Reproduisez la course et examinez les cartes, l'exécution et les journaux.

L'ordre des appels dans une file d'approbation résiste-t-il à une course ?

Une approbation humaine perd beaucoup de son sens si un appel d'agent ultérieur peut dépasser un appel antérieur simplement parce que sa carte a été cliquée en premier. Lorsque deux écritures se disputent le même état distant, la passerelle doit leur attribuer un ordre avant qu'une personne ne touche à l'une ou l'autre carte, puis conserver cet ordre lors de l'envoi et dans l'enregistrement d'audit.

Le problème passe facilement inaperçu, car le parcours normal comporte une carte, un clic et une réponse réussie. Le défaut apparaît lorsque deux processus d'agent envoient des requêtes au même moment, qu'un réviseur approuve les cartes visibles dans un ordre peu pratique et que la cible accepte la requête qui l'atteint en premier. J'ai vu des équipes appeler cela de la concurrence. Ce n'est qu'une décision non définie, prise après que l'humain a cru en avoir le contrôle.

L'ordre commence lorsque la passerelle accepte l'appel

L'ordre des appels est la séquence dans laquelle la passerelle d'action accepte les requêtes dans une voie pour une même ressource en conflit. Ce n'est ni l'ordre d'affichage des cartes, ni l'ordre des clics, ni l'ordre d'ouverture des connexions réseau, ni l'ordre de retour des réponses distantes.

Attribuez à chaque appel accepté un ticket immuable, par exemple 42, 43 et 44. Enregistrez ce ticket avec la description de la requête, l'identité du processus appelant, la cible et l'identifiant de la voie. Le ticket doit exister avant que la passerelle ne demande l'approbation. Sinon, l'interface peut seulement indiquer quelle requête le réviseur a vue en premier, ce qui ne suffit pas à reconstituer une course.

Une voie définit le périmètre dans lequel un changement d'ordre peut modifier le résultat. Deux requêtes qui remplacent toutes deux le même environnement de déploiement appartiennent à une même voie. Deux appels qui ajoutent des éléments au même incident distant appartiennent à une même voie. Une requête qui récupère les métadonnées d'un paquet et une requête qui met à jour un autre service de test n'ont peut-être pas besoin de s'attendre. Ordonner globalement chaque action semble prudent, mais une requête lente et sans rapport transforme alors chaque agent en victime d'une panne.

La difficulté consiste à choisir la voie honnêtement. Un nom d'hôte cible est souvent trop large. Un simple chemin de point de terminaison est souvent trop précis. PATCH /documents/7 et POST /documents/7/publish touchent le même document, même si leurs chemins diffèrent. Si la passerelle ne dispose pas de suffisamment d'informations pour déduire cette relation, placez ces actions dans une voie configurée commune. Ne prétendez pas que le service distant réparera une promesse d'ordre que la passerelle n'a jamais faite.

L'enregistrement d'admission devrait avoir une forme proche de celle-ci :

ticket=42 lane=release-prod event=accepted action=write-release caller=agent-a
ticket=43 lane=release-prod event=accepted action=write-release caller=agent-b

Ces lignes répondent ensuite à une question précise : pour quel appel la passerelle a-t-elle assumé la responsabilité en premier ? Elles ne prétendent pas que le ticket 42 s'est terminé en premier. Une cible lente peut faire terminer le ticket 43 plus tard ou plus tôt, selon le modèle d'exécution autorisé. La passerelle doit énoncer ce modèle au lieu de laisser le journal en inventer un après coup.

Une approbation est une décision, pas une permission de dépasser

Si un réviseur décide pour la requête B avant la requête A, B ne devient pas antérieure. Cela signifie seulement que B a rempli une condition et attend son tour.

Cette distinction devient floue, car les interfaces d'approbation traitent généralement chaque carte comme une demande isolée. Cela convient à une action sans conséquence sur l'ordre. Pour des écritures concurrentes, cela échoue. Si l'interface laisse les deux cartes agir, un clic sur B doit faire passer B à l'état approuvé en attente, et non l'envoyer directement au répartiteur. Le répartiteur vérifie le ticket en tête de la voie. Il n'envoie que le ticket le plus ancien qui dispose d'une décision d'autorisation.

Une passerelle dispose de trois résultats raisonnables pour le ticket en tête :

  • L'approbation autorise l'envoi de ce ticket.
  • Le rejet enregistre un refus final, puis libère le ticket suivant.
  • L'expiration ou l'annulation explicite enregistre un résultat final, puis libère le ticket suivant.

Un quatrième comportement pose problème : une approbation ultérieure est envoyée immédiatement parce que la requête voisine précédente n'a pas encore de décision. Cela semble agréable, car le réviseur obtient rapidement un résultat. Mais la séquence distante dépend alors du moment d'un clic dans l'interface. Une personne a peut-être ouvert A pour examiner ses paramètres tout en approuvant B, considérée comme une simple tâche d'entretien. Le système envoie alors B en premier, malgré la présentation du duo sous forme de file.

L'interface doit rendre l'état visible. La carte en tête peut proposer Approuver et Rejeter. Les cartes suivantes peuvent accepter une décision, mais leur statut doit indiquer qu'elles attendent derrière le ticket 42. L'interface peut aussi maintenir leurs commandes désactivées jusqu'à la résolution des tickets précédents. Les deux approches préservent l'ordre. La première donne davantage de contrôle au réviseur, la seconde est plus difficile à mal comprendre. Ce que l'interface ne doit jamais laisser croire, c'est que chaque clic positif entraîne une exécution immédiate.

Les contrôles d'autorisation par session et d'approbation par appel de Sallyport rendent la limite de la requête visible, mais l'ordre nécessite toujours un ticket attribué avant la décision d'approbation. Un réviseur ne peut pas évaluer une file si le produit n'enregistre qu'un ensemble non structuré de cartes.

Reproduire la course avec des écritures qui laissent une trace

Un test utile envoie deux processus d'agent indépendants vers une même voie d'écriture et demande à la cible d'enregistrer l'ordre d'arrivée. N'utilisez pas deux lectures, deux vérifications d'état idempotentes ou deux requêtes qui mettent à jour des enregistrements sans lien. Ces tests peuvent réussir alors que la file autorise les dépassements.

Utilisez un point de terminaison HTTP jetable qui accepte un corps POST et ajoute le ticket reçu à un fichier ou à une table de base de données. Il doit renvoyer le ticket reçu. La cible n'a pas besoin d'authentification pour cet exercice si elle s'exécute uniquement sur une adresse de test locale. Le but est de tester le chemin d'approbation et d'envoi de la passerelle, pas l'injection d'identifiants.

Ce petit serveur Node produit un journal d'arrivée simple :

const fs = require("node:fs");
const http = require("node:http");

http.createServer((request, response) => {
  let body = "";
  request.on("data", chunk => { body += chunk; });
  request.on("end", () => {
    const item = JSON.parse(body);
    fs.appendFileSync("arrival.log", `${item.ticket} ${item.value}\n`);
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ received: item.ticket }));
  });
}).listen(8787);

Configurez une action approuvée qui envoie POST /write à ce point de terminaison et place le ticket de la requête dans son corps JSON. Démarrez deux processus d'agent distincts, plutôt que de demander à un seul processus d'effectuer deux appels en série. Chaque processus soumet la même action avec une valeur différente, par exemple A et B. Faites en sorte que les deux appels nécessitent une approbation afin que les cartes restent en attente ensemble.

Les premières preuves attendues sont simples et précises :

Sessions
42 accepted agent-a write A
43 accepted agent-b write B

Approval cards
42 write A
43 write B

arrival.log
(empty)

Si les cartes apparaissent dans l'ordre inverse des tickets, arrêtez-vous et cherchez la cause. Un ordre visuel inversé ne prouve pas une mauvaise exécution, mais il pousse le réviseur à agir sur une fausse interprétation. Si une seule carte apparaît à la fois, vérifiez si la seconde requête possède déjà un ticket et si le journal indique qu'elle attend derrière la première. Masquer la seconde carte est acceptable. Masquer son existence ne l'est pas.

Répétez cet exercice de nombreuses fois. Commencez par A, puis par B. Lancez-les aussi près l'un de l'autre que votre banc de test le permet. Ajoutez un court délai après l'entrée d'un processus dans la passerelle pour rendre l'ordonnancement moins accidentel. Le test a besoin de chevauchements répétés, car une seule exécution réussie reflète souvent un ordonnancement favorable des threads plutôt qu'une règle réellement imposée.

L'ordre des cartes, des envois et des achèvements diffère

Le test doit recueillir trois ordres, car ils répondent à des questions différentes et ne doivent pas être regroupés dans une seule colonne.

L'ordre des cartes est l'ordre dans lequel le réviseur peut voir les décisions en attente. Il doit suivre l'ordre des tickets au sein d'une voie, même si l'application dessine les cartes lors de tours distincts de la boucle d'événements. Une carte peut afficher un horodatage d'arrivée, mais le ticket reste le champ décisif.

L'ordre d'exécution est l'ordre dans lequel la passerelle libère les actions acceptées vers le canal HTTP ou SSH. Pour une voie d'écriture strictement sérialisée, la cible doit observer le ticket 42 avant le ticket 43. La passerelle doit émettre un événement dispatched juste avant de transmettre la requête au canal. Ne déduisez pas l'envoi d'un événement de réponse. Une cible peut recevoir une écriture, modifier l'état, puis perdre la connexion avant d'envoyer une réponse.

L'ordre d'achèvement est l'ordre dans lequel reviennent les réponses, les expirations ou les erreurs de transport. Il peut différer de l'ordre d'envoi si le système autorise des appels qui se chevauchent dans des voies distinctes. Même dans une seule voie, une implémentation asynchrone peut enregistrer un achèvement tardif parce qu'elle vide les journaux après le nettoyage réseau. L'ordre d'achèvement est une information opérationnelle utile, mais il ne doit pas remplacer l'ordre causal.

Un enregistrement de test compact rend la distinction difficile à esquiver :

42 accepted
43 accepted
42 card-shown
43 card-shown
43 approved
42 approved
42 dispatched
42 succeeded
43 dispatched
43 succeeded

Le fait que 43 approved apparaisse avant 42 approved est attendu dans cette exécution. Le fait que 42 dispatched apparaisse avant 43 dispatched est le contrat. Si l'application n'enregistre que les réussites finales, ces deux faits disparaissent et l'enquêteur ne peut pas déterminer si l'utilisateur a approuvé B en premier, si le répartiteur a dépassé A ou si la cible a réordonné deux requêtes déjà libérées.

La RFC 9110 distingue les méthodes HTTP sûres des méthodes qui demandent des changements d'état. Cette distinction compte ici : une passerelle peut tolérer un ordre moins strict pour les observations, tandis que les écritures ont besoin d'un contrat de concurrence explicite. La RFC 9110 ne fournit pas d'ordre total utile entre deux connexions clientes indépendantes. La passerelle prend cette décision dès qu'elle place l'approbation avant l'appel.

Approuver la seconde carte en premier et la maintenir en attente

Verrouillez la passerelle d'action
La porte du coffre refuse toute action lorsqu'elle est verrouillée, avec Secure Enclave et Touch ID sur macOS.

Le test de course de base le plus efficace consiste à approuver délibérément B en premier. Il vérifie si l'implémentation traite l'approbation comme une transition d'état ou comme un bouton d'envoi direct.

Commencez avec les tickets 42 et 43 en attente dans la même voie. Approuvez le ticket 43. La carte ou le journal doit passer à un état équivalent à approved, waiting for 42. Le fichier arrival.log de la cible doit rester vide. Approuvez ensuite le ticket 42. La cible doit recevoir 42 A, puis 43 B, et l'enregistrement d'activité doit afficher les deux envois dans cette séquence.

Répétez le même test en rejetant le ticket 42. Le journal de la cible ne doit contenir que B. Le journal conserve tout de même les deux tickets :

42 accepted
43 accepted
43 approved
42 rejected reason=user
43 dispatched
43 succeeded

Un rejet est le résultat d'une action, pas une absence d'action. Si le journal l'omet, quelqu'un qui l'examinera plus tard verra B s'exécuter sans explication pour la requête précédente manquante. Cela conduit à de mauvaises conclusions : un attaquant a peut-être contourné l'approbation, l'application a peut-être perdu des données ou un réviseur a peut-être approuvé quelque chose qu'il n'a pas vu.

Testez ensuite une expiration. Laissez l'approbation de A expirer alors que B a déjà été approuvée. L'application doit enregistrer l'expiration de A une seule fois, rendre B éligible et envoyer B. Elle ne doit pas produire à la fois expired et rejected parce qu'un minuteur en arrière-plan et un clic tardif sont entrés en concurrence. Choisissez une décision finale au moyen d'une comparaison et d'un échange atomiques sur l'état du ticket. L'événement perdant doit constater que le ticket est déjà résolu et ne rien faire.

Enfin, annulez B après l'avoir approuvée, mais avant la résolution de A. B ne doit jamais être envoyé. Son état final doit indiquer qu'il a été annulé, et la résolution ultérieure de A ne doit pas le réactiver. Cela détecte un défaut courant de file : le répartiteur conserve une ancienne liste de tickets approuvés, puis envoie une entrée après que le gestionnaire d'annulation l'a retirée de la file visible.

Un bon test vérifie chaque résultat séparément. Ne vous contentez pas d'une seule assertion indiquant que la cible a reçu l'état final attendu. Un état final peut sembler correct après une séquence erronée si B écrase A, si des tentatives masquent un doublon ou si la cible applique sa propre règle de conflit.

Conserver le ticket lors des tentatives et des cibles lentes

Une requête conserve sa place dans la file lorsque le canal la relance. Créer un nouveau ticket après une défaillance de connexion change le sens de l'approbation et peut permettre à une requête ultérieure de dépasser la précédente.

Supposons que le ticket 42 soit envoyé, que la connexion TCP se ferme avant que la passerelle ne reçoive une réponse et que la cible ait peut-être appliqué l'écriture, ou non. Le ticket 43 attend. Plusieurs politiques sont possibles pour la passerelle : signaler un résultat inconnu et s'arrêter, réessayer avec un identifiant d'idempotence si la cible le prend en charge ou demander une nouvelle décision humaine. Elle n'a pas le droit d'abandonner discrètement 42 et d'envoyer 43 comme si 42 n'avait jamais existé.

La bonne politique dépend de l'opération distante. Un point de terminaison qui accepte un identifiant d'idempotence peut rendre une nouvelle tentative suffisamment sûre pour réutiliser le ticket d'origine. Une commande SSH lancée à l'aveugle ne le permet souvent pas. Dans ce cas, signalez un résultat inconnu pour 42, gardez la voie bloquée ou abandonnez-la explicitement après intervention humaine et enregistrez la raison. Libérer automatiquement 43 peut aggraver les dégâts si 43 part du principe que 42 a échoué.

Testez ce cas avec une cible qui reçoit A, écrit son enregistrement d'arrivée et ferme la réponse avant la fin de l'échange HTTP. La passerelle doit conserver une trace de ce type :

42 accepted
42 approved
42 dispatched attempt=1
42 outcome=unknown
43 accepted
43 approved waiting-for=42

Le fait que le ticket 43 s'exécute ou non par la suite est un choix opérationnel à documenter. Le comportement inacceptable est un journal qui affirme que 42 a échoué sans preuve de la passerelle, suivi d'un B réussi qui repose sur cette affirmation.

Les requêtes lentes révèlent une autre erreur d'implémentation. Un répartiteur qui conserve un verrou mutex pendant l'attente de la réponse distante peut tout sérialiser par accident, y compris les voies sans lien et le travail de l'interface. Un répartiteur qui libère trop tôt son état d'ordre permet au ticket suivant de prendre de l'avance. Gardez l'état d'admission et d'envoi de la voie réduit, faites persister l'événement d'envoi, transmettez la requête au canal, puis attendez le résultat sans laisser passer un autre ticket de cette voie lorsque la sérialisation stricte s'applique.

Le journal d'audit doit conserver la causalité

Conservez la trace des appels
Les enregistrements d'activité conservent chaque appel HTTP et SSH après son exécution par l'application.

Un enregistrement d'audit chaîné par hachage prouve que les enregistrements conservés n'ont pas été modifiés en silence. Il ne rend pas compréhensible une séquence d'événements confuse. Le modèle d'événements doit toujours contenir suffisamment d'informations pour expliquer une course d'approbation concurrente.

Enregistrez l'événement d'acceptation avant l'apparition d'une carte. Enregistrez la décision d'approbation avec le ticket et l'acteur ou le mode d'interaction. Enregistrez l'envoi avant que la requête HTTP ou la commande SSH n'entre dans son canal. Enregistrez le résultat final du canal sans remplacer aucun événement précédent. Chaque événement doit avoir sa propre position d'ajout, en plus de l'heure réelle.

Les horloges réelles sont utiles pour le débogage, mais ne peuvent pas définir l'ordre à elles seules. Deux événements peuvent avoir la même précision temporelle, les horloges peuvent avancer ou reculer et une application peut mettre en file des écritures provenant de threads différents. Une position d'ajout ou un numéro de séquence établit l'ordre dans lequel le journal a accepté chaque événement. Le ticket par requête établit l'ordre prévu dans la voie. Conservez les deux.

Pour l'exercice des deux écritures, comparez ces faits :

  • Les positions d'acceptation montrent 42 avant 43.
  • Les événements d'approbation peuvent montrer 43 avant 42.
  • Les positions d'envoi montrent 42 avant 43 lorsque les deux appels réussissent.
  • Le fichier d'arrivée de la cible montre 42 avant 43.
  • Les résultats finaux restent associés aux mêmes tickets au lieu de les remplacer.

Sallyport construit les vues Sessions et Activity à partir d'un même journal d'audit chiffré et chaîné par hachage, et sp audit verify peut vérifier cette chaîne hors ligne sur le texte chiffré. Le choix des événements devient donc particulièrement important : la vérification peut montrer que les enregistrements n'ont pas été modifiés, tandis que les tickets et les types d'événements indiquent à l'humain ce qui s'est réellement passé.

Ne triez pas le journal visible par heure d'achèvement pour l'appeler historique. Vous présenteriez une course réseau comme une séquence de décisions. Triez la chronologie principale par position d'ajout durable, puis affichez le numéro de ticket et les horodatages à côté. Une vue filtrée pour un processus d'agent doit conserver les positions d'origine afin qu'un réviseur puisse voir qu'une autre requête a attendu entre deux événements, sans avoir à le deviner.

FIFO s'applique aux effets en conflit, pas à chaque octet

Gardez les identifiants hors des agents
Sallyport conserve les identifiants API et SSH dans son coffre chiffré, sans jamais les exposer à l'agent.

Une file FIFO est une promesse concernant les effets qui entrent en conflit. Ce n'est pas une raison pour faire passer chaque opération réseau dans un seul thread ou pour retarder une lecture sans danger derrière une écriture non résolue.

Commencez par classer chaque action. Une commande de déploiement qui modifie un environnement partagé a besoin d'une voie. Un HTTP POST qui crée une opération de facturation a besoin d'une voie, éventuellement par compte. Une récupération qui lit un artefact statique peut souvent s'exécuter indépendamment. Une commande SSH qui indique seulement l'espace disque est peut-être une observation, mais soyez prudent : les commandes ont souvent des effets cachés via les fichiers de démarrage du shell, les fichiers temporaires ou les wrappers de commandes distantes. Traitez les commandes ambiguës comme des écritures jusqu'à ce que vous puissiez décrire leurs effets.

L'alternative populaire consiste à dire que chaque approbation est indépendante parce que le réviseur peut inspecter la requête. C'est séduisant, car cela supprime la conception de la file. Cela échoue lorsque le réviseur comprend chaque requête individuellement, mais ne sait pas que B suppose que A a déjà eu lieu. L'examen humain ne répare pas un contexte de sérialisation absent.

L'erreur inverse consiste à créer une seule file globale. Elle simplifie l'ordre, puis donne l'impression que l'application est bloquée lorsqu'un hôte distant ralentit. Les voies distinctes ont besoin de noms clairs, d'une sélection stable des ressources et de champs de journal qui les identifient. Si vous ne pouvez pas expliquer pourquoi deux actions partagent une voie, vous ne pouvez pas non plus tester la promesse d'ordre.

N'utilisez pas l'approbation par appel comme substitut aux voies. Demander un clic pour chaque utilisation aide un humain à évaluer chaque action. Cela ne définit pas si deux actions approuvées peuvent se dépasser. Ce sont deux contrôles différents, avec des modes de défaillance différents.

Faire de la course un critère de mise en production

Un défaut de file apparaît rarement sous la forme d'un plantage évident. Il se manifeste plus tard par un état distant inexpliqué, une approbation qui semble s'appliquer à la mauvaise action ou un journal incapable de trancher lors d'un examen d'incident. Traitez l'exercice des deux écritures comme un test de mise en production pour chaque type d'action capable de modifier un état partagé.

Le critère de mise en production doit vérifier plus que les réponses réussies. Générez des appels qui se chevauchent, inversez la séquence d'approbation, rejetez la première requête, laissez-la expirer, annulez la seconde et forcez un résultat inconnu après l'envoi. Pour chaque cas, sauvegardez la séquence des tickets, les états des cartes, les événements d'envoi, le journal d'arrivée de la cible et les entrées du journal.

Lorsque l'un de ces enregistrements diverge, résistez à l'envie de parler d'un problème d'affichage. Un problème d'affichage peut être le premier signe visible que plusieurs parties de l'application ont choisi des définitions différentes de l'ordre. Corrigez le contrat au moment de l'admission, puis faites en sorte que la carte, le répartiteur, le test de la cible et le journal présentent le même contrat.

La première action concrète est simple : ajoutez un ticket immuable à une voie d'écriture en conflit et faites approuver le ticket suivant en premier dans votre test. Si cette requête atteint la cible avant sa voisine précédente, la file d'approbation ne contrôle pas encore le travail qu'elle prétend approuver.

FAQ

Comment déterminer l'ordre de deux appels d'agent concurrents ?

Utilisez un ticket monotone attribué lorsque la passerelle accepte la requête, avant l'apparition de la carte d'approbation. Un simple horodatage fournit une preuve fragile, car des valeurs identiques, des changements d'horloge et plusieurs processus rendent l'ordre ambigu.

La première requête approuvée doit-elle s'exécuter en premier ?

L'ordre des approbations ne doit pas modifier l'ordre d'admission. Si la requête B est approuvée avant la requête A, B doit rester en attente jusqu'à ce que A atteigne un état final, ou que le système documente explicitement un autre contrat de sérialisation.

Que se passe-t-il lorsque la première écriture de la file est rejetée ?

Une requête antérieure rejetée occupe toujours sa position dans la file. Enregistrez ce rejet, puis laissez la requête approuvée suivante continuer. N'effacez pas discrètement la requête rejetée de l'historique.

Une approbation arrivée à expiration peut-elle bloquer toutes les requêtes suivantes ?

Une expiration doit être traitée comme un rejet : une seule décision finale liée à un seul ticket. La requête suivante ne peut continuer qu'après la persistance de l'expiration et sa visibilité dans le journal.

Tous les appels d'agent doivent-ils passer par une seule file FIFO globale ?

Séparez les files selon la ressource dont l'état peut entrer en conflit. Deux écritures sur le même document distant doivent être sérialisées, tandis que des appels indépendants vers des services sans lien peuvent utiliser des voies distinctes si le journal conserve l'ordre d'admission de chaque voie.

Pourquoi les journaux du serveur peuvent-ils contredire la file d'approbation ?

Un proxy ou un serveur HTTP peut recevoir les requêtes dans un ordre différent de celui de la passerelle lorsque les connexions entrent en concurrence. Le test doit comparer les tickets de la passerelle avec les enregistrements d'arrivée en aval, au lieu de considérer l'arrivée sur le réseau comme une preuve d'ordonnancement correct.

Quelle est la bonne manière de tester des écritures concurrentes ?

Un point d'écriture doit produire un effet observable et ordonné, par exemple ajouter un ticket et une charge utile à un fichier local. Les tests avec des requêtes GET masquent le problème, car les lectures tolèrent souvent le réordonnancement.

L'ordre du journal doit-il suivre l'exécution ou l'achèvement ?

Le journal d'activité doit conserver la séquence causale de chaque requête : acceptée, décision d'approbation, envoyée, puis résultat final. Le temps d'achèvement peut varier, mais il ne doit pas réécrire le moment où la passerelle a admis ou envoyé une requête.

L'identité du processus suffit-elle pour auditer une approbation ?

Non. L'identité signée du processus indique qui a effectué la demande, tandis que le ticket de file indique où cette requête se situe parmi les appels concurrents. Ces deux informations sont nécessaires pour auditer une décision ultérieurement.

Quels cas un test de régression de la file d'approbation doit-il couvrir ?

Répétez la course entre deux écritures en inversant le moment des approbations, puis testez le rejet, l'expiration, l'annulation et une première cible volontairement lente. Toute différence entre l'ordre des tickets, des cartes, des envois et la causalité du journal doit bloquer la mise en production de cette voie.

Sallyport

Sallyport exécute les appels d'API et les commandes SSH à la place de votre agent IA. Les clés restent dans un coffre-fort local sur votre Mac ; vous approuvez chaque exécution et chaque action est consignée dans un journal scellé.

© 2026 Sallyport · Open source sous Apache-2.0 · Oleg Sotnikov