# Canot : documentation complète

# Canot : les commandes

Canot répète votre départ d'une plateforme de données, dans vos propres comptes. Une commande suffit :

```bash
uvx canot drill SOURCE DESTINATION
```

`SOURCE` est la plateforme à quitter (Snowflake, Databricks, BigQuery), `DESTINATION` l'endroit où la copie est écrite. Les valeurs en `MY_…` sont à remplacer par les vôtres. Canot copie les tables en Apache Iceberg, compare tout, rejoue vos requêtes les plus coûteuses et écrit un dossier de preuve signé. Il ne vous envoie rien : pas de télémétrie.

```bash
# Snowflake vers un dossier local
uvx canot drill "snowflake://MY_ACCOUNT/MY_DATABASE?user=MY_USER" ./iceberg

# Databricks vers un stockage S3 de votre choix
uvx canot drill "databricks://MY_WORKSPACE.cloud.databricks.com/sql/1.0/warehouses/MY_WAREHOUSE_ID?catalog=MY_CATALOG" "s3://my-bucket/canot?endpoint=https://s3.internal:9000&region=us-east-1"

# BigQuery vers un hébergeur européen
uvx canot drill "bigquery://MY_PROJECT/MY_DATASET?location=EU" scaleway:fr-par --bucket my-bucket
```

Les connecteurs Snowflake, BigQuery et Databricks sont installés avec Canot : rien à ajouter. Sans `uv`, `pipx run canot …` ou `pip install canot` font de même.

## Les commandes

