# bion-vc — horloges vectorielles + Multi-Value Register « fork visible » ## Quoi La brique causale du xerboxion, `std`-only, zéro dépendance, zéro `unsafe` : - **`VectorClock`** — l'horloge logique canonique (Fidge/Mattern) : `increment(node)`, `merge(&other)` (max composante par composante), `observe(&other, node)` (règle de réception complète), et la comparaison causale `causality()` → `Before | After | Concurrent | Equal`. `PartialOrd` est implémenté honnêtement : `partial_cmp` rend `None` pour deux horloges concurrentes, parce que l'ordre causal est *partiel* — c'est toute la leçon. - **`Stamped`** — une valeur + son estampille causale (l'enveloppe du tsoin, §2 de la spec). - **`MvReg`** — le Multi-Value Register : `set()` (écrasement *causal* uniquement — de ce que l'écrivain a vu), `apply()`/`merge()` qui **gardent les branches concurrentes**, `values()`/`branches()`/`is_forked()` pour les exposer, `resolve()` pour fermer un fork **explicitement** avec un VC qui domine toutes les branches. - Sérialisation texte stable (`to_text`/`from_text`, formats gelés `a:2,b:1` et `mvr1`), sans serde : deux répliques convergées produisent le même texte, octet pour octet. ## Pourquoi C'était **l'algo manquant** du projet : la spec `xion-relativiste-v0` (`resources/papers/xion-relativiste-v0.md` de my_website2) gèle la décision RS-7 — > **Divergence = branche, retrouvaille = merge, conflit = fork visible.** > Jamais d'écrasement silencieux. — mais personne ne l'avait codée. La réplication actuelle du labo fait du LWW par horloge murale (`StateReplicationController`, `savedAt`/`cmpAt()`), qui jette silencieusement une des deux écritures concurrentes. Ce bion est l'arbitre de remplacement du chemin de migration §6 de la spec : le merge devient un CRDT (commutatif, associatif, idempotent — testé par permutations), l'essaim converge sans coordinateur, et aucune branche ne meurt sans un acte explicite. ## Exemple ```rust use bion_vc::{MvReg, Causality, VectorClock}; // Deux répliques divergent : chacune écrit « sa » valeur du même point. let mut bord = MvReg::new(); bord.set("cubion-bord", "cap sur Europe".to_string()); let mut sol = MvReg::new(); sol.set("cubion-sol", "cap sur Titan".to_string()); // Retrouvailles : merge. Personne ne perd rien → fork VISIBLE. bord.merge(&sol); assert!(bord.is_forked()); assert_eq!(bord.values().len(), 2); // Fermer le fork est un acte explicite, tracé, qui domine les deux branches. bord.resolve("cubion-bord", |branches| { let mut caps: Vec<&str> = branches.iter().map(|b| b.value.as_str()).collect(); caps.sort(); caps.join(" PUIS ") }); assert!(!bord.is_forked()); // Et l'horloge seule, pour estampiller n'importe quoi d'autre : let mut vc = VectorClock::new(); vc.increment("moi"); assert_eq!(vc.causality(&VectorClock::new()), Causality::After); ``` ## Liens build-your-own-x Le [dépôt build-your-own-x](https://github.com/codecrafters-io/build-your-own-x) n'a pas (encore) de section « Build your own CRDT » — ce bion en tient lieu, depuis les principes, avec les sources primaires qu'un tel tutoriel citerait : - Lamport, *Time, Clocks, and the Ordering of Events in a Distributed System*, CACM 1978 — le happened-before. - Fidge 1988, Mattern 1989 — le vector clock qui capture la causalité *complète*. - Shapiro, Preguiça, Baquero, Zawirski, *Conflict-free Replicated Data Types*, SSS 2011 — les propriétés CRDT et la spec du MV-Register. - DeCandia et al., *Dynamo*, SOSP 2007 — les « siblings » : le fork visible en production depuis 2007. Parents dans build-your-own-x : « Build your own Git » (le merge à trois voies et les branches visibles sont le même geste) et « Build your own Database » (la réplication). ## Comment l'optimiser (l'invitation au fork) L'API est gelée ; tout le reste est à toi. Pistes réelles, dans l'ordre du §7 de la spec : 1. **Compaction des VC** — un VC croît en O(n) nœuds rencontrés. À grande échelle : deltas de VC, *dotted version vectors* (Preguiça et al. 2010), ou VC hiérarchiques par agrégat. Le format texte réserve la place (c'est une map : ajoute des nœuds composites). 2. **`apply` en O(n²)** sur le nombre de branches — correct et simple ; si un point accumule des centaines de forks non résolus, indexer les branches par nœud dominant. 3. **Interner les ids de nœuds** (`Rc`/index dans un registre) au lieu de `String` par entrée — le clone de VC est aujourd'hui la seule allocation chaude. 4. **GC des branches** — un MVR n'oublie jamais tout seul (c'est voulu) : une politique de rétention/archivage se construit *au-dessus*, jamais dedans. Règle du fork : garde `to_text`/`from_text` compatibles (rétrocompatibilité éternelle) — un format v2 porte un nouvel en-tête, il ne mute jamais v1. ``` cargo test -p bion-vc ``` *Ne pas nuire. Aucune montre n'a raison contre l'autre ; aucune branche ne meurt en silence.*