# Des endpoints de rollback pour des déploiements autonomes sans collision

Les agents de déploiement autonomes doivent pouvoir échouer sans danger, pas deviner. Le rollback dangereux n'est presque jamais une commande qui échoue. C'est une commande qui réussit après un changement de situation et restaure un ancien artefact par-dessus une version que l'agent n'a pas créée.

Une action de rollback sûre identifie une tentative de déploiement précise, désigne une version antérieure précise et refuse d'agir lorsque sa vision de l'état actuel n'est plus à jour. Traitez le rollback comme une transition d'état protégée, pas comme un raccourci vers « la dernière version saine ». Cette distinction détermine si un agent répare son propre travail ou efface celui de quelqu'un d'autre.

## Un rollback doit appartenir à une seule tentative de déploiement

Les endpoints de rollback doivent rattacher l'annulation à la tentative de déploiement qui a causé le problème suspecté. Un numéro de version ne suffit pas.

Les équipes stockent souvent une séquence comme `1.8.4`, `1.8.5` et `1.8.6`, puis exposent une opération qui dit « déployer 1.8.4 en production ». Un agent déploie `1.8.5`, reçoit une alerte et appelle cette opération. Entre-temps, un ingénieur a déployé la version urgente `1.8.6`. L'endpoint de rollback accepte la demande parce que `1.8.4` existe. La production exécute alors un artefact antérieur aux deux changements. L'API a fait exactement ce qu'on lui demandait, et c'est bien le problème.

Séparez clairement ces identités :

- Une **version** est un paquet immuable de code, de références de configuration et de métadonnées. Donnez-lui un identifiant durable et le condensat de son artefact.
- Une **tentative de déploiement** est une demande de mise en production d'une version sur une cible nommée. Elle possède un identifiant de déploiement, un acteur et un cycle de vie.
- Un **état cible** correspond à ce que l'environnement exécute actuellement. Il comprend une révision ou une génération qui change à chaque transition acceptée.
- Une **intention de rollback** indique que la tentative de déploiement `D` peut restaurer la version cible `R`, uniquement si `D` possède encore l'état actuel.

Le champ souvent oublié est le lien de propriété. Lorsque le déploiement `dep_842` promeut la version `rel_105`, enregistrez que `dep_842` a créé la génération cible actuelle `gen_913`. Un rollback associé à `dep_842` ne peut continuer que tant que `gen_913` reste actuelle. Si un déploiement ultérieur crée `gen_914`, le service doit refuser l'ancienne demande.

N'inférez pas la propriété en comparant les horodatages. L'ordre des horloges est fragile avec les nouvelles tentatives, les workers en file d'attente, les réparations manuelles et tout système qui permet à un utilisateur de sélectionner une ancienne version. Enregistrez la relation directe au moment où vous acceptez la promotion.

Cela répond aussi à une question opérationnelle délicate : un agent peut-il annuler le déploiement d'un autre agent ? En général, non. Une autorité distincte peut autoriser explicitement cette intervention, mais la capacité ordinaire de rollback devrait couvrir uniquement les actions lancées par l'appelant. Une autorité étendue paraît pratique jusqu'à ce que deux boucles de déploiement réagissent au même incident.

## Une provenance immuable permet de connaître la version précédente

Le service doit capturer la provenance du rollback avant de modifier la cible, car après la promotion le mot « précédente » devient ambigu. Interroger l'historique des versions après une alerte revient à choisir ce qui se trouve par hasard à côté dans une liste.

Lorsqu'un service de déploiement accepte une promotion, il doit créer un enregistrement qui inclut la version stable précédente choisie à cet instant. Cette version peut différer de l'événement immédiatement antérieur. Par exemple, un canary peut promouvoir `rel_105` alors que `rel_103` reste la base stable et que `rel_104` était une expérimentation abandonnée. La bonne cible de récupération peut être `rel_103`, pas la ligne située juste avant `rel_105`.

Un enregistrement minimal peut ressembler à ceci :

```json
{
  "deployment_id": "dep_842",
  "environment": "production",
  "release_id": "rel_105",
  "artifact_digest": "sha256:8b2c...",
  "source_revision": "4f1d9c7",
  "config_digest": "sha256:1a06...",
  "prior_release_id": "rel_103",
  "created_target_generation": "gen_913",
  "migration_set_id": "mig_77",
  "actor_id": "agent-run-27"
}
```

