Aller au contenu

Gérer les workbooks avec git

Sortez le contenu d’un workbook sous forme de fichiers dans votre dépôt local, faites-le passer par les diffs, les revues et les PR de git, puis réécrivez-le tel quel. Les transforms écrits par l’agent du chat sortent au même endroit : le mode de travail « relire le SQL écrit par l’IA avant de l’entériner » fonctionne donc tel quel.

Fenêtre de terminal
d2b pull --workbook WB # → transforms/ sheets/ charts/ + d2b.json
d2b pull --data customers # also track a small base table as data/customers.csv (export = branch)
git add -A && git commit -m "pull from D2B"
# ... edit transforms/*.sql|py, sheets/*.json, data/*.csv
d2b push --dry-run # what would be sent (only files that changed)
d2b push --commit "$(git rev-parse --short HEAD)" # apply the changes → pin the git sha as a named version
git commit -am "d2b push" # push updates d2b.json (sync hashes) — commit it too
RépertoireContenupullpush
transforms/Transforms SQL / Python (hiérarchie possible, comme agg/monthly.sql)✅✅ Seuls les fichiers modifiés sont réexécutés via POST /transforms (compté comme opérations de données). Dans l’ordre des dépendances (l’amont d’abord)
sheets/Sheets d’affichage {"blocks": [...]}✅✅ PUT /sheets/{name}
charts/Config + recipe des charts (l’outil et les paramètres de leur génération)✅❌ Lecture seule — un chart se régénère depuis sa recipe, et une config corrigée à la main ne peut pas être régénérée. Pour l’historique et la relecture
data/CSV des tables base en opt-in via --data (avec __d2b_row_id)✅ export = branch✅ 3-way merge cellule par cellule côté serveur, sur la clé d’ID de ligne. Les cellules modifiées des deux côtés partent en queue de conflicts du workbook (la valeur D2B reste)

--data ne s’applique qu’aux tables de base (dont les lignes sont leur propre source de vérité). La sortie structurée de mode=auto est dérivée (sortie d’un transform) et ne peut pas être branchée — pour faire transiter les lignes d’une table par git, ingérez-la en --mode staged, vérifiez le parse spec puis matérialisez (cela produit une table de base), ou créez-la via l’API de lignes.

d2b.json est le manifeste. L’entrée d’un transform est {name, artifact_name, args, layer, hash} (pour ajouter un transform, écrivez le fichier et cette entrée). hash est le digest de la dernière synchronisation (= la base de merge), maintenu par la CLI — committez le d2b.json d’après push.

Quand vous écrivez une nouvelle entrée à la main, omettez hash (l’omettre — ou null — revient au même) : le premier push l’attribue et le réécrit.

{
"workbook_id": "…",
"transforms": {
"agg/monthly.sql": {
"name": "agg/monthly",
"artifact_name": "product_sales", "args": {"src": "sales"}, "layer": null,
"hash": "…"
}
},
"sheets": {"summary.json": {"name": "summary", "hash": "…"}},
"charts": {"trend.json": {"name": "trend", "readonly": true, "hash": "…"}},
"data": {"customers.csv": {"table": "customers", "branch_id": "…", "hash": "…"}}
}

Des centaines ou des milliers de classeurs se gèrent comme un dépôt = un espace de travail. Le d2b.json racine (le registre) fixe l’espace de travail, et chaque classeur arrive dans workbooks/<titre>--<8 premiers caractères de son id>/ avec la disposition ci-dessus, son propre d2b.json compris.

