# Contrôles de déploiement

Les contrôles de déploiement retiennent une release hors de la production tant que ce que vous exigez d'elle (des tests, une analyse, une relecture) n'a pas été rapporté comme réussi. Cette page explique où se déclarent les contrôles obligatoires, comment l'intégration continue les satisfait, qui peut les contourner et comment c'est tracé. Elle décrit aussi la mise en service progressive, déclarée dans le même fichier.

## Les concepts

- **Contrôle** : une exigence nommée par un identifiant stable que vous choisissez (`tests-unitaires`, `analyse-statique`). Il ne décrit aucune commande : c'est votre intégration continue qui le rapporte, sous cet identifiant, quel que soit son fournisseur.
- **Obligatoire** : un contrôle obligatoire retient la release ; un contrôle informatif se lit et ne retient rien.
- **État** d'un contrôle sur une release : `pending` (attendu), `passed` (réussi) ou `bypassed` (contourné).
- **Release en attente** : construite (`built`) et non servie, parce qu'elle attend ses contrôles.

## Déclarer les contrôles

Les contrôles se déclarent dans le fichier `.bastide/controles.yaml`, à la racine de construction du projet (la « Racine du projet » des réglages, ou la racine du dépôt). Il n'y a pas d'autre endroit : ni la console ni l'API ne les configurent, et changer de fournisseur d'intégration continue ne change rien ici.

```yaml
version: 1
controles:
  - id: tests-unitaires
  - id: tests-de-bout-en-bout
  - id: analyse-statique
    obligatoire: false
    description: Informatif seulement
```

| Champ | Règle |
| --- | --- |
| `version` | Requis, vaut `1`. |
| `controles` | Vingt au plus, sans doublon. |
| `id` | Une minuscule ou un chiffre, puis jusqu'à 62 minuscules, chiffres, points, soulignés ou tirets. |
| `obligatoire` | Vrai par défaut : oublier la ligne retient la release, ne la laisse pas passer. |
| `description` | Pour le lecteur du fichier, 200 caractères au plus ; ni gardée ni servie. |

Le fichier pèse 16 Kio au plus. Absent, la release n'exige rien. Présent et illisible (YAML invalide, champ inconnu, identifiant hors grammaire, doublon, lien symbolique), la construction est refusée sous `controles_refuses` : une porte de production que la plateforme ne sait pas lire ne s'ignore pas.

Les contrôles sont gelés sur la release à sa construction. Une reconstruction du même commit ne remet pas à « attendu » un contrôle déjà rapporté.

## Ce que les contrôles retiennent

- **Vers la production**, une release dont un contrôle obligatoire n'est ni réussi ni contourné reste en attente : elle est construite, et la version en ligne ne change pas. Un contrôle qui n'a rien rapporté retient autant qu'un échec.
- **Hors de la production** (prévisualisation, préproduction, environnement personnalisé), les contrôles se lisent et ne retiennent rien : une prévisualisation sert justement à les voir échouer.

Les contrôles d'une release se lisent dans `checks` de `deployment_read`, et `enAttente` dit si elle est retenue. La console ne montre pas encore les contrôles : l'écran de promotion qui les portera est à venir.

## Satisfaire les contrôles

Une fois vos tâches passées, promouvez la release en déclarant les contrôles réussis, par `deployment_create` avec `releaseId` et `passedChecks` :

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "environmentId": "env_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "releaseId": "rel_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "passedChecks": ["tests-unitaires", "tests-de-bout-en-bout"],
  "idempotencyKey": "promotion-rel-2026-10-28"
}
```

La même promotion depuis une tâche d'intégration continue, sous un [compte de service](service-accounts.md) qui porte la portée `deploy:write` :

```sh
export BASTIDE_TOKEN=<jeton du compte de service>
bastide deployment create --project-id prj_01JQ8ZK4M7NP2RSTVWXYZ34567 \
  --environment-id env_01JQ8ZK4M7NP2RSTVWXYZ34567 \
  --release-id rel_01JQ8ZK4M7NP2RSTVWXYZ34567 \
  --passed-checks tests-unitaires --passed-checks tests-de-bout-en-bout
