Content reconciliation
Delivering content without destroying it: that is the problem this mechanism solves. An update used to empty the pages and rewrite the sender's. Uuids survived, but everything the other side had created or changed disappeared. That was tolerable while only coordinated developers edited pages. It stops being tolerable when two developers work on the same ERP pages — the most frequent and most conflictual case, weekly rather than yearly.
One mechanism, not two
The database does not care whether it is development, preproduction or production. It sees an update file, it plays it, and it produces another one. A publisher-to-client delivery and a developer-to-developer exchange are the same object, handled by the same code. Every import produces its counter-import: the return file.
The exchange file is a full snapshot, never a delta: the complete image of the nine web tables, a date and an origin per field, the deleted records (cumulative) and a header. It travels inside the version zip (Resources/exchange/work/content-<version>/) and is applied at startup (BSPK_EXCHANGE_APPLY_DELIVERY).
Where the dates come from: the date memory
Per-field dates are kept, no longer recomputed from BSPK_HISTORY at each pass. Recomputing turned every history purge or archive into a silent loss: dates went back months, deletions vanished, and the next delivery overwrote the webmaster's work.
- Where: in the data folder, under
bspk-exchange/— files, no table. Per record: a fingerprint per field (never the value), its date and its origin (local,import,technique,garde). Per table:_deleted.json, the deletions, with their source (local,import, oraucunewhen the record vanished without a trace). - A changed fingerprint is a moved field.
BSPK_HISTORYonly says when and who; when it is gone (purge, archive), the field keeps its date. A date never goes back. - Passes (
BSPK_EXCHANGE_MEMORY_PASS): full (the 30-minute task on the development machine, the build, every exchange operation, before archiving or purging history) and quick (every minute on the development machine, only what may have moved). The git snapshot and the delivery are views of the memory plus the values read in the database. - Migrations: what an
UPDATE_x_y_zmethod writes gets the origintechnique, which never counts as « modified here ».
Anchor, effective anchor, identity
- The anchor (
BSPK.lastImportOn) says « everything before this is reconciled ». It is only ever set by a pass, and equals the start of that pass. Never callBSPK_EXCHANGE_SET_ANCHORdirectly. - The integration receipt (
meta.integrates): when a package is integrated entirely, with no pending conflict, the receiver keeps the start of the pass that produced it. Effective anchor = the latest of the anchor and what the other side integrated from me. This is what spares the developer from deciding again what production already decided. - Identities: the installation's (
meta.installationId) and the data's (meta.dataId, the uuid of the component'sBSPKrow). A package carrying my data identity comes from a copy of my data: the diff refuses it, because both databases would share anchor and history and the verdicts would be wrong.
The verdicts
For each record, a field has « moved » if its date is after the effective anchor, if its origin is neither import nor technique, and if its value really differs. The comparison uses canonical JSON: the 4D = operator ignores case and accents.
| Mine | Theirs | Verdict |
|---|---|---|
| not moved | moved | jePrends: take theirs |
| moved | not moved | jeGarde: keep mine, re-dated after the new anchor |
| moved | moved | conflit: a person decides, block by block |
| absent, no known deletion | present | nouveau |
| deleted here | present, not moved | resteSupprime |
| deleted here | present, moved | conflitSuppressionLocale: a person decides |
| present, not moved | deleted | suppression |
| present and moved, or vanished there without a trace | deleted | conflitSuppression: a person decides |
A deletion the other side has already seen and that was kept here by a human decision (origin garde) is not asked again. On the /bweb/reconcile screen (Reconcile_FC), a choice only applies to the same verdict and the same version of the record that were shown; otherwise the item comes back to be decided, or is reported as no longer relevant. A record modified or deleted during an import (site open) is sent back to arbitration instead of being overwritten.
Startup order
- migrations (
BSPK_DATA_UPDATE) — a failed migration blocks everything below; - the rebase request, if there is one (
BSPK_EXCHANGE_REBASE_REQUEST, see below); - the initial anchor of an empty database (
BSPK_EXCHANGE_INIT_ANCHOR); - the delivery (
BSPK_EXCHANGE_APPLY_DELIVERY): if conflicts remain, the site is put on hold until they are decided.
Databases born from a copy of the data
A production created from a copy of the development data file shares its identity, anchor and history: it must be rebased (BSPK_EXCHANGE_REBASE) before its first exchange.
The first installation
This is the normal case of a first production deployment: you arrive with the version zip and a zip of the development data. When the update tool knows no installation of this product on the machine, it asks whether this is a first installation or an update.
- « First installation » installs the application into an empty folder and drops an automatic rebase request:
bweb_dependencies/bspk-rebase.json, in the application folder. - At the first start, 4D asks for the data file: select the one from the unzipped data archive. If it does not ask, start while holding Alt (Option on a Mac) to choose it.
- The request only runs if the delivery carries the same data identity as the database, that is, if the data really are a copy of the development data. A fresh database files it as « no longer relevant ».
- The development machine has nothing to do: the first delivery converges, then the integration receipt links both sides.
Special cases
For a production already in service on a copy of the data, drop a bspk-rebase.json file yourself, in the data folder:
{"vt_Anchor": "2026-01-15T08:00:00.000Z", "vb_IsCopy": true}
On the original database, if its history was purged, add a reference: a Resources/exchange/snapshot extracted from git at the same date.
{"vt_Anchor": "2026-01-15T08:00:00.000Z", "vt_BaselineFolder": "<path of the snapshot>"}
The request runs at startup (step 2), or hot with /_claude/exec?method=BSPK_EXCHANGE_REBASE_REQUEST. When it succeeds it is renamed bspk-rebase-fait-<date>.json, report included; when it fails it stays in place and bspk-rebase-echec.json says why.
Choosing the date: earlier than the copy, never later. Too late, what was changed between the copy and that date passes for reconciled: a silent loss, on both sides. Too early, the worst case is a few false conflicts, visible at arbitration. The original database's reference must be the latest git snapshot before that date.
Traps
- Do not delete the
bspk-exchange/folder of the data folder: the next pass rebuilds it from what remains of the history, prudently (false conflicts rather than losses), but deletions whose trace is gone are lost. - A
rebase-pending.jsonfile inbspk-exchange/means an interrupted rebase: no other pass runs until it is run again with the same date. - Purge the history only through
BSPK_HISTORY_PURGE, or let the daily archive (CRON_EXPORT_BSPK_HISTORY) do it: both update the memory before deleting anything. - A build stops if the content to deliver could not be produced: there is never a delivery without its content.
?pk=is reserved by the router: the reconciliation screen uses?cle=.- An empty
no-update-hooksfile inResources/exchangedisables the delivery steps at startup: the emergency switch when a delivery prevents the database from starting.
See also
- The data model — what is being exchanged
- The Claude API —
export-template/import-templatefor a single template