Fenêtre de terminal
d2b pull --workspace WS # première fois : écrit le registre et récupère tous les classeurs de l'espace (en parallèle, --jobs N)
d2b pull # ensuite : depuis le registre ; un nouveau classeur apparaît de lui-même
d2b pull --prune # supprime les répertoires des classeurs sortis de l'espace (la suppression apparaît dans le diff)
d2b status --strict # exit 2 quand le registre, les répertoires et le serveur divergent (à rendre obligatoire en CI)
d2b push --commit "$(git rev-parse --short HEAD)" # seuls les classeurs modifiés sont envoyés
  • L’appartenance est un fait du serveur : pull liste tous les classeurs de l’espace et n’écrit jamais un répertoire que le registre ne connaît pas. Un autre espace de travail ne peut pas être récupéré dans le même dépôt — il n’y a pas d’option pour forcer.
  • Le répertoire d’un classeur est fixé à son premier pull ; un changement de titre ne le déplace pas. Son id figure dans le nom du répertoire et sur la première ligne de chaque fichier de transformation, -- d2b ws=… wb=… transform=… (cette ligne n’est jamais envoyée au serveur et ne compte jamais comme une modification).
  • push n’envoie rien du tout s’il trouve un répertoire inconnu du registre, un fichier dont l’en-tête nomme un autre classeur, ou un manifeste lié à un autre workbook_id.
  • Donnez à la CI un PAT épinglé à l’espace de travail (workspace_id sur POST /api/control/account/keys, ou resource: "workspace:<id>" sur POST /api/v1/me/tokens) : même mal configurée, elle reçoit un 403 du serveur pour tout autre espace. L’identifiant de d2b login couvre tout le compte et n’est pas la bonne clé pour la CI.
  • data/ (les lignes) reste un opt-in par classeur (d2b pull --data TABLE dans workbooks/<dir>/).

Un répertoire sans registre garde le comportement à un seul classeur décrit ci-dessus.

  • Le textuel (transforms / sheets / charts) n’est jamais écrasé en silence (le même contrat que le verrouillage optimiste de l’API de lignes) : si les deux côtés ont changé depuis la dernière synchronisation, pull comme push refusent et renvoient les fichiers concernés en JSON. Le sens de --force dépend de la commande : push --force impose votre côté local ; pull --force adopte le côté serveur (vos éditions locales sont perdues)
  • data/ diffère : l’édition simultanée n’est pas refusée, le 3-way merge du serveur arbitrant cellule par cellule. Après le push, le CSV est repris depuis le résultat du merge et un nouveau branch est ouvert (les changements côté D2B redescendent aussi en local). Les tables dérivées ne se branchent pas (400). Limite de merge : 100 000 lignes — pour les petites tables type référentiel ou table de correspondance
  • Un fichier supprimé en local ne supprime rien côté serveur (simple signalement dans deleted_locally). --prune supprime les tables de sortie des transforms et les sheets (la suppression d’une table de sortie demande workbooks:delete). Pour data/, seul le suivi est retiré — la table n’est pas supprimée
  • Les clés de d2b.json doivent être des chemins relatifs normalisés à l’intérieur de leur propre répertoire de section : les chemins absolus, .., les lecteurs Windows et les chemins non normalisés sont refusés au pull comme au push. Un fichier synchronisé qui est un lien symbolique (transforms/*.sql|py et consorts) est refusé lui aussi — rien n’est lu ni écrit à travers un lien (un lien vers un fichier que la section ne lit jamais est simplement ignoré)

Ni GitHub App ni configuration d’intégration côté D2B : la CLI suffit à boucler l’aller-retour entre le dépôt et le workbook.

Fenêtre de terminal
d2b github-workflow > .github/workflows/d2b.yml
# Secrets: D2B_API_KEY (a workbooks:write PAT). Variables: D2B_BASE_URL
  • Merge dans main (avec des fichiers suivis modifiés) → d2b push --commit <sha> → commit automatique du d2b.json mis à jour
  • Planifié (par défaut toutes les heures) et manuel → d2b pull → PR s’il y a un diff (branche d2b/pull). Ce que l’agent a changé côté workbook est ainsi relu par un humain puis mergé

Côté SDK, client.transforms.list(wb) / client.sheets.list(wb) / client.charts.list(wb) / client.export.branch(wb, [table]) / client.tables.merge(...) sont la même surface.