# Branches et migrations

Cette page dit comment faire évoluer le schéma de votre base sans surprise : appliquer une migration après l'avoir répétée sur la base réelle, et lire chaque nuit l'écart entre le schéma que déclare votre dépôt et la base qui tourne. Elle dit aussi où en sont les branches de base de données : prévues, elles ne sont pas encore disponibles.

## Les branches de base de données : pas encore disponibles

La grille prévoit des branches de base, une copie de la base par branche du dépôt (1 en Découverte, 3 en Standard, 25 en Pro, 50 en Souverain). Elles ne sont pas encore servies : aucun écran, aucun outil et aucune opération ne crée ni ne liste une branche.

Ce que cela veut dire aujourd'hui :

- **Toutes les classes d'environnement partagent la même base.** Une prévisualisation, la préproduction et la production reçoivent la même `DATABASE_URL` (voir [Base de données](database.md)). Une migration jouée depuis une prévisualisation touche donc la base de production.
- **Pour essayer un schéma à part**, créez un second projet avec sa propre base (`project_create_with_backend`), ou posez vous-même `DATABASE_URL` sur l'environnement de prévisualisation vers une autre base : votre variable l'emporte sur celle de Bastide.

> **Attention.** Ne lancez pas `prisma migrate deploy` ou un équivalent au démarrage d'une prévisualisation : il modifierait la base de production.

## Appliquer une migration de schéma

Une migration passe toujours par deux passes, dans cet ordre :

1. **La répétition.** La migration est jouée sur la base réelle, dans une transaction **annulée** : même schéma, mêmes lignes, mêmes verrous, et rien ne reste. La plateforme mesure sa durée, les verrous qu'elle prend (au mode le plus fort par relation) et, quand elle est certaine, sa migration inverse.
2. **L'application.** La migration est rejouée et validée. Ce qui est rendu est la mesure de cette seconde passe : la base a pu changer entre les deux.

Ce qui est refusé :

- **Un effet destructif** : une table supprimée ou vidée, une colonne retirée (`migration_destructive`). Ce geste passe par l'éditeur SQL en écriture, qui le fait confirmer et retaper (voir [Base de données](database.md)).
- **Le contrôle de transaction et l'état de session** dans le texte (`BEGIN`, `COMMIT`, `SET`, `COPY`, curseurs, `set_config`) : la migration validerait sinon sa propre répétition.
- **Plus de 200 instructions** : découpez la migration en plusieurs appels.
- **Une instruction que la base refuse** (`migration_refusee_par_la_base`) : rien n'est appliqué ; le détail nomme l'instruction et son état SQL.

La migration inverse n'est rendue que si **chaque** instruction en a une certaine : créer une table, une vue, un index, une séquence, un schéma, ou ajouter une colonne. Elle détruit ce que la migration a créé, et se joue par `database_sql_write`. Une migration validée ne se défait pas par `action_undo`.

### Depuis la console

Ouvrez **Données**, section « Migrations ».

1. Collez les instructions de la migration.
2. Cliquez sur « Répéter puis annuler » : la page montre les verrous pris, la durée de la répétition et la migration inverse si elle existe.
3. Relisez la carte « Avant d'appliquer », puis cliquez sur « Appliquer la migration ».

Seul le rang Mainteneur applique ; chaque application entre au journal d'audit. La page dit « non mesuré » pour ce que la plateforme ne publie pas encore : l'historique des migrations appliquées, les lignes touchées et les requêtes en attente.

### Par un agent, la ligne de commande ou l'API

L'outil `database_migrate` (rang `maintainer`, portée `db:write`) prend `sql` et, pour s'arrêter après la répétition, `dryRun: true`. Une personne valide l'appel avant qu'il parte ; un compte de service ne l'exécute pas.

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "sql": "alter table commandes add column livree_le timestamptz;",
  "dryRun": true,
  "idempotencyKey": "migration-livree-le-a-blanc-2026-09-14"
}
```

La réponse porte `applied`, `durationMs`, `locksAcquired` et `rollbackSql`. En ligne de commande : `bastide database migrate --project-id <projet> --sql <instructions> --dry-run`. En HTTP : `POST /v1/projects/{projectId}/database/migrations`.

## L'écart de schéma

Chaque nuit, la plateforme rejoue les migrations du dossier `supabase/migrations/` de votre dépôt, dans l'ordre de leurs noms, sur une base à part qui porte le même socle que la vôtre. Puis elle compare les deux, objet par objet : schémas, extensions, tables, vues, colonnes, contraintes, index, politiques, fonctions. Le SQL de votre dépôt s'y exécute sous un rôle de client, jamais sous l'administrateur.

Chaque écart a une nature et porte les deux définitions :

| Nature | Ce qu'elle dit |
| --- | --- |
| `missing_in_database` | Déclaré au dépôt, absent de la base. |
| `missing_in_repository` | Présent dans la base, absent du dépôt : souvent une modification faite à la main. |
| `different` | Défini autrement dans la base. |

L'état est `conforme`, `ecart` ou `non_mesure`. Une comparaison qui n'a pas pu se faire (pas de dépôt raccordé, pas de dossier de migrations, une migration qui ne se rejoue pas) est « non mesurée », avec sa raison : une absence d'écart ne se lit jamais comme une base conforme.

Un écart **nouveau** émet l'événement `database.schema.drifted` vers les points de réception du projet (voir [Webhooks](webhooks.md)). Le même écart, la nuit suivante, ne réémet pas.

### Où le lire

- **Console** : **Données**, section « Migrations » (les écarts, avec « Voir les définitions »), et section « Sécurité », sous la posture.
- **Agent** : `backend_schema_drift_read` (rang `maintainer`, portée `db:read`).
- **Ligne de commande** : `bastide backend schema_drift read --project-id <projet>`.
- **API** : `GET /v1/projects/{projectId}/backend/schema-drift`.

La réponse porte `comparedAt` (nul tant qu'aucune comparaison n'a eu lieu), `state`, `commitSha`, `declaredMigrations`, `comparedObjects`, `drifts` (cent au plus, `truncated` au-delà), `driftCount` et `notMeasured`.

## Erreurs fréquentes

- **`migration_destructive`** : votre migration retire une colonne ou une table. Passez par `database_sql_write`, qui le fait confirmer.
- **`migration_refusee_par_la_base`** : corrigez l'instruction nommée ; rien n'a été appliqué.
- **`state: non_mesure`** : lisez `notMeasured`. Le plus souvent, le dépôt n'a pas de dossier `supabase/migrations/`, ou une migration ne se rejoue pas sur une base vide.
- **Un écart `missing_in_repository` après un essai dans l'éditeur SQL** : reportez la modification dans une migration du dépôt, ou défaites-la.

## Voir aussi

- [Base de données](database.md), [Sauvegardes](backups.md), [Prévisualisations](previews.md)
- Référence : [database.migrate](../reference/operations/database.migrate.md), [backend.schema_drift.read](../reference/operations/backend.schema_drift.read.md)
