Files
bions-rust/bion-vc/README.md
cloudion-labo 2556698dd3 🦀 bions-rust vague 1 : 6 briques build-your-own-x — on ne les rebuild plus jamais
Principe RS-7 : « dès qu'on build un truc, plus personne n'a à le rebuild —
la seule chose à faire est l'optimisation. » (nexus/RepoVerse)
- bion-vc      : horloges vectorielles + MvReg fork-visible (LA spec
                 xion-relativiste-v0 enfin codée — CRDT testé par permutations)
- bion-triplet : l'Adressage Génératif (gen_hash BLAKE3, coords, résidu ;
                 résidu vide quand déjà-su ; align décidable au bit)
- bion-tsoinlog: journal append-only rejouable (CRC32 maison, crash-recovery)
- bion-kv      : magasin clé-valeur bitcask (compaction atomique, tombstones)
- bion-regex   : moteur Thompson NFA linéaire (jamais exponentiel — Russ Cox)
- bion-git     : mini-git content-addressed (SHA-1 maison + vecteurs officiels,
                 branches divergentes = le fork visible)
129 tests verts, clippy 0 warning, doc française = chaque bion est un cours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 01:07:31 +00:00

105 lines
4.9 KiB
Markdown

# 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<T>`** — une valeur + son estampille causale (l'enveloppe du tsoin, §2 de la spec).
- **`MvReg<T>`** — 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<str>`/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.*