| Commande | Ce qu'elle fait |
|---|---|
| `canot scan SOURCE` | Radiographie en lecture seule : inventaire, volumes, consommation, ce qui ne part pas, rejouabilité des requêtes. Écrit `scan.html` (à transmettre), `scan.json` et des CSV. Voir [scan](scan.md). |
| `canot drill SOURCE DESTINATION` | Exercice de sortie complet : copie, comparaison table par table, rejeu des requêtes, dossier de preuve signé. |
| `canot demo` | Démonstration de 30 secondes, sans compte ni données : le jeu TPC-H (référence des bancs d'essai de bases de données, généré en local) et un exercice complet. Hors ligne, un jeu fictif de secours. |
| `canot verify-evidence DOSSIER` | Vérifie la chaîne et la signature d'un dossier de preuve. `--expect-pub` refuse un dossier signé par une autre clé. |
| `canot teardown DESTINATION` | Supprime les données copiées. |
| `canot snowflake-setup` | Écrit le script SQL d'accès en lecture seule à faire relire et lancer par un administrateur. `--generate-key` crée la paire de clés. |
| `canot bigquery-setup` | Écrit le script `gcloud` du rôle personnalisé en lecture seule. |
| `canot init-tolerances` | Écrit un fichier de tolérances de comparaison à relire avec vous. |
| `canot demo-data CHEMIN` | Crée une base DuckDB de démonstration. |
| `canot rejeu-dbt dbt://PROJET --base COPIE.duckdb` | Preuve P3 sur un projet dbt : rejoue les modèles SQL dans DuckDB à partir de la copie des sources (base DuckDB, ou `--lac` écrit par `canot drill`), compare chaque sortie à la table d'origine (lignes, statistiques, empreinte) et signe un dossier. Voir [scan](scan.md#preuve-p3--canot-rejeu-dbt). |
| `canot connecteurs` | Liste les plateformes connues : forme d'URL, inventaire (`scan`), export (`drill`), forme de preuve, statut (testé ou écrit, non testé) et extra `pip` à installer. |
| `canot mcp` | Serveur MCP : la documentation et l'explication de vos rapports, pour un agent. Voir [Avec un agent](#avec-un-agent-mcp). |

Toutes les commandes acceptent `--debug` (trace complète en cas d'erreur).

## `canot drill`

Options utiles :

| Option | Rôle |
|---|---|
| `--bucket NOM` | Obligatoire avec un raccourci d'hébergeur (`scaleway:fr-par`, `ovh:gra`, `outscale:eu-west-2`, `ionos:eu-central-1`, `stackit:eu01`, `aws:eu-west-3`). |
| `--catalog URI` | Catalogue Iceberg : `sqlite:///…`, ou `env:NOM_DE_VARIABLE` pour PostgreSQL (`canot[postgres]`). Par défaut : un fichier SQLite dans le dossier de sortie. |
| `--out DOSSIER` | Où écrire le rapport et le dossier de preuve (défaut `canot-out`). |
| `--tables SCHEMA.TABLE …` | Limiter à certaines tables. |
| `--queries FICHIER.sql` | Rejouer vos propres requêtes. Par défaut : les plus coûteuses de l'historique de la plateforme. |
| `--top N` | Nombre de requêtes de l'historique à rejouer (défaut 20). |
| `--tolerances FICHIER.toml` | Tolérances de comparaison (arrondis, horodatages). |
| `--credit-price USD` | Prix du crédit Snowflake de votre contrat. Sans lui : une fourchette du prix catalogue, datée. |
| `--replay-max-gb N` | Taille maximale d'une table chargée en mémoire pour rejouer les requêtes (défaut 2). |
| `--key CHEMIN` | Clé de signature (défaut `~/.canot/evidence.key`, ou la variable `CANOT_EVIDENCE_KEY_PATH`). |

Résultat dans `--out` : `report.html` (lisible par un comité), `report.json`, le dossier de preuve (`evidence.jsonl`, `evidence.sig`, `evidence.pub`) et `relire.sql`. La signature atteste que le dossier n'a pas été modifié depuis sa signature. Elle n'atteste ni l'auteur ni la date.

## `canot scan`

```bash
uvx canot scan "snowflake://MY_ACCOUNT/MY_DATABASE?user=MY_USER" --level 2
```

`--level 1|2|3` (défaut 3) règle jusqu'où lire ; `--credit-price` remplace le prix catalogue ; `--include-sql-text` écrit aussi le SQL brut dans un fichier à part (jamais par défaut). Détail des lectures, des droits et des garanties : [scan](scan.md).

## Les sources

| Source | Forme | Authentification |
|---|---|---|
| BigQuery | `bigquery://PROJET/dataset1,dataset2?location=EU` ; sans dataset : tous ceux du projet ; autre projet : `bigquery://FACTURATION/projet-donnees.dataset` | identifiants applicatifs Google (`gcloud auth application-default login`) |
| Snowflake | `snowflake://COMPTE/BASE?user=…&warehouse=…&role=…&schemas=A,B` | paire de clés : variable `SNOWFLAKE_PRIVATE_KEY_PATH` (recommandé) ou `SNOWFLAKE_PASSWORD` |
| Databricks | `databricks://HÔTE/CHEMIN_HTTP_DE_L_ENTREPÔT?catalog=…` | variable `DATABRICKS_TOKEN` (jeton en lecture) ; connecteur non encore testé sur un vrai compte |
| DuckDB | `maison.duckdb` | aucune |
| dbt (projet) | `dbt://CHEMIN_DU_PROJET` : le projet, son dossier `target/` ou `manifest.json` ; scan seulement (artefacts lus hors ligne), preuve par `canot rejeu-dbt` | aucune : ni réseau, ni commande dbt |

`canot connecteurs` affiche, pour chacune, ce que Canot sait faire (inventaire complet ou léger, export, preuve) et si c'est testé. Pour dbt, la preuve P3 passe par `canot rejeu-dbt`.

## Sortie en local ou on-prem

Pas de cloud à l'arrivée : un dossier suffit.

```bash
uvx canot drill "snowflake://MY_ACCOUNT/MY_DATABASE?user=MY_USER" ./iceberg
```

Canot écrit la copie au format Iceberg dans `./iceberg` et le dossier de preuve dans `canot-out/`. La copie ne dépend pas de l'outil : DuckDB, Spark ou Trino la lisent. Le fichier `relire.sql` donne les commandes DuckDB pour recompter chaque table :

```bash
duckdb < canot-out/relire.sql
```

| Arrivée | Commande |
|---|---|
| Dossier local ou monté (disque, NAS, partage) | `canot drill SOURCE /chemin/vers/le/dossier` |
| Stockage compatible S3 chez vous (MinIO, Ceph, NetApp…) | `canot drill SOURCE "s3://coffre/canot?endpoint=https://s3.interne:9000&region=us-east-1"` |
| Proxy d'entreprise, autorité de certification interne | ajoutez `&proxy=http://proxy.interne:3128` et `&ca=/chemin/ca.pem` à l'URL S3 |
| Catalogue PostgreSQL | `--catalog env:CANOT_CATALOG_URI` (mot de passe dans la variable, jamais sur la ligne de commande) |

Les clés du stockage passent par `CANOT_S3_ACCESS_KEY` et `CANOT_S3_SECRET_KEY` (ou la configuration `scw`). État de chaque cas (testé ou seulement branché) : [sur-site](sur-site.md).

## Les hébergeurs

`scaleway:fr-par`, `ovh:gra`, `outscale:eu-west-2`, `ionos:eu-central-1`, `stackit:eu01`, `aws:eu-west-3`, avec `--bucket`. Vérifiez chaque point de terminaison dans la console de l'hébergeur. La qualification SecNumCloud, quand elle compte pour vous, dépend de l'offre précise : voir [cibles](cibles-ue.md).

## Avec un agent (MCP)

Beaucoup d'équipes travaillent avec un agent. `canot mcp` lui donne la documentation de Canot et lui explique vos rapports, sans rien envoyer à personne : le serveur tourne sur votre machine.

Installation dans Claude Code :

```bash
claude mcp add canot -- uvx canot mcp
```

Ou dans la configuration MCP de votre client (Claude Desktop, Cursor, VS Code…) :

```json
{
  "mcpServers": {
    "canot": { "command": "uvx", "args": ["canot", "mcp"] }
  }
}
```

Outils exposés :

| Outil | Ce qu'il fait |
|---|---|
| `commandes` | Liste les commandes et leurs options. |
| `chercher_doc(requete)` | Cherche dans la documentation et rend les passages. |
| `lire_doc(sujet)` | Rend une page entière : `commandes`, `scan`, `sur-site`, `cibles`, `ecarts`. |
| `capacites(plateforme)` | Ce que Canot sait faire sur chaque plateforme (inventaire, export, forme de preuve), avec le statut (testé ou écrit, non testé) et la date de test consignée. Sans argument : toutes les plateformes. |
| `expliquer_scan(chemin)` | Lit un `scan.json` et le résume : constats, lectures non faites, chiffres estimés, ce qui est à examiner. |
| `expliquer_rapport(chemin)` | Lit un `report.json` d'exercice : écarts, tables et requêtes en cause, causes connues du catalogue. Lit aussi un `rejeu-dbt.json` (preuve P3 dbt) : modèles identiques, différents, non rejouables et leurs raisons. |

Les outils lisent des fichiers que vous désignez. Ils n'ouvrent aucune connexion à vos plateformes.

Pour les agents qui lisent le web : [canot.io/llms.txt](https://canot.io/llms.txt) et [canot.io/llms-full.txt](https://canot.io/llms-full.txt).

## Pour aller plus loin

- [scan](scan.md) : ce qui est lu, avec quels droits, et les garanties.
- [sur-site](sur-site.md) : arrivée locale ou sur votre stockage.
- [cibles](cibles-ue.md) : hébergeurs et pays.
- [ecarts](ecarts.md) : ce que deux moteurs rendent différemment, mesuré.
- Une aide pour l'exécuter sur votre périmètre : [canot.io](https://canot.io/#contact).

---

# `canot scan` : ce qui est lu, avec quels droits

Le scan produit `scan.html` (à transmettre), `scan.json` et des CSV. Il décrit votre plateforme ; il ne recommande ni de partir ni de rester.

## Garanties

- **Lecture seule.** (Projet dbt : aucune requête, des fichiers lus, voir [dbt](#dbt).) Chaque requête passe par une garde avant d'être envoyée : seuls `SELECT`, `SHOW`, `DESCRIBE` et `ALTER SESSION SET` sont admis. Tout le reste est refusé sans rien envoyer. Les fonctions `SYSTEM$` sont refusées aussi.
- **Jamais le contenu des tables.** Le scan lit des métadonnées et l'historique d'exécution. Il ne demande aucun droit de lecture sur les données.
- **Le SQL brut n'est pas écrit.** Les littéraux sont masqués (`WHERE email = ?`), les requêtes qui contiennent un mot de passe ou un secret sont comptées puis jetées. Le texte brut n'est écrit qu'avec `--include-sql-text`, dans `scan-sql-brut.json` (droits 0600), et le rapport le signale.
- **Identités pseudonymisées.** Les utilisateurs et rôles de requêtes sont remplacés par un pseudonyme (HMAC, sel tiré à chaque scan et jamais écrit). Les e-mails, identifiants de connexion et adresses IP ne sont jamais lus. Les chemins de stockage sont réduits au nom du compartiment.
- **Aucune lecture silencieusement perdue.** Chaque lecture est consignée « lu » ou « non lu » avec sa raison. Une valeur non lue reste vide, jamais 0.

## Niveaux (`--level`, 3 par défaut)

| Niveau | Contenu | Droits en plus |
|---|---|---|
| 1 | Inventaire, types de colonnes, volumes, historique facturé, consommation, objets (vues, routines, tâches, stages, partages…), dépendances | métadonnées et consommation |
| 2 | + formes de requêtes normalisées et leur rejouabilité, lectures et écritures par table, politiques | accès à l'historique des requêtes |
| 3 | + rôles propriétaires, outils clients, types d'utilisateurs, capacités réservées | droits et sessions |

Ce que vos droits ne permettent pas est consigné « non lu » dans le rapport (constat F12) : le scan lit ce qui lui est ouvert et dit ce qu'il n'a pas vu.

## Snowflake

`canot snowflake-setup --generate-key --base MA_BASE` écrit le script à relire et à lancer par un administrateur. Il crée un rôle, un entrepôt XS qui se suspend après 60 s, un plafond de crédits, et accorde les rôles de base de Snowflake :

| Rôle de base | Niveau | Sert à |
|---|---|---|
| `SNOWFLAKE.OBJECT_VIEWER` | 1 | tables, colonnes, vues, routines, tâches, stages, dépendances |
| `SNOWFLAKE.USAGE_VIEWER` | 1 | volumes, crédits, historique, chargements |
| `SNOWFLAKE.GOVERNANCE_VIEWER` | 2 | historique des requêtes, lectures par table, politiques, étiquettes |
| `SNOWFLAKE.SECURITY_VIEWER` | 3 | rôles, utilisateurs (nom et type seulement), sessions |

Jamais demandés : `IMPORTED PRIVILEGES`, `MANAGE GRANTS`, `IMPORT SHARE`, `USAGE` sur les fonctions, `READ` sur les stages.

Limites connues : `ACCOUNT_USAGE` a une latence de 45 minutes à 3 heures (jusqu'à 2 jours pour l'historique de chargement). Le rapport affiche la latence de chaque lecture. `ACCESS_HISTORY` exige l'édition Enterprise ; sans elle, les lectures par table se déduisent de `TABLE_QUERY_PRUNING_HISTORY` avec une confiance moyenne.

## BigQuery

`canot bigquery-setup --project MON_PROJET --member user:…` écrit le script `gcloud` d'un rôle personnalisé `canotScanner`. Permissions obligatoires : `bigquery.jobs.create`, `bigquery.jobs.listAll`, `bigquery.datasets.get`, `bigquery.tables.list`, `bigquery.tables.get`, `bigquery.tables.getIamPolicy`, `bigquery.routines.list`, `bigquery.routines.get`, `bigquery.models.list`, `bigquery.models.getMetadata`, `bigquery.reservations.list`, `bigquery.reservationAssignments.list`, `bigquery.capacityCommitments.list`, `bigquery.bireservations.get`, `bigquery.config.get`. Optionnelles : `bigquery.rowAccessPolicies.list`, `bigquery.transfers.get`, `bigquery.connections.list`, `bigquery.dataPolicies.list`.

Jamais : `bigquery.tables.getData`, ni les rôles `bigquery.user` ou `jobUser`.

Points de méthode :
- Une requête `INFORMATION_SCHEMA` par région (les régions ne sont pas hiérarchiques : `EU` ne couvre pas `europe-west1`). BigQuery facture au moins 10 Mo par requête de ce type, sans cache : le scan plafonne chaque requête et fait un essai à blanc avant.
- Les volumes viennent de `TABLE_STORAGE` quand elle est lisible, sinon de l'API `tables.get`. Canot ne modifie jamais le paramétrage du projet pour l'activer.
- Sous une politique de lignes, `total_bytes_billed` est vide : le rapport le dit au lieu d'afficher 0.
- Le palier gratuit est partagé par le compte de facturation : il n'est pas imputable par projet.

## dbt

`canot scan dbt://CHEMIN_DU_PROJET` lit les artefacts du projet, sans réseau ni commande dbt : `target/manifest.json` (obligatoire), `target/run_results.json` et `target/catalog.json` s'ils existent. `CHEMIN` peut être le projet, son dossier `target/` ou le fichier `manifest.json` ; un dossier de projet passé sans `dbt://` est reconnu aussi. Sans manifest, `dbt parse` le produit en lisant les fichiers du projet, sans se connecter à l'entrepôt. Un seul niveau (1) : `--level` est ramené à 1.

| Lu | Ce qu'on garde |
|---|---|
| `manifest.json` | modèles (matérialisation, relation cible, dépendances), tests, sources, seeds, snapshots, expositions, métriques, macros du projet, paquets (nombre de macros), adaptateur et version de dbt, graphe des dépendances (`liens` : chaque `ref()` et `source()`) |
| SQL compilé des modèles, tests, snapshots | empreinte sha256 de la forme normalisée (littéraux masqués), nombre de lignes, fonctions et constructions propres au dialecte de l'adaptateur (sqlglot), fonctions absentes de DuckDB, catégorie de traduction vers DuckDB (C0 à C7, mêmes règles que les requêtes) ; jamais le texte |
| `run_results.json` | état de la dernière invocation par nœud, échecs, lignes en échec des tests, date de dernière écriture des tables |
| `catalog.json` | colonnes et types (squelette pour l'EXPLAIN DuckDB), lignes et octets si l'adaptateur les donne, propriétaire pseudonymisé |

Portabilité d'un traitement : `traduisible` quand sqlglot traduit le SQL vers DuckDB (accepté par EXPLAIN si `catalog.json` est lu, « non testé » sinon) ; `a_reconstruire` seulement sur un fait lu : fonction absente de DuckDB, traduction impossible, matérialisation propre à l'adaptateur (par exemple `dynamic_table`) ; `non_determine` pour un modèle Python, un SQL non compilé encore plein de Jinja, ou un SQL qui contient un secret (compté puis écarté).

Constats : F13 pour un traitement à reconstruire (avec la raison), F08 pour un modèle dont le SQL dépend de Jinja et de macros (blocs `{% %}`, appels autres que `ref`, `source`, `config` ; les tests génériques de dbt ne comptent pas), F11 pour le DAG du projet (dernière invocation, échecs ; planification non lue : elle vit dans l'orchestrateur), F01 pour une source externe (emplacement réduit au compartiment), F12 pour un artefact absent. Consommation, volumes facturés et droits ne sont pas dans ces fichiers : le rapport les dit « non lus ».

Validé le 19/10/2026 sur le projet du banc `lab/dbt` (artefacts d'un `dbt build` réel, dbt 1.12.6, adaptateur Snowflake) : 8 modèles (4 vues staging, 4 tables marts), 8 tests, 4 sources, 19 liens ; constats F11 et F12 (pas de `catalog.json`). Copie de ces artefacts et sorties figées : `tests/fixtures/dbt/banc`, `tests/golden/cli_scan_dbt`.

### Preuve P3 : `canot rejeu-dbt`

`canot rejeu-dbt dbt://PROJET --base copie.duckdb` (ou `--iceberg DESTINATION --drill-out DOSSIER` pour la copie Iceberg écrite par `canot drill`) rejoue chaque modèle SQL dans un DuckDB local, dans l'ordre du graphe, à partir des tables sources de la copie, puis compare sa sortie à la table que la plateforme avait matérialisée, avec les critères de `canot drill` : nombre de lignes, statistiques par colonne, empreinte de contenu. Les types de la sortie rejouée sont alignés sur ceux de la table d'origine avant la comparaison ; chaque conversion est consignée. Le résultat (`rejeu-dbt.json`) et le dossier signé (`evidence.jsonl`, à vérifier par `canot verify-evidence`) classent chaque modèle :

| Statut | Sens |
|---|---|
| `identique` | rejoué, comparé, aucun écart dans les tolérances |
| `different` | rejoué, comparé, écart (lignes, statistiques ou empreinte) |
| `non_compare` | rejoué, mais aucune table d'origine dans la copie (une vue) ; sa sortie alimente les modèles suivants |
| `non_rejouable` | avec la raison : langage Python, incrémental (dépend de l'état antérieur), éphémère (rejoué à travers ses utilisateurs), fonction propre ou matérialisation propre à l'adaptateur, traduction refusée, table absente de la copie, dépendance non rejouée |

Il faut le SQL compilé dans le manifest (`dbt compile` ou `dbt build` l'y met). Testé le 19/10/2026 sur les artefacts du banc et une copie DuckDB construite par les tests (tables d'origine écrites à la main, sans réseau) : 4 tables marts identiques, 4 vues rejouées non comparées, écart injecté détecté, y compris en lisant le lac écrit par `canot drill`.

Premier rejeu sur une copie réelle (19/10/2026) : `canot drill` du compte Snowflake du banc (schémas RAW et marts) vers une copie Iceberg local, puis `canot rejeu-dbt --lac`, sans nouvelle requête vers la plateforme. Résultat : `int_order_totals` identique, 4 vues rejouées non comparées, 3 marts différents (`dbt_fct_orders` : 150 000 lignes à l'origine, 25 325 rejouées ; `dbt_customer_ltv` ; `dbt_daily_revenue`). Ces écarts sont justes : la table des clients est protégée par une politique de lignes et le rôle de lecture du drill n'en voit qu'une partie. La copie des entrées est incomplète et le rejeu le montre : c'est exactement ce que P3 doit détecter (le constat F10 du scan Snowflake rappelle qu'une copie demande un principal non filtré par ces politiques).

Le même essai a montré un faux écart, corrigé depuis : `int_order_totals` était classé différent alors que lignes et statistiques étaient égales. Cause, colonne par colonne : `ORDER_ID` et `ITEMS` égaux (la conversion consignée `ITEMS : HUGEINT -> INTEGER` ne change aucune valeur) ; `BASKET_VALUE` est une somme de flottants (`UNIT_PRICE` en FLOAT) que Snowflake et DuckDB additionnent dans un ordre différent. 36 971 valeurs diffèrent au dernier bit (`121.79` contre `121.79000000000002`, écart relatif au plus 4e-16), et l'empreinte de contenu hache le texte exact. Désormais, quand lignes et statistiques sont égales et que seule l'empreinte diffère, le rejeu recalcule l'empreinte des deux côtés avec les colonnes flottantes arrondies à 12 chiffres significatifs (plus fin que la tolérance relative de 1e-9). Si elle est alors égale, le modèle est « identique dans la tolérance » et le rapport consigne les colonnes arrondies, le nombre de valeurs différentes au bit et l'écart relatif maximal mesuré (`verification.empreinte_flottants`). Un écart plus grand reste « différent ».

## Lire les chiffres

Chaque chiffre porte sa nature : **mesuré** (lu tel quel), **calculé** (déduit de mesures, formule donnée), **estimé** (fourchette et hypothèses), **non lu** ou **non estimé** (avec la raison). Les prix sont des repères datés et sourcés ; passez `--credit-price` avec le prix de votre contrat pour les remplacer. Le coût de lecture et la durée d'une copie sont des estimations de planification : `canot drill` les remplace par des mesures.

La rejouabilité mesure combien de formes de requêtes sont traduites vers DuckDB **et acceptées** par lui, sur un squelette vide typé à partir de vos colonnes. **Accepté ne veut pas dire résultat identique** : seul `canot drill` compare les résultats. Les requêtes sur des tables hors du périmètre lu, les requêtes dont la plateforme ne donne pas le texte et les requêtes de métadonnées sont comptées à part, hors taux.

## Ce que le scan ne voit pas

Les lectures par l'API Storage Read et les requêtes lancées depuis d'autres projets (BigQuery), les consommateurs de partages et `RESULT_SCAN` (Snowflake), les traitements plus rares que la fenêtre lue (365 jours sur Snowflake, 90 à 180 jours sur BigQuery). La fenêtre réelle est affichée à côté de chaque constat.

---

# Arrivée sur site

Canot écrit la copie au format Apache Iceberg. L'arrivée peut être un dossier de votre réseau, ou un stockage compatible S3 que vous hébergez.

| Destination | Commande | État |
|---|---|---|
| Dossier local ou monté (NAS, disque, partage) | `--to /chemin/vers/le/dossier` | testé |
| Stockage compatible S3 hébergé chez vous | `--to "s3://coffre/canot?endpoint=https://s3.interne:9000&region=us-east-1"` | testé sur un serveur S3 local (moto) |
| Catalogue personnalisé (SQLite ailleurs) | `--catalog sqlite:///chemin/catalogue.db` | testé |
| Catalogue PostgreSQL | `--catalog env:CANOT_CATALOG_URI` avec `pip install 'canot[postgres]'` | branché, pas encore testé contre un vrai PostgreSQL |
| Proxy d'entreprise | `...?proxy=http://proxy.interne:3128` dans l'URL S3 | branché, pas encore testé |
| Autorité de certification interne | `...?ca=/chemin/ca.pem` dans l'URL S3 (transmise par `AWS_CA_BUNDLE`) | branché, pas encore testé |

Les clés du stockage passent par `CANOT_S3_ACCESS_KEY` et `CANOT_S3_SECRET_KEY` (ou la configuration `scw`). Un mot de passe de catalogue se met dans une variable d'environnement, jamais sur la ligne de commande : `--catalog env:NOM_DE_LA_VARIABLE`.

## Relire la copie sans Canot

Chaque exercice écrit un fichier `relire.sql` à côté du rapport. Il contient les commandes DuckDB qui comptent les lignes de chaque table copiée :

```bash
duckdb < canot-out/relire.sql
```

La copie ne dépend donc pas de l'outil : n'importe quel moteur qui lit Iceberg (DuckDB, Spark, Trino) peut la lire. Pour un stockage S3, créez d'abord un secret DuckDB avec vos clés (voir le fichier).

## Ce qui n'est pas pris en charge

Écrire directement dans PostgreSQL, ClickHouse ou un autre moteur de base : la copie reste en Iceberg, que ces moteurs savent lire. Pas d'installation hors ligne documentée pour l'instant.

## Nettoyer

```bash
canot teardown --to "s3://coffre/canot?endpoint=https://s3.interne:9000&region=us-east-1"
```

---

# Cibles : hébergeurs souverains et pays

Canot écrit sur tout stockage compatible S3. Raccourcis intégrés (`--to <hébergeur>:<région> --bucket <nom>`).
**Vérifiez chaque point de terminaison dans la console de l'hébergeur avant usage : ils sont à confirmer.**

| Raccourci | Hébergeur | Pays | Remarque |
|---|---|---|---|
| `scaleway:fr-par` (aussi `nl-ams`, `pl-waw`) | Scaleway | FR, NL, PL | pas qualifié SecNumCloud (à vérifier) |
| `ovh:gra` | OVHcloud | FR | offre SNC qualifiée SecNumCloud (à vérifier sur la liste ANSSI) |
| `outscale:eu-west-2` | Outscale | FR | qualification à renouveler fin 2026 (à vérifier) |
| `ionos:eu-central-1` | IONOS | DE | |
| `stackit:eu01` | STACKIT (groupe Schwarz) | DE | |
| `aws:eu-west-3` | AWS | FR | pas un cloud souverain ; AWS European Sovereign Cloud existe aussi |

## Le même principe dans d'autres pays de l'UE

Le Data Act s'applique dans toute l'UE : mêmes règles de changement de fournisseur (art. 23 à 31), mêmes frais à zéro à partir du 12 janvier 2027.
Ce qui change d'un pays : le superviseur financier qui applique DORA, l'autorité qui contrôle le Data Act, et les exigences nationales de souveraineté.

| Pays | Pourquoi c'est intéressant | Hébergeurs locaux |
|---|---|---|
| Allemagne | plus gros marché ; BaFin applique DORA ; forte culture souveraineté | STACKIT, IONOS, Open Telekom Cloud |
| Pays-Bas | place financière, beaucoup de scale-ups sur Snowflake | Leaseweb |
| Italie | banques et assurances nombreuses | Aruba, Seeweb |
| Espagne | banques ; Data Act via la CNMC | IONOS, Arsys |
| Pologne, pays nordiques | fort usage du cloud, hébergeurs locaux | Scaleway Varsovie, UpCloud |

À confirmer avant de vendre : l'autorité compétente désignée dans chaque pays et les listes de qualification nationales (équivalents de SecNumCloud : C5 en Allemagne, ACN en Italie, ENS en Espagne).