`prior_release_id` est une décision, pas un champ pratique. Votre contrôleur de promotion doit le choisir selon des règles que les opérateurs peuvent examiner : la dernière version stable vérifiée pour cette cible, avec éventuellement une configuration et des exigences de migration compatibles. L'opération de rollback utilise cette décision enregistrée. Elle ne la recalcule pas parce que la table d'historique a changé.

L'identité de l'artefact doit aller au-delà d'un libellé lisible par les humains. Les tags peuvent changer de cible. Les libellés de build peuvent être réutilisés par erreur. Un rollback doit déployer la référence immuable de l'artefact, ou son adresse de contenu, enregistrée lors de la tentative initiale. Si votre registre permet à un tag de pointer plus tard vers des octets différents, l'identifiant de la version cible doit être résolu vers un condensat capturé au moment de la promotion.

La configuration exige la même rigueur. Restaurer les octets de l'application tout en conservant un indicateur de fonctionnalité modifié, une référence de secret d'exécution, une règle de récupération d'image ou un paramètre de ressources différent peut créer un système qui n'a jamais existé pendant les tests. Il n'est pas nécessaire de dupliquer chaque valeur dans l'enregistrement de déploiement, mais enregistrez une révision ou un condensat de configuration immuable et définissez si le rollback doit la restaurer.

L'état de la base de données constitue une limite distincte. Une version qui ajoute uniquement des colonnes acceptant `NULL` permet souvent un rollback applicatif. Une version qui supprime une colonne, réécrit des valeurs ou modifie leur sémantique peut ne pas le permettre. Ne promettez pas un « rollback complet » générique si le planificateur de versions ne peut pas prouver la compatibilité. Indiquez si la version est réversible au niveau de l'application, du trafic ou si une réparation est nécessaire. Un refus est moins embarrassant que l'exécution d'un ancien code sur un schéma qu'il ne comprend pas.

## L'endpoint doit exiger la version actuelle attendue

Une demande de rollback doit contenir à la fois la version souhaitée et l'état actif qu'elle doit remplacer. Sans cette précondition, l'endpoint ne peut pas distinguer une récupération valide d'une instruction périmée.

Utilisez un contrat de demande de cette forme :

```http
POST /v1/environments/production/rollbacks
Idempotency-Key: 7e4cd1ee-62cb-4efa-985f-4ee0b77d577b
Content-Type: application/json

{
  "origin_deployment_id": "dep_842",
  "expected_current_release_id": "rel_105",
  "expected_target_generation": "gen_913",
  "restore_release_id": "rel_103",
  "reason": "error rate exceeded release threshold",
  "approval_id": "apr_551"
}
```

Le service devrait déduire `restore_release_id` de `origin_deployment_id` lorsque c'est possible, puis comparer la valeur fournie avec `prior_release_id` enregistré. Garder les deux champs dans la demande aide les auditeurs à voir l'intention déclarée par l'agent, mais l'enregistrement du serveur fait foi. Ne laissez jamais un appelant transformer son propre déploiement en autorisation de choisir n'importe quel artefact historique.

En cas de succès, renvoyez la nouvelle tentative de déploiement créée et la nouvelle génération cible. Ne renvoyez pas une réponse vague `accepted` si le système peut réserver la transition de manière synchrone.

```json
{
  "rollback_deployment_id": "dep_849",
  "reverted_deployment_id": "dep_842",
  "previous_release_id": "rel_105",
  "current_release_id": "rel_103",
  "target_generation": "gen_914",
  "status": "running"
}
```

Si la cible active ne correspond plus, renvoyez une réponse de conflit. Le corps doit contenir suffisamment d'informations pour qu'un agent signale ce qui s'est passé, mais pas assez d'autorité pour qu'il improvise une nouvelle action.

```http
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "error": "stale_rollback",
  "origin_deployment_id": "dep_842",
  "expected_target_generation": "gen_913",
  "observed_target_generation": "gen_914",
  "observed_release_id": "rel_106"
}
```

Un 409 est un résultat de sécurité réussi. Apprenez à l'agent qu'il doit s'arrêter, joindre la réponse à son dossier d'incident et demander une nouvelle décision. Ne lui donnez pas d'instruction de secours telle que « réessayez sans la génération attendue ». Cette solution transformerait votre garde-fou en simulacre.

Certaines équipes utilisent un en-tête HTTP `If-Match` contenant un ETag plutôt qu'un champ JSON. Cela fonctionne si l'ETag représente l'état cible et change à chaque transition. Le mécanisme compte moins que l'invariant : la commande doit nommer l'état qu'elle peut remplacer.

