La réconciliation du contenu

Livrer du contenu sans le détruire : c'est le problème que ce mécanisme résout. Auparavant, une mise à jour vidait les pages puis réécrivait celles de l'émetteur. Les uuid survivaient, mais tout ce que l'autre côté avait créé ou modifié disparaissait. C'était tolérable tant que seuls des développeurs coordonnés modifiaient les pages. Cela ne l'est plus quand deux développeurs travaillent sur les mêmes pages d'un ERP — le cas le plus fréquent et le plus conflictuel, qui se présente chaque semaine plutôt qu'une fois par an.

Un seul mécanisme, pas deux

La base ne se soucie pas d'être en développement, en préproduction ou en production. Elle voit un fichier de mise à jour, elle le joue, et elle en produit un autre. Une livraison de l'éditeur vers un client et un échange entre deux développeurs sont le même objet, traité par le même code. Chaque import produit son contre-import : le fichier de retour.

Le fichier d'échange est un instantané complet, jamais un delta : l'image complète des neuf tables web, une date et une provenance par champ, les enregistrements supprimés (cumulés) et un en-tête. Il voyage dans le zip de la version (Resources/exchange/work/content-<version>/) et s'applique au démarrage (BSPK_EXCHANGE_APPLY_DELIVERY).

D'où viennent les dates : la mémoire des dates

Les dates par champ sont conservées, et non plus recalculées depuis BSPK_HISTORY à chaque passage. Recalculer faisait de chaque purge ou archivage de l'historique une perte silencieuse : les dates reculaient de plusieurs mois, les suppressions disparaissaient, et la livraison suivante écrasait le travail du webmaster.

  • Où : dans le dossier de données, sous bspk-exchange/ — des fichiers, pas de table. Pour chaque enregistrement : une empreinte par champ (jamais la valeur), sa date et sa provenance (local, import, technique, garde). Pour chaque table : _deleted.json, les suppressions, avec leur source (local, import, ou aucune quand l'enregistrement a disparu sans trace).
  • Une empreinte qui change, c'est un champ qui a bougé. BSPK_HISTORY ne sert plus qu'à dire quand et qui ; quand il n'est plus là (purge, archivage), le champ garde sa date. Une date ne recule jamais.
  • Les passes (BSPK_EXCHANGE_MEMORY_PASS) : complète (tâche des 30 minutes sur le poste de développement, build, toute opération d'échange, avant un archivage ou une purge de l'historique) et rapide (chaque minute sur le poste de développement, seulement ce qui a pu bouger). L'instantané git et la livraison sont des vues de la mémoire et des valeurs lues en base.
  • Les migrations : ce qu'écrit une méthode UPDATE_x_y_z prend la provenance technique, qui ne compte jamais comme « modifié chez moi ».

Ancre, ancre effective, identité

  • L'ancre (BSPK.lastImportOn) dit « tout ce qui précède est réconcilié ». Elle n'est posée que par une passe, et vaut le début de cette passe. N'appelez jamais BSPK_EXCHANGE_SET_ANCHOR directement.
  • L'accusé d'intégration (meta.integrates) : quand un paquet est intégré en entier, sans conflit en attente, le destinataire retient le début de la passe qui l'a produit. Ancre effective = le plus récent entre l'ancre et ce que l'autre côté a intégré de moi. C'est ce qui évite au développeur de trancher de nouveau ce que la production a déjà tranché.
  • Les identités : celle de l'installation (meta.installationId) et celle des données (meta.dataId, l'uuid de la ligne BSPK du composant). Un paquet qui porte mon identité de données vient d'une copie de mes données : le diff le refuse, parce que les deux bases partageraient ancre et historique et que les verdicts seraient faux.

Les verdicts

Pour chaque enregistrement, un champ a « bougé » si sa date est postérieure à l'ancre effective, si sa provenance n'est ni import ni technique, et si sa valeur diffère vraiment. La comparaison se fait sur le JSON canonique : l'opérateur = de 4D ignore la casse et les accents.

Chez moiChez luiVerdict
pas bougébougéjePrends : je prends sa version
bougépas bougéjeGarde : je garde la mienne, re-datée après la nouvelle ancre
bougébougéconflit : une personne tranche, bloc par bloc
absent, sans suppression connueprésentnouveau
supprimé iciprésent, pas bougéresteSupprime
supprimé iciprésent, bougéconflitSuppressionLocale : une personne tranche
présent, pas bougésupprimésuppression
présent et bougé, ou disparu chez lui sans tracesuppriméconflitSuppression : une personne tranche