```

La promotion ne reconstruit rien : la production sert l'image exacte qui a été construite (voir [Déployer](deploy.md)). Si un contrôle obligatoire reste attendu, la promotion est refusée sous `controles_non_satisfaits`, qui nomme les contrôles manquants ; la version en ligne ne change pas.

`passedChecks` et `bypassChecks` ne valent qu'avec `releaseId`.

## Qui peut rapporter, et qui peut contourner

| Geste | Une personne | Un compte de service | Un agent (session d'agent, ou canal MCP) |
| --- | --- | --- | --- |
| Rapporter des contrôles réussis (`passedChecks`) | oui | oui | non |
| Contourner les contrôles (`bypassChecks`) | oui | non | non |

Un agent ne déclare pas lui-même réussis les contrôles qui le bloquent, et ne les contourne pas : il propose, la personne décide. Le refus se fait sous `contournement_reserve` (403), avant toute écriture, et la tentative est journalisée. Le rang demandé est celui de `deployment_create` : développeur (`developer`).

### Contourner

Le contournement est le geste d'une personne qui en répond, avec un motif de 10 à 500 caractères :

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "environmentId": "env_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "releaseId": "rel_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "bypassChecks": { "reason": "Correctif de sécurité urgent, tests relancés après la mise en ligne." }
}
```

Il porte sur les contrôles obligatoires encore attendus de cette release. Chacun passe à `bypassed`, avec le nom de la personne et le motif ; un contrôle contourné le reste, même si une tâche le rapporte réussi ensuite.

### Comment c'est tracé

- La ligne du [journal d'audit](audit.md) de la promotion nomme la personne, la release promue, le déploiement d'origine, les contrôles contournés et le motif.
- Chaque contrôle de la release garde qui l'a rapporté ou contourné, et quand.
- Une tentative refusée sous `contournement_reserve` est elle aussi journalisée.

Relisez ces lignes sur l'écran **Audit** du projet, ou par `audit_read`.

## La mise en service progressive

Le même fichier peut déclarer qu'un déploiement de production reçoit d'abord une part du trafic, et qu'il est annulé de lui-même si les erreurs ou la latence dépassent un seuil :

```yaml
version: 1
mise_en_service:
  paliers: [10, 50, 100]
  erreurs_5xx_max_pct: 2
  p95_max_ms: 1000
  palier_minutes: 5
  echantillon_min: 50
controles: []
```

| Champ | Sens | Défaut |
| --- | --- | --- |
| `paliers` | Parts successives du trafic, en pourcentage, croissantes, la dernière à 100 ; dix au plus. | 10, 50, 100 |
| `erreurs_5xx_max_pct` | Taux de réponses 5xx au-delà duquel la release est annulée. | 2 |
| `p95_max_ms` | Latence p95 au-delà de laquelle elle est annulée. | 1 000 ms |
| `palier_minutes` | Durée d'un palier. | 5 |
| `echantillon_min` | Requêtes en deçà desquelles une mesure ne décide pas. | 50 |

Déclarer le bloc suffit : chaque champ a son défaut. Elle ne vaut qu'en production, et seulement quand une release précédente sert déjà. Un seuil franchi sur un échantillon suffisant annule tout de suite, et la release précédente reprend tout le trafic. Faute de trafic au bout de trois paliers, la part avance, et la plateforme le dit.

## Erreurs fréquentes

| Code | Remède |
| --- | --- |
| `controles_refuses` | Le fichier `.bastide/controles.yaml` est illisible : lire le motif (ligne et code), corriger, redéployer. Le retirer supprime les contrôles. |
| `controles_non_satisfaits` | Rapporter les contrôles manquants avec `passedChecks`, ou faire contourner par une personne. |
| `contournement_reserve` | Un agent a tenté de rapporter ou de contourner : demander à une personne de promouvoir, depuis l'API ou la ligne de commande. |
| `promotion_impossible` | Release déjà servie, environnement épinglé, non routable, ou déploiement du même commit en cours. |

Voir aussi : [Déployer](deploy.md), [Constructions](builds.md), [Comptes de service](service-accounts.md), [Sécurité des agents](agent-safety.md), [Audit](audit.md).