## La sérialisation empêche deux demandes valides d'entrer en collision

Une simple vérification de précondition ne protège pas une cible si deux workers peuvent la franchir avant que l'un ou l'autre ne valide sa modification. Le service de déploiement doit sérialiser les changements du même environnement et utiliser une comparaison-échange atomique dans le magasin d'état.

Supposons que la cible soit actuellement `(rel_105, gen_913)`. Un agent envoie un rollback et un opérateur envoie `rel_106`. Les deux demandes lisent `gen_913`. Si le service vérifie l'état en mémoire puis écrit sans condition, les deux appels peuvent se déclarer réussis. La dernière écriture l'emporte, et votre piste d'audit signale un état qui n'a peut-être jamais existé pour les utilisateurs.

Placez la comparaison et la mutation dans une seule transaction ou une seule écriture conditionnelle. Une implémentation relationnelle peut utiliser ce modèle :

```sql
UPDATE environment_targets
SET release_id = :restore_release_id,
    generation = generation + 1,
    active_deployment_id = :rollback_deployment_id,
    updated_at = CURRENT_TIMESTAMP
WHERE environment = :environment
  AND generation = :expected_generation
  AND release_id = :expected_release_id;
```

Le service vérifie le nombre de lignes modifiées. Une ligne modifiée réserve la transition d'état. Zéro ligne signifie un conflit. Il doit ensuite lire la cible actuelle et renvoyer les valeurs observées dans la réponse 409.

Une file d'attente ne remplace pas cette condition. Les files réduisent la probabilité d'un travail concurrent, mais une livraison en double, un chemin manuel hors de la file ou une nouvelle tentative du worker peuvent toujours produire des commandes concurrentes. Gardez l'écriture conditionnelle là où l'état est stocké.

L'idempotence résout un autre problème. Un agent peut perdre la réponse après l'acceptation du rollback par le service. S'il réessaie avec la même clé d'idempotence, le service doit renvoyer le déploiement de rollback et le statut d'origine. Il ne doit ni réserver une autre génération ni lancer une seconde exécution.

Limitez l'idempotence à l'appelant et à l'endpoint, enregistrez un condensat du corps de la demande et refusez toute réutilisation de clé avec un corps différent. Sinon, un client défaillant peut rattacher une nouvelle intention à une ancienne demande en réutilisant un identifiant.

## Un rollback peut conserver un état défectueux des dépendances

Le rollback applicatif et la récupération de l'environnement sont deux opérations distinctes. Un endpoint qui déploie un ancien code ne peut pas rendre automatiquement toutes les dépendances compatibles.

J'ai vu le scénario prévisible : la version `rel_105` introduisait du code qui écrivait une nouvelle valeur d'énumération. Une migration a ensuite renforcé une contrainte de base de données pour n'autoriser que les nouvelles valeurs. La version a échoué pour une raison sans rapport, et l'opérateur a remis `rel_103`. L'ancien code a écrit l'ancienne valeur, la base de données l'a rejetée et l'incident s'est aggravé parce que le tableau de bord de déploiement présentait le rollback comme terminé.

L'endpoint n'a pas provoqué la modification du schéma, mais sa réponse de succès a formulé une affirmation fausse. Évitez cette affirmation en exigeant des métadonnées de version qui décrivent la compatibilité de manière concrète. Au minimum, indiquez si la version restaurée peut lire les données actuelles, écrire les données actuelles et fonctionner avec la révision de configuration de la cible.

La gestion du trafic comporte son propre piège. Un rollback de canary devrait normalement modifier uniquement la répartition de trafic détenue par ce canary. Si une version indépendante a modifié le pool stable ou si un autre contrôleur a changé une règle de routage, un endpoint de rollback qui écrit tout le document de routage peut effacer ces changements. Utilisez des versions au niveau des ressources ou ne modifiez que les champs de répartition réservés par le déploiement.

Le même principe s'applique à l'infrastructure. Si une version a créé une file, un bucket, un rôle ou une règle de pare-feu que des travaux ultérieurs ont adoptés, sa suppression pendant l'annulation peut nuire à un autre service. Le nettoyage nécessite un enregistrement de propriété de la ressource et une vérification qu'aucun déploiement ultérieur ne l'a revendiquée. Si vous ne pouvez pas établir cette propriété, laissez la ressource en place et créez une tâche de réparation.