Une suppression déjà vue par l'autre côté et gardée ici par une décision humaine (provenance garde) n'est pas redemandée. Sur l'écran /bweb/reconcile (Reconcile_FC), un choix ne s'applique qu'au même verdict et à la même version de l'enregistrement que ceux présentés ; sinon l'élément revient à trancher, ou est signalé comme sans objet. Un enregistrement modifié ou supprimé pendant un import (site ouvert) est renvoyé à l'arbitrage au lieu d'être écrasé.

L'ordre au démarrage

  1. les migrations (BSPK_DATA_UPDATE) — une migration en échec bloque tout ce qui suit ;
  2. la demande de réalignement, s'il y en a une (BSPK_EXCHANGE_REBASE_REQUEST, voir plus bas) ;
  3. l'ancre initiale d'une base vide (BSPK_EXCHANGE_INIT_ANCHOR) ;
  4. la livraison (BSPK_EXCHANGE_APPLY_DELIVERY) : s'il reste des conflits, le site est mis en attente jusqu'à l'arbitrage.

Les bases nées d'une copie des données

Une production créée à partir d'une copie du fichier de données du développement en partage l'identité, l'ancre et l'historique : elle doit être réalignée (BSPK_EXCHANGE_REBASE) avant son premier échange.

La première installation

C'est le cas normal d'une première mise en production : on arrive avec le zip de la version et un zip des données du développement. Quand l'outil de mise à jour ne connaît aucune installation de ce produit sur la machine, il demande s'il s'agit d'une première installation ou d'une mise à jour.

  • « Première installation » installe l'application dans un dossier vide et dépose une demande de réalignement automatique : bweb_dependencies/bspk-rebase.json, dans le dossier de l'application.
  • Au premier démarrage, 4D demande le fichier de données : désignez celui du zip de données décompressé. S'il ne le demande pas, démarrez en maintenant la touche Alt (Option sur Mac) pour le choisir.
  • La demande ne s'exécute que si la livraison porte la même identité de données que la base, c'est-à-dire si les données sont bien une copie de celles du développement. Une base neuve la classe « sans objet ».
  • Le poste de développement n'a rien à faire : la première livraison converge, puis l'accusé d'intégration relie les deux côtés.

Les cas particuliers

Pour une production déjà en service sur une copie des données, posez vous-même, dans le dossier de données, un fichier bspk-rebase.json :

{"vt_Anchor": "2026-01-15T08:00:00.000Z", "vb_IsCopy": true}

Sur la base d'origine, si son historique a été purgé, ajoutez une référence : un instantané Resources/exchange/snapshot extrait de git à la même date.

{"vt_Anchor": "2026-01-15T08:00:00.000Z", "vt_BaselineFolder": "<chemin de l'instantané>"}

La demande s'exécute au démarrage (étape 2), ou à chaud avec /_claude/exec?method=BSPK_EXCHANGE_REBASE_REQUEST. Réussie, elle est renommée bspk-rebase-fait-<date>.json, compte rendu compris ; en échec, elle reste en place et bspk-rebase-echec.json dit pourquoi.

Choisir la date : plus tôt que la copie, jamais plus tard. Trop tard, ce qui a été modifié entre la copie et cette date passe pour réconcilié : perte silencieuse, des deux côtés. Trop tôt, on obtient au pire quelques faux conflits, visibles à l'arbitrage. La référence de la base d'origine doit être le dernier instantané git antérieur à cette date.

Pièges

  • Ne supprimez pas le dossier bspk-exchange/ du dossier de données : la passe suivante le reconstruit avec ce qui reste de l'historique, prudemment (de faux conflits plutôt que des pertes), mais les suppressions dont la trace a disparu sont perdues.
  • Un fichier rebase-pending.json dans bspk-exchange/ signale un réalignement interrompu : aucune autre passe ne tourne tant qu'il n'a pas été relancé avec la même date.
  • Purgez l'historique uniquement par BSPK_HISTORY_PURGE, ou laissez faire l'archivage quotidien (CRON_EXPORT_BSPK_HISTORY) : tous deux mettent la mémoire à jour avant de supprimer quoi que ce soit.
  • Un build s'arrête si le contenu à livrer n'a pas pu être produit : il n'y a jamais de livraison sans son contenu.
  • ?pk= est réservé par le routeur : l'écran de réconciliation utilise ?cle=.
  • Un fichier vide no-update-hooks dans Resources/exchange désactive les étapes de livraison au démarrage : c'est l'interrupteur de secours quand une livraison empêche la base de démarrer.

Voir aussi