Pour les opérations irréversibles, choisissez une réparation corrective. L'agent peut désactiver un indicateur de fonctionnalité, détourner le trafic ou déployer une version corrective. Les opérateurs n'aiment pas cette réponse parce que « rollback » semble plus rapide, mais une annulation propre en apparence qui détruit des données ultérieures coûte davantage qu'un plan de réparation.

## Les agents ont besoin d'une autorité limitée et d'un point d'arrêt visible

Un agent autonome doit recevoir l'autorité minimale nécessaire pour mener à bien le déploiement qui lui est attribué. Il n'a pas besoin d'identifiants cloud bruts, d'un shell général avec accès à la production ni d'un endpoint qui accepte des identifiants de version arbitraires.

Donnez à l'agent une référence de déploiement lorsqu'il démarre une version. Cette référence peut autoriser la lecture des statuts, les contrôles de santé, les changements de trafic dans la répartition du déploiement et un rollback qui nomme le déploiement initial. Faites-la expirer lorsque le déploiement atteint un état final ou lorsqu'un humain arrête l'exécution. Le service de déploiement doit tout de même appliquer la propriété côté serveur, car une référence peut être copiée ou un client peut mal fonctionner.

L'approbation humaine doit intervenir avant la limite irréversible, pas après que l'agent a déjà préparé une commande irréversible. Une politique raisonnable demande une approbation lorsqu'un agent démarre un déploiement en production, puis lui permet d'annuler ce déploiement précis tant que la précondition de propriété tient. Si l'agent rencontre une version ultérieure, toute intervention exige une nouvelle approbation. C'est un bon moment pour ralentir, car quelqu'un a changé la situation.

Sallyport peut garder les identifiants HTTP et SSH hors du processus de l'agent pendant qu'une personne approuve l'exécution de l'agent ou marque un identifiant pour approbation à chaque utilisation. Ce contrôle protège le chemin d'action, mais le service de rollback doit tout de même vérifier ses propres versions et générations ; la garde des identifiants ne peut pas définir la propriété du déploiement.

Évitez des noms de capacités comme `production:rollback:any`. Ils invitent l'appelant à choisir le périmètre au moment de l'exécution. Préférez une capacité émise par le serveur, liée à `dep_842`, à l'environnement `production` et à la route de rollback précise. Si l'agent demande une cible différente, la couche d'autorisation doit la refuser avant que le contrôleur de déploiement n'évalue la demande.

Enregistrez l'identité du processus de l'agent ou de la charge de travail avec chaque demande. Un humain doit pouvoir répondre à ces questions : qui a lancé `dep_842`, quel code a signé ou authentifié l'appelant, quelle approbation le couvrait et quelqu'un a-t-il révoqué l'accès avant la fin de l'exécution ? Les comptes d'automatisation anonymes transforment chaque incident en travail d'archéologie.

## La vérification doit tester la version restaurée, pas la demande

Un rollback n'est terminé que lorsque la cible exécute la version prévue et que le service vérifie les conditions qui justifiaient la récupération. HTTP 202, une sortie de commande réussie ou un événement du contrôleur indiquant « applied » ne prouve pas que l'ancienne version sert correctement le trafic.

Définissez la vérification en fonction du mode de défaillance réel du déploiement. Si la latence ou les erreurs ont déclenché le rollback, observez le service restauré avec le même chemin de mesure une fois que le trafic l'atteint. Si une version de worker consommait des tâches mal formées, vérifiez la version du worker et une charge contrôlée. Si une erreur de configuration causait des échecs au démarrage, examinez les instances prêtes et la révision de configuration qu'elles ont chargée.

Prévoyez une fenêtre d'observation limitée et enregistrez son résultat. L'endpoint peut signaler `verifying`, puis `succeeded`, `failed` ou `needs_operator`. N'attendez pas indéfiniment une métrique qui pourrait être indisponible. Une limite de temps doit produire un résultat explicitement non concluant, suivi d'une décision de l'opérateur.

L'événement d'audit doit relier chaque étape : l'alerte ou la règle qui a demandé l'annulation, le déploiement initial, l'état attendu, la réservation conditionnelle, les événements d'exécution, les preuves de santé, l'état final et toute révocation. Les événements ont besoin de contrôles d'ordre et d'intégrité, car une chronologie de déploiement agréable à lire ne suffit pas lors d'un litige.

Sallyport enregistre les exécutions des agents et les actions individuelles dans un journal d'audit chiffré et chaîné par hachage, et `sp audit verify` vérifie cette chaîne hors ligne sans clé de coffre-fort. Utilisez ce type de preuve pour montrer qu'un agent a demandé une action, puis conservez les enregistrements de transition d'état et de vérification du service de déploiement comme source de référence sur ce qui a changé.

## Les commandes de rollback connues ont besoin d'une enveloppe plus sûre

`kubectl rollout undo` est utile à un opérateur qui travaille directement sur un Deployment Kubernetes, mais ce n'est pas un contrat complet pour une récupération autonome. Kubernetes indique que `kubectl rollout undo` revient à la révision précédente du déploiement, sauf si l'appelant fournit `--to-revision`. Ce comportement par défaut convient à un diagnostic manuel. Il ne prouve pas que la révision précédente appartient à l'exécution de l'agent défaillant.

Un Deployment Kubernetes suit l'historique des révisions dans des ReplicaSets, tandis que `revisionHistoryLimit` contrôle la quantité d'historique conservée par Kubernetes. Cet historique est un artefact du contrôleur, pas votre registre métier de la base de référence approuvée, de la compatibilité de configuration ou de la propriété de l'agent. Une fois l'historique nettoyé, « undo » peut aussi ne plus disposer de la révision attendue par un processus de déploiement externe.

Ne donnez pas à un agent un identifiant `kubectl` général en présentant la commande comme votre endpoint de rollback. Placez un contrôleur ou un service de déploiement entre l'agent et le cluster. Le service doit résoudre l'enregistrement du déploiement initial, comparer la génération active de la cible, réserver le changement d'état et appeler la plateforme sous-jacente uniquement après ces vérifications.

La même critique s'applique aux commandes des fournisseurs cloud qui disent « déployer la révision X » ou aux contrôles CI qui disent « relancer la version précédente ». Elles agissent sur une ressource de la plateforme. Elles ne savent pas si une version est liée à l'incident actuel de l'agent, à moins que votre plan de contrôle ne fournisse ce contexte.

Gardez aussi la commande de la plateforme limitée. Si le service peut modifier la révision d'une charge de travail nommée, évitez de lui accorder des droits de mutation à l'échelle du cluster. Une enveloppe qui conserve des identifiants étendus n'a fait que déplacer le danger derrière une autre API.

## Constituez un registre des versions avant d'automatiser la récupération

Vous pouvez introduire un rollback protégé sans remplacer tous les systèmes de déploiement. Commencez par rendre le registre des versions de référence pour une cible de production, puis faites passer les chemins humains et ceux des agents par la même transition conditionnelle.

Une mise en œuvre pratique comporte cinq étapes :

1. Attribuez des identifiants immuables aux versions et aux tentatives de déploiement, puis enregistrez la version approuvée précédente et la génération cible au moment de la promotion.
2. Ajoutez un endpoint qui exige `origin_deployment_id`, la version attendue, la génération attendue et une clé d'idempotence.
3. Rendez la mise à jour de la cible conditionnelle dans la base de données ou le magasin de contrôle, et renvoyez 409 à chaque divergence.
4. Classez chaque version selon sa réversibilité applicative, de configuration, de données et de trafic avant la promotion.
5. Exigez des preuves de vérification avant que le contrôleur ne marque un rollback comme terminé.

Exécutez ce contrat en mode rapport avant d'autoriser les agents à l'utiliser. Laissez le service calculer ce qu'il restaurerait et s'il refuserait la demande. Comparez ces décisions avec les actions réelles des incidents. Cela révèle les enregistrements de provenance manquants et les chemins manuels cachés sans donner à l'automatisation la possibilité d'écraser la production.

Rendez ensuite le refus banal. Un rollback périmé doit créer un élément d'incident compréhensible avec la version et la génération observées, pas un échec mystérieux qui encourage quelqu'un à contourner l'endpoint. L'endpoint gagne la confiance lorsqu'il refuse systématiquement les demandes dangereuses, y compris celles des personnes qui l'ont conçu.

Le premier champ à ajouter n'est pas `force`. C'est `expected_target_generation`. Une fois que vos déploiements portent cette information et conservent la base de référence initiale, un agent peut annuler sa propre version avec une limite claire. Avant cela, le rollback autonome n'est qu'une ancienne commande de déploiement dirigée vers une cible mouvante.
